
  <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
      <title>spiess.dev</title>
      <link>https://spiess.dev/</link>
      <description>Philipp Spiess – Somewhere between AI and UI. Prev: Tailwind Labs, Meta, Sourcegraph, React DOM team.</description>
      <language>en</language>
      <lastBuildDate>Fri, 13 Feb 2026 00:00:00 GMT</lastBuildDate>
      
    <item>
      <guid>https://spiess.dev/blog/software-collaboration-in-the-ai-age</guid>
      <title>Software Collaboration in the AI Age</title>
      <link>https://spiess.dev/blog/software-collaboration-in-the-ai-age</link>
      <description><![CDATA[Let's start with two observations:

## 1. GitHub Is Dying (and so Is the Traditional SDLC)

With the rise of AI coding agents, software…]]></description>
      <content:encoded><![CDATA[<p>Let's start with two observations:</p>
<h2 id="1-github-is-dying-and-so-is-the-traditional-sdlc"><a class="header-anchor" aria-label="Link to this heading" href="#1-github-is-dying-and-so-is-the-traditional-sdlc">#</a>1. GitHub Is Dying (and so Is the Traditional SDLC)</h2>
<p>With the rise of AI coding agents, software engineering is undoubtedly undergoing massive changes right now.</p>
<p>Teams are seeing big differences in output for folks starting to adopt these tools early and those that still haven't, and larger organizations are outpaced by smaller teams who amplify their output with AI.</p>
<p>Teams that are all-in on AI agents report big challenges scaling <em>traditional</em> software engineering processes like code reviews or CI pipelines with the influx in code. Additionally, some teams are even starting to discourage the use of issue tracking systems altogether and instead pass a well-written bug report directly into a coding agent that will then propose a fix.</p>
<p>Outside of companies, open-source is facing similar challenges where the classical concept of pull-requests is no longer maintainable because of the increase in volume with <em>questionable quality</em>. Many well-known open-source projects are starting to close off or limit pull requests (e.g. <a href="https://github.com/openai/codex/discussions/9956">Codex</a>). Issue discussions are also getting spammed with low quality content from agents all over the world.</p>
<p>And on top of all of this, GitHub has huge reliability issues and is now referred to as a <a href="https://www.reddit.com/r/ExperiencedDevs/comments/1r0cytn/has_github_just_become_a_dumpster_fire/">dumpster fire</a> by the community already.</p>
<p>I don't think continuing this approach is sustainable for long and unless there are some massive changes in a very short time, GitHub <em>is dying</em> (and with it, probably the whole traditional SDLC). And I'm <a href="https://x.com/thorstenball/status/2022261665257971800">not</a> <a href="https://updates.prototypecap.com/p/saas-is-dead">the</a> only one with this opinion.</p>
<h2 id="2-coding-agents-are-commodity"><a class="header-anchor" aria-label="Link to this heading" href="#2-coding-agents-are-commodity">#</a>2. Coding Agents Are Commodity</h2>
<p>I started my journey with AI agents in May last year and wrote about <a href="/blog/how-i-use-claude-code">How I use Claude Code</a> in June.</p>
<p>By now, I am no longer using Claude Code. Last year, I've instead been using many different coding agents including Codex CLI, Amp, Gemini CLI, Cursor, and, most recently I became a <a href="https://pi.dev/">Pi</a> convert. While the agent harness does continue to play a huge role in the agent's performance<sup><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref aria-describedby="footnote-label">1</a></sup>, I do think that the gaps are closing and instead <em>personal preference</em> is what's going to make you pick a coding agent in the future.</p>
<p>Most people I talk to these days have at least once used a coding agent of some form already but there's far from <em>one coding agent to rule them all</em>.</p>
<p>So, my bet is that teams will end up using <em>multiple</em> coding agent harnesses in the future, maybe even specific harnesses for specific types of work, some of them hyper-personalized to the team. The collaboration platform of the future will need to support them all to some extent.</p>
<h2 id="where-to-go-from-here"><a class="header-anchor" aria-label="Link to this heading" href="#where-to-go-from-here">#</a>Where to Go from Here</h2>
<p>There are, of course, countless teams currently thinking about what's next. Big AI labs doing strategic acquisitions<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">2</a></sup>, planning software adding deeper code integrations to capture the market<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">3</a></sup>, and new startups boasting with ridiculous seed funding rounds to build <em>the next GitHub</em><sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref aria-describedby="footnote-label">4</a></sup>.</p>
<p>But don't be fooled: <em>Nobody knows where this is heading</em>. The only thing that's clear is that existing systems can't scale with the increased rate at which code is now generated.</p>
<p>My guess is that collaboration for software projects will have to move to a higher level: from the code level to the spec level. Both product decisions and knowledge sharing will need to be done at the prompt level with the code being a generated artifact of it in many cases. The collaboration platform of the future will thus need to be much more tightly integrated into AI coding agents than the current generation.</p>
<p>In the same sense, code reviews will need to change dramatically. The old standard of "every line of code must be read by at least two humans" is hitting a limit already. Humans will start to be removed from the core code loop more and more. Personally, my guess is that instead of pull requests the first step will be that the developers start reviewing their own agent's code and the team only looks over the higher-level prompts to make sure the direction is right, only zooming into the code for more critical parts of the application.</p>
<p>Product-wise, my prediction is that we'll see new features being tested much more frequently but in the same vein we'll also see functionality removed much more often. Software is going to become a lot less rigid.</p>
<h2 id="building-agentlogs"><a class="header-anchor" aria-label="Link to this heading" href="#building-agentlogs">#</a>Building AgentLogs</h2>
<p>I realized pretty early in my coding agent journey that I put a lot of effort into prompts, effort that is lost once I commit the changes to Git. In my career as software engineer, I often needed to do extensive code archeology to uncover <em>all</em> context of a change to fix issues, so not being able to access all of it feels like a big limitation.</p>
<p>One of the first things I fully vibe coded was a small project called <a href="https://github.com/philipp-spiess/claude-code-viewer">claude-code-viewer</a>: A simple (and very broken) CLI that can upload the agent transcript to a web server where it's persisted with a URL. The idea was to document the agent transcript <em>with the Git commit</em> so that it's accessible in the future.</p>
<p>This project is slop and I didn't even use it myself. However, I still briefly mentioned it in my post about <a href="/blog/how-i-use-claude-code">How I use Claude Code</a>. To my surprise, this app <em>still has users today</em>. There are transcripts uploaded in February 2026, <em>9 months after I stopped thinking about it</em>. So this is clearly something people want.</p>
<p>Over the past few weeks I started building <a href="https://github.com/agentlogs/agentlogs">AgentLogs</a>, an open-source prototype of a platform for collaboration in the age of AI coding agents.</p>
<p><img src="/blog/software-collaboration-in-the-ai-age/screenshot.png" alt="Screenshot of an agentlogs.ai transcript sharing that shows a Claude Code transcript to &#x22;Stream Claude Bash Command Output&#x22;"></p>
<p>In its current version, AgentLogs is very simple: A few agent-specific plugins that hook into a shared CLI to convert your transcript to a unified schema and then uploads it to a server for storage.</p>
<p>What makes it special are two things:</p>
<ol>
<li>You can onboard your whole team and already see how your teammates are using AI agents and what patterns work or not.</li>
<li>Since AgentLogs automatically adds links to Git commit messages, you already get a good sense of what's in the code change only by looking at the prompt.</li>
</ol>
<h2 id="why-im-open-sourcing-it"><a class="header-anchor" aria-label="Link to this heading" href="#why-im-open-sourcing-it">#</a>Why I'm Open-Sourcing It</h2>
<p>When building AgentLogs, I quickly realized that keeping it private and distributing it as a typical SaaS is a <em>bad idea</em>.</p>
<p>One aspect is that agent transcripts are a huge security liability since it's <em>very</em> easy for it to leak secrets and other confidential information. AgentLogs attempts to guard against this by searching for thousands of well-known secret patterns and eliminating them from the logs before it's uploaded, but total security can, of course, not be guaranteed. Because of this, I do think companies need to treat agent transcripts as <em>highly sensitive data</em> and should keep it on infrastructure they can reliably delete from (even though it might be tempting to store this information in Git together with the code changes).</p>
<p>Another realization is that to make this tool useful for your team, you really want to integrate all coding agents your team is using. Even if they have DIY ones. In its current version, it's very trivial to add a new integration (since LLMs are notoriously good at converting one format to another) so making everything open makes it easy to add the integrations you need.</p>
<p>But the turning point was once I started calling it a <em>collaboration platform</em>. If the vision is to rethink how teams work together in the AI age, this can't happen in a silo. Open-sourcing it felt like the only honest approach.</p>
<h2 id="whats-next"><a class="header-anchor" aria-label="Link to this heading" href="#whats-next">#</a>What's Next</h2>
<p>If you're curious, the code is on <a href="https://github.com/agentlogs/agentlogs">GitHub</a> (oh, the irony). It's not much but it's a start, and you're invited to shape it in any way it makes sense for you. Now is the time to really rethink best practices.</p>
<section data-footnotes class="footnotes"><h2 class="sr-only" id="footnote-label"><a class="header-anchor" aria-label="Link to this heading" href="#footnote-label">#</a>Footnotes</h2>
<ol>
<li id="user-content-fn-4">
<p>Check out Can's <a href="https://x.com/_can1357/status/2021828033640911196">article about hashline</a> for a recent example <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-1">
<p>E.g. <a href="https://cursor.com/blog/graphite">Cursor acquiring Graphite</a> <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>E.g. <a href="https://linear.app/reviews">Linear adding code reviews</a> <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>E.g. <a href="https://entire.io/blog/hello-entire-world">Entire announcing a $60m seed round</a> with the initial release being a very similar product as AgentLogs in its current state. <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
      <pubDate>Fri, 13 Feb 2026 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/ai/agents-as-commodity</guid>
      <title>Agents as Commodity</title>
      <link>https://spiess.dev/note/engineering/ai/agents-as-commodity</link>
      <description><![CDATA[AI agents are _the_ AI engineering trend of 2025. It's understandable: For the first time, it's possible to really start to understand the…]]></description>
      <content:encoded><![CDATA[<p>AI agents are <em>the</em> AI engineering trend of 2025. It's understandable: For the first time, it's possible to really start to understand the vision of how LLMs can work on tasks for a longer period of time and actually get meaningful work done.</p>
<p>This created a huge boom in new agent offerings across the industry: If we focus solely on coding agents, there are so many different products on the market right now that it's impossible to keep track of. I've been invited to so many coding agents previews, that I can't even remember all the names anymore.</p>
<p>More recently, we see that existing agent offerings start to create SDKs to make it even easier to build on them: <a href="https://docs.claude.com/en/docs/claude-code/sdk/sdk-overview">Claude Code SDK</a> allows you to build your own agent leveraging some of the context management and tools that make Claude Code so successful. At the same time we see fine-tuned versions of models specifically tailored around agentic tool use.</p>
<p>All of this reminds me a lot of what happened the past year with the model layer. One lab made a breakthrough and many more labs emerged that were able to replicate it. Today, we have more competition than ever in the model layer and it's now perfectly reasonable to have a "best model for a specific task" or even use open-weights models for day-to-day work. Specifically the latter category is in an extreme race towards cheaper and cheaper inference that will eventually converge to only the energy costs.</p>
<p>I think something similar is going to happen with agents very soon. The differences between coding models will become smaller and smaller and more and more solutions will be on the market resulting in the price to eventually converge to only the raw model price.</p>
<p>There are a few more things that need to be figured out still: Security and proper sandboxing, for example, is a big concern. But once we have better primitives in place, it is going to unlock a new abstraction level and I can't wait to play with that.</p>]]></content:encoded>
      <pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/blog/how-i-use-claude-code</guid>
      <title>How I Use Claude Code</title>
      <link>https://spiess.dev/blog/how-i-use-claude-code</link>
      <description><![CDATA[One month ago, I subscribed to Claude Max. I've been using AI agents including Claude Code for some time prior, but with the flat pricing, my…]]></description>
      <content:encoded><![CDATA[<p>One month ago, I subscribed to Claude Max. I've been using AI agents including Claude Code for some time prior, but with the flat pricing, my usage skyrocketed and it's become a daily driver for many tasks. I find myself going to VS Code much less often now.</p>
<p>Since AI agents are new for everyone right now, I thought it might be fun to share some patterns I've been noticing recently. Here's how I use Claude code.</p>
<h2 id="start-new-threads"><a class="header-anchor" aria-label="Link to this heading" href="#start-new-threads">#</a>Start New Threads</h2>
<p>If there's one thing I want you to take away from this, it's that you should absolutely call <code>/clear</code> more often.</p>
<p>AI agents tend to become more unpredictable the longer a conversation goes. This is especially true when you're asking different questions. I found that it's often much more effective to create a new prompt even if that means repeating some instructions.</p>
<p>Once I started doing this more aggressively, my results improved dramatically. And <a href="https://ampcode.com/how-i-use-amp">I'm definitely not the only one</a> with this advice.</p>
<h2 id="create-precise-prompts"><a class="header-anchor" aria-label="Link to this heading" href="#create-precise-prompts">#</a>Create Precise Prompts</h2>
<p>I feel like this goes without saying, but when you're working with <em>a new grad with amnesia</em> (that's how I like to think of Claude), it's important that you write out all the context that you have in your head. This is hard, and frankly I don't think I'm very good at it myself yet, but the more context you can give Claude, the more effective it'll be. If you're thinking of some edge-cases, absolutely mention it to Claude. If you remember "there's a similar pattern that we've used for this type of problem in this codebase in the past", write it down! The more precise the prompt, the better Claude will do. Mind-reading technology isn't there <em>just yet</em>.</p>
<p>Try to think of any implicit context too. For example, if you ask Claude to create a <em>modern design</em>, it probably has no idea what you refer to as modern. It might be better to give examples instead (e.g., create a Linear-like app UI design).</p>
<h2 id="let-claude-code-use-claude-code"><a class="header-anchor" aria-label="Link to this heading" href="#let-claude-code-use-claude-code">#</a>Let Claude Code Use Claude Code</h2>
<p>Did you know that you can use Claude Code's tools as an MCP server (<code>claude mcp serve</code>)? When I found out, I looked into what tools it exposes and noticed one called <code>Task</code>. The premise of this tool is to spin off a sub-agent that will have the same access to tools as your main agent (except that it cannot spawn another sub-task) and reports back the results.</p>
<p>Knowing this is available is super handy when you have a more complex problem that you need a supervisor for. Here's a recent example of how I used this feature to do a deep-dive of the same problem with four different personas:</p>
<pre class="shiki vitesse-dark claude-plaintext" style="background-color:#121212;color:#dbd7ca !important" tabindex="0"><code><span class="line"><span>> Read files in the current directory to deduct a pattern for building Tailwind Plus components. You should spawn 4 sub-tasks with slightly different priorities (e.g. design color export, accessibility export, mobile/responsive expert, overall style expert) and compare their results.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>✻ Thinking…</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  Now I'll spawn 4 specialized sub-tasks with different priorities to analyze various aspects of the components.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>⏺ Task(Design Color Expert Analysis)</span></span>
<span class="line"><span>  ⎿  Done (24 tool uses · 41.5k tokens · 3m 4.4s)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>⏺ Task(Accessibility Expert Analysis)</span></span>
<span class="line"><span>  ⎿  Done (15 tool uses · 38.0k tokens · 2m 0.0s)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>⏺ Task(Mobile/Responsive Expert Analysis)</span></span>
<span class="line"><span>  ⎿  Done (14 tool uses · 45.5k tokens · 2m 1.2s)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>⏺ Task(Overall Style Expert Analysis)</span></span>
<span class="line"><span>  ⎿  Done (23 tool uses · 58.7k tokens · 2m 22.0s)</span></span></code></pre>
<p>It's magnificent <em>and</em> can be done in parallel. This is such a good way to keep the context window of your main task in order and keep Claude focused.</p>
<h2 id="tell-claude-to-think"><a class="header-anchor" aria-label="Link to this heading" href="#tell-claude-to-think">#</a>Tell Claude to Think</h2>
<p>Just like us old humans of flesh, Claude is lazy by default. E.g., when you tell Claude to do something, it'll choose the path of least resistance. If you tell it to do <em>at least</em> three things, I bet you it will not do a single thing more.</p>
<p>The same holds true about using <a href="https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/extended-thinking-tips">extended thinking capabilities</a>. To get better results, especially during planning processes, I recommend telling Claude to <em>ultrathink</em>.</p>
<h2 id="edit-previous-messages"><a class="header-anchor" aria-label="Link to this heading" href="#edit-previous-messages">#</a>Edit Previous Messages</h2>
<p>Whenever you're too eager to hit send or just feel like a previous message could be more precise to get better results, you can press <kbd>Escape</kbd> twice to jump to a previous message and fork the conversation. I use this all the time to refine prompts or simply have Claude <em>try again</em>.</p>
<p>Oh, and if you somehow want to get back to the previous state, you can start Claude with the <code>--resume</code> flag to list all prior threads.</p>
<h2 id="yolo-mode"><a class="header-anchor" aria-label="Link to this heading" href="#yolo-mode">#</a>Yolo Mode</h2>
<p>This is probably extremely irresponsible of me, but I mostly run Claude with <code>--dangerously-skip-permissions</code> now (thanks <a href="https://steipete.me/posts/2025/claude-code-is-my-computer">Peter</a> for being a bad influence). It's not necessary for everything, but if I have Claude working on some longer-running tasks, I <em>really</em> don't want to have to focus-switch back to it every minute because it uses a new terminal command.</p>
<p>I have this set up in my zsh profile:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">alias</span><span style="color:#BD976A"> yolo</span><span style="color:#666666">=</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">claude --dangerously-skip-permissions</span><span style="color:#C98A7D77">"</span></span></code></pre>
<p>Funny enough, now that Claude can do whatever it wants, I have been running against the rate limit quota warning much more often too.</p>
<h2 id="mcp-servers"><a class="header-anchor" aria-label="Link to this heading" href="#mcp-servers">#</a>MCP Servers</h2>
<p>I'm personally not super excited about MCP servers since none have really brought me any value. In most cases, I find they just use up valuable tokens with stuff that I don't need most of the time. The built-in tools in Claude Code are enough for me (especially when used to the ways I outline here).</p>
<p>In the past, I used <a href="https://github.com/microsoft/playwright-mcp">Playwright MCP</a>. While it's incredibly fascinating to see Claude start a browser, click buttons, and make screenshots, I found that it mostly just fills the context window very quickly without really making for a better result.</p>
<h2 id="claude-sdk"><a class="header-anchor" aria-label="Link to this heading" href="#claude-sdk">#</a>Claude SDK</h2>
<p>Claude has an <a href="https://docs.anthropic.com/en/docs/claude-code/sdk">SDK</a>. It's quite powerful, especially if you're happy to deal with the <code>stream-json</code> output format. But even for small things, being able to pass a prompt directly to <code>claude</code> and have it print back the reply is making for great shortcuts.</p>
<p>As an example, I have a <code>gcauto</code> executable in my path that does the following:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#758575DD">#!/bin/bash</span></span>
<span class="line"><span style="color:#80A665">git</span><span style="color:#C98A7D"> commit</span><span style="color:#C99076"> -m</span><span style="color:#C98A7D77"> "</span><span style="color:#666666">$(</span><span style="color:#80A665">claude</span><span style="color:#C99076"> -p</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">Look at the staged git changes and create a summarizing git commit title. Only respond with the title and no affirmation.</span><span style="color:#C98A7D77">"</span><span style="color:#666666">)</span><span style="color:#C98A7D77">"</span></span></code></pre>
<p>So whenever I commit stuff now, I just stage them and run <code>gcauto</code>.</p>
<h2 id="planning-mode"><a class="header-anchor" aria-label="Link to this heading" href="#planning-mode">#</a>Planning Mode</h2>
<p>Claude Code has a built-in planning mode, that is triggered when you press <kbd>Shift+Tab</kbd> twice. In this mode, Claude won't write to your file system.</p>
<p>I personally like to do planning with another model though, just to spice it up a bit. In the past weeks, I've been using Gemini 2.5 Pro in <a href="https://aistudio.google.com">Google AI Studio</a> directly. I don't really know how that's possible but I don't think I have to pay for it even?</p>
<h2 id="let-claude-rtfm"><a class="header-anchor" aria-label="Link to this heading" href="#let-claude-rtfm">#</a>Let Claude RTFM</h2>
<p>A rookie mistake I've made in the beginning is to tell Claude to set up a new project with <em>framework x</em>. It works, sure, but I noticed Claude often using outdated setups and project structures, especially for fast-moving frontend frameworks!</p>
<p>What I do now is ask Claude to read the manual first. This can be done in different ways:</p>
<ul>
<li>Paste a link to the docs and ask Claude to read it first</li>
<li>Ask Claude to find out the state-of-the-art</li>
<li>Use the <code>Task</code> tool and have Claude conduct deep-research on a particular topic</li>
</ul>
<p>An example for a deep research task can look like this:</p>
<pre class="shiki vitesse-dark claude-plaintext" style="background-color:#121212;color:#dbd7ca !important" tabindex="0"><code><span class="line"><span>> create a deep research task to figure out how to migrate a website from tailwind 3 to 4 and then present me with a</span></span>
<span class="line"><span>  short summary of the results</span></span>
<span class="line"><span></span></span>
<span class="line"><span>⏺ I'll create a research task to investigate migrating from Tailwind CSS v3 to v4, focusing on the specific context of</span></span>
<span class="line"><span>  your Tailwind UI codebase.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>⏺ Task(Tailwind v3 to v4 migration research)</span></span>
<span class="line"><span>  ⎿  Done (4 tool uses · 29.9k tokens · 5m 47.6s)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>⏺ Summary</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  Migrating from Tailwind CSS v3 to v4 involves significant changes but comes with major performance improvements (3.5x</span></span>
<span class="line"><span>   faster builds) and a better developer experience.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  Key points:</span></span>
<span class="line"><span>  - Use the automated upgrade tool: npx @tailwindcss/upgrade@next</span></span>
<span class="line"><span>  - Configuration moves from JavaScript to CSS-first using @theme directive</span></span>
<span class="line"><span>  - Breaking changes include dropped browser support, new import syntax, and removed deprecated utilities</span></span>
<span class="line"><span>  - Performance gains: builds are now measured in microseconds</span></span>
<span class="line"><span>  - Container queries are now built-in (no plugin needed)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  The migration is straightforward with the upgrade tool, but plan for testing time to ensure all components render correctly.</span></span></code></pre>
<h2 id="voice-input"><a class="header-anchor" aria-label="Link to this heading" href="#voice-input">#</a>Voice Input</h2>
<p>Apps like <a href="https://superwhisper.com/">Superwhisper</a> make it very easy to dictate a prompt. I found this super effective when I want to write a longer prompt since it'll get the ideas out of my head much faster.</p>
<p>This works incredibly well with any LLM input field, really, because LLMs can usually figure out what you mean even if the transcript is poor and full of mistakes.</p>
<h2 id="stage-early-stage-often"><a class="header-anchor" aria-label="Link to this heading" href="#stage-early-stage-often">#</a>Stage Early, Stage Often</h2>
<p>One feature I'm missing in Claude Code is the ability to make automatic file system snapshots. It happens quite often for me that Claude gets a bit too <em>trigger happy</em> and starts to make large changes when I don't want it to. If this happens after I've been getting some good changes, this can screw up the working directory quite a bit.</p>
<p>To circumvent this, I started to stage (i.e. <code>git add</code>) changes early and often. If I'm happy after a turn, I'm usually going to stage everything just so I know I can easily revert to it later.</p>
<h2 id="git-worktrees"><a class="header-anchor" aria-label="Link to this heading" href="#git-worktrees">#</a>Git Worktrees</h2>
<p>I am so obsessed by Claude Code that I now have at least two working trees of every major project I work on on my machine. This allows me to have Claude running on two different problems <em>in the same repository</em>.</p>
<p>It's very easy to set up, too! It's like creating a branch but the code will be in a different directory. Inside your git repository, run:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#80A665">git</span><span style="color:#C98A7D"> worktree</span><span style="color:#C98A7D"> add</span><span style="color:#C98A7D"> ../tailwindcss.com-2</span><span style="color:#C98A7D"> chore/upgrade-next</span></span></code></pre>
<p>And, voila, you now have another working directory for Claude Code to go absolutely feral in.</p>
<h2 id="experiment-with-one-off-work"><a class="header-anchor" aria-label="Link to this heading" href="#experiment-with-one-off-work">#</a>Experiment with One-Off Work</h2>
<p>With AI, code is becoming <em>really cheap</em>. This means that you can now build stuff that you only ever use once without feeling bad about it. Everything that you wish would make your current task easier can just be created out of thin air. Here are some examples of things I built recently that would never have been worth my time before coding agents:</p>
<ul>
<li>A dashboard that visualizes my current progress during a large migration at work</li>
<li>A chrome extension that shows me which version of Tailwind CSS a website is using (or, god-forbid, whether it isn't using Tailwind CSS at all)</li>
<li>A <a href="https://github.com/philipp-spiess/claude-code-viewer">CLI and backend</a> to upload Claude Code transcripts to be shared publicly</li>
<li>A <a href="https://github.com/philipp-spiess/claude-code-costs">CLI to sum up my Claude Code API costs</a> to see how much value I get out of the Max plan (oh so much...)</li>
<li>An <a href="https://github.com/philipp-spiess/claude-code-app">Electron app that experiments with Claude Code inside a git GUI</a></li>
</ul>
<h2 id="if-possible-dont-attempt-to-one-shot"><a class="header-anchor" aria-label="Link to this heading" href="#if-possible-dont-attempt-to-one-shot">#</a>If Possible, Don't Attempt to One-Shot</h2>
<p>I'm currently focused on a large-scale change that requires me to touch many Tailwind Plus components in the process. My naive first idea was to create an amazing, thought-out prompt with a lot of detail that can surely make the AI do all of this in one go... That surely must be possible, right?</p>
<p>Well, spoiler alert but I failed miserably with this approach. Not only did it not do what I wanted it to do in the first place, it also made it impossible for me to review the changes or make any meaningful changes. I had to start fresh.</p>
<p>This time I asked Claude Code questions about the problems first. We discussed possible changes before writing any code. Only when I felt certain it knew what I wanted, I let it change one component. After some testing and feedback, I let it do two more. Then another five until I finally let it fan out and do the remainder of the work.</p>
<p>While this obviously wasn't as spectacular as creating the perfect prompt, it got me to the end result much faster and with tighter feedback loops and supervision. I still was able to save so much time compared to doing this change by hand across hundreds of different components.</p>
<p>This might very well be an issue of me <em>holding it wrong</em>. I've been seeing a lot of other people claiming to be successful with large one-shot tasks (some of which say that Claude is working for <em>hours</em> straight). However, in my own experience, errors compound quickly and LLMs often loose the thread with a growing context window (and that was with extensive sub-agent calls and even trying Gemini 2.5 Pro). I would love it if someone could share their secrets with me!</p>
<h2 id="autonomous-feedback-or-human-in-the-loop"><a class="header-anchor" aria-label="Link to this heading" href="#autonomous-feedback-or-human-in-the-loop">#</a>Autonomous Feedback or Human in the Loop?</h2>
<p>Related to the issue above, this is also an area I still struggle with. Whenever I see people praising AI agents, they mention the importance of some autonomous feedback cycles so that the LLM can improve the result on their own.</p>
<p>However, I have not been able to do this effectively yet. When I try to set up unit tests or linter feedback, Claude <em>will read it and then suggest everything is working just fine when there still many are issues/warnings left</em>. When I set it up so it can navigate to the page and make screenshots, the context window is quickly full with tokens. Safe to say that the experience hasn't been great for me.</p>
<p>What I like to do instead is just have the dev server running myself. Whenever Claude gets back to me, I take a look at it myself and either copy paste any eventual stack traces as-is, or give some hints as to what I want done differently. I personally find this approach is far more likely to get me to what I want.</p>
<h2 id="interrupt-early-interrupt-often"><a class="header-anchor" aria-label="Link to this heading" href="#interrupt-early-interrupt-often">#</a>Interrupt Early, Interrupt Often</h2>
<p>Whenever I see something going wrong, I usually press <kbd>Escape</kbd> to interrupt Claude right away and ask it to revert the latest changes and then guide it more to the direction I want. Of course this only works when you're keeping an eye which I frankly often don't do.</p>
<h2 id="configure-your-terminal"><a class="header-anchor" aria-label="Link to this heading" href="#configure-your-terminal">#</a>Configure Your Terminal</h2>
<p>Claude Code has a hidden <code>/terminal-setup</code> command that will look at your current terminal setup and make some changes like making it possible to use <kbd>Shift+Enter</kbd> to add newlines or register the right bell type. While I wasn't able to figure out the newline thing with Warp, my terminal now beeps whenever Claude needs some human input again.</p>
<h2 id="connect-your-ide"><a class="header-anchor" aria-label="Link to this heading" href="#connect-your-ide">#</a>Connect Your IDE</h2>
<p>Another relatively new feature is that you can connect Claude to see what files you have open in your IDE and read linter warnings, etc. This is set up either when you run <code>claude</code> from within your IDE's terminal, or by running the <code>/ide</code> command. This is handy if you want to tell Claude to "fix my linter issues."</p>
<h2 id="custom-commands"><a class="header-anchor" aria-label="Link to this heading" href="#custom-commands">#</a>Custom Commands</h2>
<p>You can create <a href="https://docs.anthropic.com/en/docs/claude-code/tutorials#create-custom-slash-commands">custom slash commands</a> in Claude Code. If you find yourself writing a similar prompt more than once, this might be your chance to save even more time.</p>
<p>I have something set up to clean up temporary scripts or helper files that Claude is very eager to create, for better or worse.</p>
<h2 id="paste-images-into-claude"><a class="header-anchor" aria-label="Link to this heading" href="#paste-images-into-claude">#</a>Paste Images into Claude</h2>
<p>You can paste in images into Claude Code. Just copy the image directly or drag a file into your Terminal, and it'll be added as an attachment to your next message. I use this sometimes when i want to make small visual changes.</p>
<h2 id="conclusion"><a class="header-anchor" aria-label="Link to this heading" href="#conclusion">#</a>Conclusion</h2>
<p>Claude Code has fundamentally changed how I approach many programming tasks. While it's not perfect and requires adapting your workflow, the productivity gains are real. The key is finding the right balance between automation and human oversight, and being willing to experiment with different approaches.</p>
<p>As AI coding assistants continue to evolve, I expect many of these patterns will change. But for now, these techniques have made my daily coding life significantly more productive and, dare I say, more fun.</p>]]></content:encoded>
      <pubDate>Wed, 11 Jun 2025 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/rise-of-the-product-focused-engineer</guid>
      <title>Rise of the Product-Focused Engineer</title>
      <link>https://spiess.dev/note/engineering/rise-of-the-product-focused-engineer</link>
      <description><![CDATA[An old colleague reached out this week with a familiar frustration. They're overseeing engineering at a growing company and while they've…]]></description>
      <content:encoded><![CDATA[<p>An old colleague reached out this week with a familiar frustration. They're overseeing engineering at a growing company and while they've hired really bright engineers, they spend half their time redirecting them from interesting technical challenges to actual business problems.</p>
<p>This made me realize how common this problem has become—and why it's about to get even more important.</p>
<h2 id="the-product-focused-engineer"><a class="header-anchor" aria-label="Link to this heading" href="#the-product-focused-engineer">#</a>The Product-Focused Engineer</h2>
<p>What they're looking for is what I call the product-focused engineer. These are software engineers with a strong sense of ownership (almost an <em>obsession</em>) over the product areas of the software they're working on.</p>
<p>They're usually characterized by being very proactive with product ideas and opinions, striving for high quality user experiences while being mindful of quick validation cycles. They prefer pragmatic solutions to unblock users and deeply care about making their users successful. I recommend reading <a href="https://blog.pragmaticengineer.com/the-product-minded-engineer/">Gergely Orosz's article</a> if you want to learn more about these roles.</p>
<p>In past organizations that I've been hiring for, it's always been hard to find these engineers that can connect to your user base and strive to build great product experiences. However, individuals with these traits have been far more impactful for the companies than their peers.</p>
<p>In traditional software companies, teams usually consist of very few of these engineers with more general purpose engineers being the main ingredient and product managers making up for it.</p>
<p>However, we're in a world now where more and more software companies are promising to replace engineers with AI agents over the next few years, so naturally, these structures are changing <em>fast</em>. A single product-focused engineer with AI assistance can now prototype, test, and ship features that previously needed many more people.</p>
<h2 id="what-this-means-for-engineering-teams"><a class="header-anchor" aria-label="Link to this heading" href="#what-this-means-for-engineering-teams">#</a>What This Means for Engineering Teams</h2>
<p>I'm writing this now because I think the future of engineering teams will center much more around this archetype. High-agency individuals that are striving to build the best experiences possible while making pragmatic trade-offs. They won't need large teams to do what traditional teams were doing before, but instead they'll leverage the new tools available to them and <em>just ship</em>.</p>
<p>My prediction is that the demand for these engineers will skyrocket. Companies that cling to traditional team structures with layers of handoffs will be outpaced by small teams of product-obsessed builders who just ship.</p>
<p>If you're an engineer reading this, the best investment you can make isn't learning another framework. It's developing that product instinct—talking to users, understanding business metrics, and treating code as a means to an end rather than the end itself.</p>
<p>The engineers who thrive in the next decade won't be the ones writing the most elegant code. They'll be the ones who can't sleep because they know exactly which user problem they're solving tomorrow.</p>]]></content:encoded>
      <pubDate>Sun, 01 Jun 2025 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/effective-writing</guid>
      <title>Effective Writing</title>
      <link>https://spiess.dev/note/engineering/effective-writing</link>
      <description><![CDATA[Clear and comprehensive communication skills are important for [good engineers](https://spiess.dev/note/engineering/great-engineers). Since a…]]></description>
      <content:encoded><![CDATA[<p>Clear and comprehensive communication skills are important for <a href="https://spiess.dev/note/engineering/great-engineers">good engineers</a>. Since a lot of day-to-day communication tends to be in written documents (be it in design docs, issue trackers, or Slack/Discord), I am always striving to be better at it. This is a collection of notes on strategies I've found useful to improve my writing:</p>
<h2 id="1-know-your-audience"><a class="header-anchor" aria-label="Link to this heading" href="#1-know-your-audience">#</a>1. Know Your Audience</h2>
<p>This is a technique that a more senior engineer at Meta shared with my pretty much after the first document I wrote there. The idea is simple: define the kind of person that your writing is for in advance. And then write only for them.</p>
<p>I use this to get an understanding of what kind of previous knowledge I can assume from the audience and will make me avoid falling into the trap of using too much jargon or technical terms when writing for product teams and customers or being too broad and unspecific when writing for engineers.</p>
<p>The second thing I really like about this technique is that it will give you a cue when your writing is addressing an audience that is too broad. I realized this when I wrote project status reports that were read by my peers but also product managers, designers, and leadership. These reports should not be focused on technical details so instead I split up the report into two parts: one for my team and one for the rest of the organization.</p>
<h2 id="2-align-on-a-shared-goal"><a class="header-anchor" aria-label="Link to this heading" href="#2-align-on-a-shared-goal">#</a>2. Align on a Shared Goal</h2>
<p>This is especially helpful whenever you reach out to someone for the first time. Before going deep into the details, give some context and align on a shared goal.</p>
<p>I use this all the time when I reach out to different engineering teams (within the organization or broader community), especially platform teams (so teams working on frameworks and infrastructure). People on these teams have a lot of requests from other teams so aligning to their goals is going to give you the best chance of getting your request prioritized.</p>
<p>What I do is usually a paragraph with the following content:</p>
<ol>
<li>Some context as to what I am trying to do (what is the feature you are building and <em>why</em> is it important)</li>
<li>Connect to their goals (e.g. we want this feature to be reliable, fast, or efficient.)</li>
</ol>
<h2 id="3-follow-a-cohesive-train-of-thought"><a class="header-anchor" aria-label="Link to this heading" href="#3-follow-a-cohesive-train-of-thought">#</a>3. Follow a Cohesive Train of Thought</h2>
<p>Whenever I work on a longer document, I try to come up with one cohesive thread ahead of time that I try to follow with a relatively fixed pacing.</p>
<p>This is a bit hard for me to put in writing since I rely on intuition for this. One example is when I write a proposal for a new feature, I start with setting up the context and goals first, then explain the problem in more detail, then discuss the different solutions considered and narrow down on the proposed one before I conclude with the next steps necessary to move along. I try to give all sections the same amount of detail so that the reader can better orient themselves in the text.</p>
<p>I don't think the actual structure matters too much as long as it's consistent within the piece.</p>
<h2 id="4-avoid-hierarchical-components"><a class="header-anchor" aria-label="Link to this heading" href="#4-avoid-hierarchical-components">#</a>4. Avoid Hierarchical Components</h2>
<p>Do you know the feeling when you read a book and it lingers in your mind for months afterwards? This happened to me this year with <a href="https://spiess.dev/note/other/reading-list">The Courage to Be Disliked</a> by Fumitake Koga and Ichiro Kishimi. This book introduced me to Adlerian psychology and the concept of <em>vertical relationships</em>—relationships with a hierarchical component like boss/employee or teacher/student.</p>
<p>It's hard in a work environment to not have vertical relationships, however I do think that relying on this hierarchy will help you write more effectively. Instead, I try to meet eye-to-eye with the reader and try to write from the same level. Creating a shared goal helps a lot with this.</p>
<h2 id="5-do-one-level-of-homework"><a class="header-anchor" aria-label="Link to this heading" href="#5-do-one-level-of-homework">#</a>5. Do One Level Of Homework</h2>
<p>Whenever I write something, I take a few minutes afterwards to think about the immediate next question that a reader might have and then incorporate the answer into the writing. This works great for any documents but is especially helpful when I need to ask for help as it will sometimes even answer the question that I had in the beginning.</p>
<p>I started to call this "doing homework" and my intention is to save time for the reader and thus improve clarity. I need to work hard to only do this for one level, though, since it can be easy to get distracted this way.</p>
<h2 id="6-keep-it-short-and-simple"><a class="header-anchor" aria-label="Link to this heading" href="#6-keep-it-short-and-simple">#</a>6. Keep It Short and Simple</h2>
<p>I used to be really impressed by Software Engineers that write long prose with lots of fancy vocabulary even if I never fully understood what they were trying to communicate. If I don't understand what they are trying to say and it "sound smart", I concluded that I must be stupid.</p>
<p>This, of course, is nonsense and making your readers feel small is not something to aspire to. Instead, I try to keep my writings as inclusive as possible by focusing on simple and concise wording, even if this means that I have to put in more work.</p>
<h2 id="conclusion"><a class="header-anchor" aria-label="Link to this heading" href="#conclusion">#</a>Conclusion</h2>
<p>Effective written communication is hard and the only real way I found to improve them is by practice. The points above might seem trivial to some but they have not always been trivial for me. Who knows, maybe you can try one of these ideas the next time you write something? If so, I'd love to hear about your experience!</p>]]></content:encoded>
      <pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/great-engineers</guid>
      <title>Great Engineers</title>
      <link>https://spiess.dev/note/engineering/great-engineers</link>
      <description><![CDATA[A friend asked me a great question this week: “Who are great engineers you know and why are they great?”. And while I immediately had a few…]]></description>
      <content:encoded><![CDATA[<p>A friend asked me a great question this week: “Who are great engineers you know and why are they great?”. And while I immediately had a few people in mind, I had a really hard time answering <em>why</em> I think of them as being so great.</p>
<h2 id="the-archetypes"><a class="header-anchor" aria-label="Link to this heading" href="#the-archetypes">#</a>The Archetypes</h2>
<p>All of the people that came to mind were very senior engineers that have very specific areas that they excel in. This is typical for Staff level engineers and it's why it's so hard to compare them to each other and to give Senior engineers a clear path to Staff.</p>
<p>There are numerous attempts to define the different archetypes and if you haven't read Will Larson's <a href="https://staffeng.com/guides/staff-archetypes/">Staff archetypes</a> before, I recommend you look it up right away. I don't think the specific areas are too important though (e.g. there are different definitions across different companies) and I also think in reality, most people are a mix of all of them.</p>
<p>Here are just some of the archetypes that I have worked with:</p>
<ul>
<li><strong>The excitement machine</strong>: This is a person that loves the area they work in so much that just talking to them makes you more enthusiastic. They often would work on a prototype for a new feature that would impress the whole organization. They have a unique skill to extrapolate the current trends into longer term visions and also know how to make the next logical step toward that vision. Their excitement helps to align different functions to work towards the same goals.</li>
<li><strong>The business expert</strong>: This person knows the industry they work in by heart. This allows them to make very good tradeoffs between when to focus on technical excellence and when to focus on the business impact. They are usually working really closely with the customers and the sales team and are able to translate the business needs into technical requirements.</li>
<li><strong>The tech lead</strong>: Having built the companies most complex features before, this person has an extremely deep expertise of the codebase. They have also learned, though, that it's not enough to just code solo and now use their experience to work on more strategical planning and mentoring. They bring alignment to the broader organization and help make sure projects are delivered on time and with the right quality.</li>
<li><strong>The hyperspecialized</strong>: This person is so deeply focused in one area that they start to use lingo that only a few people will understand. They are able to connect new problems to past research and will tell you about this one 1992 paper that they feel is massively undervalued.</li>
</ul>
<h2 id="what-they-all-have-in-common"><a class="header-anchor" aria-label="Link to this heading" href="#what-they-all-have-in-common">#</a>What They All Have In Common</h2>
<p>After thinking of some concrete examples, I was trying to come up with common traits of these folks, too. I wanted to think of something more actionable than "go find your niche". In my experience, great engineers have the following in common:</p>
<ul>
<li><strong>Broad technical knowledge</strong>: Being an engineer still means you are thinking about code and so all these folks are obviously very good at that. Even the <em>hyperspecialized</em> person has excellent mental models of areas around them. They all have a strong base for the <a href="https://en.wikipedia.org/wiki/T-shaped_skills">T-shaped skills model</a>.</li>
<li><strong>Strong communication skills</strong>: When I started out, I never thought how important communication skills are. Just show me some code, everything else is irrelevant, right... right? In reality, this can't be further from the truth. The best engineers I know are able to communicate their ideas in a way that is easy to understand for the target audience. They usually demonstrate their skills by writing technical documents or prepare talks that help shape the direction of the company or industry.</li>
<li><strong>Large networks</strong>: To scale their work beyond themselves, great engineers have built and maintain connections to lots of different people in the organization or the broader industry. They are like Saul Goodman in breaking bad and always <em>"know someone"</em> for whatever problem they face. These networks usually span across the engineering organizations and might contain sales, leadership, or even customers. It takes a village to raise a child, right?</li>
</ul>
<h2 id="the-rest"><a class="header-anchor" aria-label="Link to this heading" href="#the-rest">#</a>The Rest?</h2>
<p>I had a daunting realization after thinking through the above that I know so, so many great people that did not came to my mind at all when I thought about great engineers. This is odd because these are people that I would immediately hire and that definitely cover the common traits listed above. So why have I not thought about them?</p>
<p>The answer I arrived at is <em>scale</em>: These engineers, while being absolutely amazing individually, do not scale their impact beyond only a few people. Which lead me to my final conclusion: <strong>Great engineers have figured out how to scale their work.</strong> They do this by influencing the product/technical direction, upleveling others, being role models or simply giving excellent advice. They are the people you look up to for feedback and that will make you better at what you're doing simply by being around them.</p>]]></content:encoded>
      <pubDate>Sun, 04 Aug 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/blog/im-joining-tailwind-labs</guid>
      <title>I’m Joining Tailwind Labs</title>
      <link>https://spiess.dev/blog/im-joining-tailwind-labs</link>
      <description><![CDATA[This is a very special week for me. Not only is the heatwave in Vienna finally cooling off, but more importantly, it’s my first week joining…]]></description>
      <content:encoded><![CDATA[<p>This is a very special week for me. Not only is the heatwave in Vienna finally cooling off, but more importantly, it’s my first week joining the incredible folks at <a href="https://tailwindcss.com/">Tailwind Labs</a> as a Staff Software Engineer ✨.</p>
<p>Since I started making web apps, many technologies have caused major shifts in how I think about the platform. I began my freelancing projects with a custom PHP stack, but Ruby on Rails and Bootstrap allowed me to ship much faster. I felt a similar empowerment when I started using modern front-end libraries like React and later, full-blown frameworks like Next.js together with TypeScript.</p>
<p>My most recent shift happened when I fully embraced Tailwind CSS for some of my projects. It took some getting used to, but it was incredibly fun to create experiences with it. The success of the Tailwind CSS project, however, proves that I’m not the only one who feels this way!</p>
<p>Because of this, I am beyond excited to join the fantastic team at Tailwind Labs. Together, we’ll make building for the web even more fun and help shape the future of the platform. It’s only getting better from here on.</p>]]></content:encoded>
      <pubDate>Tue, 23 Jul 2024 12:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/life/learning-is-bidirectional</guid>
      <title>Learning is Bidirectional</title>
      <link>https://spiess.dev/note/life/learning-is-bidirectional</link>
      <description><![CDATA[I remember a situation from early on in my career where I asked my more senior coworker for some help. They came to my desk and showed me how…]]></description>
      <content:encoded><![CDATA[<p>I remember a situation from early on in my career where I asked my more senior coworker for some help. They came to my desk and showed me how things are done. Full of awe, I reached out to them afterward and told them: "Thank you so much for showing me this, I learned a ton from you!". To my surprise, they responded with: "No worries! I learned a lot from you too!"</p>
<p>This was really confusing to me. The more senior folks are supposed to be the teachers, and I am <em>just</em> the student. What is there to learn from me?</p>
<p>However, being fortunate to have been on the other end, I now <em>get it</em>.</p>
<p>Good teachers are not the ones who only preach their own way of thinking. Instead, they will be your peers when exploring the problem and will solve it <em>with</em> you, not <em>for</em> you. In doing so, they will pick up your way of articulating the problem, your way of thinking about the world, your way of approaching the problem, and so much more.</p>
<p>Learning, at its best, is a bidirectional relationship away from any hierarchical structure. Everyone gets better as a result of it.</p>]]></content:encoded>
      <pubDate>Thu, 13 Jun 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/the-50-50-goal</guid>
      <title>The 50-50 Goal</title>
      <link>https://spiess.dev/note/engineering/the-50-50-goal</link>
      <description><![CDATA[Large companies are inherently number-driven. This is not a bad thing per se: Aligning around measurable objectives has huge benefits and…]]></description>
      <content:encoded><![CDATA[<p>Large companies are inherently number-driven. This is not a bad thing per se: Aligning around measurable objectives has huge benefits and allows an organization to keep itself accountable.</p>
<p>However, if you work in larger tech companies, you will quickly come into contact with goals like this:</p>
<ul>
<li>"We are working on improving our sign-up rate by 20% in the next quarter."</li>
<li>"The goal of the platform rewrite is to reduce defect rate by 50%."</li>
</ul>
<p>These numbers seem fine at first but once you start digging into it, you learn how extremely arbitrary these are, because:</p>
<ul>
<li>It is entirely unclear to the team how to increase sign-up rate at all.</li>
<li>The platform rewrite is going to increase reliability, but there is no certainty into how it will affect the metrics.</li>
</ul>
<p>When I worked on web performance at Meta, I came into contact with this very early and my rational personality was quickly overwhelmed: How can I come up with a number that is truly achievable? Do I need to scope out all exact work streams ahead of time and hope that they are all successful? How are goals like this made?</p>
<p>Luckily, I had an amazing colleague back then who told me the one secret about goals at Meta, which she called the 50-50 goal: Choose a target that feels achievable 50% of the time (so you will have an equal chance of not hitting it). At the time this was confusing but I've seen this used extensively at many platform teams at Meta and only now, after many years, I finally understand why this system works:</p>
<ul>
<li>50-50 goals are about embracing failure: If there's an equal chance of you missing the goal, you <em>set yourself up for failure</em>. And, in this case, that's a good thing! No one actually cared whether the sign-up rate increased by 20%. It's important that it increases <em>at all</em>. The trick is to remove the psychological burden of missing a goal and instead focus on actually driving these goals.</li>
<li>These goals help with alignment: Setting an ambitious target will make it clear to everyone in the room that they need to get creative. It embraced risk-taking and encourages people to think outside the box.</li>
<li>It creates excitement across the organization: By choosing ambitious goals, you will more effectively communicate the goal of the project. This helps to get buy-in from other teams and stakeholders and sets the stage for better collaboration.</li>
</ul>
<p>So, if you ever think about setting a target for your team, think about the 50-50 goal. Oh and don't forget to communicate that it's a 50-50 goal. You don't want to set wrong expectations.</p>]]></content:encoded>
      <pubDate>Fri, 17 May 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/life/hard-choices</guid>
      <title>Hard Choices</title>
      <link>https://spiess.dev/note/life/hard-choices</link>
      <description><![CDATA[When faced with hard choices, I'm often tempted to search for _the better option_ as if it's hiding just beneath the surface. But most of the…]]></description>
      <content:encoded><![CDATA[<p>When faced with hard choices, I'm often tempted to search for <em>the better option</em> as if it's hiding just beneath the surface. But most of the time, there isn't one. Hard choices are hard not because they're unsolvable puzzles, but because each option carries its own set of values and complexities that defy simple comparison.</p>
<h2 id="most-choices-are-not-permanent"><a class="header-anchor" aria-label="Link to this heading" href="#most-choices-are-not-permanent">#</a>Most Choices Are Not Permanent</h2>
<p>When standing before a hard choice, it's easy to fall into the trap of seeing it as a permanent, life-altering event. This type of thinking has had a huge impact on me previously and made me spiral down the "thinking-train" even more. <em>If I think just a bit more, I might avoid making a big mistake</em>.</p>
<p>In reality, however, most choices are not permanent and it's important to keep that in mind when facing a hard choice. If it's only a temporary choice, it doesn't have to be the best choice, does it?</p>
<h2 id="the-false-dichotomy"><a class="header-anchor" aria-label="Link to this heading" href="#the-false-dichotomy">#</a>The False Dichotomy</h2>
<p>We often box ourselves into thinking there is only option A or B. But what if neither option is the right choice at the moment? Take the example of moving to a new house—if neither option feels quite right, perhaps the best choice is to wait.</p>
<p>Decision-making is not just about choosing between given options, though. Instead, we have the power to actively shape those options and the reasons behind our choice.</p>
<p>Not choosing now doesn't prevent you from making a similar choice in the future.</p>
<h2 id="the-gut-feeling"><a class="header-anchor" aria-label="Link to this heading" href="#the-gut-feeling">#</a>The "Gut Feeling"</h2>
<p>I have a bit of a gripe with this term because while my gut does many great things, it certainly doesn't have feelings. It took me long to understand what is meant by it but I now think a better term would be <em>emotional feeling</em>.</p>
<p>If you have no rational explanation as to why an option is better or worse than the other, but you have a strong emotional feeling, don't undervalue this signal. Emotions have, after all, made humans as successful as they are today.</p>
<h2 id="embrace-the-complexity"><a class="header-anchor" aria-label="Link to this heading" href="#embrace-the-complexity">#</a>Embrace the Complexity</h2>
<p>If you haven't seen it, I highly recommend you check out <a href="https://www.youtube.com/watch?v=8GQZuzIdeQQ">Ruth Chang's talk on how to make hard choices</a>. It has highly influenced me in this area. The core takeaway I have from this talk is that I now start to embrace the complexity of hard choices in life and see them as opportunity to steer my life's narrative towards who I truly want to be.</p>
<p>It's extremely freeing to not think of hard choices as confining moments but as rare opportunities that you can use to grow.</p>]]></content:encoded>
      <pubDate>Thu, 02 May 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/other/reading-list</guid>
      <title>Reading List</title>
      <link>https://spiess.dev/note/other/reading-list</link>
      <description><![CDATA[You can find me on [Goodreads](https://www.goodreads.com/user/show/50192165-philipp) for my up-to-date reading list. In the past months, I…]]></description>
      <content:encoded><![CDATA[<p>You can find me on <a href="https://www.goodreads.com/user/show/50192165-philipp">Goodreads</a> for my up-to-date reading list. In the past months, I really enjoyed science fiction. Let me know if you have any recommendations!</p>
<p>Here are a few books I rated 5 stars recently:</p>
<ul>
<li><a href="https://www.goodreads.com/book/show/20518872-the-three-body-problem">The Three-Body Problem</a> by Cixin Liu</li>
<li><a href="https://www.goodreads.com/book/show/16158581-the-collini-case">The Collini Case</a> by Ferdinand von Schirach</li>
<li><a href="https://www.goodreads.com/book/show/54493401-project-hail-mary">Project Hail Mary</a> by Andy Weir</li>
<li><a href="https://www.goodreads.com/book/show/41160292-exhalation">Exhalation</a> by Ted Chiang</li>
<li><a href="https://www.goodreads.com/book/show/77566.Hyperion">Hyperion</a> by Dan Simmons</li>
<li><a href="https://www.goodreads.com/book/show/112537.Rendezvous_with_Rama">Rendezvous with Rama</a> by Arthur C. Clarke</li>
<li><a href="https://www.goodreads.com/book/show/43306206-the-courage-to-be-disliked">The Courage to Be Disliked: How to Free Yourself, Change your Life and Achieve Real Happiness</a> by Fumitake Koga and Ichiro Kishimi</li>
<li><a href="https://www.goodreads.com/book/show/61714633-the-wager">The Wager: A Tale of Shipwreck, Mutiny and Murder</a> by David Grann</li>
<li><a href="https://www.goodreads.com/book/show/18373.Flowers_for_Algernon">Flowers for Algernon</a> by Daniel Keyes</li>
<li><a href="https://www.goodreads.com/book/show/48484.Blindsight">Blindsight</a> by Peter Watts</li>
</ul>
<p><em>Last update: Aug 11, 2024</em></p>]]></content:encoded>
      <pubDate>Wed, 03 Apr 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/infrastructure/deploy-workers-programatically</guid>
      <title>Deploy Workers Programatically</title>
      <link>https://spiess.dev/note/engineering/infrastructure/deploy-workers-programatically</link>
      <description><![CDATA[You can deploy Cloudflare Workers via the HTTP API and thus dynamically create Workers. The canonical solution for this is to use [Workers for…]]></description>
      <content:encoded><![CDATA[<p>You can deploy Cloudflare Workers via the HTTP API and thus dynamically create Workers. The canonical solution for this is to use <a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/">Workers for Platforms</a> however you can also do it with a free Cloudflare account. Documentation for this feature is sparse but thanks to <a href="https://community.cloudflare.com/t/deploy-a-worker-via-http-api/108439/4">this Cloudflare Community post</a> I came up with this:</p>
<p><strong>Update:</strong> Well, turns out there is actually quite good documentation on that API in <a href="https://developers.cloudflare.com/api/operations/worker-script-upload-worker-module">the Cloudflare API Docs</a> 🙃.</p>
<h3 id="1-bundle-your-worker"><a class="header-anchor" aria-label="Link to this heading" href="#1-bundle-your-worker">#</a>1. Bundle your Worker</h3>
<p>If you already have <code>wrangler</code> set up, you can bundle your worker by running <code>wrangler build</code>. This will build the worker into the <code>dist/</code> folder.</p>
<h3 id="2-prepare-your-metadata"><a class="header-anchor" aria-label="Link to this heading" href="#2-prepare-your-metadata">#</a>2. Prepare your Metadata</h3>
<p>Worker metadata is a configuration format that contains the runtime info from your <code>wrangler.toml</code> file. You can learn more about it <a href="https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/reference/metadata/">in the Worker metadata docs</a>.</p>
<p>Without any bindings, the minimum metadata might look like this:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#666666">{</span><span style="color:#B8A96577">"</span><span style="color:#B8A965">main_module</span><span style="color:#B8A96577">"</span><span style="color:#666666">:</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">worker.js</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#B8A96577">"</span><span style="color:#B8A965">bindings</span><span style="color:#B8A96577">"</span><span style="color:#666666">:[]}</span></span></code></pre>
<h2 id="3-upload-your-worker"><a class="header-anchor" aria-label="Link to this heading" href="#3-upload-your-worker">#</a>3. Upload your Worker</h2>
<p>Now upload your worker:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#80A665">curl</span><span style="color:#C99076"> -X</span><span style="color:#C98A7D"> PUT</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">https://api.cloudflare.com/client/v4/accounts/&#x3C;ACCOUNT_ID>/workers/scripts/&#x3C;SCRIPT_NAME></span><span style="color:#C98A7D77">"</span><span style="color:#C99076"> \</span></span>
<span class="line"><span style="color:#C99076">    -H</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">Authorization: Bearer &#x3C;CF_API_TOKEN></span><span style="color:#C98A7D77">"</span><span style="color:#C99076"> \</span></span>
<span class="line"><span style="color:#C99076">    -H</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">Content-Type: multipart/form-data</span><span style="color:#C98A7D77">"</span><span style="color:#C99076"> \</span></span>
<span class="line"><span style="color:#C99076">    --form</span><span style="color:#C98A7D77"> '</span><span style="color:#C98A7D">worker.js=@"dist/worker.js";type=application/javascript+module</span><span style="color:#C98A7D77">'</span><span style="color:#C99076"> \</span></span>
<span class="line"><span style="color:#C99076">    --form</span><span style="color:#C98A7D77"> '</span><span style="color:#C98A7D">metadata="{\"main_module\":\"worker.js\",\"bindings\":[]}"</span><span style="color:#C98A7D77">'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#666666">{</span></span>
<span class="line"><span style="color:#80A665">  "result"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D"> {</span></span>
<span class="line"><span style="color:#80A665">    "created_on"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">2024-04-02T21:27:40.527498Z</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "modified_on"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">2024-04-02T21:27:40.527498Z</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "id"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">&#x3C;SCRIPT_NAME></span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "tag"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">aabbcc</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "tags"</span><span style="color:#B8A965">:</span><span style="color:#DBD7CAEE"> [],</span></span>
<span class="line"><span style="color:#80A665">    "deployment_id"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">aabbcc</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "tail_consumers"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D"> null,</span></span>
<span class="line"><span style="color:#80A665">    "logpush"</span><span style="color:#B8A965">:</span><span style="color:#4D9375"> false</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "etag"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">aabbcc</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "handlers"</span><span style="color:#B8A965">:</span><span style="color:#DBD7CAEE"> [</span></span>
<span class="line"><span style="color:#80A665">      "fetch"</span></span>
<span class="line"><span style="color:#DBD7CAEE">    ],</span></span>
<span class="line"><span style="color:#80A665">    "last_deployed_from"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">api</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">    "usage_model"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">standard</span><span style="color:#C98A7D77">"</span></span>
<span class="line"><span style="color:#666666">  }</span><span style="color:#DBD7CAEE">,</span></span>
<span class="line"><span style="color:#80A665">  "success"</span><span style="color:#B8A965">:</span><span style="color:#4D9375"> true</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">  "errors"</span><span style="color:#B8A965">:</span><span style="color:#DBD7CAEE"> [],</span></span>
<span class="line"><span style="color:#80A665">  "messages"</span><span style="color:#B8A965">:</span><span style="color:#DBD7CAEE"> []</span></span>
<span class="line"><span style="color:#DBD7CAEE">}</span></span></code></pre>
<p>Notes on the params:</p>
<ul>
<li><code>ACCOUNT_ID</code> can be found in the Cloudflare dashboard URL</li>
<li><code>SCRIPT_NAME</code> is the name of your new worker and will be part of the default subdomain</li>
<li><code>CF_API_TOKEN</code> must be an <a href="https://dash.cloudflare.com/profile/api-tokens">Edit Cloudflare Workers API token</a></li>
</ul>
<h3 id="4-publish-the-worker-via-subdomain"><a class="header-anchor" aria-label="Link to this heading" href="#4-publish-the-worker-via-subdomain">#</a>4. Publish the Worker via Subdomain</h3>
<p>The last step is to publish the Worker to be accessible via subdomain. The domain for this will depend on the <code>workers.dev</code> subdomain assigned for your Cloudflare account, e.g.: <code>&#x3C;SCRIPT_NAME>.myapp.workers.dev</code>.</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#80A665">curl</span><span style="color:#C99076"> -X</span><span style="color:#C98A7D"> POST</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">https://api.cloudflare.com/client/v4/accounts/&#x3C;ACCOUNT_ID>/workers/services/&#x3C;SCRIPT_NAME>/environments/production/subdomain</span><span style="color:#C98A7D77">"</span><span style="color:#C99076"> \</span></span>
<span class="line"><span style="color:#C99076"> -H</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">Authorization: Bearer $CF_API_TOKEN</span><span style="color:#C98A7D77">"</span><span style="color:#C99076"> \</span></span>
<span class="line"><span style="color:#C99076"> -H</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">Content-Type: application/json</span><span style="color:#C98A7D77">"</span><span style="color:#C99076"> \</span></span>
<span class="line"><span style="color:#C99076"> -d</span><span style="color:#C98A7D77"> '</span><span style="color:#C98A7D">{"enabled": true}</span><span style="color:#C98A7D77">'</span></span>
<span class="line"><span style="color:#666666">{</span></span>
<span class="line"><span style="color:#80A665">  "result"</span><span style="color:#B8A965">:</span><span style="color:#C98A7D"> null,</span></span>
<span class="line"><span style="color:#80A665">  "success"</span><span style="color:#B8A965">:</span><span style="color:#4D9375"> true</span><span style="color:#C98A7D">,</span></span>
<span class="line"><span style="color:#80A665">  "errors"</span><span style="color:#B8A965">:</span><span style="color:#DBD7CAEE"> [],</span></span>
<span class="line"><span style="color:#80A665">  "messages"</span><span style="color:#B8A965">:</span><span style="color:#DBD7CAEE"> []</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>]]></content:encoded>
      <pubDate>Tue, 02 Apr 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/vs-code/profiling-extension-in-production</guid>
      <title>Profiling Extension in Production</title>
      <link>https://spiess.dev/note/engineering/vs-code/profiling-extension-in-production</link>
      <description><![CDATA[The VS Code extension runtime uses Node.js (via Electron) and you can thus use Node.js related debugging tool.

Here's a quick guide on how to…]]></description>
      <content:encoded><![CDATA[<p>The VS Code extension runtime uses Node.js (via Electron) and you can thus use Node.js related debugging tool.</p>
<p>Here's a quick guide on how to record performances traces of VS Code extensions:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#80A665">/Applications/Visual\</span><span style="color:#C98A7D"> Studio</span><span style="color:#C99076">\ </span><span style="color:#C98A7D">Code.app/Contents/MacOS/Electron</span><span style="color:#C99076"> --inspect-extensions=9333</span></span></code></pre>
<p>After VS Code is started, you want to head over to Chrome and go to <code>chrome://inspect</code> which should open up a view like this:</p>
<p><img src="https://gist.github.com/assets/458591/0a17881b-5449-48d5-a53e-5556f4f2dedd" alt="tracing overview"></p>
<p>From here you want to configure the inspect server that we just started on port 9333. To do this, click on "Open dedicated DevTools for Node", go to the "Connection" tab if that is not open yet, and make sure to add <code>localhost:9333</code> to the list like this:</p>
<p><img src="https://gist.github.com/assets/458591/972ce113-88f0-482a-99b7-5e51957981ef" alt="add localhost to the list"></p>
<p>Now, head back to the <code>chrome://inspect</code> tab and you should see a new remote target that you can inspect. For me this looks like this:</p>
<p><img src="https://gist.github.com/assets/458591/06b2e293-aea7-42e8-a9cc-592863b6fb07" alt="tracing overview with a new electron option"></p>
<p>Clicking this will open a (somewhat reduced) DevTools view. Great! We've almost got it. From here you can go to the "Performance" tab to record a trace. Then, swap tabs to the VS Code window and interact with the extension. Come back later to stop the recording and export it. Voila!</p>
<p><img src="https://gist.github.com/assets/458591/d590636b-31e5-4436-8039-ee62b7a8e59f" alt="screenshot of the performance tab in the nodejs profiler"></p>]]></content:encoded>
      <pubDate>Thu, 07 Mar 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/feature-flags</guid>
      <title>Feature Flags</title>
      <link>https://spiess.dev/note/engineering/feature-flags</link>
      <description><![CDATA[Feature flags are a powerful technique that every engineer should have in their toolbox. They allow you to ship changes incrementally and…]]></description>
      <content:encoded><![CDATA[<p>Feature flags are a powerful technique that every engineer should have in their toolbox. They allow you to ship changes incrementally and reduce risk when releasing new features. Here are my thoughts on why you should use feature flags:</p>
<ul>
<li>
<p><strong>Avoid big bang releases:</strong> With feature flags, you can push out changes regularly without affecting all users at once. This makes it easier to review pull requests since the changes are smaller. No more waiting days or weeks to merge in order to batch changes.</p>
</li>
<li>
<p><strong>Reduce risk:</strong> New features can be released to a small percentage of users first. If issues come up, simply turn off the flag before rolling out widely. This protects your whole user base from potentially breaking changes.</p>
</li>
<li>
<p><strong>Enable experimentation:</strong> By targeting feature flags to different user segments, you can run A/B tests and collect data on how new features are performing. This allows you to validate ideas before fully releasing (and allows you to collect awesome graphs for your next review period 😉).</p>
</li>
<li>
<p><strong>Simplify rollbacks:</strong> If a new feature causes problems after being released, you can instantly roll back by disabling the flag (often referred to as a kill switch). This is especially handy if you can remotely control those flags.</p>
</li>
<li>
<p><strong>Improve code hygiene:</strong> Make sure to clean up unused flags over time. Centralize flags in one place like an enum to avoid combinatorial complexity.</p>
</li>
</ul>
<p>Feature flags don't have to be complex, either. Something as simple as a config boolean can get you started. And you don't necessarily need third-party tools for simple use cases. Just try it out on your next feature branch!</p>]]></content:encoded>
      <pubDate>Wed, 21 Feb 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/demo-culture</guid>
      <title>Demo Culture</title>
      <link>https://spiess.dev/note/engineering/demo-culture</link>
      <description><![CDATA[Good engineers _ship_. That's no big secret. Shipping is, after all, one of the very core pillars of the engineering culture and celebrated a…]]></description>
      <content:encoded><![CDATA[<p>Good engineers <em>ship</em>. That's no big secret. Shipping is, after all, one of the very core pillars of the engineering culture and celebrated a lot.</p>
<p>But have you ever noticed that great engineers ship <em>all the time</em>? They ship, even if the product is not updating. They ship by sharing quick demos and building excitement and urgency by sharing their prototypes with other engineers. I started to refer to this as "demo culture" and it's something you see to great extend in high performing teams.</p>
<p>Demo culture is the practice of regularly sharing quick demos to build hype and validate ideas in the cheapest possible way. It's a way of showing off your work and getting feedback from your team and stakeholders without all the red tape. It's a way to make every team more engineering driven.</p>
<p>So how can you start building a demo culture in your team? Here are a few tips:</p>
<ol>
<li><strong>Share early and often.</strong> Someone needs to start setting an example. If you can hack something together 30. The more brittle, the better, so others understand that there's no need for quality.</li>
<li><strong>Make it fun.</strong> Demos should be engaging and exciting. I like to create little stories of potential use cases to lighten the mood.</li>
<li><strong>Keep it short and sweet.</strong> Demos should be quick and to the point. If it's over 2 minutes, it's probably too long. Make sure your audience has your attention and make it easy for them to consume the information.</li>
<li><strong>Encourage and provide feedback.</strong> After your demo, ask for feedback and suggestions. This will help you improve your work and make sure you're on the right track. Similarly, if someone else gives a demo, make sure to share your thought. And do not forget to show them how excited you are about the demo. 🔥</li>
</ol>
<p>So, the next time your meeting ends 5 minutes early, just share your screen and say "hey I have a quick demo to show you" and start a transformation in your team.</p>]]></content:encoded>
      <pubDate>Fri, 16 Feb 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/feelscraft-software</guid>
      <title>Feelscraft Software</title>
      <link>https://spiess.dev/note/engineering/feelscraft-software</link>
      <description><![CDATA[If you've never come across it, I highly recommend you read up on [Theorycraft](https://en.wikipedia.org/wiki/Theorycraft). Because *of course*…]]></description>
      <content:encoded><![CDATA[<p>If you've never come across it, I highly recommend you read up on <a href="https://en.wikipedia.org/wiki/Theorycraft">Theorycraft</a>. Because <em>of course</em> there exist a huge subculture in the internet that is trying to mathematically optimize video games and <em>of course</em> I’m going to start my article with it.</p>
<p>Whenever arguments in video game Discord chats arise about how to optimally play in a certain scenario, people would cite the research made by Theorycraft. But sometimes people insist that playing in a different way is better. Well, you know the internet folks, these people will be called <em>words</em>. One of the words that stuck with me was "Feelscraft". Huh, what an insult!</p>
<p>But what is Feelscraft software? Well, for me it is acting on intuition and experience rather than data and common knowledge when making product decisions. It is shipping a feature even though you do not have the metrics to back it up. And, as usual, I have ambivalent feelings about it.</p>
<p>Much like the Theorycraft folks in the video game scene, I strive to make the best product choices. Working on the most requested feature that is also sure to increase revenue and that has measurable impact on our user base. And, in a perfect world, we also have all the data necessary to make these decisions and there is only one valid way to solve a problem to begin with. However, in reality, this is hardly the case and this is when Feelcraft comes in handy.</p>
<p>I do think that intuition is often overused, though. When it's used, it's <em>very</em> hard to understand why certain choices were done for people outside of the decision metrics process. One example of Feelscraft Software is from my time at Meta when I read a product announcement that stated that a new implementation of an existing feature is being shipped because <em>it shows no regression in usage metrics over the old one</em>. And that after weeks and weeks of improvements on the new implementation... I think that's a low standard for pushing a new product, don't you think?</p>
<p>Another more recent example that I have seen are numerous changes of an LLM prompt in the hope to improve the product. LLMs are inherently prone to variation in their outcome and making decisions on whether a prompt is better than another normally requires <em>thousands</em> and more different samples. That's why we at <a href="https://sourcegraph.com/cody">Cody</a> are investing so heavily into online and offline evaluation methods. Yet, every now and then I see the industry making changes on prompts based on <em>intuition</em>.</p>
<p>I have been guilty, too, of course! In my most recent work on <a href="https://sourcegraph.com/blog/the-lifecycle-of-a-code-ai-completion">Cody autocomplete</a> I have often made subtle decisions in the because it <em>felt better</em>. Things like setting initial debounce times too low or cut-off ratios too aggressive. However, we had to get the product out in some way or another and only after getting usage we were able to validate these initial assumptions and make <em>informed decisions</em> (which, of course, resulted in my tweaking these values later).</p>
<p>So, the next time you try to understand how a software decision has been made, remember to think about the Feelcraft behind it. 😉</p>]]></content:encoded>
      <pubDate>Thu, 01 Feb 2024 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://sourcegraph.com/blog/the-lifecycle-of-a-code-ai-completion</guid>
      <title>The Lifecycle of a Code AI Completion</title>
      <link>https://sourcegraph.com/blog/the-lifecycle-of-a-code-ai-completion</link>
      <description><![CDATA[Generative AI, whether for code, text, images, or other use cases, appears as a magic black box to many users. Users typically navigate to a…]]></description>
      <content:encoded><![CDATA[<p>Generative AI, whether for code, text, images, or other use cases, appears as a magic black box to many users. Users typically navigate to a website, install an app, or set up an extension and start seeing the results of the AI tool. But, have you ever wondered what goes into this magic black box or how it really works?</p>]]></content:encoded>
      <pubDate>Tue, 31 Oct 2023 12:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/ai/embeddings</guid>
      <title>Embeddings</title>
      <link>https://spiess.dev/note/engineering/ai/embeddings</link>
      <description><![CDATA[Understanding and processing natural language is a complex task. Embeddings are a powerful tool in todays ML/AI discussions to address this…]]></description>
      <content:encoded><![CDATA[<p>Understanding and processing natural language is a complex task. Embeddings are a powerful tool in todays ML/AI discussions to address this problem.</p>
<h2 id="mental-model"><a class="header-anchor" aria-label="Link to this heading" href="#mental-model">#</a>Mental model</h2>
<p>In my mental model, embeddings are used to encode words into <em>meaning</em>. Embeddings are encodings in n-dimensional vector space where every parameter is a signed a specific <em>meaning</em> in the object space.</p>
<p>I can't really imagine n-dimensional vectors but for an embedding system with two vectors, I like to visualize it like this:</p>
<pre class="shiki vitesse-dark claude-plaintext" style="background-color:#121212;color:#dbd7ca !important" tabindex="0"><code><span class="line"><span>terrain (y-axis)</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>|             o (mountain bike)</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>|                       o (road bike)</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>|      o (horse)</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>|                                    o (car)</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>|</span></span>
<span class="line"><span>+------------------------------- speed (x-axis)</span></span></code></pre>
<p>In this example, each point represents a mode of transportation. Items with similar speed and terrain capabilities are placed closer together. For example, a mountain bike and a road bike are closer to each other because they have similar speeds and are both used on different terrains, while a car is further away since it operates at higher speeds on more paved terrains.</p>
<p>There are two super-powers from being able to map natural language onto an n dimensional vector space:</p>
<ul>
<li>We can use relatively simple vector algorithms to quickly find relevant words. Algorithms like <a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm">kNN</a> are easy to be optimized with spacial maps so lookup is fast. In our example, we can see that a <em>mountain bike</em> is closer to a <em>road bike</em> than it is to a horse.</li>
<li>Embeddings compress insanely well. It's entirely possible to compress <em>pages</em> of written language into a vector size of a few hundred vectors.</li>
</ul>
<p>There are also some downsides, though:</p>
<ul>
<li>Training an embedding system is like training any other ML system: It's a complex and expensive task. And even with a fully trained model, you will need a lot of compute power to convert a piece of text into embeddings. That's why it's currently common to outsource embeddings generation to third-party APIs like OpenAI.</li>
<li>When a model is trained, we don't really know what <em>meaning</em> each value of the vector is assigned to. This makes it hard to understand why a specific pair of text is considered close to each other or not.</li>
</ul>
<h2 id="how-are-embeddings-used"><a class="header-anchor" aria-label="Link to this heading" href="#how-are-embeddings-used">#</a>How are embeddings used?</h2>
<p>Because of the fast way to find related vector pairs, embeddings are often used to answer search problems. In the current AI discourse, embeddings are used to quickly and efficiently find relevant context in order to put it into the prompt of a large-language model (LLM).</p>
<p>Imagine an AI system to ask questions about your code documentation. We can, ahead of time, create embeddings for every paragraph in the documentation and store it in a vector database (a database that can hold the embedding vectors for us and has some index to efficiently query it. Like the <a href="https://github.com/pgvector/pgvector">pgvector</a> extension for Postgres).</p>
<p>Now, when the user enters a question in natural text into our AI system, we can find the most relevant paragraphs into our documentation. We usually want to pick as many as we can fit into the prompt of the LLM. The LLM now has an <em>understanding of your documentation</em> when answering the question. Fascinating!</p>
<p>One important thing from the paragraphs above is that we have to <em>split</em> the documentation into meaningful chunks. Since we can only retrieve similarity in windows of this chunk site, you can easily see how important it is to pick a proper system.</p>
<h2 id="some-links-i-want-to-remember"><a class="header-anchor" aria-label="Link to this heading" href="#some-links-i-want-to-remember">#</a>Some links I want to remember</h2>
<ul>
<li><a href="https://huggingface.co/blog/getting-started-with-embeddings">Getting Started With Embeddings</a></li>
<li><a href="https://huggingface.co/blog/mteb">MTEB: Massive Text Embedding Benchmark</a></li>
<li><a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB Leaderboard</a></li>
</ul>]]></content:encoded>
      <pubDate>Fri, 05 May 2023 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/ai/jaccard-similarity</guid>
      <title>Jaccard Similarity</title>
      <link>https://spiess.dev/note/engineering/ai/jaccard-similarity</link>
      <description><![CDATA[When building an AI code assistant like [Cody](https://about.sourcegraph.com/cody), you often find yourself in need for a quick way to rank the…]]></description>
      <content:encoded><![CDATA[<p>When building an AI code assistant like <a href="https://about.sourcegraph.com/cody">Cody</a>, you often find yourself in need for a quick way to rank the similarity of two blocks of text relative to each other.</p>
<p>This is where <a href="https://en.wikipedia.org/wiki/Jaccard_index">Jaccard similarity</a> is helpful. Jaccard similarity is a metric to find similarity between two sets of data, typically used for comparing text data.</p>
<p>The equation for Jaccard distance is:</p>
<pre class="shiki vitesse-dark claude-plaintext" style="background-color:#121212;color:#dbd7ca !important" tabindex="0"><code><span class="line"><span>J(A, B) = |A ∩ B| / |A ∪ B|</span></span></code></pre>
<p>Here's a simple example in TypeScript to calculate the Jaccard distance between two sentences:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> jaccardDistance</span><span style="color:#666666">(</span><span style="color:#BD976A">sentence1</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">,</span><span style="color:#BD976A"> sentence2</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">):</span><span style="color:#5DA994"> number</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#CB7676">  const </span><span style="color:#BD976A">set1</span><span style="color:#666666"> =</span><span style="color:#CB7676"> new </span><span style="color:#80A665">Set</span><span style="color:#666666">(</span><span style="color:#BD976A">sentence1</span><span style="color:#666666">.</span><span style="color:#80A665">split</span><span style="color:#666666">(</span><span style="color:#C98A7D77">'</span><span style="color:#C98A7D77"> '</span><span style="color:#666666">));</span></span>
<span class="line"><span style="color:#CB7676">  const </span><span style="color:#BD976A">set2</span><span style="color:#666666"> =</span><span style="color:#CB7676"> new </span><span style="color:#80A665">Set</span><span style="color:#666666">(</span><span style="color:#BD976A">sentence2</span><span style="color:#666666">.</span><span style="color:#80A665">split</span><span style="color:#666666">(</span><span style="color:#C98A7D77">'</span><span style="color:#C98A7D77"> '</span><span style="color:#666666">));</span></span>
<span class="line"><span style="color:#DBD7CAEE">  </span></span>
<span class="line"><span style="color:#CB7676">  const </span><span style="color:#BD976A">intersection</span><span style="color:#666666"> =</span><span style="color:#CB7676"> new </span><span style="color:#80A665">Set</span><span style="color:#666666">([...</span><span style="color:#BD976A">set1</span><span style="color:#666666">].</span><span style="color:#80A665">filter</span><span style="color:#666666">(</span><span style="color:#BD976A">x</span><span style="color:#666666"> =></span><span style="color:#BD976A"> set2</span><span style="color:#666666">.</span><span style="color:#80A665">has</span><span style="color:#666666">(</span><span style="color:#BD976A">x</span><span style="color:#666666">))).</span><span style="color:#BD976A">size</span><span style="color:#666666">;</span></span>
<span class="line"><span style="color:#CB7676">  const </span><span style="color:#BD976A">union</span><span style="color:#666666"> =</span><span style="color:#CB7676"> new </span><span style="color:#80A665">Set</span><span style="color:#666666">([...</span><span style="color:#BD976A">set1</span><span style="color:#666666">,</span><span style="color:#666666"> ...</span><span style="color:#BD976A">set2</span><span style="color:#666666">]).</span><span style="color:#BD976A">size</span><span style="color:#666666">;</span></span>
<span class="line"><span style="color:#DBD7CAEE">  </span></span>
<span class="line"><span style="color:#4D9375">  return</span><span style="color:#BD976A"> intersection</span><span style="color:#CB7676"> /</span><span style="color:#BD976A"> union</span><span style="color:#666666">;</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>The Jaccard similarity comes in handy for finding similar text, like detecting plagiarism or comparing documents. It might not be the most advanced method for text comparison, but it's simple and fast, making it great for many applications.</p>
<p>You can even do some pre-processing on the text to increase its accuracy. The above example is naively comparing words but you could use <a href="https://en.wikipedia.org/wiki/Stemming">stemming</a> to make it easier to compare words with the same meaning.</p>
<p>Why not give it a try with some sample texts and see how it works for you?</p>]]></content:encoded>
      <pubDate>Thu, 04 May 2023 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/blog/building-a-scheme-mapper-in-typescript</guid>
      <title>Building a Schema Mapper in TypeScript</title>
      <link>https://spiess.dev/blog/building-a-scheme-mapper-in-typescript</link>
      <description><![CDATA[I've recently been working more with structured logging and as part of this, I wanted to build a TypeScript scheme mapper for creating table…]]></description>
      <content:encoded><![CDATA[<p>I've recently been working more with structured logging and as part of this, I wanted to build a TypeScript scheme mapper for creating table definitions.</p>
<p>Inspired by <a href="https://github.com/drizzle-team/drizzle-orm">Drizzle ORM</a> and <a href="https://github.com/colinhacks/zod">Zod</a>, my goal was to create the table definitions fully in TypeScript code so that I can generate the schema by simply executing the code, and also having an easy way to get the resulting TypeScript definitions:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">const </span><span style="color:#BD976A">loggerConfig</span><span style="color:#666666"> =</span><span style="color:#80A665"> createLoggerConfig</span><span style="color:#666666">(</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">test</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#B8A965">  message</span><span style="color:#666666">: </span><span style="color:#80A665">string</span><span style="color:#666666">(),</span></span>
<span class="line"><span style="color:#B8A965">  randomNumber</span><span style="color:#666666">: </span><span style="color:#80A665">float64</span><span style="color:#666666">(),</span></span>
<span class="line"><span style="color:#B8A965">  timestamp</span><span style="color:#666666">: </span><span style="color:#80A665">datetime</span><span style="color:#666666">(),</span></span>
<span class="line"><span style="color:#666666">});</span></span></code></pre>
<p>Here is how I implemented this!</p>
<p><em>⚠️ We make heavy use of TypeScript generics for this. Make sure to <a href="https://www.typescriptlang.org/docs/handbook/2/generics.html">catch up on them</a> first.</em></p>
<p>We start by implementing the <code>createLoggerConfig</code> function. It's typed as a generic with two generic types:</p>
<ul>
<li>The event name: A literal string (<code>test</code> in the example above)</li>
<li>The properties: An object of field definitions.</li>
</ul>
<p>One neat thing about TypeScript is that it can infer the type of the generics if they are referenced from the function definition, so if we can do something like this without requiring extra work when someone calls this function:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> createLoggerConfig</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">E</span><span style="color:#666666">,</span><span style="color:#5DA994"> F</span><span style="color:#666666">>(</span><span style="color:#BD976A">event</span><span style="color:#666666">: </span><span style="color:#5DA994">E</span><span style="color:#666666">,</span><span style="color:#BD976A"> fields</span><span style="color:#666666">: </span><span style="color:#5DA994">F</span><span style="color:#666666">)</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#758575DD">  // ...</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>We can also restrict the type of the <code>event</code> and <code>fields</code> table of that generic so that it only accepts string types for the event name and valid field definitions for the fields:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> createLoggerConfig</span><span style="color:#666666">&#x3C;</span></span>
<span class="line"><span style="color:#5DA994">  E</span><span style="color:#CB7676"> extends</span><span style="color:#5DA994"> string</span><span style="color:#666666">,</span></span>
<span class="line"><span style="color:#5DA994">  F</span><span style="color:#CB7676"> extends</span><span style="color:#666666"> {</span><span style="color:#666666"> [</span><span style="color:#BD976A">key</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">]: </span><span style="color:#5DA994">Field</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">any</span><span style="color:#666666">, </span><span style="color:#5DA994">any</span><span style="color:#666666">> }</span></span>
<span class="line"><span style="color:#666666">>(</span><span style="color:#BD976A">event</span><span style="color:#666666">: </span><span style="color:#5DA994">E</span><span style="color:#666666">,</span><span style="color:#BD976A"> fields</span><span style="color:#666666">: </span><span style="color:#5DA994">F</span><span style="color:#666666">)</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#758575DD">  // ...</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>We'll look into the return type and the <code>Field</code> type more closely in a second.</p>
<h2 id="but-why-do-we-use-generics-at-all"><a class="header-anchor" aria-label="Link to this heading" href="#but-why-do-we-use-generics-at-all">#</a>But why do we use generics at all?</h2>
<p>You might be thinking: Why all this fuzz when I can also restrict the arguments via normal TypeScript syntax. That's what it's used for, after all!</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> createLoggerConfig</span><span style="color:#666666">(</span></span>
<span class="line"><span style="color:#BD976A">  event</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">,</span></span>
<span class="line"><span style="color:#BD976A">  fields</span><span style="color:#666666">: { [</span><span style="color:#BD976A">key</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">]: </span><span style="color:#5DA994">Field</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">any</span><span style="color:#666666">, </span><span style="color:#5DA994">any</span><span style="color:#666666">> }</span></span>
<span class="line"><span style="color:#666666">)</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#758575DD">  // ...</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>You're right, this <em>does restrict the argument</em>. Remember that one of the foals of this ORM is that we will be able to use the declared schema to also generate a TypeScript object definitions for the logger. The problem without using generics is that you will always have a non-generic return type. So no matter what you pass into the <code>createLoggerConfig</code>, the returned <em>thing</em> won't have any type definition that <em>depend on the arguments</em>.</p>
<h2 id="defining-the-fields"><a class="header-anchor" aria-label="Link to this heading" href="#defining-the-fields">#</a>Defining the <code>Field</code>s</h2>
<p>The ORM I needed to build only had to support a couple of types available in the ClickHouse database, specifically:</p>
<ul>
<li><code>string</code></li>
<li><code>float64</code></li>
<li><code>datetime</code></li>
</ul>
<p>If you remember the example snipped above, these should be declared as function calls. Nothing easier than that!</p>
<p>To make sure they retain the type information <em>and later have a way to map to the JavaScript type</em>, here's how I created and typed these:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">type</span><span style="color:#5DA994"> FieldType</span><span style="color:#666666"> =</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">string</span><span style="color:#C98A7D77">"</span><span style="color:#666666"> |</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">float64</span><span style="color:#C98A7D77">"</span><span style="color:#666666"> |</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">datetime</span><span style="color:#C98A7D77">"</span><span style="color:#666666">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">class</span><span style="color:#5DA994"> Field</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">T</span><span style="color:#CB7676"> extends</span><span style="color:#5DA994"> FieldType</span><span style="color:#666666">,</span><span style="color:#5DA994"> JST</span><span style="color:#CB7676"> extends</span><span style="color:#5DA994"> unknown</span><span style="color:#666666">></span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#CB7676">  constructor</span><span style="color:#666666">(</span><span style="color:#CB7676">public</span><span style="color:#BD976A"> type</span><span style="color:#666666">: </span><span style="color:#5DA994">T</span><span style="color:#666666">,</span><span style="color:#CB7676"> public</span><span style="color:#BD976A"> jsType</span><span style="color:#666666">: </span><span style="color:#5DA994">JST</span><span style="color:#666666">)</span><span style="color:#666666"> {}</span></span>
<span class="line"><span style="color:#666666">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> string</span><span style="color:#666666">():</span><span style="color:#5DA994"> Field</span><span style="color:#666666">&#x3C;</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">string</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#5DA994"> string</span><span style="color:#666666">></span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#4D9375">  return</span><span style="color:#CB7676"> new</span><span style="color:#80A665"> Field</span><span style="color:#666666">(</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">string</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#C98A7D77"> ""</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#666666">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> float64</span><span style="color:#666666">():</span><span style="color:#5DA994"> Field</span><span style="color:#666666">&#x3C;</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">float64</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#5DA994"> number</span><span style="color:#666666">></span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#4D9375">  return</span><span style="color:#CB7676"> new</span><span style="color:#80A665"> Field</span><span style="color:#666666">(</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">float64</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#4C9A91"> 0</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#666666">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> datetime</span><span style="color:#666666">():</span><span style="color:#5DA994"> Field</span><span style="color:#666666">&#x3C;</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">datetime</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#5DA994"> Date</span><span style="color:#666666">></span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#4D9375">  return</span><span style="color:#CB7676"> new</span><span style="color:#80A665"> Field</span><span style="color:#666666">(</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">datetime</span><span style="color:#C98A7D77">"</span><span style="color:#666666">,</span><span style="color:#CB7676"> new</span><span style="color:#80A665"> Date</span><span style="color:#666666">());</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>The only thing that is a bit odd here is that we define both a <code>T</code> on the field, which maps to the literal of the ClickHouse type, and a <code>JST</code> which maps to the equivalent of the type that we want to be using in TypeScript when we create entries for this logger config.</p>
<p>We also assign a placeholder value to the JS type and store it as a public member of the Field class. This is perhaps unexpected (and maybe even unnecessary? Let me know!) but I found this to be useful to later access the TypeScript type from the field definition.</p>
<h2 id="return-type"><a class="header-anchor" aria-label="Link to this heading" href="#return-type">#</a>Return type</h2>
<p>One thing that we have not specified yet is the return type of <code>createLoggerConfig</code>. To make it easier to later add implementations to the schema, I decided to create a class for this:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">class</span><span style="color:#5DA994"> LoggerConfig</span><span style="color:#666666">&#x3C;</span></span>
<span class="line"><span style="color:#5DA994">  E</span><span style="color:#CB7676"> extends</span><span style="color:#5DA994"> string</span><span style="color:#666666">,</span></span>
<span class="line"><span style="color:#5DA994">  F</span><span style="color:#CB7676"> extends</span><span style="color:#666666"> {</span><span style="color:#666666"> [</span><span style="color:#BD976A">key</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">]: </span><span style="color:#5DA994">Field</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">FieldType</span><span style="color:#666666">, </span><span style="color:#5DA994">unknown</span><span style="color:#666666">> }</span></span>
<span class="line"><span style="color:#666666">></span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#CB7676">  constructor</span><span style="color:#666666">(</span><span style="color:#CB7676">public</span><span style="color:#BD976A"> event</span><span style="color:#666666">: </span><span style="color:#5DA994">E</span><span style="color:#666666">,</span><span style="color:#CB7676"> public</span><span style="color:#BD976A"> fields</span><span style="color:#666666">: </span><span style="color:#5DA994">F</span><span style="color:#666666">)</span><span style="color:#666666"> {}</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>With this we can change the initial <code>createLoggerConfig</code> function to do something like this:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> createLoggerConfig</span><span style="color:#666666">&#x3C;</span></span>
<span class="line"><span style="color:#5DA994">  E</span><span style="color:#CB7676"> extends</span><span style="color:#5DA994"> string</span><span style="color:#666666">,</span></span>
<span class="line"><span style="color:#5DA994">  F</span><span style="color:#CB7676"> extends</span><span style="color:#666666"> {</span><span style="color:#666666"> [</span><span style="color:#BD976A">key</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">]: </span><span style="color:#5DA994">Field</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">any</span><span style="color:#666666">, </span><span style="color:#5DA994">any</span><span style="color:#666666">> }</span></span>
<span class="line"><span style="color:#666666">>(</span><span style="color:#BD976A">event</span><span style="color:#666666">: </span><span style="color:#5DA994">E</span><span style="color:#666666">,</span><span style="color:#BD976A"> fields</span><span style="color:#666666">: </span><span style="color:#5DA994">F</span><span style="color:#666666">):</span><span style="color:#5DA994"> LoggerConfig</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">E</span><span style="color:#666666">,</span><span style="color:#5DA994"> F</span><span style="color:#666666">></span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#4D9375">  return</span><span style="color:#CB7676"> new</span><span style="color:#80A665"> LoggerConfig</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">E</span><span style="color:#666666">,</span><span style="color:#5DA994"> F</span><span style="color:#666666">>(</span><span style="color:#BD976A">event</span><span style="color:#666666">,</span><span style="color:#BD976A"> fields</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<h2 id="-the-magical-infer"><a class="header-anchor" aria-label="Link to this heading" href="#-the-magical-infer">#</a>✨ The magical <code>Infer&#x3C;></code></h2>
<p>As mentioned in the beginning, one goal of this exercise was to later be able to map the schema declaration to a TypeScript object type which, for the example above, should look like this:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">interface</span><span style="color:#5DA994"> Test</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#BD976A">  message</span><span style="color:#666666">: </span><span style="color:#5DA994">string</span><span style="color:#666666">;</span></span>
<span class="line"><span style="color:#BD976A">  randomNumber</span><span style="color:#666666">: </span><span style="color:#5DA994">number</span><span style="color:#666666">;</span></span>
<span class="line"><span style="color:#BD976A">  timestamp</span><span style="color:#666666">: </span><span style="color:#5DA994">Date</span><span style="color:#666666">;</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>This way, when inserting new rows into the logger table, we can leverage the TypeScript type safety and get helpful errors if we're passing invalid values.</p>
<p>In Drizzle and Zod, this is done by a magical Infer type that can be used like this:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">const </span><span style="color:#BD976A">loggerConfig</span><span style="color:#666666"> =</span><span style="color:#80A665"> createLoggerConfig</span><span style="color:#666666">(</span><span style="color:#758575DD">/* insert config from above */</span><span style="color:#666666">);</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">type</span><span style="color:#5DA994"> Test</span><span style="color:#666666"> =</span><span style="color:#5DA994"> Infer</span><span style="color:#666666">&#x3C;</span><span style="color:#CB7676">typeof</span><span style="color:#BD976A"> loggerConfig</span><span style="color:#666666">>;</span></span></code></pre>
<p>But how do we implement this <code>Infer&#x3C;T></code> type?</p>
<p>Turns out, it's possible to do this by combining a few TypeScript features:</p>
<ul>
<li>We rely on the generics that are passed from the <code>createLoggerConfig</code> call.</li>
<li>We use <code>keyof</code> to get all keys (properties) from the fields property inside the logger config</li>
<li>We use <a href="https://www.typescriptlang.org/docs/handbook/2/mapped-types.html">mapped types</a> to map over each of the fields and access the public <code>jsType</code> property we set earlier</li>
</ul>
<p>If this sounds super confusing to you, you're not alone! Here's what I ended up with:</p>
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#CB7676">type</span><span style="color:#5DA994"> Infer</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">T</span><span style="color:#CB7676"> extends</span><span style="color:#5DA994"> LoggerConfig</span><span style="color:#666666">&#x3C;</span><span style="color:#5DA994">any</span><span style="color:#666666">,</span><span style="color:#5DA994"> any</span><span style="color:#666666">>></span><span style="color:#666666"> =</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#666666">  [</span><span style="color:#5DA994">Property</span><span style="color:#CB7676"> in</span><span style="color:#CB7676"> keyof</span><span style="color:#5DA994"> T</span><span style="color:#666666">[</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">fields</span><span style="color:#C98A7D77">"</span><span style="color:#666666">]]: </span><span style="color:#5DA994">T</span><span style="color:#666666">[</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">fields</span><span style="color:#C98A7D77">"</span><span style="color:#666666">][</span><span style="color:#5DA994">Property</span><span style="color:#666666">][</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">jsType</span><span style="color:#C98A7D77">"</span><span style="color:#666666">];</span></span>
<span class="line"><span style="color:#666666">};</span></span></code></pre>
<p>Pretty awesome! If we wire everything up together and use the power of the TypeScript IDE integrations, we can see hover over the inferred type and see that it works as we expect it to:</p>
<p><img src="/blog/building-a-scheme-mapper-in-typescript/hover.png" alt="Screenshot of a hover tooltip in TypeScript showing the inferred type as outlined above"></p>
<p>Check out the <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAYglhANgEwCrmgXigIgM7ABOcAdgOY5QA+uAZogPYCGwAbACyU07IsTBwAthBwBuAFDiAxoiZ48sBCgA8qKBAAewCCWQL4SNBgA0UAFIBlNZu26FAVxIBrEgwDuJAHxQA3uKhQUgwkBIT2UsAMhAAUYPYARohwUlCgkABcUKimcYnJUABWeOgZ5lYAlL4AvuI14rSOEXDBUKGkZNHlmQYq+ETtOKZt5N5+AYT89oQkUCQQboqG0X3E5IO4OOUSdQ0kTS30zGzsnd1KyMo4hywc6yT2gvEQhKP+UBPAUzNzCz3Iy9djusAAxbWqSXb7Ga8bQCYSnRa9GH8IQiUwAET4r3Gk2ms3miP+PD4cLR+IWmO0nTBO0aAhaUgmfAAMgwyGRngBhYK0OBkZRvACi6i0Oj0rX65GMbxgIts4p8UAA2k4ICBMsMyABdM6GZRMEggUwGkDeGqeaIQABuOmAmUFpl5hjw3S6UFZ7K5PL5ygdsGx71x3wJHo5hG5JF5-L9MAt1ttjvOeBpkhkcgUoa9kZ9QrlYoUmulAVlNnzvmVqvVEtW2t1Kj+JQgpkcLncXig5t8byCISI4UiMVySRS8ZIdqgfqH+SdKBdsEqPhqdTS0AAkpHnqo83Z3WywxGo-rDcbDZ5vNgxsqAApQUhQSsMWhZJVXJM4LU65+v53vpVXrUvkUjbvtsEipsEBBQIwnrht6ZBQNgjIQCye5ZlGyzaAQ6yXsI8hMByGqSh05RFu8BrIAwggAHIPE8hCZICHCdKRpIEEwghgJkyKksxtRguIK5ZBAkHYOutCbiuj5QahsHZmQngSEAA">TypeScript playground</a> to play around with it yourself!</p>]]></content:encoded>
      <pubDate>Wed, 03 May 2023 12:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/niche-clickhouse-features</guid>
      <title>Niche ClickHouse Features</title>
      <link>https://spiess.dev/note/engineering/niche-clickhouse-features</link>
      <description><![CDATA[A lot of companies love ClickHouse for it's great performance as LDAP system, incredible compression, and its ease of deployment. 

This is a…]]></description>
      <content:encoded><![CDATA[<p>A lot of companies love ClickHouse for it's great performance as LDAP system, incredible compression, and its ease of deployment.</p>
<p>This is a list of features that are perhaps less known about but also quite powerful:</p>
<ul>
<li><a href="https://clickhouse.com/docs/en/sql-reference/statements/alter/column#materialize-column"><strong>Materialized Columns</strong></a>: This feature allows you to add new columns (which, given the fact that ClickHouse is column based, is very cheap) that derive the data from existing fields. You can imagine a generic log table with a json text field and you want to extract a specific field from the JSON into a custom column. With this feature, you can start your log table in an unstructured form and gradually migrate to a schema over time.
Check out <a href="https://www.youtube.com/watch?v=pZkKsfr8n3M">this video from Altinity</a> for more informations.</li>
<li><a href="https://clickhouse.com/docs/en/guides/developer/ttl"><strong>TTL</strong></a>: ClickHouse allows you to define how long data inside tables and even specific columns is being retained. I think this is an amazing feature for sensitive data, ensuring the data is only retained as long as it is necessary for analytical purposes.</li>
<li><a href="https://clickhouse.com/docs/en/interfaces/schema-inference#json-formats"><strong>Automatic schema inference from JSON</strong></a>: Granted, this is a very niche feature but I assume it could be amazing in combination with materialized columns to gradually <em>and automatically</em> convert an untyped JSON payload into a typed column structure.</li>
<li><a href="https://clickhouse.com/docs/en/sql-reference/statements/create/quota"><strong>Quotas</strong></a>: A feature to allow you to limit resources in a multi-tenant ClickHouse deployment. You don't care about it until you absolutely need it, and here you just get it for free.</li>
</ul>]]></content:encoded>
      <pubDate>Tue, 02 May 2023 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/front-end/modern-front-end-problems</guid>
      <title>Modern Front-End Problems</title>
      <link>https://spiess.dev/note/engineering/front-end/modern-front-end-problems</link>
      <description><![CDATA[This is a list of non-trivial problems I have come across in the front-end community over the past few years. It mostly exists as a personal…]]></description>
      <content:encoded><![CDATA[<p>This is a list of non-trivial problems I have come across in the front-end community over the past few years. It mostly exists as a personal reminder. <a href="https://twitter.com/PhilippSpiess">Let me know</a> if you have something that should be added here!</p>
<ul>
<li>
<p><strong>Accessibility:</strong> Making web apps accessible because it’s so easy with the Web fundamentals to break things.</p>
</li>
<li>
<p><strong>Bundle coverage:</strong> A lot of the code that is shipped in today’s bundles (CSS, JS, etc.) is not needed for the first render.</p>
</li>
<li>
<p><strong>Data waterfalls:</strong> Waterfalls is a term used to describe serial loading dependencies. E.g. to render a list of users we first download the list component, wait for it to load, then the list component makes a request to an API, waits for it to load, etc.</p>
<p>This is the number one issue that I have seen causing websites to be slow. It often manifests itself as a myriad of uncontrolled loading states that sometimes end in another loading state popping up. 😵‍💫</p>
<p>An interesting observation: Most applications could be able to resolve all the data needed for the initial page load on the server side based on the URL, params, and authentication tokens.</p>
</li>
<li>
<p><strong>Overfetching/underfetching:</strong> A client should only ever get as many data as it needs to render. Anything more is not needed and everything less is not enough.</p>
</li>
<li>
<p><strong>Interaction delays</strong>: If I interact with an item on the website, it should show an instant response.</p>
</li>
<li>
<p><strong>Layout shifting:</strong> Once the first elements on a website are shown, you don't want to have them jump around. This only causes confusion to the user and results in a poor experience.</p>
</li>
<li>
<p><strong>Developer experience:</strong> I think this term is often overused but the current way we write apps is definitely not the end game. We need constant improvements to the tooling because the cost of building is still way too high.</p>
</li>
<li>
<p><strong>Hydration:</strong> When you have a server-rendered HTML page and it needs to become interactive ASAP.</p>
</li>
<li>
<p><strong>Reliability:</strong> A meta-term that is all about <em>knowing</em> if your app functions. There are many vectors this can be done, however easy mechanisms also have a weak correlation to actual user problems.</p>
<ul>
<li>Error handling/tracking. Unfortunately knowing that an error was thrown does not often relate to a broken website—Is the error recoverable by a user or is it crashing the whole page?</li>
<li>Browser crashes/freezes/loops</li>
<li>Uptime</li>
<li>Tracking core interaction uptime</li>
</ul>
</li>
<li>
<p><strong>Performance:</strong> This is a very convoluted term that justifies it’s own note. Until then, here are some thoughts:</p>
<ul>
<li>Measuring page loads is usually simpler than measuring interactions (navigations, popups).</li>
<li>You can measure performance in a lab but this will only ever be a <em>proxy</em> to the real-user performance metrics (aka RUM—Real User Monitoring).</li>
</ul>
</li>
<li>
<p><strong>Efficiency:</strong> Another meta-term for a few lower-level optimzations that rarely cause problems (but when they do, they are noticeable):</p>
<ul>
<li>Memory usage</li>
<li>Heavy CPU usage</li>
<li>Uncontrolled scheduled tasks (e.g. <code>setInterval</code>)</li>
<li>Browser rendering issues</li>
<li>Network/IO overhead</li>
</ul>
</li>
<li>
<p><strong>Testing:</strong> While software testing is hard in every environment, front-end has a couple of special complexity with things like visual regression testing, accessibility testing, the reliance on a backend API, or the flakiness of browser instrumentation.</p>
</li>
<li>
<p><strong>Observability:</strong> Knowing how your app functions is a complex problem in itself. Traditionally, the web platform was very limited in how much data we have available with a big focus on analytics. Lately there have been a few amazing developments though:</p>
<ul>
<li><a href="https://github.com/tc39/proposal-async-context">Async Context</a> is a recent proposal to bring zones.js to the platform which will allow for easer tracing APIs in the future.</li>
<li><a href="https://github.com/WICG/js-self-profiling">JavaScript Self-Profiling API</a> brings process sampling features to the web platform.</li>
</ul>
</li>
</ul>]]></content:encoded>
      <pubDate>Thu, 03 Nov 2022 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/software/new-mac-checklist</guid>
      <title>New Mac Checklist</title>
      <link>https://spiess.dev/note/software/new-mac-checklist</link>
      <description><![CDATA[- [ ] Input methods
  - [ ] Set up the [US-German Keyboard…]]></description>
      <content:encoded><![CDATA[<ul>
<li><input type='checkbox' disabled /> Input methods
<ul>
<li><input type='checkbox' disabled /> Set up the <a href="https://github.com/patrick-zippenfenig/us-with-german-umlauts#:~:text=Mac%20U.S.%20keyboard%20layout%20with%20German%20Umlauts,-This%20layout%20is&#x26;text=The%20layout%20is%20a%20modified,option%20%2B%20u%20%3D%3E%20%C3%BC">US-German Keyboard layout</a></li>
<li><input type='checkbox' disabled /> Disable natural scrolling</li>
<li><input type='checkbox' disabled /> Maximize key repeat rate and delay until repeat</li>
<li><input type='checkbox' disabled /> Map <code>tab</code> key to <code>escape</code> in Keyboards settings</li>
</ul>
</li>
<li><input type='checkbox' disabled /> <a href="https://brew.sh/">Homebrew</a></li>
<li><input type='checkbox' disabled /> <code>brew install --cask 1password</code> (set up 1Password immediately)</li>
<li><input type='checkbox' disabled /> Connect iCloud and enable Find My Mac</li>
<li><input type='checkbox' disabled /> Customize Dock</li>
<li><input type='checkbox' disabled /> Connect Google account for the Calendar integration</li>
<li><input type='checkbox' disabled /> Oh My Zsh
<ul>
<li><code>git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions</code></li>
<li><code>git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting</code></li>
</ul>
</li>
<li><input type='checkbox' disabled /> <a href="https://mise.jdx.dev/getting-started.html">Mise</a></li>
<li><input type='checkbox' disabled /> <code>brew install git</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask warp</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask raycast</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask discord</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask google-chrome</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask firefox</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask hiddenbar</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask spotify</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask visual-studio-code</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask obsidian</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask rectangle</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask monitorcontrol</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask telegram</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask whatsapp</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask flux</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask the-unarchiver</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask stats</code></li>
<li><input type='checkbox' disabled /> Fonts
<ul>
<li><input type='checkbox' disabled /> <code>brew tap homebrew/cask-fonts</code></li>
<li><input type='checkbox' disabled /> <code>brew install --cask font-fira-code</code></li>
</ul>
</li>
<li><input type='checkbox' disabled /> Sync <a href="https://github.com/philipp-spiess/dotfiles">dotfiles</a></li>
<li><input type='checkbox' disabled /> Set up SSH keys for GitHub and remote servers</li>
<li><input type='checkbox' disabled /> From the App Store
<ul>
<li><input type='checkbox' disabled /> Microsoft To Do</li>
<li><input type='checkbox' disabled /> Things</li>
<li><input type='checkbox' disabled /> Prologue</li>
</ul>
</li>
<li><input type='checkbox' disabled /> For Work Macs
<ul>
<li><input type='checkbox' disabled /> Slack</li>
<li><input type='checkbox' disabled /> Loom</li>
<li><input type='checkbox' disabled /> Carbon Black</li>
<li><input type='checkbox' disabled /> Dev Environment</li>
<li><input type='checkbox' disabled /> Zoom</li>
<li><input type='checkbox' disabled /> Set up a second Chrome profile and change the UI theme of one of the profiles (pref. work to blue)</li>
</ul>
</li>
<li><input type='checkbox' disabled /> For Personal Macs
<ul>
<li><input type='checkbox' disabled /> VPN Client and helper</li>
<li><input type='checkbox' disabled /> Sketch</li>
</ul>
</li>
<li><input type='checkbox' disabled /> Configure backups</li>
</ul>]]></content:encoded>
      <pubDate>Fri, 25 Feb 2022 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/test-matrixes</guid>
      <title>Test Matrixes</title>
      <link>https://spiess.dev/note/engineering/test-matrixes</link>
      <description><![CDATA[- When working on complex systems, there are often more than one test parameter that changes. For example when working on a component for your…]]></description>
      <content:encoded><![CDATA[<ul>
<li>When working on complex systems, there are often more than one test parameter that changes. For example when working on a component for your design system, you need to test across all these different parameters:
<ul>
<li>Responsive Breakpoints</li>
<li>Dark/Light Mode</li>
<li>Accesibility options (screen reader, mouse-only, keyboard-only)</li>
<li>Different browser versions</li>
</ul>
</li>
<li>A good abstraction to help cope with this increasing complexity is by thinking of combinational matrixes.
<ul>
<li>In code, this can be multi dimensional arrays or nested loops. <a href="https://jestjs.io/docs/api#testeachtablename-fn-timeout">Jest even has a feature for it</a>.</li>
<li>In PR descriptions, it's helpful to draw a table to make sure you test all possible combinations throghoutly.</li>
</ul>
</li>
<li>Increasing the dimensions can make a full test run very costly though, so you can’t just blindly add more.</li>
<li>A very common pattern where this is userful is for manual pull request descriptions where you want to provide proof that you have exhaustively tested a new feature. A two-dimensional table might be used to show screenshots of e.g. different browsers and different responsive breakpoints.</li>
</ul>]]></content:encoded>
      <pubDate>Fri, 25 Feb 2022 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/engineering/front-end/reacts-escape-hatch</guid>
      <title>React’s Escape Hatch</title>
      <link>https://spiess.dev/note/engineering/front-end/reacts-escape-hatch</link>
      <description><![CDATA[- Being not constraint to a specific domain is one of React's killer features.
- In React DOM, you can always fall back to creating custom…]]></description>
      <content:encoded><![CDATA[<ul>
<li>Being not constraint to a specific domain is one of React's killer features.</li>
<li>In React DOM, you can always fall back to creating custom <code>&#x3C;div></code> elements with your own styles if something doesn't work.
<ul>
<li>There's a maintainance overhead by doing so but it also unblocks so many use cases.</li>
<li>Even at the new Facebook.com, custom containers with with the full fallback to the browser were very common.</li>
</ul>
</li>
<li>Every good design systems that I have seen embraces this and allows their users to forward e.g. custom class names to their inner working components.</li>
</ul>]]></content:encoded>
      <pubDate>Mon, 21 Feb 2022 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/note/other/notes</guid>
      <title>Notes</title>
      <link>https://spiess.dev/note/other/notes</link>
      <description><![CDATA[A collection of my random thoughts. This is a place for me to write down raw ideas, so don’t expect a high bar for quality. 

Inspired by Josh…]]></description>
      <content:encoded><![CDATA[<p>A collection of my random thoughts. This is a place for me to write down raw ideas, so don’t expect a high bar for quality.</p>
<p>Inspired by Josh Branchaud’s <a href="https://github.com/jbranchaud/til">til</a>, Lee Byron’s <a href="https://leebyron.com/til/">til</a>, and Shawn Wang’s <a href="https://www.swyx.io/learn-in-public/">Learn In Public</a>.</p>]]></content:encoded>
      <pubDate>Sat, 01 Jan 2022 00:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://spiess.dev/blog/scheduling-in-react</guid>
      <title>Scheduling in React</title>
      <link>https://spiess.dev/blog/scheduling-in-react</link>
      <description><![CDATA[In modern applications, user interfaces often have to juggle multiple tasks at the same time. For example, a search component might need to…]]></description>
      <content:encoded><![CDATA[<p>In modern applications, user interfaces often have to juggle multiple tasks at the same time. For example, a search component might need to respond to user input while providing auto completion results, and an interactive dashboard might need to update charts while loading data from the server and sending analytics data to a backend.</p>
<p>All these parallel steps can lead to slow and unresponsive interfaces and unhappy users, so let’s learn how we can fix this.</p>
<h2 id="scheduling-in-user-interfaces"><a class="header-anchor" aria-label="Link to this heading" href="#scheduling-in-user-interfaces">#</a>Scheduling in User Interfaces</h2>
<p>Our users expect immediate feedback. Whether they are clicking on a button to open a modal or adding text to an input field, they don’t want to wait before seeing some kind of confirmation. For example, the button could show a modal and the input field could display the key that was typed.</p>
<p>To visualize what happens when this is not the case, let’s take a look at the demo application that Dan Abramov presented at his talk, <a href="https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html">Beyond React 16</a>, at JSConf Iceland 2018.</p>
<p>The application works like this: The more you type into the input below, the more detailed the charts below will get. Since both of the updates (the input element and the chart) run at the same time, the browser has to do so much computation that it will drop some frames. This leads to noticeable delays and a bad user experience:</p>
<p><video src="/blog/scheduling-in-react/sync-mode.mp4" muted="true" autoplay muted playsinline loop></video></p>
<p>However, a version that prioritizes updating the input with new keystrokes will appear to the end user as though it’s running a lot faster. This is because users receive immediate feedback even though the same computation time is required:</p>
<p><video src="/blog/scheduling-in-react/concurrent-mode.mp4" muted="true" autoplay muted playsinline loop></video></p>
<p>Unfortunately, current web user interface architectures make it non-trivial to implement prioritization. One way to work around this problem is by <a href="https://davidwalsh.name/javascript-debounce-function">debouncing</a> the chart update. The problem with this approach is that the charts still render synchronously when the debounced callback fires, which will again cause the user interface to be unresponsive for some time. We can do better!</p>
<h2 id="browser-event-loop"><a class="header-anchor" aria-label="Link to this heading" href="#browser-event-loop">#</a>Browser Event Loop</h2>
<p>Before we learn more about how proper prioritizing of updates can be achieved, let’s dig deeper and understand why the browser has issues with these kind of user interactions.</p>
<p>JavaScript code is executed in one thread, meaning that only one line of JavaScript can be run at any given time. The same thread is also responsible for other document lifecycles, like layout and paint.<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup> This means that whenever JavaScript code runs, the browser is blocked from doing anything else.</p>
<p>To keep the user interface responsive, we only have a very short timeframe before we need to be able to receive the next input events. At the Chrome Dev Summit 2018, Shubhie Panicker and Jason Miller gave a talk, <a href="https://developer.chrome.com/devsummit/schedule/scheduling-on-off-main-thread">A Quest to Guarantee Responsiveness</a>. During the talk, they showed the following visualization of the browser’s run loop, in which we can see that we only have 16ms (on a typical 60Hz screen) before the next frame is drawn and the next event needs to be processed:</p>
<p><img src="/blog/scheduling-in-react/event-loop-browser.png" alt="The browser event loop starts by running input handlers. Then it runs animation frame callbacks, and it ends with document lifecycles (style, layout, paint). All of this should complete within one frame, which is approximately 16ms on a 60Hz display."></p>
<p>Most JavaScript frameworks (including the current version of React) will run updates synchronously. We can think of this behavior as a function <code>render()</code> that will only return once the DOM is updated. During this time, the main thread is blocked.</p>
<h2 id="problems-with-current-solutions"><a class="header-anchor" aria-label="Link to this heading" href="#problems-with-current-solutions">#</a>Problems with Current Solutions</h2>
<p>With the information above, we can formulate two problems that we have to solve in order to get to more responsive user interfaces:</p>
<ol>
<li>
<p><strong>Long-running tasks cause frame drops.</strong> We need to make sure all of our tasks are small and can be completed within a couple of milliseconds so that we can run them within one frame.</p>
</li>
<li>
<p><strong>Different tasks have different priorities.</strong> In the example application above, we saw that prioritizing the user input leads to a better experience overall. To do this, we need a way to define the order and to schedule tasks accordingly.</p>
</li>
</ol>
<h2 id="concurrent-react-and-the-scheduler"><a class="header-anchor" aria-label="Link to this heading" href="#concurrent-react-and-the-scheduler">#</a>Concurrent React and the Scheduler</h2>
<p><em>⚠️ Warning: The following APIs are not yet stable and will change. I will do my best to keep this post updated (Last Update: April 2019).</em></p>
<p>To implement a properly scheduled user interface with React, we have to look into two upcoming React features:</p>
<ul>
<li>
<p><strong>Concurrent React (also known as Time Slicing).</strong> With the help of the new <a href="https://www.youtube.com/watch?v=ZCuYPiUIONs">Fiber architecture</a> rewrite that was released with React 16, React can now pause during rendering and yield<sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup> to the main thread.</p>
<p>We will hear more about Concurrent React in the future. For now it is important to understand that when this mode is enabled, React will split the synchronous rendering of our React components into pieces that are run over multiple frames.</p>
<p>➡️ With this feature, we’re able to split long-running rendering tasks into small chunks.</p>
</li>
<li>
<p><strong>Scheduler.</strong> The general purpose cooperative main thread scheduler is developed by the React Core team and makes it possible to register callbacks with different priority levels in the browser.</p>
<p>At the time of writing this article, the priority levels are:</p>
<ul>
<li><code>Immediate</code> for tasks that need to run synchronously.</li>
<li><code>UserBlocking</code> (250ms timeout) for tasks that should run as the result of a user interaction (e.g. a button click).</li>
<li><code>Normal</code> (5s timeout) for updates that don’t have to feel instantaneous.</li>
<li><code>Low</code> (10s timeout) for tasks that can be deferred but must still complete eventually (e.g. an analytics notification).</li>
<li><code>Idle</code> (no timeout) for tasks that do not have to run at all (e.g. hidden offscreen content).</li>
</ul>
<p>The timeouts for each priority level are necessary to make sure that lower priority work still runs even if we have so much higher priority work to do that the higher priority work could run continuously. In scheduling algorithms, this problem is referred to as <a href="https://en.wikipedia.org/wiki/Starvation_(computer_science)">starvation</a>. The timeouts give us the guarantee that every scheduled task will eventually run. For example, we won’t miss a single analytics notification, even if we have ongoing animations in our app.</p>
<p>Under the hood, the Scheduler will store all registered callbacks in a list ordered by the expiration time (which is the time at which the callback was registered plus the timeout of the priority level). Then, the Scheduler will itself register a callback that is run after the next frame is drawn by the browser.<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref aria-describedby="footnote-label">3</a></sup> Within this callback, the Scheduler will execute as many of the registered callbacks as possible until it’s time to render the next frame.</p>
<p>➡️ With this feature, we can schedule tasks with different priorities.</p>
</li>
</ul>
<h2 id="scheduling-in-action"><a class="header-anchor" aria-label="Link to this heading" href="#scheduling-in-action">#</a>Scheduling in Action</h2>
<p>Let’s see how we can use these features to make an app feel a lot more responsive. To do this, we’ll take a look at <a href="https://github.com/philipp-spiess/scheduletron3000">ScheduleTron 3000</a>, an app I built that allows users to highlight a search term in a list of names. Let’s take a look at the initial implementation first:</p>
<!-- prettier-ignore -->
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#758575DD">// The app shows a search box and a list of names. The list is</span></span>
<span class="line"><span style="color:#758575DD">// controlled by the searchValue state variable, which is updated</span></span>
<span class="line"><span style="color:#758575DD">// by the search box.</span></span>
<span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> App</span><span style="color:#666666">()</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#CB7676">  const</span><span style="color:#666666"> [</span><span style="color:#BD976A">searchValue</span><span style="color:#666666">,</span><span style="color:#BD976A"> setSearchValue</span><span style="color:#666666">]</span><span style="color:#666666"> =</span><span style="color:#BD976A"> React</span><span style="color:#666666">.</span><span style="color:#80A665">useState</span><span style="color:#666666">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">  function</span><span style="color:#80A665"> handleChange</span><span style="color:#666666">(</span><span style="color:#BD976A">value</span><span style="color:#666666">)</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#80A665">    setSearchValue</span><span style="color:#666666">(</span><span style="color:#BD976A">value</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#666666">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4D9375">  return</span><span style="color:#666666"> (</span></span>
<span class="line"><span style="color:#666666">    &#x3C;</span><span style="color:#4D9375">div</span><span style="color:#666666">></span></span>
<span class="line"><span style="color:#666666">      &#x3C;</span><span style="color:#B8A965">SearchBox</span><span style="color:#BD976A"> onChange</span><span style="color:#666666">={</span><span style="color:#BD976A">handleChange</span><span style="color:#666666">}</span><span style="color:#666666"> /></span></span>
<span class="line"><span style="color:#666666">      &#x3C;</span><span style="color:#B8A965">NameList</span><span style="color:#BD976A"> searchValue</span><span style="color:#666666">={</span><span style="color:#BD976A">searchValue</span><span style="color:#666666">}</span><span style="color:#666666"> /></span></span>
<span class="line"><span style="color:#666666">    &#x3C;/</span><span style="color:#4D9375">div</span><span style="color:#666666">></span></span>
<span class="line"><span style="color:#666666">  );</span></span>
<span class="line"><span style="color:#666666">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#758575DD">// The search box renders a native HTML input element and keeps</span></span>
<span class="line"><span style="color:#758575DD">// it controlled using the inputValue variable. When a new key</span></span>
<span class="line"><span style="color:#758575DD">// is pressed, it will first update the local inputValue, then it</span></span>
<span class="line"><span style="color:#758575DD">// will update the App component’s searchValue, and then it will</span></span>
<span class="line"><span style="color:#758575DD">// simulate an analytics notification to our servers.</span></span>
<span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> SearchBox</span><span style="color:#666666">(</span><span style="color:#BD976A">props</span><span style="color:#666666">)</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#CB7676">  const</span><span style="color:#666666"> [</span><span style="color:#BD976A">inputValue</span><span style="color:#666666">,</span><span style="color:#BD976A"> setInputValue</span><span style="color:#666666">]</span><span style="color:#666666"> =</span><span style="color:#BD976A"> React</span><span style="color:#666666">.</span><span style="color:#80A665">useState</span><span style="color:#666666">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">  function</span><span style="color:#80A665"> handleChange</span><span style="color:#666666">(</span><span style="color:#BD976A">event</span><span style="color:#666666">)</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#CB7676">    const</span><span style="color:#BD976A"> value</span><span style="color:#666666"> =</span><span style="color:#BD976A"> event</span><span style="color:#666666">.</span><span style="color:#BD976A">target</span><span style="color:#666666">.</span><span style="color:#BD976A">value</span><span style="color:#666666">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#80A665">    setInputValue</span><span style="color:#666666">(</span><span style="color:#BD976A">value</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#BD976A">    props</span><span style="color:#666666">.</span><span style="color:#80A665">onChange</span><span style="color:#666666">(</span><span style="color:#BD976A">value</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#80A665">    sendAnalyticsNotification</span><span style="color:#666666">(</span><span style="color:#BD976A">value</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#666666">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color:#4D9375">  return</span><span style="color:#666666"> (</span></span>
<span class="line"><span style="color:#666666">    &#x3C;</span><span style="color:#4D9375">input</span></span>
<span class="line"><span style="color:#BD976A">      type</span><span style="color:#666666">=</span><span style="color:#C98A7D77">"</span><span style="color:#C98A7D">text</span><span style="color:#C98A7D77">"</span></span>
<span class="line"><span style="color:#BD976A">      value</span><span style="color:#666666">={</span><span style="color:#BD976A">inputValue</span><span style="color:#666666">}</span></span>
<span class="line"><span style="color:#BD976A">      onChange</span><span style="color:#666666">={</span><span style="color:#BD976A">handleChange</span><span style="color:#666666">}</span></span>
<span class="line"><span style="color:#666666">    /></span></span>
<span class="line"><span style="color:#666666">  );</span></span>
<span class="line"><span style="color:#666666">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#BD976A">ReactDOM</span><span style="color:#666666">.</span><span style="color:#80A665">render</span><span style="color:#666666">(&#x3C;</span><span style="color:#B8A965">App</span><span style="color:#666666"> />,</span><span style="color:#BD976A"> container</span><span style="color:#666666">);</span></span></code></pre>
<p><em>ℹ️ This example uses <a href="https://reactjs.org/docs/hooks-intro.html">React Hooks</a>. If you’re not familiar with this new React feature, take a look at the <a href="https://codesandbox.io/s/j3zrqpzkr5">CodeSandbox code</a>. Additionally, you might wonder why we use two different state variables for this example. We’ll find out why this is needed in a bit.</em></p>
<p>Try it out! Type a name (e.g. “Ada Stewart”) in the search box below and see how it works:</p>
<iframe src="https://codesandbox.io/embed/j3zrqpzkr5?fontsize=14&hidenavigation=1&view=preview" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
<p>You might notice that the interface is not very responsive. To amplify the issue, I artificially slowed down the rendering time of the names list. And since this list is big, it has a significant impact on the application’s performance. This is not good 😰.</p>
<p>Our users expect immediate feedback, but the app is unresponsive for quite some time after a keystroke. To understand what’s going on, let’s take a look at the DevTools’ Performance tab. Here’s a screenshot of a recording I made while I type the name “Ada” into the search box:</p>
<p><img src="/blog/scheduling-in-react/devtools-sync.png" alt="Screenshot of Chrome DevTools that shows that the three keypress events take 733ms to render."></p>
<p>We can see there are a lot of red triangles, which is usually not a good sign. For every keystroke, we see a <code>keypress</code> event being fired. All three events run within one frame,<sup><a href="#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref aria-describedby="footnote-label">4</a></sup> which causes the duration of the frame to extend to <strong>733ms</strong>. That’s way above our average frame budget of 16ms.</p>
<p>Inside this <code>keypress</code> event, our React code will be called, which causes the input value and the search value to update and then send the analytics notification. In turn, the updated state values will cause the app to rerender down to every individual name. That’s quite a lot of work that we have to do, and with a naive approach, it would block the main thread!</p>
<p>The first step toward improving the status quo is to enable the unstable Concurrent Mode. This can be done by creating the React root with the new <code>ReactDOM.createRoot</code> API like this:</p>
<pre><code class="language-diff">- ReactDOM.render(&#x3C;App />, container);
+ const root = ReactDOM.unstable_createRoot(rootElement);
+ root.render(&#x3C;App />);
</code></pre>
<p>However, enabling Concurrent Mode alone will not change the experience in our case. React will still receive both state updates at the same time, so there’s no way of knowing which is less important.</p>
<p>We instead want to set the input value first so that we only need to update the search box in the beginning. Updates to the search value and our analytics notification should happen later. To do this, we’re using an API exposed by the Scheduler package (which can be installed with <code>npm i scheduler</code>) to enqueue a lower priority callback:</p>
<pre><code class="language-js{1,10-14}">import { unstable_next } from "scheduler";

function SearchBox(props) {
  const [inputValue, setInputValue] = React.useState();

  function handleChange(event) {
    const value = event.target.value;

    setInputValue(value);
    unstable_next(function() {
      props.onChange(value);
      sendAnalyticsNotification(value);
    });
  }

  return &#x3C;input type="text" value={inputValue} onChange={handleChange} />;
}
</code></pre>
<p>Inside the API we’re using, <code>unstable_next()</code>, all React updates will be scheduled with the <code>Normal</code> priority, which is lower then the default priority inside an <code>onChange</code> listener.</p>
<p>Indeed, with this change, our input box already feels a lot more responsive, and frames no longer get dropped while we’re typing. Let’s take another look at the Performance tab together:</p>
<p><img src="/blog/scheduling-in-react/devtools-normal.png" alt="Screenshot of Chrome DevTools that shows that React breaks the rendering work down into small chunks. All frames can be drawn very quickly, although the analytics notifications are still sent in the middle of the rendering work."></p>
<p>We see that the long-running tasks are now broken down into smaller ones that can be completed within a single frame. The red triangles that indicate frame drops are also gone.</p>
<p>However, one thing that is still not ideal is that the analytics notification (highlighted in the above screenshot) is still executed with the rendering work. Since the users of our app do not see this task, we can schedule a callback with an even lower priority for that:</p>
<!-- prettier-ignore -->
<pre class="shiki vitesse-dark" style="background-color:#121212;color:#dbd7caee" tabindex="0"><code><span class="line"><span style="color:#4D9375">import</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#BD976A">  unstable_LowPriority</span><span style="color:#666666">,</span></span>
<span class="line"><span style="color:#BD976A">  unstable_scheduleCallback</span></span>
<span class="line"><span style="color:#666666">}</span><span style="color:#4D9375"> from</span><span style="color:#C98A7D77"> "</span><span style="color:#C98A7D">scheduler</span><span style="color:#C98A7D77">"</span><span style="color:#666666">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> sendDeferredAnalyticsNotification</span><span style="color:#666666">(</span><span style="color:#BD976A">value</span><span style="color:#666666">)</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#80A665">  unstable_scheduleCallback</span><span style="color:#666666">(</span><span style="color:#BD976A">unstable_LowPriority</span><span style="color:#666666">,</span><span style="color:#CB7676"> function</span><span style="color:#666666">()</span><span style="color:#666666"> {</span></span>
<span class="line"><span style="color:#80A665">    sendAnalyticsNotification</span><span style="color:#666666">(</span><span style="color:#BD976A">value</span><span style="color:#666666">);</span></span>
<span class="line"><span style="color:#666666">  });</span></span>
<span class="line"><span style="color:#666666">}</span></span></code></pre>
<p>If we now use <code>sendDeferredAnalyticsNotification()</code> in our search box component and take another look at the Performance tab with this change and scroll toward the end, we’ll see that our analytics are now sent after all rendering work has completed, and so all the tasks in our app are perfectly scheduled:</p>
<p><img src="/blog/scheduling-in-react/devtools-normal-and-low.png" alt="Screenshot of Chrome DevTools that show that React breaks the rendering work down into small chunks. Analytics are sent at the end after all rendering work has completed."></p>
<p>Try it out:</p>
<iframe src="https://codesandbox.io/embed/71ly7qx83x?fontsize=14&hidenavigation=1&view=preview" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
<h2 id="limitations-of-the-scheduler"><a class="header-anchor" aria-label="Link to this heading" href="#limitations-of-the-scheduler">#</a>Limitations of the Scheduler</h2>
<p>With the Scheduler, it’s possible to control in what order callbacks are executed. It’s built deep into the latest React implementation and works out of the box with Concurrent mode.</p>
<p>That said, there are two limitations of the Scheduler:</p>
<ol>
<li><strong>Resource Fighting.</strong> The Scheduler tries to use all of the resources available. This causes issues if multiple instances of a scheduler run on the same thread and compete for resources. We need to ensure that all parts of our application will use the same instance.</li>
<li><strong>Balancing user-defined tasks with browser work.</strong> Since the Scheduler runs in the browser, it only has access to the APIs the browser exposes. Document lifecycles like rendering or garbage collection can interfere with the work in an uncontrollable way.</li>
</ol>
<p>To remove these limitations, the Google Chrome team is working together with React, Polymer, Ember, Google Maps, and the Web Standards Community to create a <a href="https://github.com/spanicker/main-thread-scheduling">Scheduling API in the browser</a>. What an exciting time!</p>
<h2 id="conclusion"><a class="header-anchor" aria-label="Link to this heading" href="#conclusion">#</a>Conclusion</h2>
<p>Concurrent React and the Scheduler allow us to implement scheduling of tasks in our applications which will allow us to create highly responsive user interfaces.</p>
<p>The official release for these features will likely happen in <a href="https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#react-16x-q2-2019-the-one-with-concurrent-mode">Q2 2019</a>. Until then, you can play around with the unstable APIs, but be aware that they will change.</p>
<p>If you want to be among the first to know when these APIs change or when documentation for the new features is written, subscribe to <a href="https://this-week-in-react.org">This Week in React ⚛️</a>.</p>
<section data-footnotes class="footnotes"><h2 class="sr-only" id="footnote-label"><a class="header-anchor" aria-label="Link to this heading" href="#footnote-label">#</a>Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>The MDN web docs feature a great <a href="https://developer.mozilla.org/en-US/docs/Tools/Performance/Scenarios/Intensive_JavaScript">article</a> about this issue. <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>This is a fancy term for returning for a function that is able to resume. Check out <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield">generator functions</a> for a similar concept. <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>In the <a href="https://github.com/facebook/react/blob/master/packages/scheduler/src/forks/SchedulerHostConfig.default.js">current implementation</a>, this is achieved by using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"><code>postMessage()</code></a> inside a <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame"><code>requestAnimationFrame()</code></a> callback. This will then be called right after the frame is rendered. <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p>After processing the first <code>keypress</code> event, the browser sees pending events in its queue and decides to run the event listener before rendering the frame. <a href="#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
      <pubDate>Thu, 07 Mar 2019 16:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://pspdfkit.com/blog/2018/running-native-code-in-electron-and-the-case-for-webassembly/</guid>
      <title>Running Native Code in Electron and the Case for WebAssembly</title>
      <link>https://pspdfkit.com/blog/2018/running-native-code-in-electron-and-the-case-for-webassembly/</link>
      <description><![CDATA[Electron is a powerful open source framework that allows development of cross-platform desktop GUI applications with JavaScript, HTML, and…]]></description>
      <content:encoded><![CDATA[<p>Electron is a powerful open source framework that allows development of cross-platform desktop GUI applications with JavaScript, HTML, and CSS. In the past few years, it has been adapted by major companies in the tech industry including GitHub, Facebook, Microsoft, and Slack.</p>]]></content:encoded>
      <pubDate>Wed, 26 Sep 2018 12:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://pspdfkit.com/blog/2018/a-real-world-webassembly-benchmark/</guid>
      <title>A Real-World WebAssembly Benchmark</title>
      <link>https://pspdfkit.com/blog/2018/a-real-world-webassembly-benchmark/</link>
      <description><![CDATA[In late 2016, we released the first version of our Web SDK, which relies on a server component to render documents. Only a few months later…]]></description>
      <content:encoded><![CDATA[<p>In late 2016, we released the first version of our Web SDK, which relies on a server component to render documents. Only a few months later, we released an updated version of PSPDFKit for Web — one that doesn’t require a server component and instead uses WebAssembly (Wasm, WA) to render documents directly in the browser. This was a big achievement for us as a company, and being able to provide a browser-only solution that doesn’t require any backend infrastructure lowers the barrier to entry for our customers.</p>]]></content:encoded>
      <pubDate>Thu, 05 Jul 2018 12:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://pspdfkit.com/blog/2017/webassembly-a-new-hope/</guid>
      <title>WebAssembly: A New Hope</title>
      <link>https://pspdfkit.com/blog/2017/webassembly-a-new-hope/</link>
      <description><![CDATA[In March 2017, the WebAssembly Community Group reached a consensus on the initial (MVP) binary format, JavaScript API, and reference…]]></description>
      <content:encoded><![CDATA[<p>In March 2017, the WebAssembly Community Group reached a consensus on the initial (MVP) binary format, JavaScript API, and reference interpreter. This exciting new technology is already shipping in Chrome and Firefox, and it will be fully supported in macOS High Sierra and iOS 11, with Microsoft Edge support following shortly thereafter. But what is WebAssembly and what does it mean for the web?</p>]]></content:encoded>
      <pubDate>Wed, 09 Aug 2017 12:00:00 GMT</pubDate>
    </item>
  
    <item>
      <guid>https://medium.com/@philippspiess/an-engineers-approach-to-marketing-3e8c8983fb5e</guid>
      <title>An Engineer’s Approach to Marketing</title>
      <link>https://medium.com/@philippspiess/an-engineers-approach-to-marketing-3e8c8983fb5e</link>
      <description><![CDATA[Several years ago, I was working on a platform for photographers to schedule the publication of their best shots on different platforms. You…]]></description>
      <content:encoded><![CDATA[<p>Several years ago, I was working on a platform for photographers to schedule the publication of their best shots on different platforms. You may think of it as Buffer for photographers. To drive more traffic to the website, I was struggling with finding ways to attract potential customers.</p>]]></content:encoded>
      <pubDate>Sat, 01 Mar 2014 00:00:00 GMT</pubDate>
    </item>
  
    </channel>
  </rss>
