<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Reilly Wood</title>
    <link>https://www.reillywood.com/</link>
    <description>Recent content on Reilly Wood</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 28 Dec 2025 11:13:42 -0800</lastBuildDate>
    
        <atom:link href="https://www.reillywood.com/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>2025 Retrospective</title>
      <link>https://www.reillywood.com/blog/year-end-2025/</link>
      <pubDate>Sun, 28 Dec 2025 11:13:42 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/year-end-2025/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s been quite a year. Here&amp;rsquo;s a grab bag of what stuck in my mind from 2025 and what&amp;rsquo;s coming next.&lt;/p&gt;
&lt;h2 id=&#34;personal-stuff&#34;&gt;Personal Stuff&lt;/h2&gt;
&lt;p&gt;I started 2025 with a New Year&amp;rsquo;s resolution to &lt;a href=&#34;https://www.reillywood.com/blog/strength-training&#34;&gt;deadlift&lt;/a&gt; 300lb. I hit 300 in the middle of the year, upped my resolution to 350lb, but only made it to 310lb. I&amp;rsquo;d like to hit 350 in 2026, but if I plateau and stay healthy I&amp;rsquo;m fine with that too.&lt;/p&gt;
&lt;p&gt;My big resolution for the new year is to host a gathering at least once a month. Doesn&amp;rsquo;t have to be anything fancy, just an excuse to see people. My plan to make this happen: even if I&amp;rsquo;m busy, it&amp;rsquo;s easy to make a big batch of pasta and have guests bring side dishes and wine.&lt;/p&gt;
&lt;p&gt;2025 was the first year I really noticed my dog getting old. He&amp;rsquo;s 11 and doing well for his age, but I&amp;rsquo;m more aware that I probably only have a few more years left with him. Retired racing greyhounds make great pets:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/2026/grumpy_hu_320f7f37bbe7cc51.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Author and dog on a road trip to the Okanagan Valley
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;We spent a lot of time in late 2025 looking at real estate. After 7 years we&amp;rsquo;re a little bored of our current place and it&amp;rsquo;d be nice to have a bedroom for guests. It&amp;rsquo;s been an emotional roller-coaster; in November we had an offer accepted for a gorgeous condo downtown, only to back out after the inspection found some issues. But hey, it could be worse - when we bought our current place the market was so hot that people didn&amp;rsquo;t even get inspections done 😬.&lt;/p&gt;
&lt;h2 id=&#34;work-stuff&#34;&gt;Work Stuff&lt;/h2&gt;
&lt;p&gt;I got promoted to Staff Engineer in December. This required a ton of effort plus some luck, and I&amp;rsquo;m very proud of it; feels like I finally made it, y&amp;rsquo;know? The promotion felt especially good because during the tech job rout of late 2022 I accepted a Staff offer from another mid-sized tech company, and then after 3 weeks of delay they retracted it.&lt;/p&gt;
&lt;p&gt;Overall it was a very good year for work. I shipped &lt;a href=&#34;https://docs.datadoghq.com/coterm/&#34;&gt;a product&lt;/a&gt;, worked on &lt;a href=&#34;https://www.youtube.com/watch?v=xzjIthtC72M&#34;&gt;a high-profile keynote demo&lt;/a&gt; with OpenAI, and changed teams to launch &lt;a href=&#34;https://docs.datadoghq.com/bits_ai/mcp_server/&#34;&gt;a new product&lt;/a&gt; that&amp;rsquo;s attracting a lot of interest. I also flew down to SF twice to give talks for work; &lt;a href=&#34;https://www.youtube.com/watch?v=pjdOvkT3n_U&#34;&gt;here&amp;rsquo;s one&lt;/a&gt; I&amp;rsquo;m particularly proud of.&lt;/p&gt;
&lt;h2 id=&#34;software&#34;&gt;Software&lt;/h2&gt;
&lt;p&gt;It is an incredibly crazy time to be working in software.&lt;/p&gt;
&lt;p&gt;It feels like 2025 was the year where agents blew up. &lt;a href=&#34;http://localhost:1313/blog/how-i-use-llms-sep-2024/&#34;&gt;1 year ago&lt;/a&gt;, I was occasionally using Aider to make commit-sized changes to software projects, and I felt like I was ahead of the curve. Today I tend to use Claude Code (sometimes Codex CLI) to make more ambitious changes, and they are far more capable of iterating on a change until they get it right.&lt;/p&gt;
&lt;p&gt;My day to day now involves less &amp;ldquo;hands-on&amp;rdquo; coding and more high-level management of coding agents. It&amp;rsquo;s become incredibly cheap to try things out, and Opus 4.5 is remarkably capable.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m spending a lot of time with these new tools and I still feel quite a bit of FOMO. It helps to know that &lt;a href=&#34;https://xcancel.com/karpathy/status/2004607146781278521&#34;&gt;I&amp;rsquo;m not the only one&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>What&#39;s MCP good for, anyway?</title>
      <link>https://www.reillywood.com/blog/whats-mcp-good-for/</link>
      <pubDate>Fri, 26 Dec 2025 19:27:57 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/whats-mcp-good-for/</guid>
      <description>&lt;p&gt;Among people who do a lot of agent-assisted software development, there is &lt;a href=&#34;https://lucumr.pocoo.org/2025/7/3/tools/&#34;&gt;some skepticism&lt;/a&gt; about whether MCP is useful:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A quick experiment makes this clear: try completing a GitHub task with the GitHub MCP, then repeat it with the &lt;code&gt;gh&lt;/code&gt; CLI tool. You’ll almost certainly find the latter uses context far more efficiently and you get to your intended results quicker.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a fair criticism; in some scenarios an agent does better if we let it go wild with &lt;code&gt;bash&lt;/code&gt; (which is effectively giving it the ability to write+run code) and a CLI tool. I also agree with Armin&amp;rsquo;s assertion we need better ways to compose MCP tool results. But I still think MCP is useful as-is, and I&amp;rsquo;d like to sketch out why I believe that.&lt;/p&gt;
&lt;h2 id=&#34;mcp-is-simple&#34;&gt;MCP is simple&lt;/h2&gt;
&lt;p&gt;To connect an AI agent to an MCP server, I don&amp;rsquo;t need to download anything; I just provide a URL. Authentication is taken care of as part of the connection (more on this later). Tools are &lt;a href=&#34;https://modelcontextprotocol.io/specification/2025-11-25/schema#toolannotations&#34;&gt;annotated&lt;/a&gt; with info that indicates whether they are safe to run. My agent doesn&amp;rsquo;t need to be able to execute code, and it doesn&amp;rsquo;t even need a filesystem.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s true that this agent might be less flexible or powerful than one with the ability to run arbitrary code. But that&amp;rsquo;s a tradeoff, and people are &lt;a href=&#34;https://www.anthropic.com/engineering/code-execution-with-mcp&#34;&gt;exploring&lt;/a&gt; ways to combine MCP with code execution - this is something to keep an eye on in 2026!&lt;/p&gt;
&lt;h2 id=&#34;programmers-are-weird&#34;&gt;Programmers are weird&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m a programmer who spends a lot of time with coding agents like Claude Code and Codex CLI. You could think of me as a power user driving an agent semi-interactively, and most people discussing MCP are in the same boat.&lt;/p&gt;
&lt;p&gt;CLI tools are often a viable alternative to MCP &lt;em&gt;for us&lt;/em&gt;, but a big part of that is that we can evaluate whether any given call to &lt;code&gt;bash&lt;/code&gt; looks safe. That is &lt;a href=&#34;https://www.todepond.com/wikiblogarden/better-computing/just/&#34;&gt;not a skill that most people have&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:think:&#34; alt=&#34;think&#34;
     src=&#34;https://www.reillywood.com/img/emoji/think.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        OK, sure, we can&amp;rsquo;t expect most users to validate &lt;code&gt;bash&lt;/code&gt; commands. But can&amp;rsquo;t we just sandbox their agents?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Maybe someday! But sandboxing is hard, and I don&amp;rsquo;t think anyone&amp;rsquo;s fully solved the UX around it yet. Are &lt;em&gt;you&lt;/em&gt; sandboxing all of your agents today? The answer is &amp;ldquo;no, it&amp;rsquo;s too much of a hassle,&amp;rdquo; right?
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;toward-autonomous-agents&#34;&gt;Toward Autonomous Agents&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s step away from the well-trodden path of Claude Code. Say you&amp;rsquo;re building an agent that operates autonomously based on untrusted data. To make this more concrete, let&amp;rsquo;s say it&amp;rsquo;s an incident investigator agent; when a monitor goes off, it tries to find the root cause using data from &lt;a href=&#34;https://www.datadoghq.com/&#34;&gt;your favourite observability provider&lt;/a&gt;. &lt;strong&gt;How do you give that agent access to your observability data?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If your observability provider has &lt;a href=&#34;https://docs.datadoghq.com/developers/guide/dogshell/&#34;&gt;a CLI&lt;/a&gt; available, the agent could use that. But using a CLI means:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your agent will need access to a filesystem (provisioned with a copy of the CLI)&lt;/li&gt;
&lt;li&gt;Your agent will need a sandbox to stop malicious code execution and resource exhaustion&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re opening yourself up to credential exfiltration attacks. The CLI needs credentials to talk to the observability provider; if the agent can execute arbitrary code, it can almost certainly read those credentials.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these problems go away if you connect your agent to &lt;a href=&#34;https://docs.datadoghq.com/bits_ai/mcp_server/&#34;&gt;an MCP server&lt;/a&gt; instead. MCP can get a production-ready agent off the ground almost immediately.&lt;/p&gt;
&lt;h2 id=&#34;putting-it-all-together&#34;&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;MCP is a dead-simple way to give agents access to tools safely, and it works today. For some agents, that simplicity is extremely valuable; for others it is not. As you move away from expert oversight and toward fully automated agents, the case for MCP grows stronger.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Tool Calls Are Expensive And Finite</title>
      <link>https://www.reillywood.com/blog/tool-calls-are-expensive-and-finite/</link>
      <pubDate>Thu, 18 Sep 2025 18:49:43 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/tool-calls-are-expensive-and-finite/</guid>
      <description>&lt;p&gt;Giving LLMs access to &lt;a href=&#34;https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview&#34;&gt;tools&lt;/a&gt; (which turns them into &lt;a href=&#34;https://simonwillison.net/2025/Sep/18/agents/&#34;&gt;✨agents✨&lt;/a&gt;) is an incredibly powerful way to give LLMs capabilities that go beyond generating text. But it&amp;rsquo;s important to think clearly about the costs and limitations of tool calling, and in particular, people should understand that calling a tool is &lt;em&gt;many&lt;/em&gt; orders of magnitude more costly than calling a plain old function from code. There is and probably always will be a limit on how many tool calls an agent can effectively make, and people should design their agentic systems accordingly.&lt;/p&gt;
&lt;h1 id=&#34;wait-why&#34;&gt;Wait, why?&lt;/h1&gt;
&lt;p&gt;For this to make sense, you have to consider what a tool call &lt;em&gt;is&lt;/em&gt; &amp;ldquo;under the hood.&amp;rdquo; LLMs are typically used as very fancy text generation machines. And the way they do tool calls is by &lt;em&gt;generating text&lt;/em&gt;, although that&amp;rsquo;s typically abstracted away from us.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say you have an agent with one tool, &lt;code&gt;add&lt;/code&gt;, for adding 2 numbers together. A user asks the agent a question that&amp;rsquo;s easy to answer with the &lt;code&gt;add&lt;/code&gt; tool:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What&amp;rsquo;s 15 + 27?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To actually call the &lt;code&gt;add&lt;/code&gt; tool, the model generates a message like this (simplified):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;tool_call_id&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;call_abc123&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;tool_name&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;add&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;tool_arguments&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;{\&amp;#34;a\&amp;#34;: 15, \&amp;#34;b\&amp;#34;: 27}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point the model stops generating tokens. The thing that&amp;rsquo;s driving the model (the agentic loop?) parses that message, passes those arguments to some function like &lt;code&gt;add(15, 27)&lt;/code&gt;, and then puts the output of &lt;em&gt;that&lt;/em&gt; into chat history as a new message:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;tool_call_id&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;call_abc123&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;tool_call_result&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;42&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Inference resumes, and the LLM now has everything it needs to tell the user that the answer is 42. This works! It&amp;rsquo;s the foundation of some really incredible software systems! But it wasn&amp;rsquo;t free:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The model had to generate a bunch of tokens.&lt;/li&gt;
&lt;li&gt;We used up precious context window for the 2 messages.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;but-why-does-that-matter&#34;&gt;But why does that matter?&lt;/h1&gt;
&lt;p&gt;If you&amp;rsquo;re adding 2 numbers once, it probably doesn&amp;rsquo;t matter. If you&amp;rsquo;re summing up 1,000 numbers&amp;hellip; you&amp;rsquo;re going to be waiting a &lt;em&gt;very&lt;/em&gt; long time for those 999 tool calls to finish, and you might blow through your entire context window.&lt;/p&gt;
&lt;p&gt;This might seem like an academic point, but calling a function many times in a loop is one of the most common ways to solve a problem with code. To give a contrived example, say we have 1,000 user IDs and we want to list the users whose name starts with &amp;lsquo;R&amp;rsquo;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A programmer with a &lt;code&gt;get_user_info(id)&lt;/code&gt; function can write+run a simple &lt;code&gt;for&lt;/code&gt; loop. Easy peasy.&lt;/li&gt;
&lt;li&gt;An agent with a &lt;code&gt;get_user_info(id)&lt;/code&gt; tool can &lt;em&gt;try&lt;/em&gt; to make 1,000 tool calls, but it will probably run out of context window long before it finishes
&lt;ol&gt;
&lt;li&gt;Remember, the entire result of every tool call ends up in the context window&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Designing agentic tools that are flexible enough for every use case (or even most use cases) is hard, and I don&amp;rsquo;t think enough people are talking about that.&lt;/p&gt;
&lt;h1 id=&#34;so-what-do-we-do-instead&#34;&gt;So what do we do instead?&lt;/h1&gt;
&lt;p&gt;As always, it depends. Maybe your agent is solving problems where it will never need to make large numbers of tool calls. Maybe you&amp;rsquo;re clever and you can design your tools to be very flexible+powerful so an agent can do a lot in a small number of tool calls. Maybe you can sidestep this problem by letting your agent write+run &lt;em&gt;code&lt;/em&gt; (keeping in mind all of the necessary security precautions).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>APIs don&#39;t make good MCP tools</title>
      <link>https://www.reillywood.com/blog/apis-dont-make-good-mcp-tools/</link>
      <pubDate>Tue, 05 Aug 2025 20:15:49 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/apis-dont-make-good-mcp-tools/</guid>
      <description>&lt;p&gt;The &lt;a href=&#34;https://modelcontextprotocol.io/overview&#34;&gt;Model Context Protocol&lt;/a&gt; (MCP) is a pretty big deal these days. It&amp;rsquo;s become the de facto standard for giving LLMs access to tools that someone else wrote, which, of course, turns them into &lt;a href=&#34;https://simonwillison.net/2025/May/22/tools-in-a-loop/&#34;&gt;agents&lt;/a&gt;. But writing tools for a new MCP server is hard, and so people often propose &lt;a href=&#34;https://blog.christianposta.com/semantics-matter-exposing-openapi-as-mcp-tools/&#34;&gt;auto-converting existing APIs into MCP tools&lt;/a&gt;; typically using OpenAPI metadata (&lt;a href=&#34;https://jedisct1.github.io/openapi-mcp/&#34;&gt;1&lt;/a&gt;, &lt;a href=&#34;https://www.gravitee.io/blog/turn-any-rest-api-into-mcp-server-inside-gravitee&#34;&gt;2&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;In my experience, this can work but it doesn&amp;rsquo;t work &lt;em&gt;well&lt;/em&gt;. Here are a few reasons why:&lt;/p&gt;
&lt;h2 id=&#34;agents-dont-do-well-with-large-numbers-of-tools&#34;&gt;Agents don&amp;rsquo;t do well with large numbers of tools&lt;/h2&gt;
&lt;p&gt;Infamously, &lt;a href=&#34;https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode&#34;&gt;VS Code has a hard limit of 128 tools&lt;/a&gt; - but &lt;a href=&#34;https://arxiv.org/abs/2411.15399&#34;&gt;many models struggle with accurate tool calling well before that number&lt;/a&gt;. Also, each tool and its description takes up valuable context window space.&lt;/p&gt;
&lt;p&gt;Most web APIs weren&amp;rsquo;t designed with these constraints in mind! It&amp;rsquo;s fine to have umpteen APIs for a single product area when those APIs are called from code, but if each of those APIs is mapped to an MCP tool the results might not be great.&lt;/p&gt;
&lt;p&gt;MCP tools designed from the ground up are typically &lt;a href=&#34;https://engineering.block.xyz/blog/blocks-playbook-for-designing-mcp-servers&#34;&gt;much more flexible than individual web APIs&lt;/a&gt;, with each tool being able to do the work of several individual APIs.&lt;/p&gt;
&lt;h2 id=&#34;apis-can-blow-through-context-windows-quickly&#34;&gt;APIs can blow through context windows quickly&lt;/h2&gt;
&lt;p&gt;Imagine an API that returns 100 records at a time, and each record is very wide (say, 50 fields). Sending those results to an agent as-is will use up a lot of tokens; even if a query can be satisfied with only a few fields, every field ends up in the context window.&lt;/p&gt;
&lt;p&gt;APIs are typically paginated by the number of records, but records can vary a &lt;em&gt;lot&lt;/em&gt; in size. One record might contain a large text field that takes up 100,000 &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/ai/conceptual/understanding-tokens&#34;&gt;tokens&lt;/a&gt;, while another might contain 10. Putting these API results directly into an agent&amp;rsquo;s context window is a gamble; sometimes it works, sometimes it will blow up.&lt;/p&gt;
&lt;p&gt;The format of the data can also be an issue. Most web APIs these days return JSON, but JSON is a very token-inefficient format. Take this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;firstName&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;lastName&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Johnson&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;28&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;firstName&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;lastName&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Smith&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;35&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compare to the same data in CSV format:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csv&#34; data-lang=&#34;csv&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;firstName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;lastName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;age&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Alice&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Johnson&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;28&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Bob&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Smith&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;35&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The CSV data is &lt;em&gt;much&lt;/em&gt; more succinct - it uses up half as many tokens per record. &lt;a href=&#34;https://david-gilbertson.medium.com/llm-output-formats-why-json-costs-more-than-tsv-ebaf590bd541&#34;&gt;Typically CSV, TSV, or YAML (for nested data) are better choices than JSON&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;None of these issues are insurmountable. You could imagine automatically adding tool arguments that let agents &lt;a href=&#34;https://en.wikipedia.org/wiki/Projection_(relational_algebra)&#34;&gt;project&lt;/a&gt; fields, automatically truncating or summarizing large results, and automatically converting JSON results to CSV (or YAML for nested data). But most servers I&amp;rsquo;ve seen do none of those things.&lt;/p&gt;
&lt;h2 id=&#34;apis-dont-make-the-most-of-agents-unique-capabilities&#34;&gt;APIs don&amp;rsquo;t make the most of agents&amp;rsquo; unique capabilities&lt;/h2&gt;
&lt;p&gt;APIs return structured data for programmatic consumption. That&amp;rsquo;s often what agents want from tool calls&amp;hellip; but agents can &lt;em&gt;also&lt;/em&gt; handle other, more free-form instructions.&lt;/p&gt;
&lt;p&gt;For example an &lt;code&gt;ask_question&lt;/code&gt; tool could perform a RAG query over some documentation, then return information in plain text that is used to inform the next tool call - skipping structured data entirely.&lt;/p&gt;
&lt;p&gt;Or, a call to a &lt;code&gt;search_cities&lt;/code&gt; tool could return a structured list of cities &lt;em&gt;and&lt;/em&gt; a suggestion of what to call next:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csv&#34; data-lang=&#34;csv&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;city_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;population&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;country&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;region&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Tokyo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;37194000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Japan&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Asia&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Delhi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;32941000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;India&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Asia&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Shanghai&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;28517000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;China&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Asia&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;Suggestion: To get more specific information (weather&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; attractions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; demographics)&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s&#34;&gt; try calling get_city_details with the city_name parameter.&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That sort of layering and tool chaining &lt;a href=&#34;https://engineering.block.xyz/blog/build-mcp-tools-like-ogres-with-layers&#34;&gt;can be very effective&lt;/a&gt; in MCP servers, and it&amp;rsquo;s something you&amp;rsquo;ll miss out on completely if auto-converting APIs to tools.&lt;/p&gt;
&lt;h2 id=&#34;if-an-agent-needs-to-call-an-api-it-could-just-do-that&#34;&gt;If an agent needs to call an API, it could just do that&lt;/h2&gt;
&lt;p&gt;Agents like Claude Code are remarkably capable of writing+executing code these days, including scripts that call web APIs. Some people take this so far as to &lt;a href=&#34;https://lucumr.pocoo.org/2025/7/3/tools/&#34;&gt;argue that MCP isn&amp;rsquo;t needed at all&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I disagree with that conclusion, but I do think we should skate to where the puck is going. &lt;a href=&#34;https://github.com/openai/codex&#34;&gt;Sandboxing of agents is improving rapidly&lt;/a&gt;, and if it&amp;rsquo;s easy+safe for an agent to call APIs directly then we might as well do that and cut out the middleman.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Agents are fundamentally different from the typical consumers of APIs. It&amp;rsquo;s possible to automatically create MCP tools from existing APIs, but doing that is unlikely to work &lt;em&gt;well&lt;/em&gt;. Agents do best when given tools that are designed for their unique capabilities and limitations.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Land Values and Affordability</title>
      <link>https://www.reillywood.com/blog/land-value/</link>
      <pubDate>Thu, 26 Jun 2025 20:02:19 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/land-value/</guid>
      <description>&lt;p&gt;I want to get something off my chest: attempts to keep the price of urban land down are &lt;em&gt;not&lt;/em&gt; necessarily good. Many people in local politics place a high priority on keeping land prices down. For example, the new Vancouver councillor who opposed a church building apartments on their own land:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://scoutmagazine.ca/on-everyones-rent-going-up-and-slashing-the-police-budget-to-fund-culture/&#34;&gt;Land values displace people. This will increase land values.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;hellip;and then used that same reason to vote against &lt;a href=&#34;https://morehousing.substack.com/p/megatowers&#34;&gt;apartments at a major train station&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/live/hEs6W2GpTdI?feature=shared&amp;amp;t=22551&#34;&gt;I&amp;rsquo;m worried that filtering will take too long, that &lt;strong&gt;land value increases will lead to displacement&lt;/strong&gt;&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Vancouver&amp;rsquo;s planning staff share these concerns and try to keep land values down when changing zoning. For example, &lt;a href=&#34;https://www.shapeyourcity.ca/multiplexes/widgets/142819/faqs#question31284&#34;&gt;the recent multiplex policy&lt;/a&gt; was designed to avoid raising land values:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://rezoning.vancouver.ca/applications/mm-rs/multiplex-proposal-density-bonus-update-2023-06-30.pdf?_ga=2.85860895.780077157.1688923126-1000878833.1649268132&#34;&gt;Proposed density bonus contribution requirements &amp;amp; rates (are) set to&amp;hellip; &lt;strong&gt;limit any potential land value escalation&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:thinking:&#34; alt=&#34;thinking&#34;
     src=&#34;https://www.reillywood.com/img/emoji/thinking.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        That makes sense; if land value is lower then homes are more affordable, right?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        &lt;strong&gt;WRONG&lt;/strong&gt; (if you keep land values down by stopping development)
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;land-prices-are-not-housing-prices&#34;&gt;Land Prices Are Not Housing Prices&lt;/h2&gt;
&lt;p&gt;The main way people save on housing costs in cities is by &lt;em&gt;using less land&lt;/em&gt;. For example, imagine the following uses on a 4000 sqft lot:&lt;/p&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;Building&lt;/th&gt;
        &lt;th&gt;Land/Household&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Single family home (small)&lt;/td&gt;
        &lt;td&gt;4000 sqft&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Duplex&lt;/td&gt;
        &lt;td&gt;2000 sqft&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;5-unit apartment/condo building&lt;/td&gt;
        &lt;td&gt;800 sqft&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;It is generally much cheaper to buy 800 square feet of land than it is to buy 4000. But where this gets interesting is that those denser uses may cause higher &lt;em&gt;land&lt;/em&gt; prices. Let&amp;rsquo;s walk through how:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Say that 4000 sqft lot is zoned to only allow a single-family home. Richie McRicherson is willing to pay $1M so he can build a house on that land. The land sells for &lt;strong&gt;1 MILLION DOLLARS&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Now, suppose the land is zoned to allow a duplex. 2 households who each have $600k pool their money together and outbid Richie. The land sells for &lt;strong&gt;1.2 MILLION DOLLARS&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Finally, suppose the land is zoned to allow a 5-unit condo building. 5 households who each have $400k pool their money and outbid both Richie and the duplex buyers. The land sells for &lt;strong&gt;2 MILLION DOLLARS&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;Building&lt;/th&gt;
        &lt;th&gt;Land Price&lt;/th&gt;
        &lt;th&gt;Land Price/Sqft&lt;/th&gt;
        &lt;th&gt;Land Price/Household&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Single family home&lt;/td&gt;
        &lt;td&gt;$1,000,000&lt;/td&gt;
        &lt;td&gt;$250&lt;/td&gt;
        &lt;td&gt;$1,000,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;Duplex&lt;/td&gt;
        &lt;td&gt;$1,200,000&lt;/td&gt;
        &lt;td&gt;$300&lt;/td&gt;
        &lt;td&gt;$600,000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;5-unit condo building&lt;/td&gt;
        &lt;td&gt;$2,000,000&lt;/td&gt;
        &lt;td&gt;$500&lt;/td&gt;
        &lt;td&gt;$400,000&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;It is really important to note that &lt;strong&gt;even though allowing more homes drove land prices up, households are paying &lt;em&gt;less&lt;/em&gt; for land&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&#34;ok-thats-the-theory-what-about-in-practice&#34;&gt;OK that&amp;rsquo;s the theory; what about in practice?&lt;/h2&gt;
&lt;p&gt;It can be hard to observe this in real life, because dense city centres tend to be pretty expensive. That&amp;rsquo;s a complicated topic that&amp;rsquo;s beyond the scope of this blog post, but there are places where it&amp;rsquo;s easy to see this specific phenomenon in Vancouver with &lt;a href=&#34;https://mountainmath.ca/map/assessment&#34;&gt;a map of land values&lt;/a&gt;. For example:&lt;/p&gt;
&lt;h2 id=&#34;north-west-point-grey&#34;&gt;&lt;a href=&#34;https://mountainmath.ca/map/assessment?zoom=16&amp;amp;lat=49.2745&amp;amp;lng=-123.2074&amp;amp;layer=5&amp;amp;mapBase=2&#34;&gt;North West Point Grey&lt;/a&gt;&lt;/h2&gt;

&lt;div class=&#34;local-img &#34;&gt;
    &lt;figure class=&#34;relative&#34;&gt;
        &lt;img class=&#39;local-image &#39; 
                src=&#39;https://www.reillywood.com/img/posts/land-values/nwpg.png&#39; /&gt;
        
        &lt;figcaption
                class=&#34;local-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                style=&#34;background-color: hsla(0, 0%, 0%, 0.7)&#34;&gt;
                Left: cheap land and expensive homes. Right: expensive land and &lt;em&gt;relatively&lt;/em&gt; cheap homes
        &lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;/div&gt; 
&lt;p&gt;This is one of the most expensive neighbourhoods in Vancouver, by design. Apartments are forbidden everywhere, only houses are allowed. And city planning &lt;a href=&#34;https://vancouver.ca/home-property-development/amend-subdivision-by-law-categories.aspx&#34;&gt;rules&lt;/a&gt; require each house to use up &lt;em&gt;much&lt;/em&gt; more land west of Blanca Street:&lt;/p&gt;
&lt;table&gt;
    &lt;tr&gt;
        &lt;th&gt;Area&lt;/th&gt;
        &lt;th&gt;Minimum Lot Size&lt;/th&gt;
        &lt;th&gt;Land Price/Sq Ft&lt;/th&gt;
        &lt;th&gt;Land Price/Lot&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;West of Blanca&lt;/td&gt;
        &lt;td&gt;12,000-18,000 sqft &lt;/td&gt;
        &lt;td&gt;Usually around $300&lt;/td&gt;
        &lt;td&gt;$7M-$30M&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td&gt;East of Blanca&lt;/td&gt;
        &lt;td&gt;3000-5400 sqft&lt;/td&gt;
        &lt;td&gt;Usually around $800&lt;/td&gt;
        &lt;td&gt;$3M-$8M&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;This is exactly what we were talking about. When the city lets people use less land per home, land prices go up and home prices go down. To be clear, $3M still isn&amp;rsquo;t cheap; we should go a lot further.&lt;/p&gt;
&lt;h2 id=&#34;shaughnessy&#34;&gt;&lt;a href=&#34;https://mountainmath.ca/map/assessment?zoom=16&amp;amp;lat=49.2566&amp;amp;lng=-123.1385&amp;amp;layer=5&amp;amp;mapBase=2&#34;&gt;Shaughnessy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s a similar story in Shaughnessy, historically Vancouver&amp;rsquo;s most exclusive neighbourhood:&lt;/p&gt;

&lt;div class=&#34;local-img &#34;&gt;
    &lt;figure class=&#34;relative&#34;&gt;
        &lt;img class=&#39;local-image &#39; 
                src=&#39;https://www.reillywood.com/img/posts/land-values/shaughnessy.png&#39; /&gt;
        
        &lt;figcaption
                class=&#34;local-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                style=&#34;background-color: hsla(0, 0%, 0%, 0.7)&#34;&gt;
                Top: Fairview/South Granville apartments+condos. Bottom: Shaughnessy mansions
        &lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;/div&gt; 
&lt;p&gt;South of 16th we zone for mansions on very large lots (making the land relatively cheap), and north of 16th we allow apartments and condo buildings (making the land relatively expensive). If you know Vancouver at all, you know that those apartments are a lot cheaper than the $10M+ Shaughnessy mansions!&lt;/p&gt;
&lt;h2 id=&#34;takeaway&#34;&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s important to distinguish between the cost of land &lt;em&gt;per square foot&lt;/em&gt; and the cost of land &lt;em&gt;per home&lt;/em&gt;. Limiting density does work to drive the former down, but at a terrible cost: it stops people of modest means from pooling their resources to outbid someone much richer.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>My encounter with evil SEO</title>
      <link>https://www.reillywood.com/blog/dmca-fun/</link>
      <pubDate>Mon, 16 Jun 2025 19:02:33 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/dmca-fun/</guid>
      <description>&lt;p&gt;I had an odd experience with this website, and I&amp;rsquo;m finally writing it up. The short version:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In August 2024 I wrote a blog post that documented how a local &amp;ldquo;independent journalist&amp;rdquo; had written for white nationalist websites.&lt;/li&gt;
&lt;li&gt;In October 2024 he filed a &lt;a href=&#34;https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act&#34;&gt;DMCA&lt;/a&gt; complaint with my host (Netlify).
&lt;ol&gt;
&lt;li&gt;Netlify support rubber-stamped the complaint without giving me a reasonable way to appeal.&lt;/li&gt;
&lt;li&gt;I moved to CloudFlare and cut the blog post back to a few essential facts+links, to make it easier for the next overworked support person to interpret.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;In February 2025 CloudFlare approved another DMCA complaint &lt;strong&gt;from someone who&amp;rsquo;d copied my entire post to a content mill and backdated it!&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post will mostly focus on the 2nd DMCA complaint, as it&amp;rsquo;s the most interesting one.&lt;/p&gt;
&lt;h2 id=&#34;my-post-was-copied-to-mormonfindcom&#34;&gt;My post was copied to&amp;hellip; MormonFind.com?&lt;/h2&gt;
&lt;p&gt;On February 14, while on vacation, I received the following email:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cloudflare received the below copyright infringement complaint regarding your account. If the content identified in the complaint is not removed within 48 hours, Cloudflare will take steps to disable access to the content, consistent with section 512(c) of  the Digital Millennium Copyright Act. Please note that these steps will include disabling access to the reported URL on which the content is located, which will affect any other content located on the same URL.&lt;/p&gt;
&lt;p&gt;Complaint Information:&lt;/p&gt;
&lt;p&gt;Reporter&amp;rsquo;s Name: Aaron Bennet&lt;/p&gt;
&lt;p&gt;Reporter&amp;rsquo;s Email Address: &amp;lt;redacted&amp;gt;&lt;/p&gt;
&lt;p&gt;Reporter&amp;rsquo;s Title: Copyright Infringement&lt;/p&gt;
&lt;p&gt;Reporter&amp;rsquo;s Company Name: Bennet Media Association&lt;/p&gt;
&lt;p&gt;Reporter&amp;rsquo;s Address: &amp;lt;redacted&amp;gt;&lt;/p&gt;
&lt;p&gt;Reported URL(s):
hxxps://www[.]reillywood[.]com/blog/riley-donovan/&lt;/p&gt;
&lt;p&gt;Original Work Description:
&lt;a href=&#34;https://mormonfind.com/2024/04/10/riley-donovan-contributes-to-white-supremacist-websites/&#34;&gt;https://mormonfind.com/2024/04/10/riley-donovan-contributes-to-white-supremacist-websites/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To respond to this issue, please reply to &lt;a href=&#34;mailto:abusereply@cloudflare.com&#34;&gt;abusereply@cloudflare.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sure enough, at that link (&lt;a href=&#34;https://archive.ph/lNWD3&#34;&gt;archive&lt;/a&gt;) was the text from my original post:&lt;/p&gt;

&lt;div class=&#34;local-img &#34;&gt;
    &lt;figure class=&#34;relative&#34;&gt;
        &lt;img class=&#39;local-image &#39; 
                src=&#39;https://www.reillywood.com/img/posts/dmca-1.png&#39; /&gt;
        
        &lt;figcaption
                class=&#34;local-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                style=&#34;background-color: hsla(0, 0%, 0%, 0.7)&#34;&gt;
                Uh, maybe not the most trustworthy news website.
        &lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;/div&gt; 
&lt;p&gt;I replied immediately:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It looks like the complainant has copied my blog post and posted it on that mormonfind.com website so they can file a DMCA complaint. That website is an obvious content mill and this complaint was not submitted in good faith.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I even took the post down (worried that they were going to nuke my entire site):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve taken the content down for now but it&amp;rsquo;s really concerning that Cloudflare would accept a DMCA complaint like this. It should be clear that a content mill described as &amp;ldquo;Mormon Find the Best News For You!&amp;rdquo; is not a real news website.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Alas, I never heard back and I don&amp;rsquo;t know if a human ever read my emails. CloudFlare blocked &lt;a href=&#34;https://www.reillywood.com/blog/riley-donovan/&#34;&gt;the post&lt;/a&gt; but not my whole website, and I enjoyed the rest of my vacation.&lt;/p&gt;
&lt;h1 id=&#34;further-investigation&#34;&gt;Further Investigation&lt;/h1&gt;
&lt;p&gt;This was all very frustrating, but also kind of fascinating. Someone had put a lot of effort into getting this one post taken down! Mormon Find wasn&amp;rsquo;t an especially convincing website, but it was good enough for its purposes.&lt;/p&gt;
&lt;p&gt;I poked around a bit and found &lt;a href=&#34;https://mormonfind.com/wp-sitemap-posts-post-1.xml&#34;&gt;the sitemap&lt;/a&gt; (&lt;a href=&#34;https://archive.is/sS2up&#34;&gt;archived link&lt;/a&gt;) for Mormon Find, which turned out to be a basic WordPress site. And conveniently each post had a Last Modified date:&lt;/p&gt;

&lt;div class=&#34;local-img &#34;&gt;
    &lt;figure class=&#34;relative&#34;&gt;
        &lt;img class=&#39;local-image &#39; 
                src=&#39;https://www.reillywood.com/img/posts/dmca-2.png&#39; /&gt;
        
    &lt;/figure&gt;
&lt;/div&gt; 
&lt;p&gt;From the sitemap I gathered that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The site had been &amp;ldquo;seeded&amp;rdquo; with an initial round of 30 generic news posts in 2022&lt;/li&gt;
&lt;li&gt;In February 2025 the owner posted another ~40 articles including:
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://archive.is/lNWD3&#34;&gt;My blog post, backdated to 2024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mormonfind.com/2023/01/08/wuffes/&#34;&gt;What looked like a critical review of a dog supplement, backdated to 2023&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pretty clever right? Set up a generic looking website, leave it on ice for a bit, and then use it to do evil SEO.&lt;/p&gt;
&lt;h2 id=&#34;lessons-learned&#34;&gt;Lessons Learned&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;rsquo;s pretty easy to abuse the DMCA to get content taken down online! The process is very biased towards the complainant.&lt;/li&gt;
&lt;li&gt;I should probably pay for web hosting. Web hosts are (understandably) not willing to spend much effort defending their free users.&lt;/li&gt;
&lt;/ol&gt;</description>
    </item>
    
    <item>
      <title>Agents all the way down</title>
      <link>https://www.reillywood.com/blog/visualization-agent/</link>
      <pubDate>Tue, 10 Jun 2025 18:28:48 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/visualization-agent/</guid>
      <description>&lt;p&gt;Say you&amp;rsquo;re working on an agent (&lt;a href=&#34;https://simonwillison.net/2025/May/22/tools-in-a-loop/&#34;&gt;a model using tools in a loop&lt;/a&gt;). Furthermore, let&amp;rsquo;s say your agent uses the &lt;a href=&#34;https://modelcontextprotocol.io/introduction&#34;&gt;Model Context Protocol&lt;/a&gt; to populate its set of tools dynamically. This results in an interesting UX question: &lt;strong&gt;how should you show text tool results to the user of your agent?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You could just show the raw text, but that&amp;rsquo;s a little unsatisfying when tool results are often JSON, XML, or some other structured data. You could parse the structured data, but that&amp;rsquo;s tricky too; the set of tools your agent has access to may change, and the tool results you get today could be structured differently tomorrow.&lt;/p&gt;
&lt;p&gt;I like another option: &lt;em&gt;pass the tool results to another agent&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-visualization-agent&#34;&gt;The Visualization Agent&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s add another agent to our system; we&amp;rsquo;ll call it the visualization agent. After the main agent executes a tool, it will pass the results to the visualization agent and say &amp;ldquo;hey, can you visualize this for the user?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The visualization agent has access to specialized tools like &amp;ldquo;show table&amp;rdquo;, &amp;ldquo;show chart&amp;rdquo;, &amp;ldquo;show formatted code&amp;rdquo;, etc. It handles the work of translating tool results in arbitrary formats into the structures that are useful for opinionated visualization.&lt;/p&gt;
&lt;p&gt;And if it can&amp;rsquo;t figure out a good way to visualize something, well, we can always fall back to text.&lt;/p&gt;
&lt;h2 id=&#34;why-do-it-this-way&#34;&gt;Why do it this way?&lt;/h2&gt;
&lt;p&gt;The big thing is that we can display arbitrary data to the user in a nice way, without assuming much about the tools our agent will have access to. We could also give the main agent visualization tools (tempting! so simple!), but:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;That can be &lt;em&gt;very&lt;/em&gt; wasteful of the context window
&lt;ol&gt;
&lt;li&gt;Imagine receiving 10,000 tokens from a tool, then the agent decides to pass those 10,000 tokens by calling a visualization tool - the 10,000 tokens just doubled to 20,000 in our chat history&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;The more tools an agent has access to, the more likely it is to get confused&lt;/li&gt;
&lt;li&gt;A specialized visualization agent can use a faster+cheaper model than our main agent&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;rsquo;s not all sunshine and roses; calling the visualization agent can be slow, and it adds some complexity. But I like this approach compared to the others I&amp;rsquo;ve seen, and &lt;a href=&#34;https://www.apple.com/ca/newsroom/2025/06/apple-supercharges-its-tools-and-technologies-for-developers/&#34;&gt;we&amp;rsquo;re not far away from fast local models being widely available&lt;/a&gt;. If you&amp;rsquo;ve got another approach, &lt;a href=&#34;https://bsky.app/profile/did:plc:cu5sipgjjnkg4rgnqewywf3l&#34;&gt;I&amp;rsquo;d love to hear from you&lt;/a&gt;!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Function Calling is Structured Output</title>
      <link>https://www.reillywood.com/blog/function-calling-is-structured-output/</link>
      <pubDate>Sun, 02 Mar 2025 16:01:49 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/function-calling-is-structured-output/</guid>
      <description>&lt;p&gt;This is a brief post about something that confused me a great deal when I started working with LLMs.&lt;/p&gt;
&lt;h2 id=&#34;context&#34;&gt;Context&lt;/h2&gt;
&lt;p&gt;Many LLM providers (&lt;a href=&#34;https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview&#34;&gt;Anthropic&lt;/a&gt;, &lt;a href=&#34;https://platform.openai.com/docs/guides/function-calling&#34;&gt;OpenAI&lt;/a&gt;, &lt;a href=&#34;https://ai.google.dev/gemini-api/docs/function-calling&#34;&gt;Google&lt;/a&gt;) support &amp;ldquo;function calling&amp;rdquo;, AKA &amp;ldquo;tool use&amp;rdquo;. In a nutshell:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When calling the provider&amp;rsquo;s chat completion APIs, you tell the model &amp;ldquo;if needed, I can run these specific functions for you.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;The model responds saying &amp;ldquo;hey go run function X with arguments Y and Z.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;You go and run the function with those arguments. Maybe you append the result to the chat so the model has access to it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Weather lookup is a common example. You tell the model &amp;ldquo;I have a function &lt;code&gt;get_temperature(city: String)&lt;/code&gt; that looks up the current temperature in a city&amp;rdquo;, and then when a question like &amp;ldquo;What&amp;rsquo;s the weather like in Tokyo?&amp;rdquo; comes up the model responds to your code with &amp;ldquo;please call &lt;code&gt;get_temperature(&amp;quot;Tokyo&amp;quot;)&lt;/code&gt;&amp;rdquo;.&lt;/p&gt;
&lt;h2 id=&#34;structured-output&#34;&gt;Structured Output&lt;/h2&gt;
&lt;p&gt;All well and good, but where this gets interesting is that &lt;strong&gt;function calling is &lt;em&gt;also&lt;/em&gt; a good way to get structured data out of LLMs&lt;/strong&gt;. You can provide a function definition that you have no intention of &amp;ldquo;calling&amp;rdquo;, purely to get data in the format you want.&lt;/p&gt;
&lt;p&gt;For example, using the Rust &lt;a href=&#34;https://github.com/jeremychone/rust-genai&#34;&gt;&lt;code&gt;genai&lt;/code&gt;&lt;/a&gt; library:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rs&#34; data-lang=&#34;rs&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Text to analyze
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;The quick brown fox jumps over the lazy dog.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Define a tool/function for rating grammar
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;grammar_tool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Tool&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;rate_grammar&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with_description&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Rate the grammatical correctness of English text&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;with_schema&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;fm&#34;&gt;json!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span class=&#34;s&#34;&gt;&amp;#34;object&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;properties&amp;#34;&lt;/span&gt;: &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;rating&amp;#34;&lt;/span&gt;: &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span class=&#34;s&#34;&gt;&amp;#34;integer&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;minimum&amp;#34;&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;maximum&amp;#34;&lt;/span&gt;: &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span class=&#34;s&#34;&gt;&amp;#34;Grammar rating from 1 to 10, where 10 is perfect grammar&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;explanation&amp;#34;&lt;/span&gt;: &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span class=&#34;s&#34;&gt;&amp;#34;string&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span class=&#34;s&#34;&gt;&amp;#34;Brief explanation for the rating&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;required&amp;#34;&lt;/span&gt;: &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;rating&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;explanation&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}));&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Create a chat request with the text and the grammar tool
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;chat_req&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ChatRequest&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;fm&#34;&gt;vec!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ChatMessage&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;system&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;You are a professional English grammar expert. Analyze the grammar of the given text and provide a rating.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ChatMessage&lt;/span&gt;::&lt;span class=&#34;n&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;fm&#34;&gt;format!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Please rate the grammar of this text: &amp;#39;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]).&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;append_tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;grammar_tool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...and execute it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;chat_res&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;exec_chat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;gpt-4o-mini&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;chat_req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;await&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result will include some JSON like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;rating&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;explanation&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;This sentence is grammatically perfect...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip;and we&amp;rsquo;re done. We just used function calling to get structured data, with no intention of calling any functions. This is much nicer and more reliable than string parsing on the raw chat output.&lt;/p&gt;
&lt;p&gt;This approach is probably obvious to many people, but it was unintuitive to me at first; I think &amp;ldquo;function calling&amp;rdquo; is a misleading name for this functionality that can be used for so much more.&lt;/p&gt;
&lt;h2 id=&#34;alternative-approaches&#34;&gt;Alternative Approaches&lt;/h2&gt;
&lt;p&gt;This isn&amp;rsquo;t the only way to get structured data out of an LLM; OpenAI supports &lt;a href=&#34;https://platform.openai.com/docs/guides/structured-outputs&#34;&gt;Structured Outputs&lt;/a&gt;, and Gemini lets you specify &lt;a href=&#34;https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.cachedContents#Schema&#34;&gt;a response schema&lt;/a&gt;. But for Anthropic, it seems like &lt;a href=&#34;https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview#json-output&#34;&gt;function calling is still recommended&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tools do not necessarily need to be client-side functions — you can use tools anytime you want the model to return JSON output that follows a provided schema.&lt;/p&gt;
&lt;/blockquote&gt;</description>
    </item>
    
    <item>
      <title>My latest attempt at collaborative text editing</title>
      <link>https://www.reillywood.com/blog/automerge/</link>
      <pubDate>Sun, 12 Jan 2025 14:02:41 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/automerge/</guid>
      <description>&lt;p&gt;I tried to use &lt;a href=&#34;https://automerge.org/&#34;&gt;Automerge&lt;/a&gt; again, and failed.&lt;/p&gt;
&lt;p&gt;For those of you who aren&amp;rsquo;t familiar, Automerge is a neat library that helps with building collaborative and local-first applications. It&amp;rsquo;s pretty cool! I work on &lt;a href=&#34;https://www.reillywood.com/blog/home-cooked-software&#34;&gt;a collaborative notes application&lt;/a&gt; that does not handle concurrent edits very well, and Automerge is one of the main contenders for improving that situation.&lt;/p&gt;
&lt;p&gt;I gave Automerge a try in 2023 and wasn&amp;rsquo;t able to get it working, to my chagrin. This weekend there was &lt;a href=&#34;https://lu.ma/2ul5uwdl&#34;&gt;an event in Vancouver for local-first software&lt;/a&gt; with one of the main Automerge authors, so I decided to attend and give it another try. I made a fair bit of progress, but ultimately gave up after spending ~5 hours on the problem. A few thoughts+observations:&lt;/p&gt;
&lt;h2 id=&#34;i-am-going-off-the-beaten-path-web&#34;&gt;I am going off the beaten path (web)&lt;/h2&gt;
&lt;p&gt;Automerge&amp;rsquo;s &amp;ldquo;golden path&amp;rdquo; is web apps. The core of Automerge is written in Rust, but it&amp;rsquo;s primarily used via WASM in the browser.&lt;/p&gt;
&lt;p&gt;This approach is unpleasant for me; I like Rust, I have a good understanding of how code runs+executes on a &amp;ldquo;real computer&amp;rdquo;, and I do not &lt;em&gt;want&lt;/em&gt; to write an application where 99% of the business logic runs in the browser. Instead, I tried to write an application where my Rust backend was the primary Automerge node and browser/JS Automerge nodes would talk to it.&lt;/p&gt;
&lt;p&gt;This did not go well; the documentation and ergonomics of the Rust library are lacking, and most tutorials assume that you are using the JS wrapper around the Rust library. And then when I tried to use the JS version in my simple web UI, the docs assumed a level of web development sophistication that I don&amp;rsquo;t have.&lt;/p&gt;
&lt;p&gt;To be clear, &lt;a href=&#34;https://bsky.app/profile/did:plc:cu5sipgjjnkg4rgnqewywf3l/post/3lfjjyjx4w225&#34;&gt;this is mostly a me problem&lt;/a&gt;: primarily targeting the browser is absolutely the way to go in 2025!&lt;/p&gt;
&lt;h2 id=&#34;i-am-going-off-the-beaten-path-local-first&#34;&gt;I am going off the beaten path (local-first)&lt;/h2&gt;
&lt;p&gt;Automerge tries to solve a &lt;em&gt;lot&lt;/em&gt; of problems related to local-first software. But I wanted to &amp;ldquo;start small&amp;rdquo; and solve the problem of concurrent text editing for an application that &lt;em&gt;isn&amp;rsquo;t&lt;/em&gt; local-first. In retrospect this was a mistake; the documentation was written for a very different audience than me, and I wasn&amp;rsquo;t especially aligned with what other people at the event were building.&lt;/p&gt;
&lt;h2 id=&#34;chrome-is-winning&#34;&gt;Chrome is winning&lt;/h2&gt;
&lt;p&gt;Something that was discussed at the event: if you &lt;em&gt;are&lt;/em&gt; building entirely in-browser local-first applications you may want to target Chrome, because Firefox is way behind on several new+useful APIs. This is sad, but not surprising.&lt;/p&gt;
&lt;h2 id=&#34;what-next&#34;&gt;What next?&lt;/h2&gt;
&lt;p&gt;I think it&amp;rsquo;s &lt;em&gt;possible&lt;/em&gt; to build an Automerge-based collaborative text editor the way I want, but it&amp;rsquo;s a lot harder than I expected. I&amp;rsquo;m going to shelve this and revisit it next time I have time+energy to hack on it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Strength Training</title>
      <link>https://www.reillywood.com/blog/strength-training/</link>
      <pubDate>Sun, 29 Dec 2024 10:15:45 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/strength-training/</guid>
      <description>&lt;p&gt;I started strength training for the first time in January 2024, in my late 30s. I wish I&amp;rsquo;d started earlier, but it&amp;rsquo;s been a hugely positive change in my life. Some observations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Standing up straight is a lot easier; back muscles help with posture!&lt;/li&gt;
&lt;li&gt;Stability is cool. I&amp;rsquo;m noticeably better at tightening my core in daily life, balancing, etc.&lt;/li&gt;
&lt;li&gt;I can put much more power into specific movements (ex: pedal hard on a bike) before feeling exerted or losing control&lt;/li&gt;
&lt;li&gt;I can lift my gangly 85lb dog easily without worrying that I&amp;rsquo;ll mess up my back&lt;/li&gt;
&lt;li&gt;RSI pain in my arms + wrists is nearly gone&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&amp;rsquo;ve been going to the same gym 3x/week on average. They have a pretty good model where:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They connect you with a specific personal trainer&lt;/li&gt;
&lt;li&gt;Before starting classes you do X training sessions, to make sure you have decent form and won&amp;rsquo;t injure yourself&lt;/li&gt;
&lt;li&gt;After starting classes you do a training session every other month or so&lt;/li&gt;
&lt;li&gt;The relationship with the trainer is a form of accountability; they notice if you miss classes for a couple weeks&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The classes are Crossfit-ish; about 2/3 strength, 1/3 cardio. They&amp;rsquo;re surprisingly fun now that I know what I&amp;rsquo;m doing.&lt;/p&gt;
&lt;h2 id=&#34;why-now&#34;&gt;Why now?&lt;/h2&gt;
&lt;p&gt;Sometime in 2023 I saw people I respect in a professional context (&lt;a href=&#34;https://jackrusher.com/&#34;&gt;Jack Rusher&lt;/a&gt;, Andreas Kling) talking about how it&amp;rsquo;s increasingly hard to build muscle as you grow older, and thought &amp;ldquo;hmm, concerning.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Later I started thinking about New Year&amp;rsquo;s resolutions etc. (as one does), and reached out to a local gym on a whim.&lt;/p&gt;
&lt;h2 id=&#34;why-didnt-i-start-earlier&#34;&gt;Why didn&amp;rsquo;t I start earlier?&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;d never enjoyed lifting weights before, and I didn&amp;rsquo;t know how to do it safely. To be honest, I associated weight lifting with jock culture and looked down on it a bit. I&amp;rsquo;ve gone through on-and-off phases of going to the gym, but it&amp;rsquo;s always been individual cardio work. I didn&amp;rsquo;t enjoy team sports as a kid, and I&amp;rsquo;ve always been someone who prefers to figure things out on their own. That approach has served me well in some areas, but it wasn&amp;rsquo;t working for fitness.&lt;/p&gt;
&lt;h1 id=&#34;parting-advice&#34;&gt;Parting Advice&lt;/h1&gt;
&lt;p&gt;If you haven&amp;rsquo;t been able to commit to a fitness routine, try trading some money for training and a community of like-minded people. Expert advice is useful and social accountability works!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Goodbye, Twitter</title>
      <link>https://www.reillywood.com/blog/goodbye-twitter/</link>
      <pubDate>Thu, 07 Nov 2024 20:49:18 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/goodbye-twitter/</guid>
      <description>&lt;p&gt;After 13 years of daily Twitter usage, I bit the bullet and switched to &lt;a href=&#34;https://bsky.app/profile/gridsvancouver.bsky.social&#34;&gt;Bluesky&lt;/a&gt; (urbanism stuff, mostly) and &lt;a href=&#34;https://mastodon.social/@reillywood&#34;&gt;Mastodon&lt;/a&gt; (computer stuff) full time.&lt;/p&gt;
&lt;p&gt;I made friends on Twitter, I learned a lot, and I was part of a few communities that would not exist without Twitter. I&amp;rsquo;m proud of my small contributions to Vancouver urbanism over the years, and they mostly happened on Twitter or adjacent to it.&lt;/p&gt;
&lt;p&gt;Leaving was sad but unavoidable; the site&amp;rsquo;s &lt;em&gt;really&lt;/em&gt; gone downhill since Musk purchased it. The quality of discourse has plummeted now that the replies to any popular tweet are dominated by &lt;a href=&#34;https://en.wiktionary.org/wiki/blue_check&#34;&gt;bluechecks&lt;/a&gt;. Twitter has a critical mass of shitty resentful people who wouldn&amp;rsquo;t be out of place on 4chan, and the site shoves their opinions in your face.&lt;/p&gt;
&lt;p&gt;Anyway, it&amp;rsquo;s a few months in and I&amp;rsquo;m glad I made the change. I don&amp;rsquo;t love Mastodon, but Bluesky feels a lot like an earlier, more pleasant version of Twitter.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How I Use LLMs (Sep 2024)</title>
      <link>https://www.reillywood.com/blog/how-i-use-llms-sep-2024/</link>
      <pubDate>Sun, 15 Sep 2024 08:13:36 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/how-i-use-llms-sep-2024/</guid>
      <description>&lt;p&gt;It feels a bit early to be writing an update to &lt;a href=&#34;https://www.reillywood.com/blog/how-i-use-llms/&#34;&gt;something I wrote 1.5 months ago&lt;/a&gt;, but we live in interesting times. Shortly after writing that post, I started trying out &lt;a href=&#34;https://aider.chat&#34;&gt;Aider&lt;/a&gt; with Claude 3.5 Sonnet. Aider&amp;rsquo;s an open source Python CLI app that you run inside a Git repo with an OpenAI/Anthropic/whatever API key&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h1 id=&#34;my-aider-workflow&#34;&gt;My Aider workflow&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;I direct Aider toward a file or multiple files of interest (with &lt;code&gt;/add src/main.rs&lt;/code&gt; or similar)&lt;/li&gt;
&lt;li&gt;I describe a commit-sized piece of work to do in 1 or 2 sentences&lt;/li&gt;
&lt;li&gt;Aider sends some file contents and my prompt to the LLM and translates the response into a Git commit&lt;/li&gt;
&lt;li&gt;I skim the commit and leave it as is, tell Aider to tweak it some more, tweak it myself, or &lt;code&gt;/undo&lt;/code&gt; it entirely&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This works shockingly well; most of the time, Aider+Claude can get it right on the first or second try. This workflow has a few properties that I really like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;rsquo;s IDE-agnostic (no need to switch to something like &lt;a href=&#34;https://www.cursor.com/&#34;&gt;Cursor&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s very low-friction, which encourages trying things out
&lt;ol&gt;
&lt;li&gt;No need to copy code from a browser, write commit messages, etc.&lt;/li&gt;
&lt;li&gt;Undoing work is trivial (just delete the Git commit or run &lt;code&gt;/undo&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s pay-as-you-go (I pay Anthropic by the token, no monthly subscription)&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;prompts&#34;&gt;Prompts&lt;/h1&gt;
&lt;p&gt;Here are some examples of the prompts I do in Aider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Library updates should be streamed to all connected web clients over a WebSocket. Add an /updates websocket in the Rust code that broadcasts updated LibraryItems to clients (triggered by a successful call to update_handler). The JS in index.html should subscribe to the WebSocket and call table.updateData() to update the Tabulator table&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Add a new endpoint (POST or PUT) for adding new items to the library. It will create a new LibraryItemCreatedEvent, save it to the DB, apply it to the in-memory library, then broadcast the new item over the websocket&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;add a nice-looking button that bookmarks the current song. don&amp;rsquo;t worry about hooking it up to anything just yet&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Add a new &amp;ldquo;test-api&amp;rdquo; command to &lt;code&gt;justfile&lt;/code&gt;. It should curl the API exposed by add_item_handler and check that the response status code is CREATED&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Write a throwaway C# program for benchmarking the the same SQLite insert as &lt;code&gt;create_item()&lt;/code&gt; in &lt;code&gt;lib.rs&lt;/code&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m still developing an intuition for how to write these, but with all of these examples I got results that were correct or able to be fixed up easily. Sometimes I am very precise about what I want, and sometimes I am not; it all depends on the task at hand and how confident I am that the LLM will do what I&amp;rsquo;m looking for.&lt;/p&gt;
&lt;h1 id=&#34;what-does-all-this-mean&#34;&gt;What does all this mean?&lt;/h1&gt;
&lt;p&gt;I dunno! The world is drowning in long-winded AI thinkpieces, so I&amp;rsquo;ll spare you another one.&lt;/p&gt;
&lt;p&gt;All I know for a fact is that if I have a commit-sized piece of work in mind, there&amp;rsquo;s a very good chance that Claude+Aider can do it for me in less than a minute — &lt;em&gt;today&lt;/em&gt;. I&amp;rsquo;m still exploring the implications of that, but Jamie Brandon&amp;rsquo;s &lt;a href=&#34;https://www.scattered-thoughts.net/writing/speed-matters/&#34;&gt;Speed Matters&lt;/a&gt; post feels very relevant. I can try out more ideas and generally be more ambitious with my software projects, which is very exciting.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;You can also point Aider at a locally-hosted LLM, which is cool, but in my experience the quality is nowhere near as good as Claude.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Riley Donovan contributes to white supremacist websites</title>
      <link>https://www.reillywood.com/blog/riley-donovan/</link>
      <pubDate>Wed, 28 Aug 2024 17:44:51 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/riley-donovan/</guid>
      <description>&lt;p&gt;This post has been temporarily taken down while I respond to a fraudulent DMCA complaint.&lt;/p&gt;
&lt;p&gt;The short version:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I wrote a post about how &lt;a href=&#34;https://x.com/valdombre&#34;&gt;Riley Donovan&lt;/a&gt; (a guy from Salt Spring Island who runs the &lt;a href=&#34;https://dominionreview.ca/author/rileydonovan/&#34;&gt;&amp;ldquo;Dominion Review&amp;rdquo;&lt;/a&gt; and submits op-eds to many Canadian newspapers) used to contribute to the &lt;a href=&#34;https://www.google.com/search?q=%22riley+donovan%22+%22council+of+european+canadians%22&#34;&gt;&amp;ldquo;Council of European Canadians&amp;rdquo;&lt;/a&gt;, a white nationalist website. He does not like this.&lt;/li&gt;
&lt;li&gt;He&amp;rsquo;s &lt;a href=&#34;https://mormonfind.com/2024/04/10/riley-donovan-contributes-to-white-supremacist-websites/&#34;&gt;copied the full text of my post to a content mill called &amp;ldquo;Mormon Find&amp;rdquo;&lt;/a&gt; (&lt;a href=&#34;https://archive.ph/lNWD3&#34;&gt;backup&lt;/a&gt;), backdated it, and submitted a DMCA complaint to get my post taken down. Clever!&lt;/li&gt;
&lt;/ol&gt;</description>
    </item>
    
    <item>
      <title>How I use LLMs</title>
      <link>https://www.reillywood.com/blog/how-i-use-llms/</link>
      <pubDate>Thu, 01 Aug 2024 19:46:02 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/how-i-use-llms/</guid>
      <description>&lt;p&gt;I find LLMs to be pretty useful these days. I don&amp;rsquo;t consider myself to be on the frontier of LLM experimentation, but when I talk to (technical) people it sounds like my workflow is pretty uncommon, so I should probably write about it.&lt;/p&gt;
&lt;h1 id=&#34;llm-the-command-line-tool&#34;&gt;LLM (the command-line tool)&lt;/h1&gt;
&lt;p&gt;Simon Willison&amp;rsquo;s &lt;a href=&#34;https://github.com/simonw/llm&#34;&gt;&lt;code&gt;llm&lt;/code&gt;&lt;/a&gt; command-line tool is the primary way I use LLMs. I sometimes struggle to describe the appeal of &lt;code&gt;llm&lt;/code&gt; to people because it&amp;rsquo;s &lt;em&gt;boring&lt;/em&gt;. &lt;code&gt;llm&lt;/code&gt; lets you do the following with any popular LLM (hosted or local):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ask the LLM one-off questions (optionally taking &lt;a href=&#34;https://en.wikipedia.org/wiki/Standard_streams&#34;&gt;stdin&lt;/a&gt; as context)&lt;/li&gt;
&lt;li&gt;Start a chat session (optionally starting from the last ad-hoc question)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that&amp;rsquo;s about it! It&amp;rsquo;s one of those lovely tools that does a few things well. I usually start sessions with exploratory questions/requests, sometimes piping in data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat xycursor.rs &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; llm &lt;span class=&#34;s2&#34;&gt;&amp;#34;the end() function in this file is confusing, explain it&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then if I need to follow up on a question, &lt;code&gt;llm chat --continue&lt;/code&gt; drops me into an interactive chat that starts &lt;em&gt;after the last question+response&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; llm chat --continue
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Chatting with claude-3-5-sonnet-20240620
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Type &amp;#39;exit&amp;#39; or &amp;#39;quit&amp;#39; to exit
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Type &amp;#39;!multi&amp;#39; to enter multiple lines, then &amp;#39;!end&amp;#39; to finish
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;gt; write a comment explaining that function, using ASCII diagrams
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Important things about this workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;rsquo;s trivial to &amp;ldquo;connect&amp;rdquo; the LLM to other data+files
&lt;ol&gt;
&lt;li&gt;For example, every week I used to manually rewrite the output of &lt;a href=&#34;https://github.com/nushell/nu_scripts/blob/main/make_release/this_week_in_nu_weekly.nu&#34;&gt;this script&lt;/a&gt; to be more readable before publishing it; now I pipe it to &lt;code&gt;llm&lt;/code&gt; and tell it to do an initial rewrite first&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;llm&lt;/code&gt; makes it trivial to go from exploratory work to more focused iteration&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I have &lt;code&gt;llm&lt;/code&gt; set up to use &lt;a href=&#34;https://github.com/rgwood/dotfiles/blob/master/llm/terse.txt&#34;&gt;this custom prompt&lt;/a&gt;, no matter what underlying LLM it&amp;rsquo;s using. I find that it helps make responses much more succinct.&lt;/p&gt;
&lt;h1 id=&#34;github-copilot&#34;&gt;GitHub Copilot&lt;/h1&gt;
&lt;p&gt;It&amp;rsquo;s good, I use it every day. It&amp;rsquo;s a lot more widely known than &lt;code&gt;llm&lt;/code&gt; so I won&amp;rsquo;t spill too much ink over it.&lt;/p&gt;
&lt;h1 id=&#34;observations&#34;&gt;Observations&lt;/h1&gt;
&lt;p&gt;I use LLMs and web search in a similar way: do a quick exploratory investigation into something, taking the initial results with a grain of salt. The skills+knowledge you need to evaluate Google results are very similar to the ones you need to evaluate LLM results!&lt;/p&gt;
&lt;p&gt;I mostly use LLMs for computer stuff, and it&amp;rsquo;s often really easy to verify whether a programming/computing answer is any good; just try it out! LLMs are probably not quite as useful for fields where that&amp;rsquo;s not the case.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m happy with &lt;code&gt;llm&lt;/code&gt; but it is, ultimately, a wrapper around a basic chat interface and we can probably do better. &lt;a href=&#34;https://support.anthropic.com/en/articles/9487310-what-are-artifacts-and-how-do-i-use-them&#34;&gt;Claude Artifacts&lt;/a&gt; is very appealing in that it can offer a faster iteration cycle for web development (but is unfortunately coupled to an expensive subscription service), and &lt;a href=&#34;https://simonwillison.net/2024/Jul/31/aider/&#34;&gt;Aider&lt;/a&gt; is interesting as a better way to give an LLM access to context from an entire code base. I&amp;rsquo;m hoping we&amp;rsquo;ll see more tools like these that extend what we can do with LLMs.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Recently</title>
      <link>https://www.reillywood.com/blog/recently-jul-2024/</link>
      <pubDate>Tue, 02 Jul 2024 17:57:28 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/recently-jul-2024/</guid>
      <description>&lt;p&gt;Been a while since the last update, I&amp;rsquo;ve been busy with &lt;a href=&#34;https://www.reillywood.com/blog/work-update/&#34;&gt;the new job&lt;/a&gt;. Things have been going well!&lt;/p&gt;
&lt;p&gt;I went to New York in March for my first week at Datadog, which was a good way to start the job; I met other new joiners, met people on my team, saw a bit of the city. And the view from the office is alright:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/recently/nyc_hu_5f9043593bdd1e23.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Datadog operates at a much larger scale than any company I&amp;rsquo;ve ever worked at, and that has some upsides and downsides (but mostly upsides). I feel lucky to work with a lot of smart, enthusiastic people.&lt;/p&gt;
&lt;p&gt;Last week I was in New York again, for Datadog&amp;rsquo;s annual &lt;a href=&#34;https://www.dashcon.io/&#34;&gt;DASH&lt;/a&gt; conference. I was helping run a booth for my team and it was good to talk to users (and potential users) in person. I got to see a bit more of the city outside of work; the &lt;a href=&#34;https://intrepidmuseum.org/?gad_source=1&amp;amp;gclid=EAIaIQobChMI1_L-39eJhwMVbxutBh1RlAczEAAYASAAEgI9vfD_BwE&#34;&gt;Intrepid Museum&lt;/a&gt; was a highlight (an aircraft carrier! a Space Shuttle! a submarine!).&lt;/p&gt;
&lt;p&gt;Outside of work, I&amp;rsquo;ve been spending a little bit of time on Nushell (but not as much as I would like). I&amp;rsquo;ve been driving some changes to the &lt;a href=&#34;https://www.nushell.sh/book/explore.html&#34;&gt;&lt;code&gt;explore&lt;/code&gt;&lt;/a&gt; interactive pager (which reminds me, I need to update that documentation). I&amp;rsquo;m trying to get it to a point where I&amp;rsquo;m happy with it for version 1.0; I&amp;rsquo;m not quite there yet but I&amp;rsquo;ve made a lot of changes under the hood.&lt;/p&gt;
&lt;h1 id=&#34;nonfiction-i-read-recently&#34;&gt;Nonfiction I read recently&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://db.cs.cmu.edu/papers/2024/whatgoesaround-sigmodrec2024.pdf&#34;&gt;What Goes Around Comes Around&amp;hellip; And Around&amp;hellip;&lt;/a&gt; (⭐⭐⭐⭐)&lt;/strong&gt; Michael Stonebraker&amp;rsquo;s &lt;a href=&#34;https://www.reillywood.com/blog/db-papers&#34;&gt;back&lt;/a&gt; for another opinionated overview of databases, this time with Andy Pavlo. The whole thing is good but I particularly like their take on vector databases:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;They are single-purpose DBMSs with indexes to accelerate nearest-neighbor search. RM DBMSs should soon provide native support for these data structures and search methods using their extendable type system that will render such specialized databases unnecessary&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On a related note, I like Simon Willison&amp;rsquo;s point that &lt;a href=&#34;https://simonwillison.net/2024/Jul/2/weeknotes/#livestreaming-rag-with-steve-krouse-and-val-town&#34;&gt;maybe you don&amp;rsquo;t need vectors for RAG&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The more time I spend with this RAG pattern (ed: one using full-text search) the more I like it. It’s considerably easier to reason about than RAG using vector search based on embeddings, and can provide high quality results with a relatively simple implementation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&#34;fiction-i-read-recently&#34;&gt;Fiction I read recently&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Lonesome_Dove&#34;&gt;Lonesome Dove&lt;/a&gt; (⭐⭐⭐⭐⭐)&lt;/strong&gt;
This was described to me as &amp;ldquo;the Western novel to read if you don&amp;rsquo;t normally read Westerns&amp;rdquo; and yeah, it was great.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://www.goodreads.com/book/show/127278133-glorious-exploits&#34;&gt;Glorious Exploits&lt;/a&gt; (⭐⭐⭐⭐)&lt;/strong&gt;
Funny + touching story about 2 unemployed potters in 412 BC who decide to put on a play with imprisoned Athenian soldiers as the cast.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Microserfs&#34;&gt;Microserfs&lt;/a&gt; (⭐⭐⭐⭐)&lt;/strong&gt; Strange that I hadn&amp;rsquo;t read this before, but Douglas Coupland has a not-entirely-positive reputation in his hometown. He&amp;rsquo;s kinda known as the guy who got too many undeserved public art commissions around here. Anyway! It was a really fun read and it felt like it could have been written yesterday (surprising for a book about the tech industry written nearly 30 years ago).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A bunch of  &lt;a href=&#34;https://en.wikipedia.org/wiki/Horatio_Hornblower&#34;&gt;Horatio Hornblower&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/The_Bolitho_novels&#34;&gt;Richard Bolitho&lt;/a&gt; books (⭐⭐⭐)&lt;/strong&gt; I was looking for something along the same lines as the &lt;a href=&#34;https://en.wikipedia.org/wiki/Aubrey%E2%80%93Maturin_series&#34;&gt;excellent Aubrey-Maturin series&lt;/a&gt;. These weren&amp;rsquo;t quite it. Hornblower isn&amp;rsquo;t very fun as a protagonist and Bolitho is a boring one, I couldn&amp;rsquo;t make it very far into either series.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Work Update</title>
      <link>https://www.reillywood.com/blog/work-update/</link>
      <pubDate>Fri, 01 Mar 2024 16:15:52 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/work-update/</guid>
      <description>&lt;p&gt;So, big news: I&amp;rsquo;m off to a new job at Datadog, working on their &lt;a href=&#34;https://www.coscreen.co/&#34;&gt;CoScreen&lt;/a&gt; screen sharing tool 😎. I&amp;rsquo;m gonna be working remotely from Vancouver, which will be interesting because I&amp;rsquo;ll be dogfooding CoScreen as I work on it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;ll be good to work on native software again. It feels like the vast majority of jobs today operate at a level of abstraction where the OS doesn&amp;rsquo;t matter much; when you&amp;rsquo;re writing REST services it&amp;rsquo;s rare that you actually interact with the OS itself, right? CoScreen is very different in that it&amp;rsquo;s native software that needs to do lots of interaction with the OS to provide a great UX. Which is exciting and fun and cool, at least until I get bogged down in the details of &lt;a href=&#34;https://learn.microsoft.com/en-us/windows/win32/gdi/windows-gdi&#34;&gt;GDI&lt;/a&gt; or whatever 😅.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>My home-cooked software</title>
      <link>https://www.reillywood.com/blog/home-cooked-software/</link>
      <pubDate>Fri, 05 Jan 2024 21:42:28 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/home-cooked-software/</guid>
      <description>&lt;p&gt;I reread &lt;a href=&#34;https://www.robinsloan.com/notes/home-cooked-app/&#34;&gt;Robin Sloan&amp;rsquo;s post about a &amp;ldquo;home-cooked&amp;rdquo; app&lt;/a&gt;, and it continues to resonate; there&amp;rsquo;s something special about writing software for very small audiences. In that vein, here&amp;rsquo;s something that my partner and I have been using since last year:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/homecooked/notes_hu_4379ba758d925343.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;We needed a solution for shared notes that works well across Android, iOS, and the (desktop) web&amp;hellip; so I made one. We use it a lot, mostly for things like our grocery list and cooking notes. It&amp;rsquo;s simple, &lt;a href=&#34;https://twitter.com/reillywood/status/1679257534098714624&#34;&gt;fast&lt;/a&gt;, and it works for us.&lt;/p&gt;
&lt;p&gt;Technology-wise, it&amp;rsquo;s just a website that works well on desktop and mobile. The back-end is written in Rust, it uses &lt;a href=&#34;https://htmx.org/&#34;&gt;HTMX&lt;/a&gt; for some dynamic updates, and the note contents get saved to a SQLite database (backed up to S3 with &lt;a href=&#34;https://litestream.io/&#34;&gt;Litestream&lt;/a&gt;). The whole thing compiles down to a single-file executable that gets run on a cheap VPS.&lt;/p&gt;
&lt;p&gt;Many corners were cut during the development of this project! Proper collaborative text editing &lt;a href=&#34;https://twitter.com/reillywood/status/1680021914985840646&#34;&gt;is hard&lt;/a&gt;, and I took a quick-and-dirty approach. Eventually I&amp;rsquo;ll get it working better with &lt;a href=&#34;https://automerge.org/&#34;&gt;Automerge&lt;/a&gt; or &lt;a href=&#34;https://github.com/y-crdt/y-crdt&#34;&gt;whatever&lt;/a&gt;, but for now I have something good enough for a user base of 2.&lt;/p&gt;
&lt;p&gt;And now it&amp;rsquo;s really easy to &lt;em&gt;do stuff&lt;/em&gt; with our shared notes:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/homecooked/eink_hu_dcb051ea1602931e.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;That&amp;rsquo;s &lt;a href=&#34;https://shop.pimoroni.com/products/inky-impression-7-3?variant=40512683376723&#34;&gt;a cheap 7&amp;quot; e-ink display&lt;/a&gt; with a Raspberry Pi on the back, showing a customized view of our grocery list plus enough information to answer the question &amp;ldquo;do I need an umbrella today?&amp;rdquo;. It&amp;rsquo;s like a little household dashboard that we can take a quick look at as we head out the door. Eventually I need to make a frame for it and mount it on the wall, but it&amp;rsquo;s pretty handy as-is.&lt;/p&gt;
&lt;h1 id=&#34;why-do-all-this&#34;&gt;Why do all this?&lt;/h1&gt;
&lt;p&gt;Shared notes are  a solved problem, I&amp;rsquo;m sure I could have found an existing service for this. But:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I like having a project to tinker on for someone I care for, in the same way that I like cooking for them&lt;/li&gt;
&lt;li&gt;This is &lt;em&gt;permanent&lt;/em&gt; in a way that cloud services are not. It will never change unless we want it to, and it will will work for decades without much effort. We might be using this in a retirement home one day&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s trivial to tweak and extend software I wrote myself. I didn&amp;rsquo;t need to figure out an API to get the  the e-ink display, I just built a new HTML view on top of existing data&lt;/li&gt;
&lt;li&gt;Software designed for an audience of 2 is able to be much simpler than software that anticipates the needs of a large+diverse user base&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;bonus-burninatorexe&#34;&gt;Bonus: Burninator.exe&lt;/h1&gt;
&lt;p&gt;Now that I&amp;rsquo;ve shown you something useful, &lt;a href=&#34;https://github.com/rgwood/burninator&#34;&gt;here&amp;rsquo;s some much sillier home-cooked software&lt;/a&gt;:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/homecooked/burninator_hu_72ffc6dc535b4466.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;A friend asked:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is a program that I can run in the background to raise the temperature of a laptop? If my laptop gets too cold the screen starts to flicker&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wasn&amp;rsquo;t aware of any, so I wrote one that queries &lt;a href=&#34;https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page&#34;&gt;WMI&lt;/a&gt; for the current CPU temperature and then does busy work until the temperature is high enough. Is it dumb? Yes. Does it solve this one person&amp;rsquo;s very specific problem? Also yes!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Recently</title>
      <link>https://www.reillywood.com/blog/recently-aug-2023/</link>
      <pubDate>Mon, 21 Aug 2023 20:26:41 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/recently-aug-2023/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s been a good summer so far. Some things I&amp;rsquo;ve been up to:&lt;/p&gt;
&lt;h2 id=&#34;systemctl-tui&#34;&gt;systemctl-tui&lt;/h2&gt;
&lt;p&gt;I got annoyed that I couldn&amp;rsquo;t find a decent GUI for systemd services, so &lt;a href=&#34;https://github.com/rgwood/systemctl-tui&#34;&gt;I built one&lt;/a&gt;:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/recently/systemctl-tui_hu_17439e06740f2580.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;It was partially an excuse to experiment with &lt;a href=&#34;https://github.com/ratatui-org/ratatui&#34;&gt;&lt;code&gt;ratatui&lt;/code&gt;&lt;/a&gt; for terminal UIs in Rust, and I think I like it quite a bit. &lt;code&gt;ratatui&lt;/code&gt; gives you tools you need to do immediate-mode rendering efficiently, but the broad strokes of your application&amp;rsquo;s architecture are up to you.&lt;/p&gt;
&lt;h2 id=&#34;gardening--patio&#34;&gt;Gardening + Patio&lt;/h2&gt;
&lt;p&gt;I spent more time than usual on our little outdoor space. Lots of flowers, a new planter, pressure washing, etc.&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/recently/garden1_hu_ff3c3adfbdf81bc0.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        So many petunias
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/recently/garden2_hu_fe60da45705866e6.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Patio furniture is a work in progress
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;I&amp;rsquo;ve organized a fair number of get-togethers/dinners on the patio, it&amp;rsquo;s been really nice meeting new people and catching up with people who I haven&amp;rsquo;t seen since before the pandemic.&lt;/p&gt;
&lt;h2 id=&#34;new-puter&#34;&gt;New &amp;lsquo;Puter&lt;/h2&gt;
&lt;p&gt;I finally took the plunge and bought a &lt;a href=&#34;https://frame.work&#34;&gt;Framework Laptop&lt;/a&gt;:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/recently/framework_hu_86de4dafcdeb4eec.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        A normal computer
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Intel&amp;rsquo;s multicore performance has gotten a lot better in recent generations, so I figured I could replace my desktop for Rust work. So far, so good!&lt;/p&gt;
&lt;p&gt;I like the hardware a lot. There were some teething pains getting Linux working properly, but that kinda comes with the territory.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>New Project: Escape Artist</title>
      <link>https://www.reillywood.com/blog/escape-artist/</link>
      <pubDate>Sun, 23 Apr 2023 11:03:55 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/escape-artist/</guid>
      <description>&lt;p&gt;I recently participated in the &lt;a href=&#34;https://handmade.network/jam/visibility-2023&#34;&gt;Handmade Network Visibility Jam&lt;/a&gt; and built &lt;a href=&#34;https://github.com/rgwood/escape-artist&#34;&gt;a tool that I&amp;rsquo;d been wanting for my own use&lt;/a&gt;:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/escape-artist/screenshot_hu_3827a1378d83c81f.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Escape Artist in action
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;In a nutshell, Escape Artist shows ANSI escape sequences that are normally invisible. It&amp;rsquo;s kinda like View Source for the terminal; my hope is that it will be useful for anyone working on shells and terminal applications.&lt;/p&gt;
&lt;h2 id=&#34;tech-stack&#34;&gt;Tech Stack&lt;/h2&gt;
&lt;p&gt;Escape Artist is primarily written in Rust, using &lt;a href=&#34;https://github.com/tokio-rs/axum&#34;&gt;Axum&lt;/a&gt; to serve up a web UI using Tailwind+Preact. Events get streamed to the front-end over a websocket. I took great pains to keep the front-end simple; nearly all business logic lives in the Rust back-end, there is no front-end build step, and all dependencies are bundled with the application.&lt;/p&gt;
&lt;p&gt;Escape Artist compiles down to a single-file 1.8MB executable, and nearly half of that is Tailwind and an embedded font.&lt;/p&gt;
&lt;h2 id=&#34;lessons-learned&#34;&gt;Lessons Learned&lt;/h2&gt;
&lt;p&gt;Parsing escape sequences out of a stream of bytes is &lt;a href=&#34;https://vt100.net/emu/dec_ansi_parser&#34;&gt;remarkably tricky&lt;/a&gt;. Thankfully the &lt;a href=&#34;https://github.com/alacritty/vte&#34;&gt;&lt;code&gt;vte&lt;/code&gt;&lt;/a&gt; crate does most of the heavy lifting for us.&lt;/p&gt;
&lt;p&gt;This minimal approach to web UI was generally pretty nice! I occasionally ran into a bit of trouble wrangling JS libraries that assume everyone is using NPM, React, and a bundler like webpack, but overall it wasn&amp;rsquo;t too bad.&lt;/p&gt;
&lt;p&gt;I ended up needing to roll my own tooltip solution using &lt;a href=&#34;https://floating-ui.com/&#34;&gt;Floating UI&lt;/a&gt;, and&amp;hellip; wow. &lt;a href=&#34;https://floating-ui.com/docs/tutorial&#34;&gt;Their introductory tutorial&lt;/a&gt; is one of the best I&amp;rsquo;ve ever seen for a library in any language.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Vancouver Building Age Map</title>
      <link>https://www.reillywood.com/blog/building-age-map/</link>
      <pubDate>Sat, 25 Mar 2023 17:15:35 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/building-age-map/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s a pandemic project that I think turned out pretty well:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/building-age-map/photo_hu_f9ece82429fee5b6.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        An 18-piece map of Vancouver above my couch
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;In 1954, Vancouver&amp;rsquo;s planning department put together a remarkable hand-coloured map showing the age of every building in the city. Thankfully for us, the Vancouver Archives &lt;a href=&#34;https://searcharchives.vancouver.ca/age-of-buildings&#34;&gt;made scans of the map available in impressively high resolution&lt;/a&gt;:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/building-age-map/closeup_hu_ab4cab9d006916d5.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Imagine how long it took to colour every building!
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;I took the scans for the northern (and most interesting) half of the city, had them cleaned up and colour-corrected, then &lt;a href=&#34;https://www.vancouveroncanvas.com/include/plaque_mounting.php&#34;&gt;printed them on wooden blocks&lt;/a&gt;. The whole thing is about 2 metres wide, and let me tell you it was a pain to hang.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m quite happy with how the project turned out, but if I were to redo it I&amp;rsquo;d whiten the yellowed background; I think that would make the colours really pop.&lt;/p&gt;
&lt;h3 id=&#34;ok-but-whats-the-point&#34;&gt;OK but what&amp;rsquo;s the point?&lt;/h3&gt;
&lt;p&gt;My sole motivation when starting this project was &amp;ldquo;dang, that&amp;rsquo;s a neat-looking map!&amp;rdquo; However, looking at it daily really drives home how little Vancouver&amp;rsquo;s built form has changed since 1954. Most of Vancouver was single-family houses then; &lt;a href=&#34;https://twitter.com/GRIDSVancouver/status/640544192045826049&#34;&gt;most of Vancouver is single-family houses now&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like a copy of the cleaned up scans, &lt;a href=&#34;https://www.reillywood.com/about/&#34;&gt;just give me a shout&lt;/a&gt;.&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/building-age-map/downtown_hu_50c0685ed80533f7.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        The panels for the downtown peninsula
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Recently on reillywood.com</title>
      <link>https://www.reillywood.com/blog/recently-website-mar-2023/</link>
      <pubDate>Sun, 05 Mar 2023 22:39:25 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/recently-website-mar-2023/</guid>
      <description>&lt;p&gt;I find working on &lt;a href=&#34;https://www.reillywood.com/blog/website-rewrite/&#34;&gt;my personal website&lt;/a&gt; to be soothing. It&amp;rsquo;s one of those never-ending projects in a good way, and I work on it in small bursts of energy whenever I have the time and inspiration. I&amp;rsquo;ve added a handful of new features in recent months and, uh, I&amp;rsquo;m sorry.&lt;/p&gt;
&lt;p&gt;Some of these features are an attempt to revisit the glory that was the Geocities-era web; you might have noticed the glittery cursor trail (based on the excellent &lt;a href=&#34;https://github.com/tholman/cursor-effects/pull/46&#34;&gt;&lt;code&gt;cursor-effects&lt;/code&gt;&lt;/a&gt;), or the animated snowy background that only shows up in December.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve added custom emoji that I can easily use throughout the website (with &lt;a href=&#34;https://gohugo.io/content-management/shortcodes/&#34;&gt;Hugo shortcodes&lt;/a&gt;), here&amp;rsquo;s a small sample:&lt;/p&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/disappointed-cowboy.png&#34; title=&#34;:disappointed-cowboy:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/disappointed-ghost.png&#34; title=&#34;:disappointed-ghost:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/grimacing-cowboy.png&#34; title=&#34;:grimacing-cowboy:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/microheart.png&#34; title=&#34;:microheart:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/drake-dislike.png&#34; title=&#34;:drake-dislike:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/drake-like.png&#34; title=&#34;:drake-like:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/thonk.png&#34; title=&#34;:thonk:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/thinkrosoft.png&#34; title=&#34;:thinkrosoft:&#34;/&gt;
&lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/pwease.png&#34; title=&#34;:pwease:&#34;/&gt;
&lt;p&gt;I use a lot of custom emoji on Slack and Discord; adding them to my website was the logical next step. I&amp;rsquo;ve also been toying with the idea of letting readers emoji-react to my blog posts but haven&amp;rsquo;t quite come up with a design I&amp;rsquo;m happy with. Speaking of emoji&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:raised-eyebrow:&#34; alt=&#34;raised-eyebrow&#34;
     src=&#34;https://www.reillywood.com/img/emoji/raised-eyebrow.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Whoa, what&amp;rsquo;s this?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        I also added these, uh, Socratic dialogue bubbles between me and arbitrary emoji heads. These are heavily inspired by &lt;a href=&#34;https://fasterthanli.me/articles/the-http-crash-course-nobody-asked-for&#34;&gt;&amp;ldquo;Cool Bear&amp;rdquo;&lt;/a&gt; but with more characters because why not?
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:cowboy:&#34; alt=&#34;cowboy&#34;
     src=&#34;https://www.reillywood.com/img/emoji/cowboy.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Got it. I&amp;rsquo;m sure everyone will love this and nobody will find it annoying.
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These features are pretty dumb and they&amp;rsquo;d never make it past design review in the real world. In a way, that&amp;rsquo;s the point. &lt;a href=&#34;https://makefrontendshitagain.party/&#34;&gt;Let&amp;rsquo;s make websites fun again.&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Why Nushell?</title>
      <link>https://www.reillywood.com/blog/why-nu/</link>
      <pubDate>Sat, 04 Feb 2023 12:25:26 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/why-nu/</guid>
      <description>&lt;p&gt;I work on a command-line shell called &lt;a href=&#34;https://www.nushell.sh/&#34;&gt;Nushell&lt;/a&gt; (Nu for short) with some internet friends, and I think it&amp;rsquo;s pretty cool. To convince &lt;em&gt;you&lt;/em&gt; that it&amp;rsquo;s cool (or at least worth a try), here&amp;rsquo;s a whirlwind tour.&lt;/p&gt;
&lt;script id=&#34;asciicast-ZXnKdNfE8uCYeLivXdhN0oXzP&#34; src=&#34;https://asciinema.org/a/ZXnKdNfE8uCYeLivXdhN0oXzP.js&#34; async 
data-cols=112 data-autoplay=&#34;true&#34; data-loop=&#34;true&#34;&gt;
&lt;/script&gt;
&lt;h2 id=&#34;basic-querying&#34;&gt;Basic Querying&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start by taking a look around the filesystem. We&amp;rsquo;ll use &lt;code&gt;ls&lt;/code&gt; to take a look at the files in &lt;code&gt;/usr/bin&lt;/code&gt;:&lt;/p&gt;
&lt;script id=&#34;asciicast-yHVFvLrMK6aXwuCJPjGzLer9j&#34; src=&#34;https://asciinema.org/a/yHVFvLrMK6aXwuCJPjGzLer9j.js&#34; async 
data-cols=112 data-loop=&#34;true&#34;&gt;&lt;/script&gt;
&lt;p&gt;We got an actual table back! It has columns! 1551 rows is a bit much though; why don&amp;rsquo;t we see what the 10 biggest files are? We can use &lt;code&gt;sort-by&lt;/code&gt; to sort by size, and then grab the first 10 with &lt;code&gt;first&lt;/code&gt;:&lt;/p&gt;
&lt;script id=&#34;asciicast-ihnzqYUvIGCiPxemQbEGT52sc&#34; src=&#34;https://asciinema.org/a/ihnzqYUvIGCiPxemQbEGT52sc.js&#34; async 
data-loop=&#34;true&#34;&gt;&lt;/script&gt;
&lt;p&gt;Maybe we&amp;rsquo;re only interested in files that have been updated recently. Let&amp;rsquo;s add a &lt;code&gt;where&lt;/code&gt; to the pipeline to &lt;em&gt;filter&lt;/em&gt; data:&lt;/p&gt;
&lt;script id=&#34;asciicast-xHeW3CpezXNmdbzGuCBveZX6r&#34; src=&#34;https://asciinema.org/a/xHeW3CpezXNmdbzGuCBveZX6r.js&#34; async 
data-loop=&#34;true&#34;&gt;&lt;/script&gt;
&lt;p&gt;And to cap it all off, let&amp;rsquo;s &lt;em&gt;project&lt;/em&gt; the results; we&amp;rsquo;ll use &lt;code&gt;select&lt;/code&gt; to grab only the columns we care about:&lt;/p&gt;
&lt;script id=&#34;asciicast-hI058TaXhoZbZJIB8dgIZmKPF&#34; src=&#34;https://asciinema.org/a/hI058TaXhoZbZJIB8dgIZmKPF.js&#34; async 
data-cols=112 data-rows=16 data-loop=&#34;true&#34;&gt;&lt;/script&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:think:&#34; alt=&#34;think&#34;
     src=&#34;https://www.reillywood.com/img/emoji/think.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        OK, so what&amp;rsquo;s all the fuss about? Those seeem like pretty basic operations.
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        They are basic! Sorting, filtering, and projecting are all table stakes (🥁) for working with tabular data. But think about how how much harder they are in a POSIX shell like Bash:
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Sorting&lt;/strong&gt;: maybe the command you&amp;rsquo;re using has sorting functionality built in, like &lt;code&gt;-S&lt;/code&gt; in coreutils &lt;code&gt;ls&lt;/code&gt;. That&amp;rsquo;s the best case, and it involves remembering a bunch of different sorting flags for different commands. In the worst case, you&amp;rsquo;ll have to do string parsing to get a substring out of each line and then sort based on that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filtering&lt;/strong&gt;: good luck! Filtering strings in simple ways with &lt;code&gt;grep&lt;/code&gt; isn&amp;rsquo;t too bad. But if you&amp;rsquo;re working with more complex data types (like dates), you&amp;rsquo;ll have trouble. For example there &lt;em&gt;isn&amp;rsquo;t&lt;/em&gt; a good way to filter &lt;code&gt;ls&lt;/code&gt; output by date, and so most people &lt;a href=&#34;https://unix.stackexchange.com/a/10043/395923&#34;&gt;give up on &lt;code&gt;ls&lt;/code&gt; and use &lt;code&gt;find&lt;/code&gt; instead&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Projecting&lt;/strong&gt;: I hope you like string parsing. You&amp;rsquo;ll have to use &lt;code&gt;awk&lt;/code&gt; or something similar to extract substrings from each line, and hope that your input data never changes in a way that causes things to break.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;working-with-data&#34;&gt;Working With Data&lt;/h2&gt;
&lt;p&gt;Nushell prides itself on making it easy to work with data from external sources — not just our built-in commands like &lt;code&gt;ls&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Say you have a TOML file. Reading it into a Nushell value is as simple as &lt;code&gt;open foo.toml&lt;/code&gt;, and once that&amp;rsquo;s done you can sort/filter/project it with exactly the same commands as we used with &lt;code&gt;ls&lt;/code&gt; above.&lt;/p&gt;
&lt;script id=&#34;asciicast-Ip1iEP0vSqw8qqZkt8iijPR3E&#34; src=&#34;https://asciinema.org/a/Ip1iEP0vSqw8qqZkt8iijPR3E.js&#34; async&gt;&lt;/script&gt;
&lt;p&gt;This isn&amp;rsquo;t limited to TOML; you can do the same with XML, JSON, CSV, Excel, and even SQLite. If the usual Nu commands work for your SQLite needs, great. If not, it&amp;rsquo;s easy to switch to SQL with &lt;code&gt;query db&lt;/code&gt;:&lt;/p&gt;
&lt;script id=&#34;asciicast-axD1PW6iYDRr7IvDNqk9UGLhZ&#34; src=&#34;https://asciinema.org/a/axD1PW6iYDRr7IvDNqk9UGLhZ.js&#34; async&gt;&lt;/script&gt;
&lt;p&gt;And if you want to bring in data from a web API, that&amp;rsquo;s easy too. Just run &lt;code&gt;fetch &amp;lt;url&amp;gt;&lt;/code&gt;, and JSON content will be automatically converted to a Nu table:&lt;/p&gt;
&lt;script id=&#34;asciicast-eZ6EyENrHEh2ut5htpgLpJwWB&#34; src=&#34;https://asciinema.org/a/eZ6EyENrHEh2ut5htpgLpJwWB.js&#34; async&gt;&lt;/script&gt;
&lt;h2 id=&#34;cross-platform-really&#34;&gt;Cross-platform. Really.&lt;/h2&gt;
&lt;p&gt;Nu takes cross-platform support seriously. We develop and test against Linux, macOS, &lt;em&gt;and&lt;/em&gt; Windows. This is a big selling point for me; other shells tend to support *nix well (bash, zsh) or Windows well (PowerShell) but not both.&lt;/p&gt;
&lt;h2 id=&#34;whats-the-catch&#34;&gt;What&amp;rsquo;s the catch?&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s still (relatively) early days for Nu. We didn&amp;rsquo;t get everything right on the first try, there are still a few rough edges when it comes to command design. Accordingly, breaking changes can and do occur.&lt;/p&gt;
&lt;p&gt;If you need a shell that will remain unchanged for decades to come, Nu is probably not right for you (yet). But if you&amp;rsquo;re willing to join us for a ride, you won&amp;rsquo;t be disappointed.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Why does Vancouver have 895 zoning districts?</title>
      <link>https://www.reillywood.com/blog/cd-zones/</link>
      <pubDate>Sun, 29 Jan 2023 11:28:09 +0100</pubDate>
      
      <guid>https://www.reillywood.com/blog/cd-zones/</guid>
      <description>&lt;p&gt;If you try to do any kind of analysis of Vancouver&amp;rsquo;s urban planning, you quickly run into a problem:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/cd-zones/legend_hu_89c6152b25f963f1.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        An amusing map &lt;a href=&#34;https://twitter.com/mikefeaver/status/1463386736877191172/&#34;&gt;by Mike Feaver&lt;/a&gt;
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Vancouver has a &lt;em&gt;lot&lt;/em&gt; of zoning districts. 895 of them, to be exact&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. That works out to roughly 1 zoning district for every 740 residents; I hope you weren&amp;rsquo;t planning on reading every one.&lt;/p&gt;
&lt;h1 id=&#34;comprehensive-development&#34;&gt;&amp;ldquo;Comprehensive Development&amp;rdquo;&lt;/h1&gt;
&lt;p&gt;About 9/10 of those zoning districts are bespoke ones; unique zoning bylaws that apply to one specific property, never to be reused. The pejorative term for this practice is &amp;ldquo;spot zoning,&amp;rdquo; but the official name is &amp;ldquo;comprehensive development&amp;rdquo; (CD). You can see the full list of 813 CD zones &lt;a href=&#34;https://cd1-bylaws.vancouver.ca/index.htm&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:think:&#34; alt=&#34;think&#34;
     src=&#34;https://www.reillywood.com/img/emoji/think.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Why would 1 property ever get its own zone? Don&amp;rsquo;t zoning districts usually apply to a whole neighbourhood?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        The theory is that sometimes people want to build unusual things that the city never thought to plan for. Like maybe you want to build an amusement park, but the city doesn&amp;rsquo;t &lt;em&gt;have&lt;/em&gt; a zoning district that allows for amusement parks — and the easiest solution is to make you a new, unique zoning district.
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:thinking:&#34; alt=&#34;thinking&#34;
     src=&#34;https://www.reillywood.com/img/emoji/thinking.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Does that mean we have 813 super special buildings in Vancouver?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Well&amp;hellip;
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;If you look back at the earliest CD zones in Vancouver (starting in the 1950s), they often fit the theory; they apply to unusual sites like &lt;a href=&#34;https://cd1-bylaws.vancouver.ca/cd-1(001).PDF&#34;&gt;the (huge) Oakridge Centre mall&lt;/a&gt; and &lt;a href=&#34;https://cd1-bylaws.vancouver.ca/cd-1(003B).PDF&#34;&gt;the PNE fairgrounds&lt;/a&gt;. Unfortunately things got out of hand, and over the years CD zones became a go-to tool for &lt;em&gt;everything:&lt;/em&gt;&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/cd-zones/mountpleasantduplex_hu_fdcbab48df33d07d.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        A duplex in Mount Pleasant with &lt;a href=&#34;https://cd1-bylaws.vancouver.ca/cd-1(412).PDF&#34;&gt;its own CD zone&lt;/a&gt;
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/cd-zones/alicesaunders_hu_8d598ac5b3c665b0.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Low-rise seniors&amp;rsquo; housing with &lt;a href=&#34;https://cd1-bylaws.vancouver.ca/cd-1(109).pdf&#34;&gt;its own CD zone&lt;/a&gt;
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/cd-zones/e-12-ave_hu_ed7e30639ea225d4.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Apartments 2 blocks from Commercial+Broadway station, with &lt;a href=&#34;https://bylaws.vancouver.ca/consolidated/13329.PDF&#34;&gt;their own CD zone&lt;/a&gt;
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/cd-zones/yaletown-towers_hu_da0643e5a46a92c9.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        &lt;a href=&#34;https://www.hdwallpapers.cc/wallpaper/yaletown-vancouver-sunset/&#34;&gt;Yaletown towers&lt;/a&gt;. Nearly every building in this photo has its own CD zone
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;If you pick a random multifamily building from the 1980&amp;rsquo;s or later, the odds are good that it has its own CD zone.&lt;/p&gt;
&lt;h1 id=&#34;ok-but-does-it-matter&#34;&gt;OK, but does it matter?&lt;/h1&gt;
&lt;p&gt;Giving nearly every new building its own zoning bylaw creates some problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;This doesn&amp;rsquo;t scale. Writing a new zoning bylaw takes a lot of time for a trained professional, and every new bylaw needs to be approved by council at a public hearing.&lt;/li&gt;
&lt;li&gt;The more zones we have, the harder it is to make changes to our urban planning regime. Changing 1 zoning bylaw is really hard; changing 100 bylaws is nearly impossible.&lt;/li&gt;
&lt;li&gt;No one person&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; can read and understand 800+ zoning bylaws; we&amp;rsquo;ve long since reached a point where our planning code is beyond the grasp of professionals (to say nothing of us interested amateurs).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As an example of #2, consider one train station on the &lt;a href=&#34;https://en.wikipedia.org/wiki/Canada_Line&#34;&gt;Canada Line&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34; data-theme=&#34;dark&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;A decade after the Canada Line was built, the new things around the busy King Edward/Cambie station are as follows:&lt;br&gt;- 9 midrise market condos&lt;br&gt;- Only one has retail outlets&lt;br&gt;- Those outlets are a fitness place, an insurance company, and a Tim Hortons&lt;br&gt;- That&amp;#39;s it&lt;br&gt;Vancouver!&lt;/p&gt;&amp;mdash; Justin McElroy (@j_mcelroy) &lt;a href=&#34;https://twitter.com/j_mcelroy/status/1370441764914434048?ref_src=twsrc%5Etfw&#34;&gt;March 12, 2021&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;
&lt;p&gt;Each condo building was given a unique CD zone, and for nearly all of those the planners just&amp;hellip; didn&amp;rsquo;t allow retail uses.&lt;/p&gt;
&lt;p&gt;If the buildings had all been assigned a standard multifamily zone, the zoning could be changed for multiple buildings at once. As is, every building will need its unique zoning bylaw updated individually — but Vancouver rarely does proactive zoning changes, and frankly doesn&amp;rsquo;t have the administrative capacity to rewrite 8 distinct bylaws at once. The best we can hope for is that one or two strata councils pressure the city to fix &lt;em&gt;their&lt;/em&gt; building, and even that&amp;rsquo;s unlikely.&lt;/p&gt;
&lt;h2 id=&#34;why-do-we-do-this&#34;&gt;Why do we do this?&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;em&gt;Warning: Speculation Ahead&lt;/em&gt; ⚠️&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s about control. CD zones can be (and usually are) narrowly written to allow one specific building form and use; no more, no less. This approach gives the planning department a great deal of control over what gets built, as compared to reusable zoning districts which need to be a little more flexible. And &lt;a href=&#34;https://www.reillywood.com/blog/director-of-planning/&#34;&gt;as we know&lt;/a&gt;, the planning department prizes their ability to shape and control urban design.&lt;/p&gt;
&lt;p&gt;Typically this approach gets justified in the name of &amp;ldquo;good urban design&amp;rdquo;, but from what I hear it&amp;rsquo;s more about risk aversion. Planning is a highly politicized profession, and politicians+planners don&amp;rsquo;t want to get blamed for any unintended consequences of a zoning bylaw. One way to mitigate that risk is to be &lt;em&gt;extremely&lt;/em&gt; prescriptive about what can be built, and CD zones accomplish that.&lt;/p&gt;
&lt;p&gt;Admittedly, this is speculation and hearsay — but it&amp;rsquo;s informed by a decade of paying close attention to Vancouver planning and politics. If I got this wrong, please &lt;a href=&#34;https://www.reillywood.com/about&#34;&gt;let me know&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Determined using the official &lt;a href=&#34;https://opendata.vancouver.ca/explore/dataset/zoning-districts-and-labels/&#34;&gt;Zoning districts and labels&lt;/a&gt; data set on January 30, 2023.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;To mitigate this, the &lt;a href=&#34;https://zoning.sociology.ubc.ca/&#34;&gt;UBC Sociology Zoning Project&lt;/a&gt; stepped up with a small army of research assistants to read and summarize zoning codes for us. They&amp;rsquo;ve done incredible work, but their work shouldn&amp;rsquo;t be necessary.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>&#34;Requires the decision of the Director of Planning&#34;</title>
      <link>https://www.reillywood.com/blog/director-of-planning/</link>
      <pubDate>Wed, 11 Jan 2023 16:40:25 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/director-of-planning/</guid>
      <description>&lt;p&gt;For a long time, my mental model of urban planning was basically &amp;ldquo;there are written rules about what you can build, and to build something you just follow the rules.&amp;rdquo; Unfortunately, this is not an accurate way to think about Vancouver.&lt;/p&gt;
&lt;p&gt;I get alerts for &lt;a href=&#34;https://www.shapeyourcity.ca/hub-page/rezoning-and-development&#34;&gt;new rezoning+development applications&lt;/a&gt; (using &lt;a href=&#34;https://github.com/rgwood/RezoningScraper&#34;&gt;a small tool I wrote&lt;/a&gt;), and this little snippet shows up over and over:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;the application is &amp;ldquo;conditional&amp;rdquo; so it may be permitted. However, it requires the decision of the Director of Planning.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can see this on virtually any type of housing proposed in Vancouver, here&amp;rsquo;s a selection:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/director-of-planning/director1_hu_7e0bf2e90477c99c.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/director-of-planning/director2_hu_292b5746d6956177.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/director-of-planning/director3_hu_6cf100b60ac70000.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/director-of-planning/director4_hu_8f7188857e2ad702.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;I&amp;rsquo;ve been tracking rezoning+development applications for about a year and a half, and my data set has 599 items with the words &amp;ldquo;Director of Planning&amp;rdquo;.&lt;/p&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:thonk:&#34; alt=&#34;thonk&#34;
     src=&#34;https://www.reillywood.com/img/emoji/thonk.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        OK, but&amp;hellip; why? Doesn&amp;rsquo;t the Director of Planning have better things to do than approve individual houses and small apartment buildings?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        I&amp;rsquo;ll defer that question to a senior planner at the City of Vancouver:
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;blockquote class=&#34;twitter-tweet&#34; data-conversation=&#34;none&#34; data-lang=&#34;en&#34; data-theme=&#34;light&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Oh you mean real Outright! There’s not much. The discretionary system, invented 30’yrara ago, kept outright aspecrs pretty low in order to empower staff to negotiate good urban design for conditional density and height.&lt;/p&gt;&amp;mdash; Paul C P Cheng (@PaulCPCheng) &lt;a href=&#34;https://twitter.com/PaulCPCheng/status/1402384706839011328?ref_src=twsrc%5Etfw&#34;&gt;June 8, 2021&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt; 
&lt;p&gt;To translate: Vancouver intentionally allows &lt;em&gt;very&lt;/em&gt; little development without giving planners a &lt;del&gt;discretionary&lt;/del&gt; subjective veto. Nearly anything other than a suburban house (but also many suburban houses!) is &amp;ldquo;conditional&amp;rdquo;, and for conditional uses the Director of Planning can impose any requirements they like. A typical zoning bylaw often looks &lt;a href=&#34;https://bylaws.vancouver.ca/zoning/zoning-by-law-district-schedule-rt-5.pdf&#34;&gt;like this&lt;/a&gt;:&lt;/p&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/director-of-planning/conditional_hu_73e80154e1850c34.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Conditional uses go something like this for developers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Submit a detailed architectural proposal&lt;/li&gt;
&lt;li&gt;Wait a long time (often over a year)&lt;/li&gt;
&lt;li&gt;Receive a document with many pages of conditions requiring specific changes to the initial proposal - or a rejection
&lt;ol&gt;
&lt;li&gt;The changes can be to architecture, landscaping, parking, or whatever the planners fancy this month - conditions are entirely up to the discretion of staff&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, here&amp;rsquo;s &lt;a href=&#34;https://www.shapeyourcity.ca/2886-commercial-dr&#34;&gt;a small apartment building near Commercial-Broadway&lt;/a&gt; that did not require a rezoning but it did get 17 pages of conditions.&lt;/p&gt;
&lt;p&gt;The Director of Planning &lt;a href=&#34;https://twitter.com/ianwrob/status/1465567726626246663&#34;&gt;can delegate this work to other planners with architecture credentials&lt;/a&gt;. I&amp;rsquo;m told that &amp;ldquo;about 7&amp;rdquo; people other than the Director have the authority to approve a project.&lt;/p&gt;
&lt;h2 id=&#34;ok-so-what&#34;&gt;OK, so what?&lt;/h2&gt;
&lt;p&gt;You could tell a story in which this system makes sense. Some plots of land are unique, and perhaps important development sites really do benefit from senior planners&amp;rsquo; time and site-specific requirements.&lt;/p&gt;
&lt;p&gt;On the other hand&amp;hellip; &lt;a href=&#34;https://twitter.com/PaulCPCheng/status/1402384706839011328&#34;&gt;like Paul said&lt;/a&gt;, there&amp;rsquo;s not much that this system doesn&amp;rsquo;t apply to. The law, in its majestic equality, forbids outright development of townhouses and large condo towers alike.&lt;/p&gt;
&lt;p&gt;In practice, our conditional approval system means that a lot of important planning rules live in the heads of planning staff. This isn&amp;rsquo;t so bad if you&amp;rsquo;re &lt;a href=&#34;https://www.onni.com&#34;&gt;a&lt;/a&gt; &lt;a href=&#34;https://amacon.com/&#34;&gt;large&lt;/a&gt; &lt;a href=&#34;https://westbankcorp.com/&#34;&gt;developer&lt;/a&gt;; they&amp;rsquo;re sophisticated enough to know what staff are likely to approve, in no small part due to their ability to &lt;a href=&#34;https://www.theglobeandmail.com/canada/british-columbia/article-thousands-of-vancouver-housing-units-in-limbo-in-tug-of-war-between/&#34;&gt;hire the well-connected&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is, however, remarkably difficult for small builders (and interested amateurs like me) to answer the question &amp;ldquo;what can be built on this land?&amp;rdquo;. Good luck out there!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Windows UI in 2022</title>
      <link>https://www.reillywood.com/blog/windows-ui-frameworks/</link>
      <pubDate>Thu, 24 Nov 2022 08:42:46 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/windows-ui-frameworks/</guid>
      <description>&lt;p&gt;Like many other people, I unexpectedly found myself stuck at home without much to do in early 2020. To stay sane, I set about answering a question I&amp;rsquo;d had for a while: &lt;strong&gt;what&amp;rsquo;s the deal with native GUI frameworks on Windows these days?&lt;/strong&gt; &lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/thinkrosoft.png&#34; title=&#34;:thinkrosoft:&#34;/&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s rarely obvious which of Microsoft&amp;rsquo;s several supported technologies is the best choice for a new project, and Windows doesn&amp;rsquo;t have the same culture of &lt;a href=&#34;https://panic.com/&#34;&gt;idiomatic+consistent native GUIs&lt;/a&gt; as macOS. After months of obsessive &lt;del&gt;escapism&lt;/del&gt; focus I emerged with a decent understanding of the problem space; let&amp;rsquo;s start with an overview of the technologies in use today:&lt;/p&gt;
&lt;h2 id=&#34;win32&#34;&gt;Win32&lt;/h2&gt;
&lt;p&gt;This is kind of a catch-all term for the old-school way to build a Windows application using arcane, less-than-ergonomic C APIs. This is unpleasant and your application will look ugly, but one big advantage is that the Win32 APIs will live forever and they underpin all the other UI frameworks.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s well worth your time to learn a bit of this (read &lt;a href=&#34;http://www.charlespetzold.com/pw5/&#34;&gt;Charles Petzold&amp;rsquo;s book&lt;/a&gt; &lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/microheart.png&#34; title=&#34;:microheart:&#34;/&gt;), but it&amp;rsquo;s rare to see people building pure-Win32 GUIs these days.&lt;/p&gt;
&lt;h2 id=&#34;windows-forms-winforms&#34;&gt;Windows Forms (WinForms)&lt;/h2&gt;
&lt;p&gt;A designer-first GUI framework for .NET applications. It gets a bad rap because the average WinForms UI is pretty ugly, but it remains surprisingly practical. WinForms is a thin layer over the old-school Win32 APIs, which is both a blessing (simplicity) and a curse (mediocre layout+styling options). WinForms is well-supported by the .NET team.&lt;/p&gt;
&lt;h2 id=&#34;windows-presentation-foundation-wpf&#34;&gt;Windows Presentation Foundation (WPF)&lt;/h2&gt;
&lt;p&gt;A sophisticated GUI framework using a declarative HTML-like language (XAML). It&amp;rsquo;s pretty good but complex and nearly abandoned; it hasn&amp;rsquo;t received new features in a decade. Ownership has changed to the Windows team (previously WPF was owned by the .NET team) and they&amp;rsquo;re doing the absolute bare minimum to support it.&lt;/p&gt;
&lt;p&gt;Many people still use WPF to build new applications today, and I can&amp;rsquo;t blame them. But it doesn&amp;rsquo;t exactly have a bright future.&lt;/p&gt;
&lt;h2 id=&#34;universal-windows-platform-uwp--winui-2&#34;&gt;Universal Windows Platform (UWP) / WinUI 2&lt;/h2&gt;
&lt;p&gt;To make a long story short, this is what Microsoft started pushing heavily with Windows 8. It uses XAML just like WPF, but the default styling looks a lot nicer. UWP applications are quite nice to &lt;em&gt;use&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately Microsoft saddled UWP developers with incredibly painful sandboxing and packaging requirements, and as a result it never saw much adoption. &lt;a href=&#34;https://www.theregister.com/2021/10/26/microsofts_uwp_unwanted_windows_platform/&#34;&gt;Microsoft finally pulled the plug about a year ago&lt;/a&gt;, and UWP is in maintenance mode; &lt;em&gt;do not start new projects in UWP&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;winui-3&#34;&gt;WinUI 3&lt;/h2&gt;
&lt;p&gt;A small team is trying to salvage what they can from UWP&amp;rsquo;s UI stack (WinUI). Slow, buggy, and weighed down by a lot of historical baggage. Very few teams at Microsoft are dogfooding WinUI 3, and so I hesitate to recommend it.&lt;/p&gt;
&lt;h1 id=&#34;webview2&#34;&gt;WebView2&lt;/h1&gt;
&lt;p&gt;The Windows team has been going full steam ahead embedding Edge/Chromium in Windows, and &lt;a href=&#34;https://developer.microsoft.com/en-us/microsoft-edge/webview2/&#34;&gt;WebView2&lt;/a&gt; is the result. WebView2 makes it easy to embed web UI in any native application (&lt;a href=&#34;https://github.com/rgwood/MinimalWebView&#34;&gt;here&amp;rsquo;s a demo&lt;/a&gt;), and Microsoft teams are adopting it en masse.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a little sad, but if you&amp;rsquo;re looking for an actively developed UI technology with a lot of energy+investment behind it&amp;hellip; this is it. All the usual complaints about web UI apply, but if you can get past those I have no qualms recommending WebView2.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:big-frown:&#34; alt=&#34;big-frown&#34;
     src=&#34;https://www.reillywood.com/img/emoji/big-frown.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Wow, what a mess. Why did things end up this way?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        &lt;del&gt;It&amp;rsquo;s all Steven Sinofsky&amp;rsquo;s fault.&lt;/del&gt; Well, it&amp;rsquo;s complicated.
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Win32 has many shortcomings for historical reasons, and Microsoft&amp;rsquo;s obsession with backwards compatibility means that it can&amp;rsquo;t be improved.&lt;/p&gt;
&lt;p&gt;WinForms made it a lot easier to build a UI, but it&amp;rsquo;s a thin layer over Win32 and it can only go so far.&lt;/p&gt;
&lt;p&gt;WPF was a huge leap forward with a big team behind it. But then it got abandoned because:&lt;/p&gt;
&lt;p&gt;The Windows team bet big on UWP. In some ways they did a great job; UWP made it easy to build attractive, fast UIs that work well on multiple devices (computer, tablet, phone). Unfortunately, UWP&amp;rsquo;s good parts were coupled with an unacceptable bargain: to use UWP technologies, developers had to cede a lot of control over distribution of their application. Most developers decided this was a raw deal, and rejected it.&lt;/p&gt;
&lt;p&gt;Microsoft realized their mistakes with UWP a few years ago, and have been trying to make various UWP-related technologies (including the UI framework WinUI) available with fewer restrictions. Unfortunately I suspect these efforts are too little, too late; I believe that if WinUI 3 had a bright future, we&amp;rsquo;d be seeing Microsoft adopt it internally. By and large, they are not.&lt;/p&gt;
&lt;!--more--&gt;</description>
    </item>
    
    <item>
      <title>S3 buckets and custom domains</title>
      <link>https://www.reillywood.com/blog/s3-custom-domain/</link>
      <pubDate>Tue, 02 Aug 2022 10:06:15 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/s3-custom-domain/</guid>
      <description>&lt;p&gt;Say you want to host some files in an S3 bucket, under your own custom subdomain with nice short HTTPS URLs. For example, you own &lt;code&gt;foo.com&lt;/code&gt; and you want files to be accessible at URLs like &lt;code&gt;https://files.foo.com/bar.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a lot more complex than it should be! It involves configuring 3 separate AWS services and I&amp;rsquo;m already forgetting the boring details, so let&amp;rsquo;s write them down for future reference.&lt;/p&gt;
&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:cloud:&#34; alt=&#34;cloud&#34;
     src=&#34;https://www.reillywood.com/img/emoji/cloud.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        Cloud is the future&amp;hellip; wait, &lt;em&gt;3&lt;/em&gt; separate AWS services?
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&#34;flex flex-row-reverse&#34;&gt;
    &lt;img class=&#34;h-12 self-center rounded-full ml-3 transition hover:scale-95&#34; src=&#34;https://www.reillywood.com/img/main/headshot-2025.jpg&#34; alt=&#34;Reilly&#34; title=&#34;it&#39;s-a me, Reilly!&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-yellow-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        YEP.
        &lt;div class=&#34;absolute h-2 w-2 border-t border-r rotate-45 right-[-0.28rem] top-0 bottom-0 my-auto bg-yellow-50
        dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&#34;flex&#34;&gt;
    &lt;img class=&#34;h-12 self-center pr-3 transition hover:scale-95&#34; title=&#34;:expressionless:&#34; alt=&#34;expressionless&#34;
     src=&#34;https://www.reillywood.com/img/emoji/expressionless.png&#34; alt=&#34;&#34; /&gt;
    &lt;div class=&#34;relative self-center border rounded-sm bg-blue-50 dark:bg-solarized-base02 dark:border-solarized-blue px-2 py-1&#34;&gt;
        &amp;hellip;
        &lt;div class=&#34;absolute h-2 w-2 border-b border-l left-[-0.28rem] rotate-45 top-0 bottom-0 my-auto bg-blue-50
            dark:bg-solarized-base02 dark:border-solarized-blue z-50&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;creating-the-s3-bucket&#34;&gt;Creating the S3 bucket&lt;/h3&gt;
&lt;p&gt;Naming is important here - the S3 bucket must have the same name as the subdomain it will be accessed at. Open up S3 in the AWS console, and:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new bucket named &lt;code&gt;files.foo.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Disable &amp;ldquo;Block all public access&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Under the bucket&amp;rsquo;s Permissions tab, add a bucket policy to make all objects public by default (replace &lt;code&gt;files.foo.com&lt;/code&gt; with the name of your bucket):&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;Version&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nt&#34;&gt;&amp;#34;Sid&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;AddPerm&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nt&#34;&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nt&#34;&gt;&amp;#34;Principal&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nt&#34;&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;s3:GetObject&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nt&#34;&gt;&amp;#34;Resource&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;arn:aws:s3:::files.foo.com/*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;certificate-creationconfig&#34;&gt;Certificate Creation+Config&lt;/h3&gt;
&lt;p&gt;Next up, we need to create a certificate in AWS &lt;a href=&#34;https://aws.amazon.com/certificate-manager/&#34;&gt;Certificate Manager&lt;/a&gt;.&lt;/p&gt;
&lt;div
    class=&#34;flex flex-col rounded-sm border bg-yellow-50 dark:bg-solarized-base02  dark:border-solarized-blue px-2 pt-1 pb-2&#34;&gt;
    &lt;div class=&#34;flex self-center&#34; title=&#34;With apologies to @fasterthanlime&#34;&gt;
        &lt;img class=&#34;h-8 mr-1 self-center&#34; src=&#34;https://www.reillywood.com/img/emoji/star.png&#34; alt=&#34;&#34; /&gt;
        &lt;span class=&#34;self-center font-bold text-xl&#34;&gt;Hot Tip&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class=&#34;&#34;&gt;
        Certificates &lt;em&gt;must&lt;/em&gt; be created in the &lt;code&gt;us-east-1&lt;/code&gt; region to work properly with CloudFront. Learn from my mistake, make sure you&amp;rsquo;re in the right region when performing this step.
    &lt;/div&gt;
&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Request a public certificate in Certificate Manager (us-east-1)
&lt;ol&gt;
&lt;li&gt;Fully Qualified Domain Name: the subdomain you want (ex: &lt;code&gt;files.foo.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Use DNS validation&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Open up the certificate in CM. It will have &amp;ldquo;CNAME name&amp;rdquo; and &amp;ldquo;CNAME value&amp;rdquo; fields under Domains, those are used for verification of ownership&lt;/li&gt;
&lt;li&gt;Go to your domain registrar and create a new CNAME that redirects &amp;ldquo;CNAME name&amp;rdquo; to &amp;ldquo;CNAME value&amp;rdquo;
&lt;ul&gt;
&lt;li&gt;note: for some reason AWS puts a &lt;code&gt;&#39;.&#39;&lt;/code&gt; at the end of each field, strip those off when creating the CNAME&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Wait a bit (5 minutes?) until AWS verifies that you own the domain&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;cloudfront-setup&#34;&gt;CloudFront Setup&lt;/h3&gt;
&lt;p&gt;Finally, you need to set up a CloudFront distribution that uses the certificate to serve content from your S3 bucket.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open up CloudFront (in &lt;code&gt;us-east-1&lt;/code&gt;) and create a new distribution
&lt;ol&gt;
&lt;li&gt;Origin domain: pick your S3 bucket&lt;/li&gt;
&lt;li&gt;Pick &amp;ldquo;Redirect HTTP to HTTPS&amp;rdquo; (optional)&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;files.foo.com&lt;/code&gt; to the &amp;ldquo;Alternate domain name (CNAME)&amp;rdquo; list&lt;/li&gt;
&lt;li&gt;Pick your certificate in &amp;ldquo;Custom SSL certificate&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Open the distribution behaviors and set cache policy = CachingDisabled (optional)
&lt;ol&gt;
&lt;li&gt;Without this, CloudFront will cache files and serve stale ones for a while. Probably not what you want&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Get the &amp;ldquo;Distribution domain name&amp;rdquo; from the CloudFront distribution page&lt;/li&gt;
&lt;li&gt;Go to your domain name registrar and create a new CNAME redirecting &lt;code&gt;foo.bar.com&lt;/code&gt; to the CloudFront distribution domain name.&lt;/li&gt;
&lt;li&gt;Wait a bit for DNS and the CloudFront distribution to catch up, and&amp;hellip; you&amp;rsquo;re (hopefully) done! 🤞&lt;/li&gt;
&lt;/ol&gt;</description>
    </item>
    
    <item>
      <title>Recent Nushell/Rust Work</title>
      <link>https://www.reillywood.com/blog/recently-may-2022/</link>
      <pubDate>Sun, 29 May 2022 19:43:53 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/recently-may-2022/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve joined the &lt;a href=&#34;https://www.nushell.sh/&#34;&gt;Nushell&lt;/a&gt; core team. This doesn&amp;rsquo;t really change what I&amp;rsquo;m doing day-to-day, but it makes my work on Nu feel a little more official 🙂.&lt;/p&gt;
&lt;h2 id=&#34;sqlite-support&#34;&gt;SQLite Support&lt;/h2&gt;
&lt;p&gt;This is the biggest feature I&amp;rsquo;ve implemented so far:

&lt;div class=&#39;carousel border border-gray-border&#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/nu-sqlite_hu_5f21ecb9454d06e2.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty proud of how this turned out; it&amp;rsquo;s very convenient to be able to browse SQLite databases in your shell and interact with them the same way you would any other data source. Nu is often-but-not-always smart enough to avoid unnecessary work when loading things from the database; there&amp;rsquo;s still some work to do here and it will probably involve rearchitecting how Nushell queries data.&lt;/p&gt;
&lt;h2 id=&#34;file-watcher&#34;&gt;File watcher&lt;/h2&gt;
&lt;p&gt;I also implemented &lt;a href=&#34;https://www.nushell.sh/book/commands/watch.html&#34;&gt;a &lt;code&gt;watch&lt;/code&gt; command&lt;/a&gt; that runs arbitrary Nu code in response to file changes. Nothing groundbreaking, but I find myself needing this kind of low-key automation all the time: run tests when code changes, restart a development server, log changes in a directory, etc. I think the ability to respond to file changes should be a more widely available primitive, and now it is.&lt;/p&gt;
&lt;h2 id=&#34;rust-for-windows&#34;&gt;Rust for Windows&lt;/h2&gt;
&lt;p&gt;Against all odds, I somehow got sucked back into Windows development. I spent a solid week helping one of Nushell&amp;rsquo;s dependencies do &lt;a href=&#34;https://github.com/Byron/trash-rs/pull/51&#34;&gt;a big upgrade of their Windows functionality&lt;/a&gt;. This required a deep dive into the current state of calling Windows APIs from Rust, and&amp;hellip; it&amp;rsquo;s a mixed bag.&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&#34;https://github.com/microsoft/windows-rs&#34;&gt;the &lt;code&gt;windows&lt;/code&gt; crate&lt;/a&gt; which is maintained by Microsoft. It&amp;rsquo;s an automatically generated set of Rust bindings for Windows APIs, which is both good (very comprehensive, always kept up to date) and bad (some rough edges that might be solved in a handmade solution like &lt;a href=&#34;https://github.com/retep998/winapi-rs&#34;&gt;&lt;code&gt;winapi&lt;/code&gt;&lt;/a&gt;). The crate is actively being worked on and it frequently has breaking changes; this means documentation is a little scarce and often out of date. Overall I was impressed and I think the crate has a bright future &lt;img class=&#34;inline h-5 mb-1&#34; src=&#34;https://www.reillywood.com/img/emoji/microheart.png&#34; title=&#34;:microheart:&#34;/&gt;. But until it settles down a bit, expect some growing pains.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Rust in 2022</title>
      <link>https://www.reillywood.com/blog/rust-thoughts/</link>
      <pubDate>Sat, 07 May 2022 10:29:50 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/rust-thoughts/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been using Rust full time for the last month and a bit while contributing to &lt;a href=&#34;https://www.nushell.sh&#34;&gt;Nushell&lt;/a&gt; (more on that later). A lot has changed since &lt;a href=&#34;https://www.reillywood.com/blog/rust-todo/&#34;&gt;I first tried Rust in 2019&lt;/a&gt; and this is my first time working on a &lt;em&gt;big&lt;/em&gt; Rust project. Here are some thoughts on the language while they&amp;rsquo;re still fresh in my head.&lt;/p&gt;
&lt;h2 id=&#34;compile-times-and-feedback-loops&#34;&gt;Compile times and feedback loops&lt;/h2&gt;
&lt;p&gt;Rust&amp;rsquo;s compile times are notoriously slow. Rust development was slow enough on my laptop that I finally gave up on mobile computing and bought a desktop with a top-of-the line CPU (12900K). Along the way I switched from Windows to Linux (more on that later) and started using &lt;a href=&#34;https://github.com/rui314/mold&#34;&gt;the &lt;code&gt;mold&lt;/code&gt; linker&lt;/a&gt;, and now&amp;hellip; things are OK!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m able to do incremental builds of Nushell (a huge project) in a second or 2, and a full debug build takes 25s. For smaller projects, incremental builds are nearly instant. There&amp;rsquo;s certainly room to improve here, and the development experience is not great on average hardware, but&amp;hellip; this works for me.&lt;/p&gt;
&lt;p&gt;Another thing to consider is that the typical Rust feedback loop is tighter than you might expect from the slow compile times. The Rust compiler catches a &lt;em&gt;lot&lt;/em&gt; of bugs before a full build needs to happen, and that reduces the need to do a full build and try things out.&lt;/p&gt;
&lt;h2 id=&#34;complexity--monotony&#34;&gt;Complexity + monotony&lt;/h2&gt;
&lt;p&gt;Rust is not a simple language. In total I&amp;rsquo;ve spent nearly 3 months working mostly in Rust, and the language still has a &lt;em&gt;lot&lt;/em&gt; of corners that I don&amp;rsquo;t have a solid grasp on. To improve on this I&amp;rsquo;m going to need to branch out from Nushell and write a lot of little tools for myself.&lt;/p&gt;
&lt;p&gt;Despite the complexity, I&amp;rsquo;ve found that writing Rust is sometimes a bit&amp;hellip; braindead? The type system is very expressive and the compiler catches a ton of errors, so I spend 25% of my time thinking real hard and 75% painting by numbers to make the compiler happy. I can&amp;rsquo;t quite decide how I feel about this style of development, it can be a little tedious but it also makes for a better end product.&lt;/p&gt;
&lt;h2 id=&#34;i-sometimes-want-a-higher-level-rust&#34;&gt;I (sometimes) want a higher-level Rust&lt;/h2&gt;
&lt;p&gt;Rust has a lot of great things going for it; the tooling, community, package ecosystem, compiler, and syntax are all fantastic. But the focus on systems programming does mean that Rust isn&amp;rsquo;t quite as ergonomic as it could be for many use cases.&lt;/p&gt;
&lt;p&gt;Sometimes I just want a garbage collector! Sometimes I&amp;rsquo;d be perfectly happy for Rust to implicitly allocate memory if it makes my code work (for example: converting from a &lt;code&gt;&amp;amp;str&lt;/code&gt; to a &lt;code&gt;String&lt;/code&gt;)! I don&amp;rsquo;t know if that will ever be possible in standard Rust, but&amp;hellip; &lt;a href=&#34;https://twitter.com/ekuber/status/1520138816484388864&#34;&gt;maybe there&amp;rsquo;s room for a Rust variant intended for higher-level use cases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On the other hand, the &lt;em&gt;ability&lt;/em&gt; to go as low as you want is great. It&amp;rsquo;s nice to work in a language with a very &amp;ldquo;high ceiling&amp;rdquo;; no matter where your Rust project goes, you won&amp;rsquo;t have to switch to C or C++.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>How to Make Rust CI 2-3x Faster</title>
      <link>https://www.reillywood.com/blog/rust-faster-ci/</link>
      <pubDate>Sat, 30 Apr 2022 15:14:43 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/rust-faster-ci/</guid>
      <description>&lt;blockquote class=&#34;twitter-tweet&#34; data-dnt=&#34;true&#34; data-theme=&#34;light&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Big shout-outs to &lt;a href=&#34;https://twitter.com/reillywood?ref_src=twsrc%5Etfw&#34;&gt;@reillywood&lt;/a&gt;, who managed cut the CI times for &lt;a href=&#34;https://twitter.com/nu_shell?ref_src=twsrc%5Etfw&#34;&gt;@nu_shell&lt;/a&gt; in half!&lt;br&gt;&lt;br&gt;If you do &lt;a href=&#34;https://twitter.com/rustlang?ref_src=twsrc%5Etfw&#34;&gt;@rustlang&lt;/a&gt; and see your CI build times creeping up, you should give his fixes a look. Smarter test ordering and build caching makes a huge difference.&lt;/p&gt;&amp;mdash; JT (@jntrnr) &lt;a href=&#34;https://twitter.com/jntrnr/status/1520509729771954176?ref_src=twsrc%5Etfw&#34;&gt;April 30, 2022&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt; 
&lt;p&gt;I recently spent a few days tuning &lt;a href=&#34;https://www.nushell.sh/&#34;&gt;Nushell&amp;rsquo;s&lt;/a&gt; GitHub Actions CI pipelines and it paid off: CI used to take about 30 minutes, and now it&amp;rsquo;s closer to 10. This is not pleasant or glamorous work, but it has a big payoff; every Nu change going forward will spend a lot less time waiting for essential feedback. Here&amp;rsquo;s how you can do the same.&lt;/p&gt;
&lt;h2 id=&#34;use-rust-cache&#34;&gt;Use rust-cache&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/Swatinem/rust-cache&#34;&gt;Seriously, it&amp;rsquo;s really good&lt;/a&gt;! GitHub build runners are slow. But GitHub gives every repo &lt;a href=&#34;https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows&#34;&gt;10GB of cache space&lt;/a&gt;, and &lt;code&gt;rust-cache&lt;/code&gt; takes advantage of that. It caches temporary files for your build dependencies across CI runs, so if you have a lot of dependencies you&amp;rsquo;ll likely see a big performance boost.&lt;/p&gt;
&lt;p&gt;One gotcha to be aware of: &lt;a href=&#34;https://stackoverflow.com/a/66632107&#34;&gt;GitHub Actions has slightly unintuitive behavior across PRs&lt;/a&gt;. PR X is unable to see cache data from PR Y, but they can both see cache data from the base branch (usually &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt;). This makes sense from an isolation perspective, but it&amp;rsquo;s not especially well-documented; I ended up adding &lt;a href=&#34;https://github.com/nushell/nushell/blob/07893e01c15e7b4598141fb6d3876d5632b7ae0a/.github/workflows/ci.yml#L3-L5&#34;&gt;an extra CI trigger on &lt;code&gt;main&lt;/code&gt;&lt;/a&gt; just to fill caches properly.&lt;/p&gt;
&lt;h2 id=&#34;split-your-build-and-test-jobs&#34;&gt;Split your build and test jobs&lt;/h2&gt;
&lt;p&gt;Previously we were running &lt;code&gt;cargo build&lt;/code&gt; &lt;em&gt;then&lt;/em&gt; &lt;code&gt;cargo test&lt;/code&gt; in a single job. This was suboptimal for a few reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;cargo test&lt;/code&gt; often needed to recompile crates that had just been built for &lt;code&gt;cargo build&lt;/code&gt;. &lt;a href=&#34;https://doc.rust-lang.org/book/ch11-03-test-organization.html#the-tests-module-and-cfgtest&#34;&gt;&lt;code&gt;#[cfg(test)]&lt;/code&gt;&lt;/a&gt; is the most likely culprit here; it makes sense that build output might be different in &amp;ldquo;test mode&amp;rdquo;. This has implications for caching too!&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s faster to run build and test in parallel; GitHub gives us &lt;a href=&#34;https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits&#34;&gt;20 build runners for free&lt;/a&gt;, and we might as well use them.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;run-clippy-after-cargo-build&#34;&gt;Run Clippy after cargo build&lt;/h2&gt;
&lt;p&gt;Previously we were running &lt;a href=&#34;https://github.com/rust-lang/rust-clippy&#34;&gt;Clippy&lt;/a&gt; before &lt;code&gt;cargo build&lt;/code&gt;. Just switching their order shaved about 5 minutes off every test run! It seems like Clippy can reuse build artifacts from &lt;code&gt;cargo build&lt;/code&gt;, but not vice versa.&lt;/p&gt;
&lt;p&gt;(Dec 2024: I&amp;rsquo;ve been told that this doesn&amp;rsquo;t work anymore. Possible that something&amp;rsquo;s changed in Cargo/Rust)&lt;/p&gt;
&lt;h2 id=&#34;use-cargo-nextest&#34;&gt;Use cargo nextest&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://nexte.st/&#34;&gt;&lt;code&gt;cargo nextest&lt;/code&gt;&lt;/a&gt; is &amp;ldquo;a next-generation test runner for Rust projects.&amp;rdquo; It&amp;rsquo;s dead simple to install in CI, and it&amp;rsquo;s often faster than &lt;code&gt;cargo test&lt;/code&gt;. We didn&amp;rsquo;t see a &lt;em&gt;huge&lt;/em&gt; benefit from this (maybe 30-40s faster?), but that&amp;rsquo;s because our CI time is dominated by compilation; YMMV depending on your code base and test suite.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;d like to see the actual changes, &lt;a href=&#34;https://github.com/nushell/nushell/pulls?q=is%3Apr+author%3Argwood+CI+updated%3A2022-04-28T11%3A30%3A15%2B00%3A00..2022-04-30+is%3Amerged&#34;&gt;they&amp;rsquo;re all here&lt;/a&gt;. Like anything GitHub Actions, this took a lot of tries to get right; those 5 PRs are just the tip of the iceberg, there were a &lt;em&gt;lot&lt;/em&gt; more experimental changes in my private fork. I&amp;rsquo;m hopeful that someday we&amp;rsquo;ll be able to stop &lt;a href=&#34;https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions&#34;&gt;programming in YAML files&lt;/a&gt;, but we&amp;rsquo;re not there yet!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Recently</title>
      <link>https://www.reillywood.com/blog/recently-mar-2022-2/</link>
      <pubDate>Thu, 24 Mar 2022 22:01:30 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/recently-mar-2022-2/</guid>
      <description>&lt;h2 id=&#34;nushell&#34;&gt;Nushell&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.nushell.sh/blog/2022-03-22-nushell_0_60.html&#34;&gt;Nushell v0.60 is out&lt;/a&gt;, and it&amp;rsquo;s fantastic. This is the first Nu release where I made meaningful contributions (mostly to the website+documentation) and it feels like a good use of my sabbatical time. It&amp;rsquo;s been interesting figuring out how to sell+explain Nu succinctly; writing good public-facing documentation is hard!&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t tried Nu, this is a great time to do so; Nu&amp;rsquo;s not stable yet, but I think you&amp;rsquo;ll be &lt;em&gt;very&lt;/em&gt; pleasantly surprised by the level of polish. I&amp;rsquo;ve finally made it my default shell on both Windows+Linux.&lt;/p&gt;
&lt;p&gt;Most of the work I&amp;rsquo;m doing for Nushell has a selfish motivation: I want to live in a world where POSIX shells are a thing of the past, and Nushell seems like the most promising way to get there.&lt;/p&gt;
&lt;h2 id=&#34;learning&#34;&gt;Learning&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve started working through &lt;a href=&#34;https://craftinginterpreters.com/&#34;&gt;Crafting Interpreters&lt;/a&gt; by Bob Nystrom. My first exposure to Nystrom&amp;rsquo;s work was &lt;a href=&#34;https://gameprogrammingpatterns.com/&#34;&gt;Game Programming Patterns&lt;/a&gt;, one of the best programming books I&amp;rsquo;ve ever read. The title&amp;rsquo;s a little unfortunate because it covers design patterns that are useful in &lt;em&gt;any&lt;/em&gt; field of programming; I genuinely think GPP is much more useful to today&amp;rsquo;s programmer than &lt;a href=&#34;https://en.wikipedia.org/wiki/Design_Patterns&#34;&gt;the book that inspired it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Crafting Interpreters walks you through building a scripting language from the ground up. The book walks you through an interpreter implementation in Java then C; I&amp;rsquo;m doing the Java version in C#  (personal preference and experience).&lt;/p&gt;
&lt;h2 id=&#34;other&#34;&gt;Other&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve started rekindling some old friendships with people I haven&amp;rsquo;t seen in person in 2 years, and that&amp;rsquo;s been really great.&lt;/p&gt;
&lt;p&gt;Spring is finally arriving here in Vancouver, so I&amp;rsquo;ve been finding lots of excuses to be outdoors. My patio&amp;rsquo;s never been cleaner and I&amp;rsquo;m looking forward to a lot of spring gardening. I&amp;rsquo;d like to get some more trellises set up this year; I have a fairly small urban patio so it&amp;rsquo;s important to make good use of vertical space. &lt;a href=&#34;https://oldurbanist.blogspot.com/2012/02/greening-dc-zoning-code.html&#34;&gt;&amp;ldquo;Green to the eye, not green on the ground.&amp;rdquo;&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Recently</title>
      <link>https://www.reillywood.com/blog/recently-mar-2022-1/</link>
      <pubDate>Thu, 10 Mar 2022 09:10:25 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/recently-mar-2022-1/</guid>
      <description>&lt;h3 id=&#34;work&#34;&gt;&amp;ldquo;Work&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/nushell/nushell.github.io/commits?author=rgwood&#34;&gt;I&amp;rsquo;ve been spending a lot of time writing Nushell documentation&lt;/a&gt;. The Nushell core team has been banging out new features rapidly and the documentation hasn&amp;rsquo;t always kept up; improving the documentation feels like a high-impact way to help out.&lt;/p&gt;
&lt;p&gt;Nushell&amp;rsquo;s got a big release coming up later this month and it has the potential to attract many new users. I want the documentation to be in a better place by then, I&amp;rsquo;m going to keep spending time on this.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve tried to make a few commits to Nushell itself, but I keep bouncing off Rust. I&amp;rsquo;m &lt;a href=&#34;https://github.com/rgwood/rust-tar&#34;&gt;reasonably&lt;/a&gt; &lt;a href=&#34;https://www.reillywood.com/blog/rust-todo/&#34;&gt;familiar&lt;/a&gt; with Rust, but this is my first time working on a &lt;em&gt;big&lt;/em&gt; Rust project. The compile times are painful and they inhibit rapid iteration; I haven&amp;rsquo;t found a development approach that I like yet.&lt;/p&gt;
&lt;h3 id=&#34;reading&#34;&gt;Reading&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.goodreads.com/book/show/164449.Typhoid_Mary&#34;&gt;Anthony Bourdain&amp;rsquo;s &lt;em&gt;Typhoid Mary&lt;/em&gt;&lt;/a&gt; was pretty disappointing (3 stars?); you can tell that Bourdain was short on source material so most of the book is him imagining what Mary might have done.&lt;/p&gt;
&lt;p&gt;I started rereading the Wheel of Time series in December and I&amp;rsquo;m on book 9 now. My interest started to wane around book 8; I think I&amp;rsquo;ll press onward but I&amp;rsquo;m really looking forward to the later books (written by another author).&lt;/p&gt;
&lt;h3 id=&#34;other&#34;&gt;Other&lt;/h3&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/recently/ddr_mat_hu_3f644de5c506083f.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        DDR mat with confused dog
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;I think I&amp;rsquo;m a Dance Dance Revolution guy now. I ordered &lt;a href=&#34;https://www.maty-taneczne.pl/&#34;&gt;a fancy mat from Poland&lt;/a&gt; and have been playing &lt;a href=&#34;https://www.clubfantastic.com/&#34;&gt;Club Fantastic&lt;/a&gt;. I&amp;rsquo;m still pretty bad but it&amp;rsquo;s a lot of fun.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Recently</title>
      <link>https://www.reillywood.com/blog/recently-mar-2022/</link>
      <pubDate>Thu, 03 Mar 2022 14:33:22 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/recently-mar-2022/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m officially unemployed again 😎. In the interest of self-accountability, I&amp;rsquo;m going to try to document what I&amp;rsquo;m up to on my break; expect more frequent updates to this blog.&lt;/p&gt;
&lt;h2 id=&#34;hytradboi&#34;&gt;HYTRADBOI&lt;/h2&gt;
&lt;p&gt;I bought a ticket to &lt;a href=&#34;https://www.hytradboi.com/&#34;&gt;Have You Tried Rubbing A Database On It?&lt;/a&gt;, which could loosely be described as a hipster database conference; lots of people using databases in unusual ways, not much in the way of enterprise RDBMSs. The speaker list is like a Who&amp;rsquo;s Who for offbeat database work, and I&amp;rsquo;m really looking forward to it.&lt;/p&gt;
&lt;h2 id=&#34;nushell&#34;&gt;Nushell&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been using &lt;a href=&#34;https://www.nushell.sh/&#34;&gt;Nushell&lt;/a&gt; as my shell on both Windows and Linux, about half the time. Nushell is a fascinating project; it&amp;rsquo;s a shell that operates on structured data like PowerShell, but without PowerShell&amp;rsquo;s (many) pain points.&lt;/p&gt;
&lt;p&gt;Nushell has recently seen some massive upgrades (the parsing and evaluation engine was completely rewritten) and it&amp;rsquo;s a very good time to give it a try. It&amp;rsquo;s still early days, but I&amp;rsquo;m hopeful Nushell will be able to displace POSIX shells; &lt;a href=&#34;https://www.notamonadtutorial.com/nushell-the-shell-where-traditional-unix-meets-modern-development-written-in-rust/&#34;&gt;it&amp;rsquo;s liberating to work with much richer data types than plain text&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nu is a way of saying “what if we didn’t need tools like awk so often?” Since you’re working with structured data, as we add more support for file types, it’s less often you need to reach for “awk”, “jq”, “grep”, and the array of other tools to open and work with common file types. &lt;strong&gt;In a way, it’s taking the original spirit of Unix — where you use pipelines to combine a set of tools — and imagining how that original spirit would work today, with what we know about programming languages and tools.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;building-data-centric-apps-with-a-reactive-relational-database&#34;&gt;&lt;a href=&#34;https://riffle.systems/essays/prelude/&#34;&gt;Building data-centric apps with a reactive relational database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This essay touches on a lot of my favourite things: SQLite! &lt;a href=&#34;https://github.com/rgwood/MinimalWebView&#34;&gt;The intersection of native apps and web UI&lt;/a&gt;! &lt;a href=&#34;https://github.com/rgwood/reitunes&#34;&gt;iTunes clones&lt;/a&gt;! In a nutshell, it&amp;rsquo;s a very cool approach to building GUI applications in which &lt;em&gt;all&lt;/em&gt; of the application&amp;rsquo;s state lives in a local database.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s more of a provocation than a fully finished system, but I think it shows promise. I&amp;rsquo;d like to see a bit more investigation of &amp;ldquo;escape hatches&amp;rdquo;; how hard would it be mix in a little imperative code when SQL/SQLite aren&amp;rsquo;t the right fit for a task? Also, this was a bit depressing:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One challenge has been inter-process communication. When the reactive graph is running in the UI thread and the SQLite database is on a web worker or native process, each query results in an asynchronous call that has to serialize and deserialize data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This would have been a non-issue in traditional GUI code (just query SQLite on a background thread in the same process); one more thing we lose as web UI takes over, I guess 😞.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>On The Road Again...</title>
      <link>https://www.reillywood.com/blog/on-the-road-again/</link>
      <pubDate>Thu, 24 Feb 2022 19:35:28 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/on-the-road-again/</guid>
      <description>&lt;p&gt;I handed in my notice; my last day at work is next Wednesday. &lt;a href=&#34;https://canalyst.com/&#34;&gt;My employer for the last 2 years&lt;/a&gt; has been excellent to me, but I&amp;rsquo;ve been itching to try something new.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have a new job lined up yet; I&amp;rsquo;m planning to use this time partially as a sabbatical, and partially to see what&amp;rsquo;s out there on the job market. A &lt;a href=&#34;https://www.gchicco.com/2019/04/23/the-serendipity-engine/&#34;&gt;&amp;ldquo;serendipity break&amp;rdquo;&lt;/a&gt; during which I will actively explore different possible paths.&lt;/p&gt;
&lt;p&gt;One of my plans is to explore the intersection of native apps and web UI; I&amp;rsquo;ve done &lt;a href=&#34;https://github.com/rgwood/MinimalWebView&#34;&gt;some promising experiments with that&lt;/a&gt; and it would be a good excuse to overhaul &lt;a href=&#34;https://github.com/rgwood/ReiTunes&#34;&gt;ReiTunes&lt;/a&gt;. I&amp;rsquo;d also like to build some new tools for working with &lt;a href=&#34;https://sqlite.org/&#34;&gt;the best database&lt;/a&gt;. Here we go!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Building .NET CLI Apps in 2022</title>
      <link>https://www.reillywood.com/blog/dotnet-2022/</link>
      <pubDate>Fri, 11 Feb 2022 17:51:44 -0600</pubDate>
      
      <guid>https://www.reillywood.com/blog/dotnet-2022/</guid>
      <description>&lt;p&gt;.NET has been taking huge leaps and bounds in the last few years, and not everyone is aware of it! We&amp;rsquo;re now able to build small fast single-file .NET applications; this is arguably Go&amp;rsquo;s biggest strength, and now you can do it in C# and F# too.&lt;/p&gt;
&lt;p&gt;Meanwhile, the .NET community has been making excellent libraries to make console applications slick, polished, and easy to write.&lt;/p&gt;
&lt;p&gt;The conditions seem right for a .NET renaissance of sorts; all the pieces are in place for us to build great CLI-first software. Here are some useful+easy things you can do in .NET &lt;em&gt;today&lt;/em&gt;:&lt;/p&gt;
&lt;h2 id=&#34;publish-small-zero-dependency-executables&#34;&gt;Publish Small Zero-Dependency Executables&lt;/h2&gt;
&lt;p&gt;In .NET 6 it&amp;rsquo;s easy to build your application &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file&#34;&gt;as a single file&lt;/a&gt; with no external dependencies (even the .NET runtime!). Applications that include their own runtime are called &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/core/deploying/#publish-self-contained&#34;&gt;&lt;em&gt;self-contained&lt;/em&gt;&lt;/a&gt;. Self-contained apps can be &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained&#34;&gt;&lt;em&gt;trimmed&lt;/em&gt;&lt;/a&gt; to remove unused code; trimming reduces a Hello World application from about 60MB to 12MB.&lt;/p&gt;
&lt;p&gt;Trimming is well-supported by nearly the entire standard library. Ecosystem support varies; if a library makes heavy use of reflection, C++/CLI, or COM interop then it might not work with trimming &lt;em&gt;yet&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The easiest way to build a trimmed self-contained single-file executable is &lt;code&gt;dotnet publish&lt;/code&gt; with the args &lt;code&gt;--self-contained=true -p:PublishSingleFile=true -p:PublishTrimmed=true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/xoofx/dotnet-releaser/&#34;&gt;&lt;code&gt;dotnet-releaser&lt;/code&gt;&lt;/a&gt; makes publishing your app a breeze; it can build for multiple platforms+architectures, then publish the resulting artifacts in a GitHub release.&lt;/p&gt;
&lt;h2 id=&#34;console-ui&#34;&gt;Console UI&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://spectreconsole.net/&#34;&gt;&lt;code&gt;Spectre.Console&lt;/code&gt;&lt;/a&gt; is a great replacement for &lt;code&gt;Console.WriteLine()&lt;/code&gt; and friends. Coloring text with Spectre is as simple as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;AnsiConsole&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;MarkupLine&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;[blue]Hello[/] [yellow]World![/]&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It can also do much more; if you need to print tables or prompt users for input, &lt;code&gt;Spectre.Console&lt;/code&gt; has you covered. But if you need to build a full-blown terminal UI, check out &lt;a href=&#34;https://github.com/migueldeicaza/gui.cs&#34;&gt;&lt;code&gt;gui.cs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;running-external-processes&#34;&gt;Running External Processes&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;System.Diagnostics.Process&lt;/code&gt; APIs built into .NET are clumsy, to say the least. Thankfully we have much better options these days! Here&amp;rsquo;s how to run &lt;code&gt;git status&lt;/code&gt; with the excellent &lt;a href=&#34;https://github.com/adamralph/simple-exec&#34;&gt;&lt;code&gt;SimpleExec&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stdout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;stderr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;RunAsync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;git&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For a more powerful solution, check out &lt;a href=&#34;https://github.com/Tyrrrz/CliWrap&#34;&gt;&lt;code&gt;CliWrap&lt;/code&gt;&lt;/a&gt;; anything you can do in a Bash script, you can do in &lt;code&gt;CliWrap&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Pretending to be a Postgres server - Part 1</title>
      <link>https://www.reillywood.com/blog/postgres-wire/</link>
      <pubDate>Sun, 30 Jan 2022 15:34:49 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/postgres-wire/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/rgwood/odbc&#34;&gt;I recently spent a bit of time messing around with the Postgres wire protocol.&lt;/a&gt; I&amp;rsquo;d like to build a library that lets any application accept connections as if it were a Postgres database; many database clients and databases speak &lt;a href=&#34;https://www.postgresql.org/docs/current/protocol.html&#34;&gt;the Postgres wire protocol&lt;/a&gt;, and it seems like a practical way to expose tabular data over the network.&lt;/p&gt;
&lt;p&gt;Implementing the protocol isn&amp;rsquo;t too bad; the docs aren&amp;rsquo;t great but the protocol itself is relatively straightforward. Unfortunately, client behaviour seems harder to accommodate. Clients can issue arbitrarily complex SQL queries to the database to inspect its schema, and sometimes they expect accurate answers. For example, &lt;a href=&#34;https://www.npgsql.org/&#34;&gt;Npgsql&lt;/a&gt; issues &lt;a href=&#34;https://gist.github.com/rgwood/d4125a7e551a2c08eb9ee11841e14afb&#34;&gt;this beast&lt;/a&gt; and then disconnects if it gets an invalid response.&lt;/p&gt;
&lt;p&gt;So I&amp;rsquo;m stuck with a question: &lt;strong&gt;how far should I go to support legitimate client behaviour outside of the protocol?&lt;/strong&gt; The easiest approach for now is to test with a few popular clients and hardcode responses for any queries that they need implemented, but that will leave a long tail of unsupported clients. On the other end of the complexity scale, I could try running schema queries on an in-memory SQLite instance with database objects that mimic Postgres (certainly a massive yak shave, but tempting nonetheless).&lt;/p&gt;
&lt;p&gt;I think my next step will be to dig through the source of a few Postgres-compatible databases to see how they solve this.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Scripting in C#</title>
      <link>https://www.reillywood.com/blog/scriptcompiler/</link>
      <pubDate>Fri, 28 Jan 2022 17:15:29 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/scriptcompiler/</guid>
      <description>&lt;p&gt;I recently assembled &lt;a href=&#34;https://github.com/rgwood/ScriptCompiler&#34;&gt;a tool&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://user-images.githubusercontent.com/26268125/150707633-82eb85fd-3247-450f-b918-879c41ac4090.png&#34; alt=&#34;ScriptCompiler screenshot&#34;&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a helper for scripting in C#. It watches a directory with C# files for changes, and whenever a file changes it gets compiled into its own executable. You can install it as an always-on service, and it comes with an opinionated set of utilities to make scripting easier.&lt;/p&gt;
&lt;h2 id=&#34;why&#34;&gt;Why?&lt;/h2&gt;
&lt;p&gt;C# &lt;em&gt;the language&lt;/em&gt; has recently become much more succinct (&lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements&#34;&gt;top-level statements&lt;/a&gt;, &lt;a href=&#34;https://www.hanselman.com/blog/implicit-usings-in-net-6&#34;&gt;global usings&lt;/a&gt;); seems like it should be good for writing fast, maintainable scripts! But the tooling around the language still isn&amp;rsquo;t quite there yet; you need a &lt;code&gt;.csproj&lt;/code&gt; project file for every program, and &lt;code&gt;dotnet run&lt;/code&gt; often takes a second or 2 to start up, it&amp;rsquo;s not (yet) optimized for scripting use. &lt;a href=&#34;https://github.com/dotnet/designs/pull/213&#34;&gt;This proposal should help in many ways&lt;/a&gt; (upvote it!) but who knows if or when it will be done.&lt;/p&gt;
&lt;p&gt;Also, &lt;a href=&#34;https://twitter.com/davidcrawshaw/status/1300614954865876992?s=20&#34;&gt;this&lt;/a&gt; has been stuck in my head for a while:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My OS has no notion of build system. It has all the source code on it and I can edit anything and run the command again with the change immediately applied. Interpreter or compiler, I don&amp;rsquo;t know, that&amp;rsquo;s an implementation detail. Then I wake up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;give-it-a-spin&#34;&gt;Give it a spin&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/rgwood/ScriptCompiler&#34;&gt;Instructions, source and pre-built executables for Linux+macOS+Windows are available on GitHub&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Blog.exe version 0.1</title>
      <link>https://www.reillywood.com/blog/blog-exe-0_1/</link>
      <pubDate>Thu, 13 Jan 2022 18:47:47 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/blog-exe-0_1/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s something I&amp;rsquo;ve been messing around with:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/rgwood/MaximalWebView/releases/tag/v0.1&#34;&gt;&lt;img src=&#34;https://user-images.githubusercontent.com/26268125/141703830-a1f360f0-ef05-4f76-87d3-169d42367209.png&#34; alt=&#34;screenshot of blog.exe, powered by MaximalWebView&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/rgwood/MaximalWebView/releases/tag/v0.1&#34;&gt;Download here&lt;/a&gt; or get more info on the &lt;a href=&#34;https://github.com/rgwood/MinimalWebView/&#34;&gt;MinimalWebView&lt;/a&gt; and &lt;a href=&#34;https://github.com/rgwood/MaximalWebView/&#34;&gt;MaximalWebView&lt;/a&gt; repos. I&amp;rsquo;ll write more about these projects later!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>.NET Hot Reload:  Where Do We Go From Here?</title>
      <link>https://www.reillywood.com/blog/hot-reload/</link>
      <pubDate>Wed, 12 Jan 2022 12:18:31 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/hot-reload/</guid>
      <description>&lt;div class=&#39;carousel border border-gray-border&#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/hot-reload/hot-reload-github_hu_573417344ec485a2.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;I had a weird week back in October, and it all started when I posted &lt;a href=&#34;https://github.com/dotnet/sdk/issues/22247&#34;&gt;this GitHub issue&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-happened&#34;&gt;What happened?&lt;/h2&gt;
&lt;p&gt;The .NET team built a feature called Hot Reload for .NET 6, which would allow for code to be changed while an application is running. &lt;a href=&#34;https://github.com/dotnet/runtime/issues/45629&#34;&gt;It was planned as a feature for &lt;em&gt;all&lt;/em&gt; of .NET from the start:&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Our goal is to provide a consistent dev inner loop experience in .NET Core across all platforms, architectures, and workloads.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A preview version of the feature &lt;a href=&#34;https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-net-6-preview-3/#initial-net-hot-reload-support&#34;&gt;landed in April 2021&lt;/a&gt; and by October it was pretty much done. I used Hot Reload throughout the preview period and loved it. Then at the last minute, &lt;a href=&#34;https://devblogs.microsoft.com/dotnet/update-on-net-hot-reload-progress-and-visual-studio-2022-highlights/&#34;&gt;Microsoft announced that the feature would be locked to Visual Studio&lt;/a&gt;; to say I felt disappointed would be an understatement. I posted on GitHub asking for more information, and I soon found out that many, &lt;em&gt;many&lt;/em&gt; others felt the same way.&lt;/p&gt;
&lt;p&gt;There was &lt;a href=&#34;https://dusted.codes/can-we-trust-microsoft-with-open-source&#34;&gt;a huge community uproar&lt;/a&gt;, I was quoted in multiple &lt;a href=&#34;https://www.theverge.com/2021/10/22/22740701/microsoft-dotnet-hot-reload-removal-decision-open-source&#34;&gt;news&lt;/a&gt; &lt;a href=&#34;https://www.theregister.com/2021/10/22/microsoft_net_hot_reload_visual_studio/&#34;&gt;articles&lt;/a&gt;, and Microsoft &lt;a href=&#34;https://devblogs.microsoft.com/dotnet/net-hot-reload-support-via-cli/&#34;&gt;eventually backed down&lt;/a&gt;. It was a happy ending, but I was still left with a bitter taste in my mouth.&lt;/p&gt;
&lt;h2 id=&#34;ok-so-what&#34;&gt;OK, so what?&lt;/h2&gt;
&lt;p&gt;To many people, it&amp;rsquo;s not obvious &lt;em&gt;why&lt;/em&gt; this was such a big deal. I think there are 2 main reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Quick feedback isn&amp;rsquo;t a niche feature; it&amp;rsquo;s an essential element of any creative activity.&lt;/li&gt;
&lt;li&gt;Limiting Hot Reload to Visual Studio would exacerbate .NET&amp;rsquo;s existing weaknesses.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/blog/inventing-on-principle/&#34;&gt;I&amp;rsquo;ve rambled about 1 before&lt;/a&gt;, so I&amp;rsquo;ll focus on 2 here.&lt;/p&gt;
&lt;p&gt;.NET&amp;rsquo;s at a bit of a disadvantage when it comes to cross-platform development, in both perception and reality. The tooling situation outside of Visual Studio (or Rider) often leaves much to be desired, and newcomers often get a poor first impression from the OmniSharp tooling in VS Code. Hot Reload in &lt;code&gt;dotnet watch&lt;/code&gt; is a big step forward for .NET outside of Visual Studio; it&amp;rsquo;s a feature I can point to and say &amp;ldquo;yes, .NET has good tooling no matter where you use it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the crux of the matter; removing Hot Reload from &lt;code&gt;dotnet watch&lt;/code&gt; would harm .NET in an area it needs to do much better in. The .NET team understands this, but the change was imposed on them by senior Developer Division leadership against &lt;em&gt;strong&lt;/em&gt; internal opposition. This suggests that senior leadership does not understand .NET (bad) or prioritizes Visual Studio over the long-term health of .NET (worse).&lt;/p&gt;
&lt;p&gt;I like working in .NET, and the .NET team is doing a world-beating job of pushing the ecosystem forward from its stodgy enterprise-first origins. It&amp;rsquo;s unsettling that senior Developer Division leadership was willing to throw so much away to sell a few Visual Studio licenses.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Recently</title>
      <link>https://www.reillywood.com/blog/2021-update/</link>
      <pubDate>Sun, 14 Mar 2021 11:15:38 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/2021-update/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s been a while since my last update. I wrapped up my sabbatical, did a whole bunch of interviewing, and have since been working on some interesting spreadsheet-related problems at &lt;a href=&#34;https://canalyst.com/&#34;&gt;a new job&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Oh, and there&amp;rsquo;s been a global pandemic on, it&amp;rsquo;s been weird. Anyway, I kept busy over the last year and a bit:&lt;/p&gt;
&lt;h2 id=&#34;switched-to-windows&#34;&gt;Switched to Windows&lt;/h2&gt;
&lt;p&gt;I switched to Windows full time, after a lifetime of Macs for personal use and a decade of Windows for professional use.&lt;/p&gt;
&lt;p&gt;This was an experiment in intentionally shaping my own interests; I knew that if I used Windows at home I&amp;rsquo;d be more interested in the minutia of making good Windows applications.&lt;/p&gt;
&lt;p&gt;It worked; I&amp;rsquo;m just over a year in, and I&amp;rsquo;ve learned a lot. I still miss a lot of things about macOS but I&amp;rsquo;m mostly happy on Windows. The Surface devices are really neat pieces of hardware.&lt;/p&gt;
&lt;h2 id=&#34;reitunes&#34;&gt;ReiTunes&lt;/h2&gt;
&lt;p&gt;I built &lt;a href=&#34;https://github.com/rgwood/reitunes&#34;&gt;my own music player&lt;/a&gt;. This was the culmination of something I&amp;rsquo;ve been thinking about for years; I listen to a lot of long-form music and existing music services don&amp;rsquo;t serve that niche well. iTunes is progressively less interested in catering to users who bring their own music, and the Windows client has many irritating bugs.&lt;/p&gt;
&lt;p&gt;So I decided to build my own music library system with an offline-first native client; how hard could it be? Well, 500+ commits later&amp;hellip;&lt;/p&gt;
&lt;p&gt;This was a fun opportunity to get my hands dirty with a distributed system; I designed it with easy push/pull metadata syncing across multiple clients. It also made me grapple more with the current state of client applications on Windows, which led to:&lt;/p&gt;
&lt;h2 id=&#34;windows-native-development-deep-dive---winrtuwpwinuiproject-reunion&#34;&gt;Windows native development deep dive - WinRT/UWP/WinUI/Project Reunion&lt;/h2&gt;
&lt;p&gt;I missed the culture of high-quality native apps from macOS, and I wanted to make ReiTunes feel like a proper native Windows application. After some agonizing, I decided to build it as a UWP application; I&amp;rsquo;d heard many complaints about the limits of UWP in the past, but the UWP applications I used on Windows often felt better than the alternatives (especially on touch devices).&lt;/p&gt;
&lt;p&gt;UWP was largely rejected by people building enterprise applications for Windows (or to be more accurate, UWP rejected them) so it was mostly new to me. My feelings about the area are complex and I&amp;rsquo;ll probably write about them another time, but some high-level thoughts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Windows team is currently catching up from a lost decade with Project Reunion. &lt;a href=&#34;https://news.ycombinator.com/item?id=23237086&#34;&gt;This comment explains it well&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The development experience of WinUI is similar to working with WPF XAML. Some nice improvements have been made in binding and animation, but many pain points (styling, verbosity) still remain.&lt;/li&gt;
&lt;li&gt;The Windows Runtime APIs are remarkably extensive; it&amp;rsquo;s sad how much work went into them only to go mostly unused.&lt;/li&gt;
&lt;li&gt;It will probably be another year before WinUI 3 is useful in production scenarios, and I suspect that&amp;rsquo;s going to be too little too late. Blazor Desktop will make Electron-like applications much more attractive.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I would not recommend that most people learn UWP in 2021. You can deliver a user experience that is nicer than many alternatives, but there&amp;rsquo;s a &lt;em&gt;lot&lt;/em&gt; to learn and it&amp;rsquo;s unclear how much will be obsolete soon. But conveniently, I was stuck at home with a lot of free time; it really gave me a better understanding of where Windows has been recently and where it&amp;rsquo;s going.&lt;/p&gt;
&lt;p&gt;Overall, I&amp;rsquo;m mostly optimistic about the future of Windows. Some very good people are working on Project Reunion, in particular Rich Turner who drove a lot of the WSL and Windows Terminal+&lt;a href=&#34;https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/&#34;&gt;ConPTY&lt;/a&gt; work. I&amp;rsquo;m confident that Windows is moving in the right direction, but time will tell if it&amp;rsquo;s moving quickly enough.&lt;/p&gt;
&lt;h2 id=&#34;performance-work&#34;&gt;Performance work&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been doing a lot of .NET performance work and it&amp;rsquo;s been really fun. It&amp;rsquo;s satisfying to chip away at a slow process and speed it up bit by bit; I&amp;rsquo;ve made the main product I work on an order of magnitude faster, which starts to open up some cool possibilities for interactive use.&lt;/p&gt;
&lt;h2 id=&#34;interactive-applications&#34;&gt;Interactive applications&lt;/h2&gt;
&lt;p&gt;This past year has given me the opportunity to build a few greenfield projects where interactivity and immediate feedback are critical. It&amp;rsquo;s been a steep learning curve; I mostly focused on back-end systems in the earlier stages of my career, and if I&amp;rsquo;m being honest I looked down a bit on front-end work as being easier. Was I ever wrong!&lt;/p&gt;
&lt;p&gt;Trying to build performant interfaces has been a great learning experience; I&amp;rsquo;ve never had to deal with so many multithreading and concurrency problems before.&lt;/p&gt;
&lt;p&gt;This has some nice synergy with the performance work.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Wat: a tiny CLI documentation tool</title>
      <link>https://www.reillywood.com/blog/wat/</link>
      <pubDate>Tue, 18 Feb 2020 10:38:54 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/wat/</guid>
      <description>&lt;p&gt;Robin Sloan&amp;rsquo;s &lt;a href=&#34;https://www.robinsloan.com/notes/home-cooked-app/&#34;&gt;post about &amp;ldquo;home-cooked&amp;rdquo; code&lt;/a&gt; has been making the rounds, and for good reason. It&amp;rsquo;s about highly personalized programs; when you build something purely for yourself or a small audience, it&amp;rsquo;s more like home cooking than industrial production. I really love the analogy, and it&amp;rsquo;s inspired me to write about one of my own &amp;ldquo;home-cooked&amp;rdquo; tools: &lt;a href=&#34;https://github.com/rgwood/dotfiles/blob/master/bin/wat&#34;&gt;&lt;code&gt;wat&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;I spend a lot of my day in a terminal, and command line interfaces have notoriously poor discoverability. It&amp;rsquo;s easy to remember the handful of commands+flags you use on a regular basis; it&amp;rsquo;s much harder to remember the ones you only use a couple times a year. So, like many people, I used to spend a lot of time digging through &lt;a href=&#34;https://twitter.com/b0rk/status/802011306136109057&#34;&gt;man pages&lt;/a&gt; when 1) I know what I want to accomplish 2) I can&amp;rsquo;t remember the exact CLI syntax to make it happen.&lt;/p&gt;
&lt;p&gt;Unfortunately, man pages usually aren&amp;rsquo;t designed for this. The average man page is more like a lengthy novel than Cliff&amp;rsquo;s Notes; it&amp;rsquo;s thorough+exhaustive documentation when I just want a quick reference.&lt;/p&gt;
&lt;p&gt;There are a few excellent projects that provide succinct documentation for *nix commands: &lt;a href=&#34;https://tldr.sh/&#34;&gt;&lt;code&gt;tldr&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;http://cht.sh/&#34;&gt;&lt;code&gt;cht.sh&lt;/code&gt;&lt;/a&gt; are my favourites. But they don&amp;rsquo;t provide good facilities for adding personalized documentation of my own; that&amp;rsquo;s where &lt;code&gt;wat&lt;/code&gt; comes in.&lt;/p&gt;
&lt;h1 id=&#34;what-wat-does&#34;&gt;What Wat Does&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;wat&lt;/code&gt; started out as a simple wrapper around &lt;code&gt;tldr&lt;/code&gt; and &lt;code&gt;cht.sh&lt;/code&gt;. The two provide similar content, but they do so in different ways; &lt;code&gt;tldr&lt;/code&gt; is installed locally, &lt;code&gt;cht.sh&lt;/code&gt; is usually accessed through a dead-simple web interface: &lt;code&gt;curl cht.sh/command&lt;/code&gt;. The first iteration of &lt;code&gt;wat&lt;/code&gt; was just a simple &lt;a href=&#34;https://fishshell.com/&#34;&gt;fish&lt;/a&gt; function that would call &lt;code&gt;tldr&lt;/code&gt; if it&amp;rsquo;s installed, and otherwise fall back on &lt;code&gt;cht.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These days, &lt;code&gt;wat&lt;/code&gt; is written in Python and it&amp;rsquo;s a &lt;em&gt;tiny&lt;/em&gt; bit more sophisticated: it also displays custom Markdown notes which are synced across all my machines using Git.&lt;/p&gt;
&lt;p&gt;Say I want to get some information about a video file, but I can&amp;rsquo;t remember the exact &lt;code&gt;ffprobe&lt;/code&gt; syntax. I just type &lt;code&gt;wat ffprobe&lt;/code&gt; in my terminal:&lt;/p&gt;

&lt;div class=&#39;carousel border border-gray-border&#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/wat/wat_hu_82dfaaa04ec90301.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;This returns a handy &lt;code&gt;tldr&lt;/code&gt;/&lt;code&gt;cht.sh&lt;/code&gt; summary &lt;strong&gt;and&lt;/strong&gt; it renders the contents of &lt;code&gt;~/notes/ffprobe.md&lt;/code&gt; if it exists. In this case, it looks like I got annoyed enough by &lt;code&gt;ffprobe&lt;/code&gt; to write down &lt;a href=&#34;https://github.com/rgwood/dotfiles/blob/master/notes/ffprobe.md&#34;&gt;a note&lt;/a&gt; for the &lt;code&gt;-hide_banner&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been maintaining command line notes for a long time, in various tools that just happened to be at hand: OneNote, Notes.app, &lt;a href=&#34;https://github.com/rgwood/scrapbook&#34;&gt;Markdown in a repo&lt;/a&gt;&amp;hellip; but &lt;code&gt;wat&lt;/code&gt; lets me organize that information better, and view it inline with excellent community-provided documentation.&lt;/p&gt;
&lt;h2 id=&#34;how-wat-works&#34;&gt;How Wat Works&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;wat&lt;/code&gt; is embarrassingly simple; it&amp;rsquo;s &lt;a href=&#34;https://github.com/rgwood/dotfiles/blob/master/bin/wat&#34;&gt;a single-file Python script&lt;/a&gt; gluing together more sophisticated tools.&lt;/p&gt;
&lt;p&gt;I sync &lt;code&gt;wat&lt;/code&gt; and its notes across all of my computers with &lt;a href=&#34;https://github.com/rgwood/dotfiles&#34;&gt;a Git repo&lt;/a&gt; and the excellent &lt;a href=&#34;https://github.com/anishathalye/dotbot&#34;&gt;Dotbot&lt;/a&gt; tool. This is just piggybacking on my existing approach for dotfiles. The note files are rendered from Markdown using &lt;a href=&#34;https://github.com/lunaryorn/mdcat&#34;&gt;mdcat&lt;/a&gt;, and tldr pages are cached/accessed using the excellent &lt;a href=&#34;https://github.com/dbrgn/tealdeer/&#34;&gt;tealdeer&lt;/a&gt; client. I&amp;rsquo;m also using the lovely &lt;a href=&#34;https://github.com/amoffat/sh&#34;&gt;&lt;code&gt;sh&lt;/code&gt;&lt;/a&gt; module, which greatly reduces the friction of &amp;ldquo;shelling out&amp;rdquo; in Python.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;wat&lt;/code&gt; solves CLI documentation for &lt;em&gt;me&lt;/em&gt;. I have an unusually strong preference for Markdown, and I wanted something that would integrate nicely with my existing dotfile syncing. That&amp;rsquo;s all I really need. It might not work for everyone, and that&amp;rsquo;s totally okay! I&amp;rsquo;m &lt;del&gt;cooking&lt;/del&gt; coding for an audience of one.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Vancouver Mapping Cheat Sheet</title>
      <link>https://www.reillywood.com/blog/vancouver-map/</link>
      <pubDate>Sun, 02 Feb 2020 20:44:38 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/vancouver-map/</guid>
      <description>&lt;p&gt;You might have already seen Jens von Bergmann’s &lt;a href=&#34;https://mountainmath.ca/map/assessment&#34;&gt;interactive map of Vancouver&lt;/a&gt; which integrates a ton of different data sets. It’s really useful for looking up zoning, year of construction, tax data, and even the estimated amount of floor space in buildings. I find it essential for understanding the city.&lt;/p&gt;
&lt;p&gt;It has also has a great feature that many people don’t know about: the ability to filter data for better visualization through the use of a query parameter in the URL. With Jens&amp;rsquo;s permission, I&amp;rsquo;ve compiled a list of ways to use this.&lt;/p&gt;
&lt;h2 id=&#34;basics&#34;&gt;Basics&lt;/h2&gt;
&lt;p&gt;Say you want to see the impact of Vancouver&amp;rsquo;s early building boom, &lt;a href=&#34;https://twitter.com/BrendanDawe/status/1016772562271891456&#34;&gt;circa 1905-1914&lt;/a&gt;. Just add a &lt;code&gt;years_1905_1914&lt;/code&gt; filter to the URL like so: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Byears_1905_1914%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;years_1905_1914&lt;/strong&gt;]&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&#39;carousel border border-gray-border&#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/assessment-map/light_hu_657e41b96bc719c1.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;And those are just the buildings still left standing! It&amp;rsquo;s pretty wild how Mount Pleasant, Grandview-Woodland, and Shaughnessy all sprung into existence in a decade.&lt;/p&gt;
&lt;p&gt;If you wanted to see all buildings with an estimated floor space ratio (FSR) between 0 and 1, add a &lt;code&gt;fsr_0.0_1.0&lt;/code&gt; filter: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bfsr_0.0_1.0%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;fsr_0.0_1.0&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Filters can easily be combined with a comma. If you wanted buildings that were built between 1900-1940 &lt;em&gt;and&lt;/em&gt; are between 0 and 1 FSR, the URL would look like this: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bfsr_0.0_1.0,years_1900_1940%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;fsr_0.0_1.0,years_1900_1940&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;area&#34;&gt;Area&lt;/h3&gt;
&lt;p&gt;This lets you filter on lot size (in square metres). Small lots between 100 and 300 square metres: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Barea_100_300%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;area_100_300&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;assessed-value&#34;&gt;Assessed Value&lt;/h3&gt;
&lt;p&gt;You can filter by building value (&lt;code&gt;building&lt;/code&gt;), land value (&lt;code&gt;land&lt;/code&gt;), and total value (&lt;code&gt;total&lt;/code&gt;, building+land).&lt;/p&gt;
&lt;p&gt;Lots with building value between $500k and $1M: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bbuilding_500000_1000000%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;building_500000_1000000&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can also filter on &lt;em&gt;relative land value&lt;/em&gt; with the &lt;code&gt;rlv&lt;/code&gt; filter. This shows lots with land value between $1000 and $2000 per square metre: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Brlv_1000_2000%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;rlv_1000_2000&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;floor-space-ratio-fsr&#34;&gt;Floor Space Ratio (FSR)&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Floor_area_ratio&#34;&gt;Floor Space Ratio or Floor Area Ratio&lt;/a&gt; is one measure of how dense a building is. This data is not exact - it is estimated by Jens based on Vancouver’s LIDAR survey of buildings.
Buildings with an estimated FSR between 0 and 1: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bfsr_0.0_1.0%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;fsr_0.0_1.0&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;geographic-filters&#34;&gt;Geographic filters&lt;/h3&gt;
&lt;p&gt;If you want to see everything west of Ontario Street at -123.105118 degrees latitude, you&amp;rsquo;d use the &lt;code&gt;westof&lt;/code&gt; filter: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bwestof_-123.105118%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;westof_-123.105118&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Same idea for &lt;code&gt;eastof_&lt;/code&gt;, &lt;code&gt;northof_&lt;/code&gt;, and &lt;code&gt;southof_&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;land-use&#34;&gt;Land Use&lt;/h3&gt;
&lt;p&gt;Jens has also imported Metro Vancouver data on land use. This is sometimes different from how the land is zoned - it shows how the land is actually being used, not what&amp;rsquo;s allowed. Some older properties may have grandfathered noncompliant uses.&lt;/p&gt;
&lt;p&gt;You can filter for specific land use codes with the &lt;code&gt;lu&lt;/code&gt; filter, or exclude codes with the &lt;code&gt;nlu&lt;/code&gt; filter.&lt;/p&gt;
&lt;p&gt;Here are all lots with &amp;ldquo;Residential - High-rise Apartment&amp;rdquo; uses: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Blu_S135%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;lu_S135&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Land use codes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;S110: &amp;ldquo;Residential - Single Detached &amp;amp; Duplex&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S131: &amp;ldquo;Residential – Townhouse&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S130: &amp;ldquo;Residential - Low-rise Apartment&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S135: &amp;ldquo;Residential - High-rise Apartment&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S410: &amp;ldquo;Residential - Institutional and Non-Market Housing&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S200: &amp;ldquo;Commercial&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S230: &amp;ldquo;Mixed Residential Commercial - Low-rise Apartment&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S235: &amp;ldquo;Mixed Residential Commercial - High-rise Apartment&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S400: &amp;ldquo;Institutional&amp;rdquo;&lt;/li&gt;
&lt;li&gt;A500: &amp;ldquo;Agriculture&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S300: &amp;ldquo;Industrial&amp;rdquo;&lt;/li&gt;
&lt;li&gt;R100: &amp;ldquo;Recreation Open Space and Protected Natural Areas&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S420: &amp;ldquo;Cemetery&amp;rdquo;&lt;/li&gt;
&lt;li&gt;U100: &amp;ldquo;Undeveloped and Unclassified&amp;rdquo;&lt;/li&gt;
&lt;li&gt;M300: &amp;ldquo;Industrial – Extractive&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S120: &amp;ldquo;Residential – Rural&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S100: &amp;ldquo;Residential - Mobile Home Park&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S700: &amp;ldquo;Rail Rapid Transit Other Transportation Utility and Communication&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S600: &amp;ldquo;Port Metro Vancouver&amp;rdquo;&lt;/li&gt;
&lt;li&gt;R200: &amp;ldquo;Lakes Large Rivers and Other Water&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S500: &amp;ldquo;Road Right-of-Way&amp;rdquo;&lt;/li&gt;
&lt;li&gt;W400: &amp;ldquo;Protected Watershed&amp;rdquo;&lt;/li&gt;
&lt;li&gt;S650: &amp;ldquo;Airport/Airstrip&amp;rdquo;&lt;/li&gt;
&lt;li&gt;F100: &amp;ldquo;Harvesting and Research&amp;rdquo;&lt;/li&gt;
&lt;li&gt;J000: &amp;ldquo;Recent Redeveloped / Misclassified&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;residential-other-than-purpose-built-rental&#34;&gt;Residential other than purpose-built rental&lt;/h3&gt;
&lt;p&gt;Per Jens, the &lt;code&gt;priv_res&lt;/code&gt; filter does this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Land use residential or mixed or affordable, but taking out cases where unit count=1 and not single family/duplex. So trying to filter out purpose-built rental.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bpriv_res%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;priv_res&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;teardown-coefficient&#34;&gt;Teardown Coefficient&lt;/h3&gt;
&lt;p&gt;The &amp;ldquo;Teardown Coefficient&amp;rdquo; is &lt;a href=&#34;https://doodles.mountainmath.ca/blog/2016/01/18/redevelopment/&#34;&gt;a measure developed to estimate the chance that a building is likely to be torn down&lt;/a&gt;. Use the &lt;code&gt;tdc&lt;/code&gt; filter.&lt;/p&gt;
&lt;p&gt;This shows all buildings with teardown coefficient between 0.05 and 0.1 (so building value between 5% and 10% of total value): &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Btdc_0.05_0.1%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;tdc_0.05_0.1&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;years&#34;&gt;Years&lt;/h3&gt;
&lt;p&gt;This lets you filter by the year a building was constructed. Look how much the West End grew between 1960 and 1975: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Byears_1960_1975%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;years_1960_1975&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;zone&#34;&gt;Zone&lt;/h3&gt;
&lt;p&gt;This lets you view specific zoning districts. If you want to see how many lots are zoned RS-1, Vancouver’s most common “single family”* zoning district: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bzone_RS-1%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;zone_RS-1&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can specify only the first part of a zoning code if you like. For example, if you want to see all RS lots not just RS-1: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bzone_RS%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;zone_RS&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What if you want everything &lt;em&gt;but&lt;/em&gt; a specific district? You can use “nzone” instead of “zone”: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bnzone_RS%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;nzone_RS&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Exact matches can be done using &lt;code&gt;zoneE&lt;/code&gt;. This will only show RS-1 and exclude, say, RS-1A: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5BzoneE_RS-1%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;zoneE_RS-1&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want to include multiple zones you can separate them with underscores: &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Bzone_RT-1_RT-5%5D&#34;&gt;https://mountainmath.ca/map/assessment?filter=[&lt;strong&gt;zone_RT-1_RT-5&lt;/strong&gt;]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;sub&gt;*I&amp;rsquo;m very aware that most RS lots can now have up to 3 suites (1 main, secondary, 1 laneway house). But it&amp;rsquo;s hard to describe that succinctly, and even the City of Vancouver still calls RS &amp;ldquo;One-Family Districts&amp;rdquo; and state that a major goal is to retain their &amp;ldquo;single-family residential character&amp;rdquo;.&lt;/sub&gt;&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Immediate Feedback in Programming</title>
      <link>https://www.reillywood.com/blog/inventing-on-principle/</link>
      <pubDate>Sun, 29 Dec 2019 19:01:49 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/inventing-on-principle/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://worrydream.com/&#34;&gt;Bret Victor&lt;/a&gt;’s talk Inventing on Principle (&lt;a href=&#34;https://vimeo.com/36579366&#34;&gt;video&lt;/a&gt;, &lt;a href=&#34;https://jamesclear.com/great-speeches/inventing-on-principle-by-bret-victor&#34;&gt;transcript&lt;/a&gt;) changed the way I think about computing in 2019. Inventing on Principle is partly about Bret&amp;rsquo;s guiding principle:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;&lt;strong&gt;Creators need an immediate connection to what they create.&lt;/strong&gt; And what I mean by that is when you&amp;rsquo;re making something, if you make a change, or you make a decision, you need to see the effect of that immediately.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;the-edit-compile-run-cycle&#34;&gt;The Edit-Compile-Run Cycle&lt;/h3&gt;
&lt;p&gt;Although Bret doesn’t use the term, programmers are deeply familiar with his principle. We’ve all worked with toolchains that introduce significant delay before you can “see” the results of a change, and we know they’re painful. Everyone wants a short edit-compile-run cycle.&lt;/p&gt;
&lt;p&gt;But until IoP, I’d assumed that slow cycles wouldn’t materially change the output – you&amp;rsquo;d eventually get to the same place. This was wrong. I also didn’t appreciate the very small time scales involved; a 5 second delay used to seem trivial to me, but it’s still meaningfully different from a response time measured in milliseconds.&lt;/p&gt;
&lt;p&gt;Through some very impressive custom tools, Bret shows how immediate feedback enables exploration, which then gives birth to ideas which would otherwise never see the light of day. This was an epiphany for me. Since IoP I’ve constantly been looking for better ways to code, and re-evaluating my existing processes for shorter feedback cycles. The results:&lt;/p&gt;
&lt;h3 id=&#34;rust&#34;&gt;Rust&lt;/h3&gt;
&lt;p&gt;My typical Rust development workflow goes something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write a small function that does roughly what I want&lt;/li&gt;
&lt;li&gt;Write a small unit test inline to exercise the function (even if it’s a private function)&lt;/li&gt;
&lt;li&gt;Iterate using &lt;code&gt;cargo test&lt;/code&gt; until the function is correct&lt;/li&gt;
&lt;li&gt;Later, “productionize” the tests if necessary&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Rust’s native support for inline unit tests helps a lot here, and the excellent type system catches a lot of issues before I even run &lt;code&gt;cargo test&lt;/code&gt;. On the other hand, Rust’s compiler is notoriously slow and that extends to IDE tooling that depends on the &lt;a href=&#34;https://github.com/rust-lang/rls&#34;&gt;Rust Language Server&lt;/a&gt;. I&amp;rsquo;m looking forward to &lt;a href=&#34;https://github.com/bytecodealliance/cranelift/blob/master/rustc.md&#34;&gt;Cranelift&lt;/a&gt; for faster debug builds.&lt;/p&gt;
&lt;h3 id=&#34;javascripttypescriptnode&#34;&gt;JavaScript/TypeScript/Node&lt;/h3&gt;
&lt;p&gt;I use &lt;a href=&#34;https://nodemon.io/&#34;&gt;Nodemon&lt;/a&gt; to watch my files and automatically run a JS/TS file as soon as my code changes on disk. With pure JS, the feedback time is nearly instant. For TypeScript using &lt;a href=&#34;https://github.com/TypeStrong/ts-node&#34;&gt;ts-node&lt;/a&gt;, it tends to take a couple seconds – not ideal, but still pretty good. It’s a tradeoff; TypeScript catches bugs as I code and enables better IDE tooling, but JS enables immediate execution+feedback for quick iterations. The best choice depends on the nature of the task.&lt;/p&gt;
&lt;h3 id=&#34;cnet&#34;&gt;C#/.NET&lt;/h3&gt;
&lt;p&gt;This is an area where .NET’s recent cross-platform support stumbles a bit.&lt;/p&gt;
&lt;p&gt;For .NET developers on Windows, &lt;a href=&#34;https://www.ncrunch.net/&#34;&gt;NCrunch&lt;/a&gt; is a fantastic enabler of rapid feedback when coding. It’s a sophisticated automatic test runner; you make a code change, and it runs the relevant tests. It works very well, and has a lot of fans among the Domain Driven Design crowd. My only quibble is more to do with C#: private methods can’t be called by unit tests, which is (arguably) justified for production code but a hindrance for rapid exploration+prototyping. Visual Studio for Windows is also great; the tight integration with the Roslyn compiler allows for detailed code feedback before you even save a file.&lt;/p&gt;
&lt;p&gt;However, these strengths don’t always extend to .NET development on other platforms. There is no serious equivalent to NCrunch for Visual Studio Code or VS for Mac; the VS Code extensions in that space are pretty unreliable. In theory, OmniSharp provides first-class language support for C# in VS Code; in practice, it &lt;a href=&#34;https://github.com/OmniSharp/omnisharp-vscode/issues/2927#issuecomment-480647017&#34;&gt;needs to be restarted several times a day&lt;/a&gt; and I can never &lt;em&gt;quite&lt;/em&gt; trust that I’m getting up-to-date feedback on my code as written.&lt;/p&gt;
&lt;p&gt;The dotnet watch CLI tool can be quite useful in the same way as nodemon, but it typically takes a few seconds to compile even trivial applications. Hopefully it will get faster.&lt;/p&gt;
&lt;h3 id=&#34;golanghugo&#34;&gt;Golang/Hugo&lt;/h3&gt;
&lt;p&gt;I’m not an expert Go programmer, but I get a lot of exposure to it from &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; (the templating engine I used to build this website).&lt;/p&gt;
&lt;p&gt;I appreciate how fast the Go compiler is; speed is a primary goal for the Go team, and it shows. I can make a small change to a Hugo template, and see the results of that change in my browser nearly instantly. On the other hand, Hugo tooling in VS Code is poor, so I rarely get useful information from my IDE before saving a file. I intend to spend some time exploring better Hugo tooling.&lt;/p&gt;
&lt;p&gt;(Aside: I’m also not convinced that Go is the right language for templating, because the lack of interfaces greatly complicates working with collections of structured data.)&lt;/p&gt;
&lt;h3 id=&#34;grab-bag&#34;&gt;Grab bag&lt;/h3&gt;
&lt;p&gt;IoP has changed my perspective on Unix scripting; I better appreciate the rapid iteration and observability of chaining multiple independent steps together with pipes. On the other hand, I get frustrated with the lack of typing; when every input is just raw strings, my development environment is unable to give me useful feedback before I run the script. I feel more warm and fuzzy toward PowerShell these days.&lt;/p&gt;
&lt;p&gt;VS Code’s &lt;a href=&#34;https://code.visualstudio.com/docs/remote/remote-overview&#34;&gt;remote development feature&lt;/a&gt; is superb; it’s as if your IDE was running on a remote server. This eliminates a whole class of “copy the executable elsewhere to run it” delays (like when writing Linux-specific code on macOS or Windows).&lt;/p&gt;
&lt;p&gt;I still really like &lt;a href=&#34;https://www.reillywood.com/blog/tailwind&#34;&gt;Tailwind CSS&lt;/a&gt;, but it takes a few seconds to compile after configuration changes. This is a pain for rapid iteration on UIs, hopefully it gets faster.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>What I&#39;ve been up to: Nov-Dec 2019</title>
      <link>https://www.reillywood.com/blog/december-2019/</link>
      <pubDate>Sun, 15 Dec 2019 11:48:48 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/december-2019/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been busy for the last month, and I completely forgot to update the blog. In no particular order, here&amp;rsquo;s what&amp;rsquo;s been occupying my time lately:&lt;/p&gt;
&lt;h2 id=&#34;coworking&#34;&gt;Coworking&lt;/h2&gt;
&lt;p&gt;I joined &lt;a href=&#34;https://www.werklab.com/&#34;&gt;a coworking space&lt;/a&gt; in East Vancouver with a friend, and I&amp;rsquo;m working from there 3-4 days/week. It&amp;rsquo;s like an airy spacious café with fast internet and quiet space, I love it. I find that getting out of the house helps me be more disciplined with my working hours; I&amp;rsquo;m much less likely to disappear down a Wikipedia+YouTube rabbit hole at the office. And then when I inevitably do that at home, I feel less guilty about it because I&amp;rsquo;ve accomplished so much at the office.&lt;/p&gt;
&lt;h2 id=&#34;nodejs&#34;&gt;Node.js&lt;/h2&gt;
&lt;p&gt;I got really into Node and the modern back-end JS ecosystem.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://javascript.info/&#34;&gt;javascript.info&lt;/a&gt; has been remarkably helpful (I can finally remember &lt;em&gt;exactly&lt;/em&gt; what a closure is!), it might be the first &lt;code&gt;.info&lt;/code&gt; website that is genuinely a great source of information.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=p-iiEDtpy6I&#34;&gt;This talk&lt;/a&gt; by Franziska Hinkelmann on the V8 team is a great overview of JS engine internals.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been pleasantly surprised by Node APIs a few times; the &lt;a href=&#34;https://nodejs.org/api/cluster.html&#34;&gt;Cluster&lt;/a&gt; module makes it trivial to fork workers and take advantage of multiple cores, and &lt;a href=&#34;https://nodejs.org/api/buffer.html&#34;&gt;Buffer&lt;/a&gt;+friends make low-level bit manipulation quite pleasant. Which leads me to:&lt;/p&gt;
&lt;h2 id=&#34;cryptopals&#34;&gt;Cryptopals&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&#34;https://cryptopals.com/&#34;&gt;Cryptopals Crypto Challenges&lt;/a&gt; by Matasano Security are a delightful introduction to practical cryptography. I decided to do them in Node to get more practice with back-end JS, my solutions are on &lt;a href=&#34;https://github.com/rgwood/cryptopals-node&#34;&gt;GitHub&lt;/a&gt;. The problems are very well-designed. They&amp;rsquo;re small enough that you can (usually) do each one in a single sitting, and it feels &lt;em&gt;great&lt;/em&gt; every time you decode a ciphertext.&lt;/p&gt;
&lt;h2 id=&#34;raspberry-pi&#34;&gt;Raspberry Pi&lt;/h2&gt;
&lt;p&gt;I bought a Raspberry Pi 4 and have been loving it. For some reason I always thought of the Pi as only relevant for education and hardware hackers, but I was wrong – it&amp;rsquo;s a remarkably capable little Linux machine. I&amp;rsquo;m currently turning mine into a private Dropbox clone using &lt;a href=&#34;https://owncloud.org&#34;&gt;ownCloud&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;anki&#34;&gt;Anki&lt;/h2&gt;
&lt;p&gt;I started using &lt;a href=&#34;https://en.wikipedia.org/wiki/Anki_(software)&#34;&gt;Anki&lt;/a&gt; flashcards. I have decks for Node internals, uncommon JS syntax, and infrequently-used keyboard shortcuts and CLI options. Creating cards is a bit of an initial investment, but once that&amp;rsquo;s done I find that 5 minutes of daily study is enough.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Stupid Rust Tricks</title>
      <link>https://www.reillywood.com/blog/rust-todo/</link>
      <pubDate>Sun, 17 Nov 2019 17:09:46 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/rust-todo/</guid>
      <description>&lt;p&gt;Rust has a &lt;a href=&#34;https://doc.rust-lang.org/book/ch19-06-macros.html&#34;&gt;really powerful macro system&lt;/a&gt;. You can use it to &lt;a href=&#34;https://blog.rust-lang.org/2018/12/21/Procedural-Macros-in-Rust-2018.html#procedural-macros-in-the-wild&#34;&gt;do great things safely&lt;/a&gt;&amp;hellip; or you can have fun with it and quickly prototype coding productivity features. I chose the latter.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/rgwood/todo-macro/&#34;&gt;&lt;code&gt;todo-macro&lt;/code&gt;&lt;/a&gt; is a procedural Rust macro that stops compiling after a user-specified deadline. It&amp;rsquo;s like TODO comments, but with teeth. It&amp;rsquo;s probably best explained with an example:&lt;/p&gt;
&lt;h1 id=&#34;using-todo-macro&#34;&gt;Using todo-macro&lt;/h1&gt;
&lt;p&gt;It&amp;rsquo;s January 1, 2020. I&amp;rsquo;m working on some Rust code that compiles, but it&amp;rsquo;s not quite ready to ship.&lt;/p&gt;
&lt;p&gt;I want to take a break, but I know myself – I&amp;rsquo;ll probably forget about the deficiency. I could add a TODO comment, but that depends on me actively searching for TODO comments next time I open the project.&lt;/p&gt;
&lt;p&gt;To save me from myself, I add a quick todo macro with a deadline of January 2 (in ISO 8601 format):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Implement the timeout handling
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;fm&#34;&gt;todo!&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;2020-01-02&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That compiles for now, but as soon as the deadline is passed (i.e. our system clock returns Jan 3), builds start failing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;error: proc macro panicked
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; --&amp;gt; src/main.rs:5:5
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;m&#34;&gt;5&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;     todo!&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;2020-01-02&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;     ^^^^^^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; help: message: Tsk tsk. You missed your deadline.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Think of &lt;code&gt;todo-macro&lt;/code&gt; as a reminder that actively forces your future self to deal with a problem.&lt;/p&gt;
&lt;p&gt;Obviously, don&amp;rsquo;t use this in real projects unless you&amp;rsquo;re &lt;em&gt;really&lt;/em&gt; comfortable with non-deterministic builds (I&amp;rsquo;m not). But still, wouldn&amp;rsquo;t it be nice to have a (safe) feature like this in your favourite IDE?&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Linux .NET Development in 2019</title>
      <link>https://www.reillywood.com/blog/dotnet-linux/</link>
      <pubDate>Tue, 05 Nov 2019 08:51:59 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/dotnet-linux/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve recently been building .NET Core back-end services that run on Linux. Linux .NET development is in an interesting place; it&amp;rsquo;s clearly the future of back-end .NET, but it&amp;rsquo;s still a little rough around the edges compared to our old friend .NET-on-Windows.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s dive into what you (an experienced Windows .NET developer or a .NET-curious Linux developer) need to know to start building .NET services for Linux. I&amp;rsquo;ll cover IDEs, service hosting, Linux system calls and more.&lt;/p&gt;
&lt;h1 id=&#34;background--motivation&#34;&gt;Background &amp;amp; Motivation&lt;/h1&gt;
&lt;p&gt;.NET development for Linux has been possible via &lt;a href=&#34;https://en.wikipedia.org/wiki/Mono_(software)&#34;&gt;Mono&lt;/a&gt; since 2004, but it was always a bit&amp;hellip; &lt;em&gt;fringe&lt;/em&gt; compared to Windows .NET development. That all changed when Microsoft released &lt;a href=&#34;https://en.wikipedia.org/wiki/.NET_Core&#34;&gt;.NET Core&lt;/a&gt; in 2016 as a cross-platform .NET implementation; first-party support from Microsoft is a big deal to most .NET developers.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re also in a world where Linux is the lingua franca for back-end development; if you want to do anything involving cloud services, distributed systems, or containerization, Windows is typically an afterthought (if it&amp;rsquo;s supported at all). I want to skate to where the puck is going (&lt;a href=&#34;https://www.macleans.ca/economy/business/why-business-people-wont-stop-using-that-gretzky-quote/&#34;&gt;sorry!&lt;/a&gt;), and it&amp;rsquo;s headed toward Linux.&lt;/p&gt;
&lt;h1 id=&#34;which-ide-should-i-use&#34;&gt;Which IDE should I use?&lt;/h1&gt;
&lt;p&gt;You can eschew an IDE and do everything with the &lt;code&gt;dotnet&lt;/code&gt; CLI tool, but personally I want more tooling. There are 4 big IDEs for modern .NET development:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visual Studio for Windows&lt;/li&gt;
&lt;li&gt;Visual Studio Code (Windows, Linux, Mac)&lt;/li&gt;
&lt;li&gt;Visual Studio for Mac&lt;/li&gt;
&lt;li&gt;JetBrains Rider (Windows, Linux, Mac)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The best choice for Linux development will depend on your personal OS and the specifics of your application.&lt;/p&gt;
&lt;p&gt;Visual Studio for Windows is the one to beat, it&amp;rsquo;s got excellent .NET tooling. However, it doesn&amp;rsquo;t have a great story for remote Linux .NET development; to test Linux-specific functionality, you&amp;rsquo;ll need to deploy to a remote system after compiling. Not the end of the world, but I prefer a tighter edit-compile-debug loop. Interestingly, VS support for &lt;a href=&#34;https://devblogs.microsoft.com/cppblog/c-with-visual-studio-2019-and-windows-subsystem-for-linux-wsl/&#34;&gt;remote Linux C++ development&lt;/a&gt; has seen a lot of investment recently; it&amp;rsquo;s a safe bet that this feature will come to .NET eventually.&lt;/p&gt;
&lt;p&gt;VS Code supports &lt;a href=&#34;https://code.visualstudio.com/docs/remote/ssh&#34;&gt;remote development on a Linux server&lt;/a&gt;, and it&amp;rsquo;s a killer feature. I just point VS Code at my Ubuntu VM, and bam, it&amp;rsquo;s like I&amp;rsquo;m on a Linux workstation. Debugging and file browsing &lt;em&gt;just work&lt;/em&gt;. The downside is that VS Code&amp;rsquo;s C# tooling is much less mature than &amp;ldquo;real&amp;rdquo; VS. I need to restart the OmniSharp language server multiple times a day (&lt;a href=&#34;https://github.com/OmniSharp/omnisharp-vscode/issues/2927#issuecomment-480647017&#34;&gt;Microsoft employees complain about this too!&lt;/a&gt;), and you&amp;rsquo;ll need to fall back on the CLI frequently.&lt;/p&gt;
&lt;p&gt;VS for Mac is the descendant of Xamarin Studio. It has much better .NET tooling than VS Code, but no remote development feature. I&amp;rsquo;m on a Mac and I still prefer VS Code most of the time, but YMMV.&lt;/p&gt;
&lt;p&gt;JetBrains Rider runs natively on Mac, Windows, and Linux. I&amp;rsquo;m told that it&amp;rsquo;s a great choice if you run Linux as your main OS, but it doesn&amp;rsquo;t have a remote development feature.&lt;/p&gt;
&lt;h1 id=&#34;systemd-services&#34;&gt;Systemd Services&lt;/h1&gt;
&lt;p&gt;If you want a long-running service on Windows, typically you host your code in a &lt;a href=&#34;https://en.wikipedia.org/wiki/Windows_service&#34;&gt;Windows Service&lt;/a&gt;. The equivalent on Linux (for most distros) is a &lt;a href=&#34;https://en.wikipedia.org/wiki/Systemd&#34;&gt;&lt;code&gt;systemd&lt;/code&gt;&lt;/a&gt; service. It&amp;rsquo;s &lt;em&gt;really&lt;/em&gt; easy to write a &lt;code&gt;systemd&lt;/code&gt; service in .NET Core using the &lt;a href=&#34;https://www.nuget.org/packages/Microsoft.Extensions.Hosting.Systemd&#34;&gt;Microsoft.Extensions.Hosting.Systemd&lt;/a&gt; package:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Host your application using &lt;a href=&#34;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.0&#34;&gt;the .NET Generic Host&lt;/a&gt; (HostBuilder)
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;dotnet new worker&lt;/code&gt; template is the easiest way to do this from scratch&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Add the &lt;a href=&#34;https://www.nuget.org/packages/Microsoft.Extensions.Hosting.Systemd&#34;&gt;Microsoft.Extensions.Hosting.Systemd&lt;/a&gt; NuGet package to your project&lt;/li&gt;
&lt;li&gt;Call the &lt;code&gt;UseSystemd()&lt;/code&gt; extension method on your HostBuilder&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://devblogs.microsoft.com/dotnet/net-core-and-systemd/&#34;&gt;Create a systemd unit file&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;IHostBuilder&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CreateHostBuilder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;Host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CreateDefaultBuilder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;UseSystemd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ConfigureServices&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;hostContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;n&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;AddHostedService&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Worker&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once you do that, the service acts just like any other systemd service; manage it with &lt;code&gt;systemctl&lt;/code&gt;, view its logs with &lt;code&gt;journalctl&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Microsoft.Extensions.Hosting.Systemd isn&amp;rsquo;t magic, and it&amp;rsquo;s surprisingly easy to implement your own &lt;code&gt;systemd&lt;/code&gt; layer if you need to; under the hood that library is just &lt;a href=&#34;https://github.com/aspnet/Extensions/blob/master/src/Hosting/Systemd/src/SystemdHelpers.cs#L67&#34;&gt;making a Linux syscall&lt;/a&gt;, &lt;a href=&#34;https://github.com/aspnet/Extensions/blob/master/src/Hosting/Systemd/src/SystemdHelpers.cs#L57&#34;&gt;reading from &lt;code&gt;/proc&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/aspnet/Extensions/blob/master/src/Hosting/Systemd/src/SystemdNotifier.cs&#34;&gt;writing to a Unix Datagram socket&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;syscalls&#34;&gt;Syscalls&lt;/h1&gt;
&lt;p&gt;Sometimes you want to do platform-specific things, right? Good news, that&amp;rsquo;s (usually) easy.&lt;/p&gt;
&lt;p&gt;You can make syscalls directly using &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke&#34;&gt;P/Invoke&lt;/a&gt;. Here&amp;rsquo;s how to call &lt;a href=&#34;http://man7.org/linux/man-pages/man2/getpid.2.html&#34;&gt;getpid&lt;/a&gt; to get the current process ID:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// leave EntryPoint off if you want it inferred from the name of the C# function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;na&#34;&gt;[DllImport(&amp;#34;libc&amp;#34;, EntryPoint = &amp;#34;getpid&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;getpid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, most of the time you&amp;rsquo;ll want to use the &lt;a href=&#34;https://www.nuget.org/packages/Mono.Posix.NETStandard/&#34;&gt;Mono.Posix.NETStandard&lt;/a&gt; package instead. It handles the thankless work of wrapping native calls &lt;em&gt;safely&lt;/em&gt;. Don&amp;rsquo;t be turned off by the Mono namespace; &lt;a href=&#34;https://github.com/dotnet/corefx/issues/15289#issuecomment-274116942&#34;&gt;Mono.Posix.NETStandard is the current Microsoft-blessed way to do syscalls&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update: &lt;a href=&#34;https://developers.redhat.com/blog/2019/09/06/interacting-with-native-libraries-in-net-core-3-0/&#34;&gt;the new NativeLibrary class&lt;/a&gt; in .NET Core 3 is a handy alternative to P/Invoke that can be configured at runtime.&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;miscellaneous&#34;&gt;Miscellaneous&lt;/h1&gt;
&lt;p&gt;Sometimes you&amp;rsquo;ll run into situations where common .NET APIs don&amp;rsquo;t handle *nix features well. For example, &lt;a href=&#34;https://github.com/dotnet/corefx/issues/26310&#34;&gt;the .NET I/O APIs weren&amp;rsquo;t designed for symlinks&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;FileInfo does not resolve symbolic links, leading to subtle bugs. There&amp;rsquo;s no API that gives information about the link target.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I expect that this will get better as .NET development for Linux becomes more mainstream.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Open-Source Projects for Learning Modern C and C&#43;&#43;</title>
      <link>https://www.reillywood.com/blog/c-cpp-educational-projects/</link>
      <pubDate>Sun, 20 Oct 2019 09:52:25 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/c-cpp-educational-projects/</guid>
      <description>&lt;p&gt;Every now and then, I wonder &amp;ldquo;Is low-level programming still unpleasant?&amp;rdquo; Call it a morbid fascination, but I really did enjoy &lt;em&gt;some&lt;/em&gt; aspects of working in C and C++; in particular, reasoning about low-level behaviour becomes a lot easier with fewer layers of abstraction on top of the metal. On the other hand: memory management and interoperability are hard, the C/C++ ecosystems have accumulated decades of cruft, and both languages are missing a lot of features that are now par for the course.&lt;/p&gt;
&lt;p&gt;It turns out that the short answer is &amp;ldquo;No, because Rust is great&amp;rdquo;, but it&amp;rsquo;s still useful to get a feel for modern C and C++. Here are some projects and tools that I&amp;rsquo;ve found particularly indispensable for that.&lt;/p&gt;
&lt;h2 id=&#34;preface-why-not-book-learnin&#34;&gt;Preface: Why Not Book Learnin&#39;?&lt;/h2&gt;
&lt;p&gt;There are a lot of books out there that will teach you how to use newer C++ features. &lt;a href=&#34;https://www.goodreads.com/book/show/22800553-effective-modern-c&#34;&gt;Effective Modern C++&lt;/a&gt; is a popular one, but personally, that&amp;rsquo;s not what I&amp;rsquo;m looking for. I know that I learn best from hands-on experience, and so what I really wanted was to play with some decent-sized example projects using modern tooling.&lt;/p&gt;
&lt;h2 id=&#34;cmus-researcheducational-databases-for-c&#34;&gt;CMU&amp;rsquo;s Research/Educational Databases for C++&lt;/h2&gt;
&lt;p&gt;I can&amp;rsquo;t say enough good things about &lt;a href=&#34;https://db.cs.cmu.edu/&#34;&gt;CMU&amp;rsquo;s Database Group&lt;/a&gt;, and specifically professor Andy Pavlo&amp;rsquo;s work. CMU has 3 (yes, 3!) open source relational databases written in modern C++ on &lt;a href=&#34;https://github.com/cmu-db&#34;&gt;GitHub&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/cmu-db/bustub&#34;&gt;BusTub&lt;/a&gt;, an educational system written for the &lt;a href=&#34;https://15445.courses.cs.cmu.edu/fall2019/&#34;&gt;Database Systems&lt;/a&gt; course&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/cmu-db/terrier&#34;&gt;Terrier&lt;/a&gt;, CMU&amp;rsquo;s current research database&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/cmu-db/peloton&#34;&gt;Peloton&lt;/a&gt;, CMU&amp;rsquo;s older research database&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;BusTub and Terrier use C++17, but &lt;a href=&#34;https://github.com/cmu-db/peloton/blob/a045cfc95bf349742a8101aee65e22efd9ec8096/CMakeLists.txt#L47&#34;&gt;Peloton uses C++11&lt;/a&gt;. I&amp;rsquo;d recommend BusTub and Terrier since they&amp;rsquo;re both under active development. Terrier has &lt;a href=&#34;https://github.com/cmu-db/terrier/wiki&#34;&gt;excellent documentation&lt;/a&gt; for getting up and running on the wiki. They recommend using &lt;a href=&#34;https://www.jetbrains.com/clion/&#34;&gt;CLion&lt;/a&gt;, a modern C/C++ IDE from JetBrains.&lt;/p&gt;
&lt;p&gt;CMU&amp;rsquo;s even provided some small pieces of work to dip your toes in; &lt;a href=&#34;https://15445.courses.cs.cmu.edu/fall2019/assignments.html&#34;&gt;the projects for the Database Systems course&lt;/a&gt; involve implementing basic functionality in BusTub, and grading scripts for the projects &lt;a href=&#34;https://twitter.com/andy_pavlo/status/1171854391814631424&#34;&gt;will be available soon&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;quantum-mechanical-keyboard-firmware-qmk&#34;&gt;Quantum Mechanical Keyboard Firmware (QMK)&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m a bit of a keyboard dork, so of course I jumped at the chance to program my keyboard&amp;rsquo;s firmware.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://qmk.fm/&#34;&gt;QMK&lt;/a&gt; is a very popular open-source keyboard firmware project , mostly written in C. &lt;a href=&#34;https://docs.qmk.fm/#/&#34;&gt;QMK&amp;rsquo;s documentation&lt;/a&gt; is outstanding, and will help you get up and running with hardly any fuss. I was able to implement &lt;a href=&#34;https://github.com/rgwood/qmk_firmware/blob/master/keyboards/massdrop/ctrl/keymaps/reilly/keymap.c&#34;&gt;a custom keymap in C&lt;/a&gt; in just a few hours, armed with only the C I remembered from school 10 years ago.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>New Project: ETL Sheets</title>
      <link>https://www.reillywood.com/blog/etl-sheets/</link>
      <pubDate>Tue, 15 Oct 2019 07:32:49 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/etl-sheets/</guid>
      <description>&lt;p&gt;I recently started experimenting with building tooling for &lt;a href=&#34;https://en.wikipedia.org/wiki/Extract,_transform,_load&#34;&gt;ETL&lt;/a&gt; systems. After many years of wrestling with ETL in industry, I had a few questions on my mind:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Can we make common data issues &lt;em&gt;quick&lt;/em&gt; to resolve?&lt;/li&gt;
&lt;li&gt;Can we make automated data transformations as easy to work with as spreadsheets?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I think the answer to both questions is yes, but don&amp;rsquo;t take my word for it – you can try the prototype at &lt;a href=&#34;http://etlsheets.netlify.app&#34;&gt;etlsheets.netlify.app&lt;/a&gt; (double-click on an issue to get started), and view the &lt;a href=&#34;https://github.com/rgwood/etl-sheets&#34;&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;motivation&#34;&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Importing data at scale is painful. Your data providers &lt;em&gt;will&lt;/em&gt; screw up the formatting, systems &lt;em&gt;will&lt;/em&gt; experience connectivity issues, and your transformation logic &lt;em&gt;will&lt;/em&gt; fail on cases you didn&amp;rsquo;t expect. What if our tools focused on helping with those failures, instead of assuming the &lt;a href=&#34;https://en.wikipedia.org/wiki/Happy_path&#34;&gt;happy path&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Speaking of transformations, how should we write them? Some systems take a code-first approach, which is great for coders and impenetrable for everyone else. Others take a GUI-driven approach, which usually becomes &lt;a href=&#34;https://www.iri.com/blog/wp-content/uploads/2016/03/EDIT-IN-WP-From-Informatica-to-Voracity-via-ADS-CATfX-Paul-Kinnier-AnalytiX-DS-2.jpg&#34;&gt;the stuff of nightmares&lt;/a&gt;. I think we can do better, by drawing inspiration from a tool that&amp;rsquo;s found in every office:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/etl-sheets/excel_hu_c24781739d7f0343.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;h2 id=&#34;transformations&#34;&gt;Transformations&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://etlsheets.netlify.app/new/&#34;&gt;Here&amp;rsquo;s what building a new transformation might look like&lt;/a&gt;, and &lt;a href=&#34;https://etlsheets.netlify.app/transform/?id=4564980&#34;&gt;here&amp;rsquo;s what it might look like when that transformation fails to run successfully&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/etl-sheets/transform_hu_dd030b37264419d2.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;This works very much like a spreadsheet, albeit with visually distinct stages/steps. New columns can be created by entering a simple formula, and existing rows can be filtered+altered with simple row-level formulae. Significantly, formula changes are reflected immediately in the data – this might seem like a trivial feature, but I think it&amp;rsquo;s a key part of usability. Bret Victor&amp;rsquo;s made an entire career out of &lt;a href=&#34;http://blog.ezyang.com/2012/02/transcript-of-inventing-on-principleb/&#34;&gt;his &amp;ldquo;creators need an immediate connection to what they&amp;rsquo;re creating&amp;rdquo; principle&lt;/a&gt;, and for good reason.&lt;/p&gt;
&lt;p&gt;Under the hood, formulae are written in JavaScript with some syntactic sugar (to make it easier to refer to columns) and macros (for common uses like filtering). I&amp;rsquo;m not tied to JS in particular, but using a &amp;ldquo;real&amp;rdquo; programming language gives us a lot more power and flexibility. Interoperating with existing libraries+code becomes trivial.&lt;/p&gt;
&lt;p&gt;Try opening up &lt;a href=&#34;https://etlsheets.netlify.app/transform/?id=4564980&#34;&gt;the failed extraction&lt;/a&gt;. Our counterparty has sent us an identifier with a typo (MSFY instead of MSFT), but we can just fix the data inline.&lt;/p&gt;
&lt;h2 id=&#34;extractions&#34;&gt;Extractions&lt;/h2&gt;
&lt;p&gt;Extractions are a bit less interesting than transformations, but we can go a long way with some relatively simple interfaces. We know that connectivity and formatting issues are common, so when they happen let&amp;rsquo;s show users exactly where the problem is:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/etl-sheets/extractFailure_hu_ccf6d640aa0d94b2.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;To be honest, I&amp;rsquo;m not 100% sure where to take this next. The prototype makes for a great demo, but it would need a &lt;em&gt;lot&lt;/em&gt; of work to make it production-ready. I currently run JS formulae in-browser because that was quick to develop, but I&amp;rsquo;d want to evaluate other languages and build out server-side execution. Change management needs some thought too; given that these transformation definitions are essentially code, do we serialize them and put them in source control?&lt;/p&gt;
&lt;p&gt;These challenges are definitely surmountable, but they do require a lot of thought+work and I&amp;rsquo;ve mostly moved on to other projects. If you have any ideas or suggestions, &lt;a href=&#34;https://www.reillywood.com/about&#34;&gt;give me a shout&lt;/a&gt;!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Managing Postgres with Pulumi and Terraform</title>
      <link>https://www.reillywood.com/blog/pulumi-postgres/</link>
      <pubDate>Thu, 19 Sep 2019 13:52:18 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/pulumi-postgres/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m a big fan of &lt;a href=&#34;https://www.pulumi.com/&#34;&gt;Pulumi&lt;/a&gt;, and I&amp;rsquo;ve previously used it for cloud infrastructure. That&amp;rsquo;s their main selling point; Pulumi&amp;rsquo;s slogan is literally &lt;a href=&#34;https://www.pulumi.com/about/&#34;&gt;&amp;ldquo;Program the cloud.&amp;rdquo;&lt;/a&gt; Interestingly, Pulumi can also be used to manage &lt;em&gt;on-premise&lt;/em&gt; infrastructure, including PostgreSQL databases. Let&amp;rsquo;s dive into the details.&lt;/p&gt;
&lt;h2 id=&#34;pulumi-basics&#34;&gt;Pulumi basics&lt;/h2&gt;
&lt;p&gt;Pulumi takes a declarative infrastructure definition and handles provisioning said infrastructure, just like &lt;a href=&#34;https://www.terraform.io/&#34;&gt;Terraform&lt;/a&gt;, &lt;a href=&#34;https://aws.amazon.com/cloudformation/&#34;&gt;AWS CloudFormation&lt;/a&gt;, and &lt;a href=&#34;https://docs.microsoft.com/en-us/azure/azure-resource-manager/template-deployment-overview&#34;&gt;Azure Resource Manager&lt;/a&gt;. The main difference is that in Pulumi you&amp;rsquo;re writing real code (Node, Python, or Go) to build that infrastructure definition instead of a YAML or JSON file, &lt;a href=&#34;https://www.reillywood.com/blog/pulumi-basics/&#34;&gt;it&amp;rsquo;s great&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;pulumiterraform-postgresql-provider&#34;&gt;Pulumi/Terraform PostgreSQL Provider&lt;/h2&gt;
&lt;p&gt;After Pulumi has executed your code to build up an infrastructure definition, it needs to interact with external infrastructure resources (cloud providers, databases, etc.) to turn that definition into a reality. Some of Pulumi&amp;rsquo;s functionality for working with external resources is derived from &lt;a href=&#34;https://www.terraform.io/docs/providers/index.html&#34;&gt;Terraform providers&lt;/a&gt;. For example, Pulumi has a &lt;a href=&#34;https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/postgresql/index.html&#34;&gt;PostgreSQL provider&lt;/a&gt; which is derived from the &lt;a href=&#34;https://www.terraform.io/docs/providers/postgresql/index.html&#34;&gt;Terraform PostgreSQL provider&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At a high level, the PostgreSQL provider lets you define databases, roles, schemas, permissions, and PostgreSQL extensions. It stops short of letting you manage DDL objects and data within the database (which is probably for the best given the complexities of schema+data migrations).&lt;/p&gt;
&lt;h2 id=&#34;defining-a-postgres-database-with-typescript&#34;&gt;Defining a Postgres database with TypeScript&lt;/h2&gt;
&lt;p&gt;Pulumi &lt;em&gt;can&lt;/em&gt; be used to instantiate entire Postgres clusters, but for this tutorial let&amp;rsquo;s assume you already have an existing cluster managed outside of Pulumi. It can be running anywhere: in the cloud, in a container, on your local machine, whatever.&lt;/p&gt;
&lt;p&gt;First, &lt;a href=&#34;https://www.pulumi.com/docs/get-started/install/&#34;&gt;install Pulumi&lt;/a&gt; then instantiate a new TypeScript project with &lt;code&gt;pulumi new typescript&lt;/code&gt;. This scaffolds a bare-bones Node+TypeScript Pulumi project with the following files:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;index.ts                package-lock.json       tsconfig.json
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Pulumi.yaml             node_modules            package.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Install the Pulumi PostgreSQL provider with &lt;code&gt;npm i @pulumi/postgresql&lt;/code&gt; (or just edit your &lt;code&gt;package.json&lt;/code&gt; directly).&lt;/p&gt;
&lt;p&gt;Next, we need to tell Pulumi which Postgres cluster to connect to and how. The PostgreSQL provider&amp;rsquo;s configuration points are documented &lt;a href=&#34;https://github.com/pulumi/pulumi-postgresql&#34;&gt;here&lt;/a&gt;. My Postgres cluster is running on a local VM with the hostname &lt;code&gt;fedora-vm&lt;/code&gt;, so I set the &lt;code&gt;postgresql:host&lt;/code&gt; variable like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pulumi config set postgresql:host fedora-vm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This creates a YAML configuration file for the current Pulumi &lt;a href=&#34;https://www.pulumi.com/docs/intro/concepts/stack/&#34;&gt;stack&lt;/a&gt;, or modifies one if it already exists. For my &lt;code&gt;dev&lt;/code&gt; stack, this creates a file named &lt;code&gt;Pulumi.dev.yaml&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;postgresql:host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;fedora-vm&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll want to do the same for &lt;code&gt;postgresql:username&lt;/code&gt; and &lt;code&gt;postgresql:password&lt;/code&gt;, to tell Pulumi which credentials to use. Sensitive configuration values like passwords can be encrypted using the &lt;code&gt;--secret&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;Finally, we&amp;rsquo;re ready to work with the fun stuff: configuring a database in real TypeScript code. Let&amp;rsquo;s say we want to instantiate a Pulumi-managed database, create a role with login permissions, and grant the role &lt;code&gt;SELECT&lt;/code&gt; permission on tables in the &lt;code&gt;public&lt;/code&gt; schema in the new database. This can all be done in the &lt;code&gt;index.ts&lt;/code&gt; file like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;pulumi&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;@pulumi/pulumi&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postgresql&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;@pulumi/postgresql&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;pulumi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;managedDatabase&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postgresql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Database&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;managedDatabase&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;pulumi&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;publicReaderRole&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postgresql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Role&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;publicReaderRole&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;login&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;public_reader&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;password&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;config.require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;publicReaderPassword&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;publicReaderSelectTablesGrant&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postgresql&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Grant&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;publicReaderSelectTablesGrant&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;database&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;managedDatabase.name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;objectType&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;table&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;privileges&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;SELECT&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;role&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;publicReaderRole.name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;schema&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;public&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s fairly self-explanatory, but some points of interest:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We can retrieve arbitrary configuration values, even encrypted ones, using &lt;code&gt;config.require()&lt;/code&gt;.
&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;config.get()&lt;/code&gt; for optional configuration that might not be present in every stack.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s a bit weird that we write names like &lt;code&gt;managedDatabase&lt;/code&gt; twice, right?
&lt;ol&gt;
&lt;li&gt;The names in the constructors are Pulumi &lt;a href=&#34;https://www.pulumi.com/docs/intro/concepts/programming-model/#resources&#34;&gt;resource names&lt;/a&gt;, which Pulumi uses to track resources across multiple deployments.&lt;/li&gt;
&lt;li&gt;The TypeScript variable names could be anything, Pulumi doesn&amp;rsquo;t use them outside of when our code is run.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s a bit awkward that we need to repeat ourselves – in most cases I don&amp;rsquo;t have any reason to name Pulumi resources differently from the variables that represent them. Perhaps Pulumi should use reflection to find default resource names, or perhaps I should come up with a better naming convention.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;deployment&#34;&gt;Deployment&lt;/h2&gt;
&lt;p&gt;Once we have something like the above, deployment is trivial with the &lt;code&gt;pulumi up&lt;/code&gt; command. It runs our TypeScript program then provides a nice visual preview of the resources that will be created:&lt;/p&gt;

&lt;div class=&#39;carousel border border-gray-border&#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/pulumi-postgres/pulumi-up_hu_9d15876e88481db2.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;Selecting yes will create the database, role, and grant in just a few seconds. With just a little bit of work, we&amp;rsquo;ve defined our core database infrastructure as &lt;em&gt;real&lt;/em&gt; code.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Future Imperfect 2.0</title>
      <link>https://www.reillywood.com/blog/website-rewrite/</link>
      <pubDate>Sat, 14 Sep 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.reillywood.com/blog/website-rewrite/</guid>
      <description>&lt;p&gt;I spent a few weeks in August rewriting this website, and &lt;a href=&#34;https://www.reillywood.com/blog/dark-mode/&#34;&gt;as promised&lt;/a&gt; here are the deets. The source code is available &lt;a href=&#34;https://github.com/rgwood/hugo-future-imperfect&#34;&gt;here&lt;/a&gt; under the MIT license.&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;When I initially put this website together in January 2018, I used &lt;a href=&#34;https://github.com/jpescador/hugo-future-imperfect&#34;&gt;Julio Pescador&amp;rsquo;s Hugo port of the Future Imperfect theme&lt;/a&gt;. I&amp;rsquo;d heard good things about Hugo, I wanted to write blog posts in Markdown, and it was a good-looking theme that required minimal additional setup. It served me well for over a year, but a few things kept bothering me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The styling was very difficult to modify – it was nearly &lt;a href=&#34;https://github.com/jpescador/hugo-future-imperfect/blob/master/static/css/main.css&#34;&gt;3000 lines of CSS in a single file&lt;/a&gt;, with many duplicated colours and styles.&lt;/li&gt;
&lt;li&gt;The theme relied on a &lt;em&gt;lot&lt;/em&gt; of JavaScript libraries: &lt;a href=&#34;https://jquery.com/&#34;&gt;jQuery&lt;/a&gt;, &lt;a href=&#34;https://highlightjs.org/&#34;&gt;highlight.js&lt;/a&gt;, &lt;a href=&#34;http://fancyapps.com/fancybox/3/&#34;&gt;Fancybox&lt;/a&gt;, &lt;a href=&#34;https://github.com/ajlkn/skel&#34;&gt;Skel&lt;/a&gt;&amp;hellip;
&lt;ol&gt;
&lt;li&gt;My simple static website was serving up hundreds of kilobytes of largely unnecessary scripts. As someone who grew up using a dialup modem, this offended me.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My goal was to rewrite the Hugo theme for extensibility and performance, and I figured it would take maybe a week. Of course, it took about 3 times that.&lt;/p&gt;
&lt;h2 id=&#34;major-changes&#34;&gt;Major Changes&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;I rewrote the CSS using &lt;a href=&#34;https://tailwindcss.com/&#34;&gt;Tailwind CSS&lt;/a&gt;, because &lt;a href=&#34;https://www.reillywood.com/blog/tailwind/&#34;&gt;Tailwind is great&lt;/a&gt;.
&lt;ol&gt;
&lt;li&gt;Tailwind&amp;rsquo;s generated CSS is automatically pruned using &lt;a href=&#34;https://www.purgecss.com/&#34;&gt;PurgeCSS&lt;/a&gt; as part of a PostCSS workflow to keep file sizes down.&lt;/li&gt;
&lt;li&gt;The layout looks &lt;em&gt;roughly&lt;/em&gt; the same as before, but I&amp;rsquo;ve made a lot of small tweaks.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reillywood.com/blog/dark-mode/&#34;&gt;Dark mode&lt;/a&gt; using the new &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query and the &lt;a href=&#34;https://ethanschoonover.com/solarized/&#34;&gt;Solarized&lt;/a&gt; colour scheme.&lt;/li&gt;
&lt;li&gt;I rewrote the side-menu JavaScript because it used jQuery extensively.&lt;/li&gt;
&lt;li&gt;The full FontAwesome icon set has been replaced with a custom font from &lt;a href=&#34;https://icomoon.io/&#34;&gt;IcoMoon&lt;/a&gt; that only contains the icons used in the website.
&lt;ol&gt;
&lt;li&gt;Yes, I went a bit overboard with performance.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;I replaced the &lt;a href=&#34;http://fancyapps.com/fancybox/3/&#34;&gt;Fancybox&lt;/a&gt; lightbox with a customized &lt;a href=&#34;https://flickity.metafizzy.co/&#34;&gt;Flickity&lt;/a&gt; implementation.
&lt;ol&gt;
&lt;li&gt;This allows for nicer fullscreen interactions, image carousels, and best of all – no jQuery.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;I replaced the &lt;a href=&#34;https://highlightjs.org/&#34;&gt;highlight.js&lt;/a&gt; client-side code syntax highlighting with &lt;a href=&#34;https://gohugo.io/content-management/syntax-highlighting/&#34;&gt;Hugo&amp;rsquo;s compile-time highlighting&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The theme now uses Hugo&amp;rsquo;s built-in bundling and minification for CSS and JS files.&lt;/li&gt;
&lt;li&gt;Image hosting+transformation+optimization is now handled by &lt;a href=&#34;https://cloudinary.com/&#34;&gt;Cloudinary&lt;/a&gt;
&lt;ol&gt;
&lt;li&gt;I strongly considered using &lt;a href=&#34;https://www.netlify.com/docs/large-media/&#34;&gt;Netlify Large Media&lt;/a&gt;, but the requirement to use Netlify for continuous deployment scared me away – I want to avoid vendor lock-in.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Generated HTML is automatically formatted using &lt;a href=&#34;https://prettier.io/&#34;&gt;Prettier&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;CSS is automatically linted using &lt;a href=&#34;https://stylelint.io/&#34;&gt;Stylelint&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;There are new shortcodes for images, email obfuscation, card elements&amp;hellip;&lt;/li&gt;
&lt;li&gt;Site search is now done with &lt;a href=&#34;https://duckduckgo.com&#34;&gt;DuckDuckGo&lt;/a&gt; instead of Google.&lt;/li&gt;
&lt;li&gt;The elaborate social sharing functionality has been removed, because my target audience is technical people who would rather just copy a URL.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;inspiration&#34;&gt;Inspiration&lt;/h2&gt;
&lt;p&gt;Tom MacWright&amp;rsquo;s &lt;a href=&#34;https://macwright.org/2016/05/03/the-featherweight-website.html&#34;&gt;explanation&lt;/a&gt; of how he optimized his personal website was a major inspiration, and Carlos Bueno&amp;rsquo;s &lt;a href=&#34;http://carlos.bueno.org/optimization/&#34;&gt;Mature Optimization Handbook&lt;/a&gt; helped curb the worst of my tendency toward premature+unnecessary optimization. My former coworker &lt;a href=&#34;https://colin.is/&#34;&gt;Colin Bate&lt;/a&gt; helped with ideas – check out his &lt;a href=&#34;https://develop.cheap/&#34;&gt;Develop.cheap&lt;/a&gt; newsletter for great recommendations.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>UI components: Angular vs React</title>
      <link>https://www.reillywood.com/blog/angular-vs-react/</link>
      <pubDate>Mon, 09 Sep 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.reillywood.com/blog/angular-vs-react/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been building web UIs in Angular and React for the last few years, and I&amp;rsquo;ve started to greatly prefer React. Extracting and using UI components is just easier and, for lack of a better word, more &lt;em&gt;JavaScripty&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;extracting-components-in-react&#34;&gt;Extracting components in React&lt;/h2&gt;
&lt;p&gt;Say I notice that I&amp;rsquo;m creating multiple &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; elements with the same class and icon:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-react&#34; data-lang=&#34;react&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;alert-tag&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;fa alert&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;foo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;alert-tag&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;fa alert&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;alert-tag&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;fa alert&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;baz&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s trivial to refactor this repeated element into its own &lt;code&gt;&amp;lt;Alert&amp;gt;&lt;/code&gt; component function within the same file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-react&#34; data-lang=&#34;react&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Alert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;foo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Alert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Alert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Alert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Alert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;baz&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Alert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;div&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Alert&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;children&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;alert-tag&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;fa alert&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;children&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Super easy, uses JavaScript&amp;rsquo;s native language constructs, and I was able to do it all within the same file (but I can easily move &lt;code&gt;Alert()&lt;/code&gt; elsewhere for wider re-use if needed). It&amp;rsquo;s just like extracting a function in a &amp;ldquo;regular&amp;rdquo; programming language, it&amp;rsquo;s something you do without even thinking about it.&lt;/p&gt;
&lt;h2 id=&#34;extracting-components-in-angular&#34;&gt;Extracting Components in Angular&lt;/h2&gt;
&lt;p&gt;First, we have to create the new component. The most common way to do this in Angular is with &lt;a href=&#34;https://cli.angular.io/&#34;&gt;Angular CLI&lt;/a&gt; and its various &lt;code&gt;ng&lt;/code&gt; commands. By default, the command to create a new component (&lt;code&gt;ng generate component Alert&lt;/code&gt;) literally touches 5 files:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;CREATE src/app/alert/alert.component.css (0 bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;CREATE src/app/alert/alert.component.html (23 bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;CREATE src/app/alert/alert.component.spec.ts (614 bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;CREATE src/app/alert/alert.component.ts (261 bytes)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;UPDATE src/app/main.module.ts (720 bytes)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yikes – that&amp;rsquo;s definitely overkill for our tiny helper component! Thankfully we can get that down to 2 with the following parameters instead: &lt;code&gt;ng generate component Alert --inlineStyle=true --inlineTemplate=true --skipTests=true&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once we&amp;rsquo;ve done that, the component file still has an awful lot of boilerplate:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Component&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;OnInit&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;@angular/core&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Component&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;selector&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;alert&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&amp;lt;span class=&amp;#34;alert-tag&amp;#34;&amp;gt;&amp;lt;i class=&amp;#34;fa alert&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;ng-content&amp;gt;&amp;lt;/ng-content&amp;gt;&amp;lt;/span&amp;gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;styles&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AlertComponent&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;implements&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;OnInit&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;constructor&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;ngOnInit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That can be trimmed down a bit:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Component&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;@angular/core&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Component&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;selector&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;alert&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&amp;lt;span class=&amp;#34;alert-tag&amp;#34;&amp;gt;&amp;lt;i class=&amp;#34;fa alert&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;ng-content&amp;gt;&amp;lt;/ng-content&amp;gt;&amp;lt;/span&amp;gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AlertComponent&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; but we also need to reference the new component in our module file, even if it&amp;rsquo;s a tiny helper within another component:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;MainComponent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AlertComponent&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;./main.component&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;@NgModule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;declarations&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;MainComponent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;AlertComponent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These steps aren&amp;rsquo;t impossible. They&amp;rsquo;re negligible for large components, to be honest. Where they become a problem is &lt;em&gt;small&lt;/em&gt; components, especially those which are only used within a limited context. The fixed cost of extracting a component is simply much higher in Angular than in React, which limits our ability to write small components in a &lt;a href=&#34;https://www.goodreads.com/book/show/3735293-clean-code&#34;&gt;Clean Code&lt;/a&gt; style.&lt;/p&gt;
&lt;h2 id=&#34;using-extracted-components&#34;&gt;Using Extracted Components&lt;/h2&gt;
&lt;p&gt;To make matters worse, Angular components often need to be used with Angular-specific syntax instead of standard JavaScript. If you want to generate an &lt;code&gt;&amp;lt;Alert&amp;gt;&lt;/code&gt; for every variable in a collection you&amp;rsquo;d use &lt;a href=&#34;https://angular.io/api/common/NgForOf&#34;&gt;&lt;code&gt;ngFor&lt;/code&gt;&lt;/a&gt; instead of a standard JavaScript function like &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map&#34;&gt;&lt;code&gt;map()&lt;/code&gt;&lt;/a&gt;. You might also use &lt;code&gt;ngIf&lt;/code&gt;, &lt;code&gt;ngSwitch&lt;/code&gt;, or Angular&amp;rsquo;s &lt;a href=&#34;https://angular.io/guide/template-syntax#template-expressions&#34;&gt;not-exactly-JavaScript template expressions&lt;/a&gt;. It&amp;rsquo;s not good that Angular is reinventing fundamental language functionality.&lt;/p&gt;
&lt;h2 id=&#34;closing-thoughts&#34;&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Working with components in Angular just involves so much &lt;em&gt;ceremony&lt;/em&gt; compared to React. I&amp;rsquo;m always aware that I&amp;rsquo;m working within the constraints of a framework, whereas React feels like I&amp;rsquo;m working in regular JavaScript (after the brief introduction to JSX, anyway). I know which I prefer.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Relational Database History</title>
      <link>https://www.reillywood.com/blog/db-papers/</link>
      <pubDate>Mon, 02 Sep 2019 16:43:23 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/db-papers/</guid>
      <description>&lt;p&gt;I recently read 2 papers that I&amp;rsquo;d highly recommend if you ever wonder about the underpinnings of modern relational databases:&lt;/p&gt;
&lt;h1 id=&#34;what-goes-around-comes-around-stonebraker-and-hellerstein-2005&#34;&gt;&lt;a href=&#34;https://15721.courses.cs.cmu.edu/spring2016/papers/whatgoesaround-stonebraker.pdf&#34;&gt;What Goes Around Comes Around (Stonebraker and Hellerstein, 2005)&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This is an opinionated history of database architectures from roughly 1970 to 2005, and it&amp;rsquo;s got some serious credentials behind it – Michael Stonebraker was the driving force behind Postgres and many other things.
It&amp;rsquo;s an easy read, and it&amp;rsquo;s a good explanation of how relational DBs and SQL came to (mostly) rule the world. It was written amidst a lot of hype over XML databases, and it (rightly, in retrospect) critiques XML DBs and suggests that they won&amp;rsquo;t see much commercial adoption.&lt;/p&gt;
&lt;p&gt;One interesting bit: Stonebraker and Hellerstein do not like SQL, and they claim that it became dominant largely because of IBM&amp;rsquo;s dominance in the marketplace during the 1980&amp;rsquo;s. Which leads us to the next item&amp;hellip;&lt;/p&gt;
&lt;h1 id=&#34;a-critique-of-the-sql-database-language-chris-date-1983&#34;&gt;&lt;a href=&#34;https://www2.cs.duke.edu/courses/spring03/cps216/papers/date-1983.pdf&#34;&gt;A Critique of the SQL Database Language (Chris Date, 1983)&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This was cited approvingly in the 1st paper as a &amp;ldquo;scathing critique of the semantics of SQL&amp;rdquo;, which caught my eye - virtually every database supports SQL, how bad can it be?&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t summarize the entire thing, it&amp;rsquo;s a laundry list of complaints, but this resonated with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Notice that it is just table-names that appear in the FROM clause. Completeness suggests that it should be table-expressions (as Gray puts it, &amp;ldquo;anything in computer science that is not recursive is no good&amp;rdquo;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;a simple table-reference (i.e. a table-name) ought to be just a special case of a general table-expression&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To give a (trivial) example, say we want to union 2 tables and extract a column. We&amp;rsquo;d write that like this in SQL:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;EMPLOYEE_ID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;EMPLOYEES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;UNION&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;EMPLOYEE_ID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CONTRACTORS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But if table names were just special cases of table expressions, we could write it like this instead:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;EMPLOYEE_ID&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;EMPLOYEES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;UNION&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CONTRACTORS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SQL gets the job done, and it could be a lot worse - but it is a little sad that many of the issues academics have known about for 35+ years still exist today. It&amp;rsquo;s a reminder of how entrenched something that&amp;rsquo;s only &amp;ldquo;good enough&amp;rdquo; can become.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Dark Mode on the Web</title>
      <link>https://www.reillywood.com/blog/dark-mode/</link>
      <pubDate>Tue, 20 Aug 2019 16:08:09 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/dark-mode/</guid>
      <description>&lt;p&gt;I recently rewrote most of this website (more on that soon!) and it&amp;rsquo;s now much easier to work on. Last week, I shipped a feature that I really like: dark mode using &lt;a href=&#34;https://web.dev/prefers-color-scheme/&#34;&gt;the new &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query&lt;/a&gt;. If you have dark mode enabled in your OS and a reasonably new browser, reillywood.com now shows you a dark UI using the &lt;a href=&#34;https://ethanschoonover.com/solarized/&#34;&gt;Solarized&lt;/a&gt; colour scheme:&lt;/p&gt;

&lt;div class=&#39;carousel border border-gray-border&#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/dark_hu_ef3e7f6692a17f3.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Dark mode
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/light_hu_cf5038d4b2b3dfc.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Light mode
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;This is really easy to enable, and I hope more sites start using it soon. Under the hood, it&amp;rsquo;s just a straightforward media query in CSS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;media&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;prefers-color-scheme&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;dark&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;Dark&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;style&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;rules&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;go&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I borrowed &lt;a href=&#34;https://markdotto.com/2018/11/05/css-dark-mode/&#34;&gt;a trick from Mark Otto&lt;/a&gt; where I dim images slightly in dark mode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;media&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;prefers-color-scheme&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nt&#34;&gt;dark&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;img&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;opacity&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;0.75&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;transition&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;opacity&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;0.5&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;s&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;ease-in-out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;img&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;hover&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;opacity&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;dark-mode-in-tailwind&#34;&gt;Dark mode in Tailwind&lt;/h1&gt;
&lt;p&gt;Integrating it with Tailwind CSS was pleasantly simple. I added a custom &lt;code&gt;dark&lt;/code&gt; media query in my Tailwind config like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;extend&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;screens&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s1&#34;&gt;&amp;#39;dark&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;raw&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;(prefers-color-scheme: dark)&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And once that&amp;rsquo;s in place, I use it with a &lt;code&gt;dark:&lt;/code&gt; prefix on my Tailwind classes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;// This sets a white background normally, and a black background in dark mode
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;div&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;bg-white dark:bg-black&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;browser-support&#34;&gt;Browser support&lt;/h1&gt;
&lt;p&gt;For detailed information on browser support for &lt;code&gt;prefers-color-scheme&lt;/code&gt;, &lt;a href=&#34;https://caniuse.com/#search=prefers-color-scheme&#34;&gt;caniuse.com is the place to go&lt;/a&gt;. It&amp;rsquo;s supported in the latest versions of Safari, Firefox, and Chrome, but it is on the bleeding-edge – Firefox and Chrome only shipped support for it last month, and dark mode itself is only supported in recent versions of macOS+Windows.&lt;/p&gt;
&lt;p&gt;Still, it&amp;rsquo;s a nice &lt;a href=&#34;https://en.wikipedia.org/wiki/Progressive_enhancement&#34;&gt;progressive enhancement&lt;/a&gt; – any browser that doesn&amp;rsquo;t support it will just render the &amp;ldquo;regular&amp;rdquo; website styling. You can start using &lt;code&gt;prefers-color-scheme&lt;/code&gt; &lt;em&gt;now&lt;/em&gt;, even if most of your audience is stuck on older browsers.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>I&#39;m taking a sabbatical</title>
      <link>https://www.reillywood.com/blog/sabbatical/</link>
      <pubDate>Wed, 14 Aug 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.reillywood.com/blog/sabbatical/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m taking a self-funded sabbatical after 8 years of working full-time. It feels like the right thing to do at this stage of my career.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had an unusually stable career for a software developer. When I graduated from university in 2011 I interviewed at a few places, including &lt;a href=&#34;https://www.orbis.com&#34;&gt;a mid-sized investment firm&lt;/a&gt;. I didn&amp;rsquo;t know anything about finance and &lt;a href=&#34;https://web.archive.org/web/20110926195616/http://www.orbis.com/&#34;&gt;the&amp;hellip; &lt;em&gt;rustic&lt;/em&gt; state of their website&lt;/a&gt; was a little concerning, but the people seemed great. So I took the job, and told myself I&amp;rsquo;d stick around for 2 or 3 years.&lt;/p&gt;
&lt;p&gt;8 years later I was still at the same company. Orbis offers a lot of different opportunities; I wrangled big financial data systems, ran a small team, got my hands dirty building a modern web stack, and worked in London and Cape Town. It was a blast, and I&amp;rsquo;d highly recommend Orbis as an employer.&lt;/p&gt;
&lt;p&gt;Still, after 8 years, it&amp;rsquo;s time to try something new. I miss the open-ended learning that&amp;rsquo;s so common in school, and I want to explore my technical interests with no regard for immediate relevance to my day job. &lt;a href=&#34;https://www.gchicco.com/2019/04/23/the-serendipity-engine/&#34;&gt;Gianfranco Chicco&amp;rsquo;s description of a &amp;ldquo;serendipity break&amp;rdquo;&lt;/a&gt; really resonated with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the note I sent out to my friends and network I mentioned that I’d be undertaking a Serendipity Break, which wasn’t a nice way to say that I wasn’t going to work for a few months but that I wanted to actively explore different possible paths.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;In the book The Craftsman, sociologist Richard Sennett describes how “skill builds by moving irregularly, and sometimes by taking detours”, which is akin to keeping the Serendipity Engine in perpetual motion to encourage the strengthening of current skills and allowing the development of new ones.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Leaving a great job at a great company was &lt;del&gt;a little&lt;/del&gt; scary, but I think it&amp;rsquo;s necessary for my long-term growth. Reading about &lt;a href=&#34;https://www.joelonsoftware.com/2000/03/18/more-on-sabbaticals/&#34;&gt;Joel Spolsky&amp;rsquo;s sabbaticals&lt;/a&gt; helped a lot; it&amp;rsquo;s reassuring to see successful developers following similar paths.&lt;/p&gt;
&lt;p&gt;My last day at Orbis was July 27th, and since then I&amp;rsquo;ve been trying all kinds of things. I&amp;rsquo;ve been &lt;a href=&#34;https://www.youtube.com/channel/UCHnBsf2rH-K7pn09rb3qvkA&#34;&gt;diving into database internals&lt;/a&gt;, rewriting this website, and even &lt;a href=&#34;https://www.goodreads.com/book/show/548914.The_Little_Schemer&#34;&gt;learning Lisp/Scheme&lt;/a&gt;. I&amp;rsquo;m not exactly sure where my interests will take me next, but I&amp;rsquo;m looking forward to finding out.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Quick T-SQL Regression Testing</title>
      <link>https://www.reillywood.com/blog/regression-sql/</link>
      <pubDate>Mon, 10 Jun 2019 18:40:45 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/regression-sql/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s something I&amp;rsquo;ve found useful in SQL Server, but it should apply in any DBMS with checksum functions. Next time you&amp;rsquo;re refactoring some database objects and you want to do some regression testing, give the &lt;code&gt;CHECKSUM&lt;/code&gt; (&lt;a href=&#34;https://docs.microsoft.com/en-us/sql/t-sql/functions/checksum-transact-sql?view=sql-server-2017&#34;&gt;MSDN&lt;/a&gt;) and &lt;code&gt;CHECKSUM_AGG&lt;/code&gt;  (&lt;a href=&#34;https://docs.microsoft.com/en-us/sql/t-sql/functions/checksum-agg-transact-sql&#34;&gt;MSDN&lt;/a&gt;) functions a try.&lt;/p&gt;
&lt;h1 id=&#34;checksum-and-checksum_agg&#34;&gt;CHECKSUM and CHECKSUM_AGG&lt;/h1&gt;
&lt;p&gt;They behave pretty much as you would expect; &lt;code&gt;CHECKSUM&lt;/code&gt; returns 1 checksum given 1 row, and &lt;code&gt;CHECKSUM_AGG&lt;/code&gt; is an aggregate function that returns 1 checksum given multiple checksum rows. Between the two of them, you can get a checksum for any arbitrary collection of data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;-- Returns 2 rows with 2 columns
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TempTable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- Returns 2 rows with 1 checksum column
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CHECKSUM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TempTable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;-- Returns 1 row with 1 checksum column
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CHECKSUM_AGG&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CHECKSUM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;VALUES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TempTable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Field2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;regression-testing&#34;&gt;Regression testing&lt;/h1&gt;
&lt;p&gt;These help with a common problem: when you&amp;rsquo;re refactoring a database object, how can you be sure that you haven&amp;rsquo;t changed the existing functionality? Sometimes it&amp;rsquo;s enough to spot check a few inputs, but often the logic is complex enough that this does not provide a high level of confidence. With &lt;code&gt;CHECKSUM&lt;/code&gt; and &lt;code&gt;CHECKSUM_AGG&lt;/code&gt;, you can quickly check large numbers of test cases.&lt;/p&gt;
&lt;p&gt;For example, say we have a TVF called MyDateTVF that takes in a date parameter and returns columns Column1 and Column2. I want to refactor this function and then test that the functionality is unchanged for every date in 2018. If we have a table called DateList which contains every date (more useful than you might expect in a DB with a lot of temporal aspects), I can simply run the following before and after my change:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CHECKSUM_AGG&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;CHECKSUM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;MDT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Column1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;MDT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Column2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DateList&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;CROSS&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;APPLY&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Column1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Column2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;dbo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;MyDateTVF&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;MDT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;WHERE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;DL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;BETWEEN&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2018-01-01&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;AND&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2018-12-31&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If that query returns the same checksum before and after my change, we can rule out any regressions for those inputs. Nice and easy!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Tailwind CSS</title>
      <link>https://www.reillywood.com/blog/tailwind/</link>
      <pubDate>Mon, 18 Feb 2019 07:50:59 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/tailwind/</guid>
      <description>&lt;p&gt;I recently overhauled the UI for &lt;a href=&#34;https://github.com/rgwood/AHVLetterBuilder&#34;&gt;my letter builder web app&lt;/a&gt;, switching from Bootstrap to &lt;a href=&#34;https://tailwindcss.com/docs/what-is-tailwind/&#34;&gt;a neat framework named Tailwind CSS&lt;/a&gt;. It&amp;rsquo;s been great so far.&lt;/p&gt;
&lt;p&gt;I dabble in web development, but it&amp;rsquo;s not &amp;ldquo;my thing&amp;rdquo;. Most of my time is spent on back-end systems and the occasional native UI. When I&amp;rsquo;m building a web UI, I usually spend a lot of time on &lt;a href=&#34;https://developer.mozilla.org/en-US/&#34;&gt;MDN&lt;/a&gt; or &lt;a href=&#34;https://www.w3schools.com&#34;&gt;W3Schools&lt;/a&gt; looking up syntax details.&lt;/p&gt;
&lt;h1 id=&#34;tailwind&#34;&gt;Tailwind&lt;/h1&gt;
&lt;p&gt;Enter Tailwind. It&amp;rsquo;s a utility-first CSS framework, which in practice means they give you a whole bunch of &amp;ldquo;utility&amp;rdquo; classes which are effectively aliases for common styles. Here&amp;rsquo;s an example from when I was styling a button:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;button class=&amp;quot;bg-blue-dark text-sm text-white rounded-sm py-2 px-4 my-2&amp;quot;/&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;With these classes, I&amp;rsquo;m saying that I want a dark blue background, small white text, 2 units of padding on the top and bottom (&lt;code&gt;py-2&lt;/code&gt;), 4 units of padding on the left and right (&lt;code&gt;px-4&lt;/code&gt;), 2 units of margin on the top and bottom (&lt;code&gt;my-2&lt;/code&gt;), and rounded corners. Whew!&lt;/p&gt;
&lt;p&gt;Right away, we can notice a few advantages relative to the normal way of doing things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;This is &lt;em&gt;fast&lt;/em&gt; to iterate on. I can add another utility class in-line without digging through my CSS file and reasoning about selectors.&lt;/li&gt;
&lt;li&gt;The class names are concise yet informative. &lt;code&gt;py-2&lt;/code&gt; is clearly operating on the Y axis (top and bottom), which is much easier to remember than a &lt;code&gt;padding&lt;/code&gt; style with multiple unlabeled values.&lt;/li&gt;
&lt;li&gt;I have fewer choices to make. I didn&amp;rsquo;t need to worry about whether to use &lt;code&gt;#0033CC&lt;/code&gt; or &lt;code&gt;#0000FF&lt;/code&gt; for a dark blue, I just asked for dark blue. Same thing with padding+margins, I just chose from a small set of integers instead of wondering whether to do 0.5rem or 0.6rem.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;isnt-putting-all-your-styles-inline-just-asking-for-trouble&#34;&gt;Isn&amp;rsquo;t putting all your styles inline just asking for trouble?&lt;/h1&gt;
&lt;p&gt;Yes, this can get out of hand if you put too much stying inline – but Tailwind utility classes can be extracted into components as soon as you need some additional abstraction. &lt;a href=&#34;https://tailwindcss.com/docs/extracting-components&#34;&gt;The authors even recommend that&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tailwind encourages a &amp;ldquo;utility-first&amp;rdquo; workflow, where new designs are initially implemented using only utility classes to avoid premature abstraction.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;While we strongly believe you can get a lot further with just utilities than you might initially expect, &lt;strong&gt;we don&amp;rsquo;t believe that a dogmatic utility-only approach is the best way to write CSS.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&#34;curation-vs-organic-growth&#34;&gt;Curation VS organic growth&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m being a little hard on the &amp;ldquo;usual&amp;rdquo; way of writing styles in CSS. The web grew more-or-less organically, so it&amp;rsquo;s not fair to expect an overall &lt;em&gt;design&lt;/em&gt; from the mess of styles available to modern browsers. Thankfully, that&amp;rsquo;s where projects like Tailwind come in.&lt;/p&gt;
&lt;p&gt;It’s remarkable how much nicer it is to build web UIs when working with a thoughtfully curated and documented subset of styles. Sometimes a big step forward doesn&amp;rsquo;t need to come from a clever technical breakthrough – simply organizing existing information+symbols in a better way can reap massive benefits.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Pulumi for Cloud Infrastructure Management</title>
      <link>https://www.reillywood.com/blog/pulumi-basics/</link>
      <pubDate>Tue, 07 Aug 2018 16:03:34 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/pulumi-basics/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve recently been playing around with some cloud development tooling by a startup named &lt;a href=&#34;https://www.pulumi.com&#34;&gt;Pulumi&lt;/a&gt;, and I thought I&amp;rsquo;d write up my first impressions.&lt;/p&gt;
&lt;p&gt;Pulumi can be summed up as &amp;ldquo;Infrastructure as Code – but really, we mean it this time.&amp;rdquo; Instead of mucking around with YAML files and proprietary syntax, you define the infrastructure you need &lt;em&gt;in actual code&lt;/em&gt; (JavaScript or Python, with more languages coming later). Is your production environment slightly different from your test environment? No problem, that&amp;rsquo;s just an &lt;code&gt;if&lt;/code&gt; statement (or a more complex abstraction) away.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s still very early days (as of this writing, Pulumi is on version 0.14.3), but the general approach shows a ton of promise.&lt;/p&gt;
&lt;h2 id=&#34;pulumi-basics&#34;&gt;Pulumi basics&lt;/h2&gt;
&lt;p&gt;I first heard about Pulumi from &lt;a href=&#34;https://conferences.oreilly.com/velocity/vl-ca/public/schedule/detail/67950&#34;&gt;this &amp;lsquo;Tooling in the age of serverless computing&amp;rsquo; talk by Donna Malayeri&lt;/a&gt; (of Pulumi, formerly of the Azure Functions team at Microsoft). It&amp;rsquo;s a great overview of serverless tooling options, and I learned a lot from the slides. Best of all, &lt;a href=&#34;https://github.com/lindydonna/velocity-examples&#34;&gt;there are code samples for each tooling provider available on GitHub&lt;/a&gt;. Let&amp;rsquo;s take a look at &lt;a href=&#34;https://github.com/lindydonna/velocity-examples/tree/master/pulumi&#34;&gt;the Pulumi ones&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can work at a few different levels of abstraction with Pulumi. You can write a serverless function with &lt;a href=&#34;https://github.com/lindydonna/velocity-examples/tree/master/pulumi/pulumi-raw-serverless&#34;&gt;complete control over every aspect of AWS Lambda and API Gateway&lt;/a&gt;. If that&amp;rsquo;s too nitty-gritty, you can &lt;a href=&#34;https://github.com/lindydonna/velocity-examples/blob/master/pulumi/pulumi-serverless&#34;&gt;use &lt;code&gt;@pulumi/aws-serverless&lt;/code&gt; to simplify configuration of API Gateway+Swagger&lt;/a&gt;. If you like, &lt;a href=&#34;https://github.com/lindydonna/velocity-examples/blob/master/pulumi/pulumi-inline-lambda/index.js&#34;&gt;you can even move your Lambda function&amp;rsquo;s code into the Pulumi script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then, at the highest level of abstraction, you can &lt;a href=&#34;https://github.com/lindydonna/velocity-examples/tree/master/pulumi/pulumi-cloud&#34;&gt;use &lt;code&gt;@pulumi/cloud&lt;/code&gt; to define cloud components that aren&amp;rsquo;t tied to any particular cloud&lt;/a&gt;. This isn&amp;rsquo;t fully built out yet – it&amp;rsquo;s only been implemented for AWS at this time – but it&amp;rsquo;s by far the most exciting to me. Seriously, take a look at &lt;a href=&#34;https://github.com/lindydonna/velocity-examples/blob/master/pulumi/pulumi-cloud/index.js&#34;&gt;the example where Donna defines a NoSQL DB table and a serverless function that uses it in just 24 lines of code&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;my-motivation-for-using-pulumi&#34;&gt;My motivation for using Pulumi&lt;/h2&gt;
&lt;p&gt;Most of my hobby coding in the last year and a bit has been done using AWS Lambda and Azure Functions. I&amp;rsquo;m using Serverless Framework for &lt;a href=&#34;https://github.com/rlisagor/ahv-council-thing&#34;&gt;the most complex one&lt;/a&gt;, and it&amp;rsquo;s been OK. It provides a layer of abstraction around Lambda and other AWS resources, which is nice. What&amp;rsquo;s not so nice is that your infrastructure definition is moved to &lt;a href=&#34;https://github.com/rlisagor/ahv-council-thing/blob/master/serverless.yml&#34;&gt;an ever-growing YAML file&lt;/a&gt; with unpleasant syntax once you go beyond very basic scenarios.&lt;/p&gt;
&lt;p&gt;This is how to define an S3 bucket &lt;em&gt;conditionally&lt;/em&gt; in Serverless Framework, i.e. if and only if an environment variable named &lt;code&gt;S3_LOGGING_BUCKET&lt;/code&gt; is not empty:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Conditions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;CreateS3Bucket&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Fn::Not&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;Fn::Equals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;${file(env.${opt:stage}.yml):S3_LOGGING_BUCKET}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Resources&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;S3LoggingBucket&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;AWS::S3::Bucket&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Condition&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;CreateS3Bucket&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;Properties&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is&amp;hellip; unpleasant. It&amp;rsquo;s verbose, it&amp;rsquo;s not especially well documented, and I probably spent an hour of my life figuring out how to accomplish this conceptually trivial thing. When I first read about Pulumi, a lightbulb went off. &amp;ldquo;You mean I could have written that logic with an &lt;code&gt;if&lt;/code&gt; statement instead? Sign me up!&amp;rdquo;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;S3_LOGGING_BUCKET&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// provision S3 bucket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;my-experience-with-pulumi&#34;&gt;My experience with Pulumi&lt;/h2&gt;
&lt;p&gt;Over a few weekends, I attempted to migrate &lt;a href=&#34;https://github.com/rlisagor/ahv-council-thing&#34;&gt;my Serverless-managed Node function&lt;/a&gt; to Pulumi. I&amp;rsquo;ve run into a few barriers and so I haven&amp;rsquo;t been able to successfully finish that yet, but it is still very early days for Pulumi. Pulumi is progressing at an impressive rate and I&amp;rsquo;m fairly confident that I&amp;rsquo;ll be able to accomplish this in the not-so-far future.&lt;/p&gt;
&lt;p&gt;I was hoping to use the highest level of abstraction, and so I started using Pulumi&amp;rsquo;s abstract API class (formerly HttpEndpoint). It all went well &lt;a href=&#34;https://github.com/pulumi/pulumi-aws/issues/249&#34;&gt;until I started trying to use other Node packages in my function&lt;/a&gt;. Turns out this is a scenario where 1) you need to do something a little nonstandard to import modules, 2) the recommended approach wasn&amp;rsquo;t documented. Pulumi team members were super responsive and helpful in explaining how to fix this.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m now able to import+use Node packages in my function, but then &lt;a href=&#34;https://github.com/pulumi/pulumi-cloud/issues/521&#34;&gt;I encountered an issue with POST APIs&lt;/a&gt;. The short version is that standalone POST calls work, but &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS&#34;&gt;modern browsers &amp;lsquo;preflight&amp;rsquo; POST calls with an OPTIONS call&lt;/a&gt; and OPTIONS calls fail even when I try to handle them explicitly. I&amp;rsquo;m guessing this is a Pulumi bug/missing feature that will be implemented eventually.&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m considering ditching the higher-level &lt;code&gt;@pulumi/cloud&lt;/code&gt; API abstraction and just using Pulumi to upload &lt;a href=&#34;https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html&#34;&gt;a Lambda deployment package&lt;/a&gt;. This will require some further investigation – I need to figure out the best way to put together a transpiled-from-TypeScript deployment package that only contains my production npm dependencies. I don&amp;rsquo;t think that will be especially difficult, but it needs to wait until I have another spare weekend afternoon. Perhaps the POST issue will get sorted out before that. :)&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>The State of Serverless</title>
      <link>https://www.reillywood.com/blog/serverless/</link>
      <pubDate>Tue, 22 May 2018 22:00:43 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/serverless/</guid>
      <description>&lt;p&gt;In my spare time I have been mucking around with 2 big Functions-as-a-Service (FaaS) offerings, &lt;a href=&#34;https://aws.amazon.com/lambda/&#34;&gt;AWS Lambda&lt;/a&gt; and &lt;a href=&#34;https://azure.microsoft.com/en-us/services/functions/&#34;&gt;Azure Functions&lt;/a&gt;. I&amp;rsquo;ve been meaning to write up a &amp;ldquo;The state of serverless development&amp;rdquo; post, but today &lt;a href=&#34;https://twitter.com/mikebroberts&#34;&gt;Mike Roberts&lt;/a&gt; updated &lt;a href=&#34;https://martinfowler.com/articles/serverless.html&#34;&gt;his overview of the market for serverless computing&lt;/a&gt; and it&amp;rsquo;s far more thorough than I could ever be.&lt;/p&gt;
&lt;p&gt;The whole thing is well worth a read if you&amp;rsquo;re interested in the area, but these parts (emphasis mine) really resonated with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Serverless is not the correct approach for every problem, so be wary of anyone who says it will replace all of your existing architectures. Be careful if you take the plunge into Serverless systems now, especially in the FaaS realm. &lt;strong&gt;While there are riches — of scaling and saved deployment effort — to be plundered, there also be dragons — of debugging and monitoring — lurking right around the next corner.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Serverless services, and our understanding of how to use them, are today (May 2018) in the “slightly awkward teenage years” of maturity.&lt;/strong&gt; There will be many advances in the field over the coming years, and it will be fascinating to see how Serverless fits into our architectural toolkit.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is exactly right in my experience.&lt;/p&gt;
&lt;p&gt;Lambda and Azure Functions let you write+deploy code quickly without an infrastructure team and an execution platform. However, the developer experience is often a big step backwards – Lambda doesn&amp;rsquo;t offer any remote debugging support, and just running+debugging functions locally is a big pain. Azure is further ahead in debugging, but things are still more complicated and less reliable than when debugging traditional apps. Integration testing is difficult on both platforms.&lt;/p&gt;
&lt;p&gt;The benefits of serverless platforms outweigh the costs for my small hobby projects, but they almost certainly would not for larger-scale development in in most organizations.&lt;/p&gt;
&lt;p&gt;The good news is that this area is maturing very rapidly; the tooling for .NET Azure Functions is much better today than it was 1 year ago. Serverless might not be right for enterprise development today, but just a few years from now a lot of these rough edges will be a distant memory.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>At First, Everyone Hated Vancouver&#39;s West End</title>
      <link>https://www.reillywood.com/blog/west-end/</link>
      <pubDate>Thu, 26 Apr 2018 21:09:28 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/west-end/</guid>
      <description>&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/west-end/aerial_hu_46471e9197a39056.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Vancouver&amp;rsquo;s West End, 1966. Source: &lt;a href=&#34;https://searcharchives.vancouver.ca/aerial-view-of-west-end-from-english-bay-looking-east&#34;&gt;Vancouver Archives&lt;/a&gt;
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;A few years ago, &lt;a href=&#34;https://pricetags.ca/2015/11/05/the-historical-irony-of-a-great-place-the-west-end/&#34;&gt;this short piece by former Vancouver councillor Gordon Price&lt;/a&gt; made a big impression on me.&lt;/p&gt;
&lt;p&gt;The Canadian Institute of Planners had decided to celebrate Vancouver&amp;rsquo;s &lt;a href=&#34;https://en.wikipedia.org/wiki/West_End,_Vancouver&#34;&gt;West End&lt;/a&gt; neighbourhood by giving it their 2015 &amp;ldquo;Great Neighbourhood&amp;rdquo; award. Price noted that this is ironic given that during its most recent development boom, the modern West End was widely regarded as a terrible planning decision:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;this neighbourhood of old converted single-family homes was largely bulldozed to create the Great Neighbourhood of today. The West End, during the boom era of highrise construction in the 1960s, was considered a concrete jungle – what most Vancouverites didn’t want anywhere near them: everyone’s best bad example of urban redevelopment.
&lt;br&gt; &lt;br&gt;
Impossible to do that today. Imagine taking a square mile anywhere south of 16th Avenue and rezoning it for the kind of development that characterizes the West End.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This really surprised me as someone who moved to Vancouver in the 2000s. Nearly everyone loves the West End now! It&amp;rsquo;s got a lot of relatively affordable rental apartments, it&amp;rsquo;s close to all kinds of natural amenities, and it&amp;rsquo;s a short walk from downtown.&lt;/p&gt;
&lt;p&gt;Fast-forward to late 2017, and I&amp;rsquo;m digging through old newspapers at the &lt;a href=&#34;http://www.vpl.ca/&#34;&gt;VPL&lt;/a&gt; for &lt;a href=&#34;http://www.abundanthousingvancouver.com&#34;&gt;Abundant Housing Vancouver&lt;/a&gt;. When I get to the 1960s, I find out that Price is right: many people &lt;em&gt;really hated&lt;/em&gt; the modern West End when it was being built. Here are just some of the articles I found.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quality in the West End (June 24, 1965)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/QualityInTheWestEnd.jpg&#34;&gt;With the greedy onrush of speculators crowding boxy apartment block next to dumpy apartment block – each new one shutting out the coveted view from another – the city has reason to be ashamed of its lack of supervision in a site so fortunate.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick freeze for West End skyline (Nov. 2, 1972)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/QuickFreezeForWestEnd.jpg&#34;&gt;Most of those council members who are not seeking re-election have been in office throughout the building-boom years, in which the West End was allowed to reach a density unparalleled in this country, perhaps anywhere this side of Calcutta. At last the first steps have been taken to control the planning nightmare which resulted.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;fairview-slopes-rezoning&#34;&gt;Fairview Slopes rezoning&lt;/h2&gt;
&lt;p&gt;Fairview Slopes underwent a major rezoning in the early 1970s. During the planning process, negative comparisons to the West End were very common. Common concerns: too dense, not suitable for families, and too expensive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Another West End Not Wanted (Nov 1970)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/AnotherWestEndNotWanted.jpg&#34;&gt;Concern that redevelopment on the south shore of False Creek could create another West End was expressed by several members of city council at a meeting Tuesday. Councillors called the West End “too densely populated” without enough “accommodation for families.”&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fairview for Families  (Nov 25, 1970)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/FairviewForFamilies.jpg&#34;&gt;City council doesn’t want redevelopment of the south shore of False Creek to result in another West End.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Untitled column (Vancouver Sun, Feb 13 1971)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Alderman Hugh Bird of the NPA: “We don’t want another West End, where it’s just for bachelors or boys and girls getting together to have fun.” (&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/Fairview1.jpg&#34;&gt;1&lt;/a&gt;, &lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/Fairview2.jpg&#34;&gt;2&lt;/a&gt;, &lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/Fairview3.jpg&#34;&gt;3&lt;/a&gt;, &lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/Fairview4.jpg&#34;&gt;4&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Students face ‘push-out’ (Vancouver Sun, May 27 1971)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/StudentsFacePushOut.jpg&#34;&gt;AMS president Steve Garrod said the area would get the bad social environment of the West End if the rezoning goes into effect. “It will be a place of fairly expensive high-rises where students won’t be able to live,” he said.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;kitsilano&#34;&gt;Kitsilano&lt;/h2&gt;
&lt;p&gt;You know &lt;a href=&#34;https://www.google.com/maps/@49.2680482,-123.1588576,190a,35y,357.21h,49.37t/data=!3m1!1e3&#34;&gt;the small handful of West End-like concrete apartment buildings in Kitsilano&lt;/a&gt;? Shortly after they went up, Kitsilano residents mobilized to stop others from being built.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Row sparks new look at zoning (Vancouver Sun, Feb 16 1972)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/RowSparksNewLook.jpg&#34;&gt;[Alderman Ed Sweeney] said it was important to move immediately before Kitsilano becomes “another West End.”&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Finish Lines (Vancouver Sun, Feb 16 1972)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/FinishLines.jpg&#34;&gt;&amp;hellip;a nine-storey apartment in the 2200 block Cornwall, which they feared would be the start of a West-End-type wall of highrises across the waterfront&amp;hellip;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;High-rise plan protested (Vancouver Sun, Feb 17 1972)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/HighRisePlanProtested.jpg&#34;&gt;Vancouver Tenants Council secretary Bruce Yorke said assurances by mayor Tom Campbell “cannot be considered a sufficient guarantee that the Kitsilano area won’t in the future become like the West End.”&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Plan to limit building height in Kitsilano to be drafted (1972)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/PlanToLimitBuildingHeights.jpg&#34;&gt;Alderman Ed Sweeney said council must take immediate steps to prevent Kitsilano developing the way the West End has.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Project halted in Kitsilano (Vancouver Sun, Mar 13 1972)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/ProjectHaltedInKitsilano.jpg&#34;&gt;Residents in the area have been angered about the proposed 10-storey building, saying it would lead to a jungle of concrete and glass, like the West End, in Kitsilano.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Highrise limit backed at Kitsilano meeting (Vancouver Sun, Apr 13 1972)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Brian Mason, another spokesman for [the Kitsilano Area Resources Council], said people in Kitsilano do not want their area to suffer the same fate as the West End. (&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/HighriseLimitBacked.jpg&#34;&gt;1&lt;/a&gt;, &lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/HighriseLimitBacked1.jpg&#34;&gt;2&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;High-rise plans opposed (Vancouver Sun, Mar 15 1973)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/HighrisePlansOpposed.jpg&#34;&gt;This is the foot in the door. Before you know it, Kits will be another West End.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;‘Threat to Kitsilano’ High-rise plan protested (Vancouver Sun, Aug 17 1973)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.reillywood.com/img/posts/west-end/ThreatToKitsilano.jpg&#34;&gt;On a 13-storey Legion seniors’ housing building: “The situation in the West End is deplorable. We don’t want this area in Kitsilano to have a repetition of that.”&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The West End really was dramatically transformed during the 1960s and 1970s, &lt;a href=&#34;https://mountainmath.ca/map/assessment?filter=%5Byears_1960_1979%5D&amp;amp;zoom=15&amp;amp;lat=49.2863&amp;amp;lng=-123.1349&amp;amp;layer=5&amp;amp;mapBase=2&#34;&gt;as can be seen in a casual glance at this map&lt;/a&gt;. With the benefit of hindsight, most contemporary objections to this seem wrong or at least short-sighted. To this day the West End provides a significant amount of Vancouver&amp;rsquo;s purpose-built rental units, and &lt;a href=&#34;http://vancouver.ca/files/cov/West%20End-census-data.pdf&#34;&gt;half of the West End&amp;rsquo;s population lives in buildings constructed 1960-1980&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Perhaps we should take a longer view when evaluating objections to densification and new apartment buildings. Wouldn&amp;rsquo;t Vancouver be nice with a few more West Ends?&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/west-end/aerial2018_hu_8afbb6c8bf0543ee.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        The West End and Stanley Park in April 2018. Photo by author.
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;/h2&gt;
&lt;p&gt;All articles were found on microfiche at the Central Vancouver Public Library in the Pacific Press newspaper clippings, subject “zoning”. Some clippings did not specify the newspaper, but the Vancouver Sun or Province are safe bets.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Land Use Visualization using Angular and the Canvas element</title>
      <link>https://www.reillywood.com/blog/visualizers/</link>
      <pubDate>Sat, 10 Mar 2018 17:50:34 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/visualizers/</guid>
      <description>&lt;p&gt;I recently built a web tool to solve a simple question that comes up often in urban planning: after taking &lt;a href=&#34;https://en.wikipedia.org/wiki/Setback_(land_use)&#34;&gt;setback requirements&lt;/a&gt; into account, &lt;a href=&#34;http://setbacks.reillywood.com/&#34;&gt;how much of lot can be built on?&lt;/a&gt; The answer is often surprising: for example, Vancouver&amp;rsquo;s most common residential zone only allows houses to cover about 28% of the land.&lt;/p&gt;
&lt;p&gt;It was a fun weekend project, and a few weeks later I decided to upgrade it on a long plane ride. &lt;a href=&#34;http://neighbourhood.reillywood.com/&#34;&gt;It&amp;rsquo;s now a neighbourhood-level simulator with many more parameters&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://neighbourhood.reillywood.com/&#34;&gt;


    
    
        
        
        
        
    

    
    
    

    
    







  


    

    
        &lt;p&gt;&lt;span class=&#34;image center&#34;&gt;
            &lt;img src=&#34;https://www.reillywood.com/img/posts/neighbourhood-visualizer.png&#34; alt=&#34;Alt Text&#34;&gt;
        &lt;/span&gt;&lt;/p&gt;
    

&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These are mobile and desktop-friendly, and the visualization is entirely done in the browser. Here&amp;rsquo;s how it all works.&lt;/p&gt;
&lt;h2 id=&#34;technologies-used&#34;&gt;Technologies Used&lt;/h2&gt;
&lt;p&gt;The visualization is drawn natively in the browser using &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API&#34;&gt;the HTML5 Canvas element&lt;/a&gt;. This was my first time using Canvas and it (unsurprisingly) feels very similar to 3D graphics programming; simple operations are generally done by moving the drawing context instead of the objects you&amp;rsquo;re drawing. Once you wrap your head around that it&amp;rsquo;s not bad at all, especially with judicious use of the &lt;code&gt;save()&lt;/code&gt; and &lt;code&gt;restore()&lt;/code&gt; methods on &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D&#34;&gt;CanvasRenderingContext2D&lt;/a&gt; to push and pop rendering contexts for later use.&lt;/p&gt;
&lt;p&gt;As I often do, I used &lt;a href=&#34;https://cli.angular.io/&#34;&gt;Angular CLI&lt;/a&gt; to create an Angular project. It makes setting up an Angular project dead simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ng new neighbourhood-visualizer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Angular isn&amp;rsquo;t really &lt;em&gt;needed&lt;/em&gt; for a small single-page project like this, and I admit that I wince every time I see the size of its compiled output. Still, it&amp;rsquo;s a really nice development experience. You get a lot for free (TypeScript integration, build+test tooling, templating, reactive forms&amp;hellip;) just by creating an Angular project.&lt;/p&gt;
&lt;p&gt;In this case, I wanted to use &lt;a href=&#34;https://angular.io/guide/reactive-forms&#34;&gt;Reactive Forms&lt;/a&gt; to bind controls to the underlying data model. Specifically, I was worried about the    &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; visualization being too expensive, and I wanted an easy way to throttle the rendering if needed (say someone moves the sliders very quickly and the rendering can&amp;rsquo;t keep up).&lt;/p&gt;
&lt;p&gt;The reactive programming model makes this super easy because the data changes are exposed as &lt;em&gt;observables&lt;/em&gt; and we can trivially transform the stream of changes from the observable. For example, this code calls &lt;code&gt;render()&lt;/code&gt; every time the form values change:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;inputForm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;valueChanges&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subscribe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we want to throttle it so &lt;code&gt;render()&lt;/code&gt; is called only every 200 milliseconds at most, we just add a call to &lt;a href=&#34;https://www.learnrxjs.io/operators/filtering/debouncetime.html&#34;&gt;&lt;code&gt;debounceTime()&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;inputForm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;valueChanges&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;debounceTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subscribe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As it turns out, &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; performance is more than fast enough and I don&amp;rsquo;t need to throttle rendering at all. &lt;a href=&#34;http://wiki.c2.com/?PrematureOptimization&#34;&gt;Premature optimization something something&lt;/a&gt;. :)&lt;/p&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What&amp;rsquo;s Next?&lt;/h2&gt;
&lt;p&gt;Extrapolating from recent progress, I should have a decent SimCity clone in a few years&amp;rsquo; time. In the meantime, I have a few smaller goals on my plate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;d like to add some presets – it would be nice if people could easily compare different neighbourhoods in popular cities.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;d like to add more esoteric options like laneway houses
&lt;ul&gt;
&lt;li&gt;This might require a rethink of the UI – perhaps separate &amp;lsquo;beginner&amp;rsquo; and &amp;rsquo;expert&amp;rsquo; modes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/rgwood/NeighbourhoodVisualizer&#34;&gt;The source code for the visualizer is available on GitHub&lt;/a&gt;, so if you&amp;rsquo;d like to customize it for your own needs please do! I&amp;rsquo;m also keen to see this used for real world pedagogy. If you&amp;rsquo;d like to use this for teaching just give me a shout and I&amp;rsquo;d be happy to discuss potential customization.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>AHV Letter Builder</title>
      <link>https://www.reillywood.com/blog/housing-projects/</link>
      <pubDate>Mon, 19 Feb 2018 18:53:59 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/housing-projects/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m a member of a nonprofit called &lt;a href=&#34;http://www.abundanthousingvancouver.com&#34;&gt;Abundant Housing Vancouver&lt;/a&gt;, and as you can probably tell, I happen to do some programming too. In 2017 I was able to spend a lot of time combining these interests which was pretty great!&lt;/p&gt;
&lt;p&gt;Over a few blog posts I&amp;rsquo;ll briefly outline-solid the projects I worked on – they&amp;rsquo;re all open source and who knows, they might even be useful for other housing advocacy groups someday. First up: the &lt;a href=&#34;https://github.com/rgwood/AHVLetterBuilder&#34;&gt;Abundant Housing Vancouver Letter Builder&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;background&#34;&gt;Background&lt;/h3&gt;
&lt;p&gt;In Vancouver, many housing projects are decided on at contentious &lt;a href=&#34;http://vancouver.ca/your-government/what-happens-at-a-public-hearing.aspx&#34;&gt;public hearings&lt;/a&gt;. At AHV we often try to rally support for good projects at the public hearing stage, but it&amp;rsquo;s &lt;em&gt;hard&lt;/em&gt; because feedback at hearings tends to be biased against new housing for structural reasons. Without going into too much theory, we need to make it easy for people to support new projects, or nobody will. Thankfully Vancouver accepts hearing comments via email – it&amp;rsquo;s much easier to send an email than it is to spend an entire evening waiting at City Hall to speak!&lt;/p&gt;
&lt;p&gt;Still, it takes a long time to write an email if you&amp;rsquo;re starting from scratch: first you need to familiarize yourself with the project and the public hearing process, then take quite a bit of time to actually write it. We needed a way to quickly 1) describe a project to supporters, 2) help them compose a personalized email to the City. Inspired by &lt;a href=&#34;https://github.com/savechinatownheritage/105keefer&#34;&gt;a clever letter generator project by Melody Ma and Ivy Tsai&lt;/a&gt;, we decided to write our own tool for this.&lt;/p&gt;
&lt;h3 id=&#34;design-goals&#34;&gt;Design Goals&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The tool should be really quick to use&lt;/li&gt;
&lt;li&gt;The letters generated by the tool should not look like form letters! They should vary from one another and be easily customized by users&lt;/li&gt;
&lt;li&gt;It should be &lt;strong&gt;super&lt;/strong&gt; easy to configure the tool for new projects&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;finished-product&#34;&gt;Finished Product&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;http://www.abundanthousingvancouver.com/letter_generator?p=ryersonUnited&amp;amp;testenv=true&#34;&gt;Here&amp;rsquo;s what we ended up with&lt;/a&gt;. You can give it a spin, the &lt;code&gt;testenv=true&lt;/code&gt; parameter ensures that it won&amp;rsquo;t really send emails. It&amp;rsquo;s quite easy to use:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fill in personal information (name, address)&lt;/li&gt;
&lt;li&gt;Pick the following from a list of options: your relation to the project, what you like about the project, and what you think could be improved&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Generate Letter&lt;/code&gt; and a letter is generated based on your input, using randomized text.&lt;/li&gt;
&lt;li&gt;Edit the letter if desired then click &lt;code&gt;Send Letter&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After that, the letter is sent to a Slack channel where we can approve it:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/housing-projects/letter-approve_hu_a75a07ef7da542e6.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;If the letter isn&amp;rsquo;t spam, we click &lt;code&gt;Approve&lt;/code&gt; and it gets sent to the City of Vancouver. Done!&lt;/p&gt;
&lt;h3 id=&#34;technical-details&#34;&gt;Technical Details&lt;/h3&gt;
&lt;p&gt;I wrote the letter generator front-end in TypeScript, using Angular 4 with Bootstrap theming. I used angular-cli for setup and scaffolding.&lt;/p&gt;
&lt;p&gt;All configuration for the front-end (project info, text snippets for letter generation) lives in a Google spreadsheet and I read it in using the excellent &lt;a href=&#34;https://github.com/jsoma/tabletop&#34;&gt;Tabletop.js&lt;/a&gt;. Configuring the letter generator for a new project is as easy as adding a new row to the spreadsheet. Project descriptions are written in Markdown and then converted to HTML using the handy &lt;a href=&#34;https://www.npmjs.com/package/ng2-markdown-to-html&#34;&gt;ng2-markdown-to-html library&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/rgwood/AHVLetterBuilder&#34;&gt;The front-end code is available on GitHub&lt;/a&gt; and if it sounds useful I encourage you to fork it and give it a spin.&lt;/p&gt;
&lt;p&gt;The front end passes messages off to &lt;a href=&#34;https://github.com/rlisagor/ahv-council-thing&#34;&gt;an Amazon Lambda function&lt;/a&gt; which was written by the remarkable &lt;a href=&#34;https://github.com/rlisagor/&#34;&gt;Roman Lisagor&lt;/a&gt;. This function handles notification+approval (via &lt;a href=&#34;https://api.slack.com/incoming-webhooks&#34;&gt;a Slack webhook&lt;/a&gt;) and then sends the email using &lt;a href=&#34;https://docs.aws.amazon.com/ses/latest/DeveloperGuide/Welcome.html&#34;&gt;AWS SES&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;lessons-learned&#34;&gt;Lessons Learned&lt;/h3&gt;
&lt;p&gt;This was largely an educational project for me – it had been years since I paid much attention to web development and I was keen to try building something in Angular. As such, &lt;em&gt;caveat emptor&lt;/em&gt; if you end up using the code.&lt;/p&gt;
&lt;p&gt;Programming in TypeScript is pretty nice, especially compared to the JavaScript of yore. Compile-time safety has saved me many times, and declaring variables with &lt;code&gt;let&lt;/code&gt; &lt;a href=&#34;https://www.typescriptlang.org/docs/handbook/variable-declarations.html&#34;&gt;eliminates the usual JavaScript scoping confusion&lt;/a&gt;. Newer versions of JavaScript also support &lt;code&gt;let&lt;/code&gt; but&amp;hellip; personally I&amp;rsquo;ll stick with TS for the static typing alone.&lt;/p&gt;
&lt;p&gt;I generally liked Angular, but good lord does it ever generate huge script files. A simple project like this compiles to 1.3MB of scripts (in the production build, mostly vendor scripts). I cringe to think about how much bandwidth is being used to for relatively little program logic.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://cli.angular.io/&#34;&gt;angular-cli&lt;/a&gt; is very helpful for getting all the necessary scaffolding set up, and I wouldn&amp;rsquo;t hesitate to use it again.&lt;/p&gt;
&lt;p&gt;Tabletop.js was a lifesaver. For small+casual projects like this, sticking our config in a Google spreadsheet has a lot of advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google handles permissioning and authentication pretty well! It&amp;rsquo;s trivial to control access to the &amp;lsquo;database&amp;rsquo; and everyone already has a Google account&lt;/li&gt;
&lt;li&gt;The configuration is easily read and edited by non-technical people&lt;/li&gt;
&lt;li&gt;You don&amp;rsquo;t have to spend time administering and hosting a database somewhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would shy away from this approach for most professional projects, but if you just want to hack something small together you get a &lt;em&gt;lot&lt;/em&gt; for free with Google Sheets + Tabletop.js.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Japanese Apartment Hunting</title>
      <link>https://www.reillywood.com/blog/japanese-craigslist/</link>
      <pubDate>Thu, 25 Jan 2018 21:10:33 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/japanese-craigslist/</guid>
      <description>&lt;p&gt;Over the past 10 years, I&amp;rsquo;ve spent a lot of time looking for apartments in both Vancouver and in Tokyo. It&amp;rsquo;s been an interesting chance to compare two very different cities.&lt;/p&gt;
&lt;p&gt;Vancouver has a &lt;a href=&#34;https://www.theglobeandmail.com/news/british-columbia/bc-rental-market-remains-strained-survey/article37122063/&#34;&gt;remarkably low vacancy rate&lt;/a&gt;, so right off the bat renters are competing for a very small pool of available homes. Apartment hunters are also usually stuck with Craigslist. Craigslist is far and away the best place for browsing apartments in Vancouver, &lt;em&gt;but not because it&amp;rsquo;s a well-designed site&lt;/em&gt;. It&amp;rsquo;s &lt;a href=&#34;https://digit.hbs.org/submission/craigslist-the-dawn-of-the-pioneer-in-network-effects/&#34;&gt;a perfect example of network effects&lt;/a&gt;: it offers a poor user experience, but landlords can&amp;rsquo;t afford to ignore the large pool of renters using Craigslist and vice versa.&lt;/p&gt;
&lt;p&gt;Things are very different in Tokyo. It seems difficult to get comparable rental vacancy data (see the numbers and caveats &lt;a href=&#34;http://japanpropertycentral.com/2017/01/residential-vacancy-rate-situation-in-tokyo/&#34;&gt;here&lt;/a&gt;), but the sheer number of rental listings available online makes me suspect that the vacancy rate is much higher than in Metro Vancouver. The tools are better too: apartments tend to be listed by brokers on a few big websites, and the experience for renters on those websites is &lt;em&gt;much&lt;/em&gt; better than on Craigslist.&lt;/p&gt;
&lt;p&gt;Since most North Americans don&amp;rsquo;t know what they&amp;rsquo;re missing, I&amp;rsquo;ll give a quick overview of what it&amp;rsquo;s like to look for an apartment in Japan.&lt;/p&gt;
&lt;h1 id=&#34;apartment-listing-sites-in-japan&#34;&gt;Apartment listing sites in Japan&lt;/h1&gt;
&lt;p&gt;There are a handful of big listing sites in Japan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.apamanshop.com&#34;&gt;Apamanshop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.athome.co.jp&#34;&gt;At Home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://suumo.jp&#34;&gt;Suumo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Suumo and At Home handle all kinds of real estate listings, but Apamanshop specializes in the rental market. Let&amp;rsquo;s take a look at &lt;a href=&#34;https://suumo.jp/chintai/kanto/&#34;&gt;rental options in the Kanto region (which includes Tokyo) on Suumo&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-kanto_hu_f5202263886cf8b2.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;Here we have a map of the different Kanto prefectures plus Tokyo. For each of those areas you can pick whether you want to search by train line or by sub-area. Up top, it says that there are 339,703 new listings this week in the Kanto region (!!!). If we want to search the entire Kanto region, there are yet more options on the bottom and right:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://suumo.jp/jj/chintai/kensaku/FR301FB005/?ar=030&amp;amp;bs=040&#34;&gt;Search by commute time&lt;/a&gt; (enter your places of work or study and how long you&amp;rsquo;re willing to spend on the train, and this will show you listings within commuting distance)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://suumo.jp/chintai/soba/kanto/&#34;&gt;Search by rental price&lt;/a&gt; (this lets you specify which type of home you&amp;rsquo;re looking for, then shows you the average monthly rent for that type near various neighbourhoods and train stations)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://suumo.jp/edit/chizu/chintai/?ar=030&#34;&gt;Search by regular map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://suumo.jp/jj/chintai/kensaku/FR301FB007/?ar=030&amp;amp;bs=040&#34;&gt;Search by train map&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Just to get a sense of what&amp;rsquo;s out there, let&amp;rsquo;s try &lt;a href=&#34;https://suumo.jp/chintai/tokyo/ensen/&#34;&gt;browsing Tokyo by train line&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-tokyolines_hu_77bcf2a35fc5d338.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;In this screen we&amp;rsquo;re just picking train lines to narrow down our search. The train lines are divided up by operator (&lt;a href=&#34;https://en.wikipedia.org/wiki/Japan_Railways_Group&#34;&gt;JR&lt;/a&gt;, Tokyo Metro, and various private railways), and the numbers in parentheses show how many housing units are available for rent near each line. There is certainly some overlap between lines, but it&amp;rsquo;s still amazing to see so many train lines with thousands and thousands of homes listed nearby.&lt;/p&gt;
&lt;h1 id=&#34;a-specific-apartment-search&#34;&gt;A specific apartment search&lt;/h1&gt;
&lt;p&gt;Now let&amp;rsquo;s look for something more specific. I&amp;rsquo;m a &lt;a href=&#34;https://www.metafilter.com&#34;&gt;Metafilter&lt;/a&gt; regular, and last year I stumbled across &lt;a href=&#34;https://ask.metafilter.com/310379/How-to-find-some-cheap-horrible-room-to-rent-in-Seattle&#34;&gt;this heartbreaking question&lt;/a&gt;. A young Seattle resident was looking for &amp;ldquo;a room in the $500/month range, within a 90 minute commute from downtown,&amp;rdquo; and multiple commenters jumped in to say that this isn&amp;rsquo;t possible. This would be very difficult in Vancouver too – let&amp;rsquo;s see if it&amp;rsquo;s achievable in Tokyo.&lt;/p&gt;
&lt;p&gt;$500 USD is about 55,000 JPY. Tokyo doesn&amp;rsquo;t have a single &amp;ldquo;downtown&amp;rdquo; persay, but it does have a few &lt;em&gt;major&lt;/em&gt; transit hubs. &lt;a href=&#34;https://en.wikipedia.org/wiki/Shinjuku_Station&#34;&gt;Shinjuku Station&lt;/a&gt; is the busiest train station in the world, due to the huge number of rail lines connecting to it. To approximate a &amp;ldquo;90 minute commute from downtown&amp;rdquo;, let&amp;rsquo;s look for an apartment within a 1-hour commute to Shinjuku (since from Shinjuku you should be able to get just about anywhere in ultra-central Tokyo in 30 minutes).&lt;/p&gt;
&lt;p&gt;To search for something like that, I&amp;rsquo;ll use &lt;a href=&#34;https://suumo.jp/jj/chintai/kensaku/FR301FB005/?ar=030&amp;amp;bs=040&#34;&gt;the following conditions&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-500searchconditions_hu_e6e08e45fcbe248f.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Within 40 minutes (by train) of Shinjuku Station&lt;/li&gt;
&lt;li&gt;Within a 20 minute walk of a local train station&lt;/li&gt;
&lt;li&gt;Between 35,000 and 55,000 JPY/month (the minimum is intended to exclude &lt;em&gt;really&lt;/em&gt; low-end places), including management fees&lt;/li&gt;
&lt;li&gt;In Tokyo (we could get more results by including the surrounding prefectures, but Suumo only lets you choose one area at a time)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With those search conditions &lt;a href=&#34;https://suumo.jp/jj/chintai/ichiran/FR301FC001/?ar=030&amp;amp;bs=040&amp;amp;ta=13&amp;amp;cb=3.5&amp;amp;ct=5.5&amp;amp;mb=0&amp;amp;mt=9999999&amp;amp;et=20&amp;amp;cn=9999999&amp;amp;co=1&amp;amp;shkr1=03&amp;amp;shkr2=03&amp;amp;shkr3=03&amp;amp;shkr4=03&amp;amp;ekInput=19670&amp;amp;nk=-1&amp;amp;tj=40&amp;amp;sngz=&amp;amp;po1=15&#34;&gt;we get &lt;em&gt;76,884 results&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-500results_hu_40875c0fc24bd90.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;Most of these apartments are small (as you&amp;rsquo;d expect on a budget like this), but you might be surprised by how clean+modern many of them are:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-image0_hu_9ad7298259782bcf.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-image1_hu_302ce71d2eadabcf.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-image2_hu_e52c3bd0c2d43abe.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/japanese-craigslist/suumo-image3_hu_f49305131ff53113.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;At this point it should be clear that low-income urban residents have a &lt;em&gt;much&lt;/em&gt; easier time finding housing in Tokyo than in many other world cities. I think this is largely due to &lt;a href=&#34;https://www.amazon.com/Making-Urban-Japan-INSTITUTE-ROUTLEDGE/dp/0415354226&#34;&gt;Japan&amp;rsquo;s unique land use policies&lt;/a&gt;, and I&amp;rsquo;ll elaborate on that in future posts.&lt;/p&gt;
&lt;h1 id=&#34;more-search-options&#34;&gt;More Search Options&lt;/h1&gt;
&lt;p&gt;I used some pretty vanilla search parameters above, but you can specify a &lt;em&gt;lot&lt;/em&gt; more search conditions on Japanese websites. You can browse &lt;a href=&#34;https://translate.google.com/translate?sl=auto&amp;amp;tl=en&amp;amp;js=y&amp;amp;prev=_t&amp;amp;hl=en&amp;amp;ie=UTF-8&amp;amp;u=https%3A%2F%2Fsuumo.jp%2Fjj%2Fchintai%2Fkensaku%2FFR301FB005%2F%3Far%3D030%26bs%3D040&amp;amp;edit-text=&amp;amp;act=url&#34;&gt;the search page translated to English&lt;/a&gt; if you want to see all of the available search parameters. These are just a few of the options that I find useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distance from the nearest train station (can look for homes within a 5 minute walk of a train station, for example)&lt;/li&gt;
&lt;li&gt;Floor area&lt;/li&gt;
&lt;li&gt;Age of building&lt;/li&gt;
&lt;li&gt;Construction material (wood frame, steel frame, etc.)&lt;/li&gt;
&lt;li&gt;Distance from amenities (supermarkets, convenience stores, parks)&lt;/li&gt;
&lt;li&gt;Does it have a balcony/air conditioner/bike storage?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The especially nice thing about this is that nearly every listing has this information populated correctly. Anyone who&amp;rsquo;s ever suffered through Craigslist&amp;rsquo;s sea of low-effort listings will be thankful for that.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Databases as file formats</title>
      <link>https://www.reillywood.com/blog/mbtiles-format/</link>
      <pubDate>Sun, 21 Jan 2018 07:30:31 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/mbtiles-format/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m building an interactive online map of all properties in Vancouver, and along the way there have been a few pleasant surprises. Most recently: &lt;a href=&#34;https://github.com/mapbox/mbtiles-spec&#34;&gt;the .MBTiles tileset format&lt;/a&gt; is surprisingly cool.&lt;/p&gt;
&lt;h1 id=&#34;background&#34;&gt;Background&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;http://mapbox.com&#34;&gt;Mapbox&lt;/a&gt; is one of the biggest players in the open source mapping space (especially now that Mapzen and Carto have thrown in the towel – Mapzen is closing and Carto is now using Mapbox tech). One of the many nice things about Mapbox is that they developed &lt;a href=&#34;https://github.com/mapbox/vector-tile-spec&#34;&gt;an efficient open standard for vector map tiles&lt;/a&gt;, appropriately named Mapbox Vector Tiles (read &lt;a href=&#34;https://www.mapbox.com/vector-tiles/&#34;&gt;this&lt;/a&gt; if you&amp;rsquo;re not sure why vector tiles are great).&lt;/p&gt;
&lt;p&gt;Map tiles are often pre-computed for each zoom level, and once you&amp;rsquo;ve done that you need to store them somewhere. Enter &lt;a href=&#34;https://github.com/mapbox/mbtiles-spec&#34;&gt;the .MBTiles tileset format&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;poking-around-under-the-hood&#34;&gt;Poking around under the hood&lt;/h1&gt;
&lt;p&gt;My first encounter with this file format occurred when I used Eric Fischer&amp;rsquo;s excellent &lt;a href=&#34;https://github.com/mapbox/tippecanoe&#34;&gt;tippecanoe&lt;/a&gt; tool to simplify my data set at lower zoom levels. Tippecanoe generates .mbtiles files, which are easy to serve to clients either by uploading to Mapbox, using a third party tile server, or even by rolling your own server with something like the &lt;a href=&#34;https://www.npmjs.com/package/mbtiles&#34;&gt;mbtiles Node.js package&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All great&amp;hellip; but after setting up &lt;a href=&#34;https://github.com/rgwood/mbtiles-server&#34;&gt;a server&lt;/a&gt; my Mapbox GL JS client refused to render the tiles. I tried a few things without much luck, and then as a last resort I decided to poke around in the .mbtiles file. I was expecting to need a hex editor or similar, but then I saw this beauty in the spec:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MBTiles is a specification for storing tiled map data in SQLite databases&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The files themselves are just relational databases in a known schema – how cool is that? Emboldened, I grabbed &lt;a href=&#34;http://cutedgesystems.com/software/Liya/&#34;&gt;a SQLite client&lt;/a&gt; and opened up my .mbtiles file:&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/mbtiles-format/mbtiles-sqlite_hu_341d3769e92c5d8.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        My .mbtiles file in a SQLite client
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;Right away, I spotted my issue in the &lt;code&gt;metadata&lt;/code&gt; table: my client was attempting to &lt;a href=&#34;https://www.mapbox.com/mapbox-gl-js/style-spec/#root-layers&#34;&gt;bind a style to a layer&lt;/a&gt; using the wrong layer ID.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Relational databases are a great way to store data for easy retrieval+manipulation – SQL is &lt;a href=&#34;https://www.quora.com/Which-is-the-most-popular-database-language?share=1&#34;&gt;far and away&lt;/a&gt; the most popular query language, and most developers are already familiar with it. &lt;a href=&#34;https://sqlite.org/mostdeployed.html&#34;&gt;SQLite is probably the most frequently used relational database in the world&lt;/a&gt; and it&amp;rsquo;s supported by tons of clients as a result.&lt;/p&gt;
&lt;p&gt;Mapbox has done something extremely simple but very clever here. By using SQLite databases as data files, they&amp;rsquo;ve established a tileset format that is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to query, even in complex ways&lt;/li&gt;
&lt;li&gt;Already supported by hundreds of tools (there are a &lt;em&gt;lot&lt;/em&gt; of SQLite clients out there)&lt;/li&gt;
&lt;li&gt;Instantly familar to most developers&lt;/li&gt;
&lt;li&gt;Conceptually very simple&lt;/li&gt;
&lt;li&gt;Flexible (&lt;a href=&#34;https://github.com/mapbox/mbtiles-spec/blob/master/1.1/spec.md&#34;&gt;from the spec&lt;/a&gt;: &amp;ldquo;the schemas outlined are meant to be followed as interfaces. SQLite views that produce compatible results are equally valid&amp;rdquo;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It seems like this is already paying off for them, as there are &lt;a href=&#34;https://github.com/mapbox/mbtiles-spec/wiki/Implementations&#34;&gt;quite a few&lt;/a&gt; third-party implementations of the .mbtiles format.&lt;/p&gt;
&lt;p&gt;More generally, I really love the idea of using databases as a file format. It wouldn&amp;rsquo;t be suitable for everything (maybe don&amp;rsquo;t write your next-gen word processor document format as a &lt;code&gt;position int, word varchar(max)&lt;/code&gt; table&amp;hellip;) but it seems great for general collections of data like this. Versioning of the underlying database format is theoretically a concern, but that&amp;rsquo;s significantly mitigated by &lt;a href=&#34;http://sqlite.org/formatchng.html&#34;&gt;SQLite&amp;rsquo;s exceptional focus on maintaining compatibility&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>First!</title>
      <link>https://www.reillywood.com/blog/first-post/</link>
      <pubDate>Sat, 20 Jan 2018 00:00:00 +0000</pubDate>
      
      <guid>https://www.reillywood.com/blog/first-post/</guid>
      <description>&lt;p&gt;Hi there - I&amp;rsquo;m Reilly Wood, a software developer from Vancouver, Canada. This site is still under development, but I&amp;rsquo;m planning to fill it up with thoughts and impressions about software, cities, finance, and whatever strikes my fancy. Stay tuned (perhaps with &lt;a href=&#34;http://www.reillywood.com/index.xml&#34;&gt;the handy RSS feed&lt;/a&gt;).&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Vancouver’s rocky start with secondary suites</title>
      <link>https://www.reillywood.com/blog/secondary-suites/</link>
      <pubDate>Sun, 03 Dec 2017 13:35:03 -0800</pubDate>
      
      <guid>https://www.reillywood.com/blog/secondary-suites/</guid>
      <description>&lt;h2 id=&#34;or-that-time-that-vancouver-decided-single-family-zoning-was-more-important-than-defeating-hitler&#34;&gt;&lt;em&gt;Or: that time that Vancouver decided single-family zoning was more important than defeating Hitler&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;Secondary suites (often just &amp;ldquo;basement suites”, or &lt;a href=&#34;https://accessorydwellings.org/what-adus-are-and-why-people-build-them/&#34;&gt;ADUs&lt;/a&gt;) are an everyday part of Vancouver neighbourhoods now. Even before they were fully legalized in 2004, they provided a large amount of Vancouver’s low-cost housing stock.&lt;/p&gt;
&lt;p&gt;The story I usually hear about suites goes like this: people started building a lot of unauthorized suites in &lt;a href=&#34;https://en.wikipedia.org/wiki/Vancouver_Special&#34;&gt;Vancouver Specials&lt;/a&gt; in the 1970s and 1980s, and this set the stage for a long drawn-out political battle that eventually ended with suites being legalized.&lt;/p&gt;
&lt;p&gt;It’s a good story, and it’s true! But Vancouver’s history with secondary suites goes back much further. People were trying to live in secondary suites – and Vancouver was trying to stop them – for a long time before the Vancouver Special.&lt;/p&gt;
&lt;h1 id=&#34;world-war-ii&#34;&gt;World War II&lt;/h1&gt;
&lt;p&gt;Vancouver’s first zoning code &lt;a href=&#34;https://www.reillywood.com/blog/vancouvers-first-zoning&#34;&gt;was introduced in the late 1920s&lt;/a&gt;, and it outlawed apartments, townhouses, and even secondary suites in most of the city. But even then there were Vancouverites who could not afford a single-family home, and the restrictive code soon caused growing pains. The city’s first brush with legalizing suites began just over a decade later, prompted by Canada’s involvement in World War II.&lt;/p&gt;
&lt;p&gt;Canada’s wartime government had an especially strong incentive to let more people live in productive urban areas. One of the approaches taken was to restrict cities’ ability to use exclusionary single-family zoning.&lt;/p&gt;
&lt;p&gt;In 1942, the federal Wartime Prices and Trade Board issued &lt;a href=&#34;https://archive.org/stream/canadianwarorde1942v1cana#page/176/mode/2up&#34;&gt;Order No. 200&lt;/a&gt;, also known as the “Respecting Housing Accommodation in Congested Areas” order. This order effectively said &amp;ldquo;if a homeowner wants to rent a suite or room out to someone, cities can&amp;rsquo;t prohibit that”. According to historian Jill Wade, Order 200 was a success:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As a result, many larger houses in older, one-family dwelling zones and in Shaughnessy Heights became multiple-family units.
-Houses for All: The Struggle for Social Housing In Vancouver, 1919-50&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The response from Vancouver council was swift. Less than a year after the introduction of Order 200, &lt;a href=&#34;https://twitter.com/GRIDSVancouver/status/914633516553461760&#34;&gt;council ordered a bylaw amendment expressly designed to constrain the order as much as possible&lt;/a&gt;. The city was still bound by the terms of the order for existing homes, but they could use a legal loophole to ensure that it did not apply to new homes. The city’s chief lawyer Donald McTaggart was incredulous:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The corporation counsel told the committee that the amendment it suggests will be quite legal, but he expressed the opinion that the idea of Order 200 is “being lost sight of.” &amp;hellip; “The government,” he reminded aldermen, “said ‘forget zoning bylaws’ for the sake of getting on with the war.”
–&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1943_Bylaw_Ordered.jpg&#34;&gt;Bylaw Ordered on Plea of Town Planning Board, The Vancouver Sun, 1943&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&#34;ubc-students-and-the-post-war-era&#34;&gt;UBC students and the post-war era&lt;/h1&gt;
&lt;p&gt;11 years after the war, Vancouver started eliminating the wartime suites – even the ones that had been created in existing houses:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With the City’s adoption of the Zoning and Development By-law in 1956, secondary suites and other multiple-unit buildings in the areas zoned RS-1 (single-family) became illegal. In the late 1950s, Council decided to remove all illegal suites over a 10-year period, and by 1966, over 2,000 suites had been removed under the program.
–&lt;a href=&#34;http://vancouver.ca/docs/policy/housing-secondary-suites.pdf&#34;&gt;The Role of Secondary Suites, City of Vancouver 2009&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The strongest opposition to the city came from UBC students. In 1960, UBC student council begged the city to relent and allow “up to four students per single family dwelling until the housing situation on campus eases”. They were opposed by local homeowners:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Members of the Northwest Point Grey Homeowners Association fear their area will become a slum unless city zoning bylaws are enforced.
…
The homeowners spokesman said: “The best information we can get shows conversions to boarding houses is a prominent factor in the cause of slums.”
-&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1960_Boarding_Houses_Irk.jpg&#34;&gt;Boarding Houses Irk Homeowners, The Vancouver Sun, 1960&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Point Grey homeowners won, but UBC students would keep trying in vain for another 8 years. The UBC Alma Mater Society came back in 1966 with a smaller “ask”: this time they asked for 3 boarders in a house (down from 4), and only in the West Side neighbourhoods closest to UBC. Again, they were rejected by Vancouver council and planners.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Planners agreed that there is a shortage of suite accommodation at UBC but said provision of such accommodation for out-of-town students should not be at the expense of single-family residential areas in Vancouver.
–&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1966_Council_Rejects_UBC.jpg&#34;&gt;Council Rejects UBC Suite Plea, The Vancouver Sun, 1966&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Alma Mater Society tried twice more in 1968 without success:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Alderman Earle Adams said the first responsibility of council is to taxpayers. He said students from Vancouver can live at home, so the shortage really affects only those who come from outside the city. “Why should the city be responsible for housing out-of-town students?” he asked.
–&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1968_UBC_Housing_Crisis.jpg&#34;&gt;UBC Housing Crisis: City Won’t Okay Illegal Suites, The Vancouver Sun, 1968&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even in the 1960s, there was significant demand for more affordable housing in Vancouver’s exclusive single-family neighbourhoods. Council didn’t eliminate that demand when they forced the closure of wartime suites, but they were able to ignore it until the Vancouver Special forced another city-wide conversation.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Vancouver is &lt;a href=&#34;https://www.cnu.org/publicsquare/2017/10/03/why-small-will-soon-be-large&#34;&gt;often&lt;/a&gt; &lt;a href=&#34;http://www.sightline.org/2016/02/17/why-vancouver-trounces-the-rest-of-cascadia-in-building-adus/&#34;&gt;cited&lt;/a&gt; as a leader in terms of legalizing secondary suites, and with good reason – we’ve gone far beyond what most North American cities have done to date. However, when put in historical context, it becomes clear that Vancouver’s policy changes were still too little, too late.&lt;/p&gt;
&lt;p&gt;The federal government forced the city’s hand during World War II and showed that there was demand for denser living in single-family neighbourhoods, even in 1942. That demand didn’t go away after the war – students were begging council to stop barring secondary suites near UBC all throughout the 1960s.&lt;/p&gt;
&lt;p&gt;It’s great that Vancouver eventually decided to allow secondary suites in all neighbourhoods. It would have been even better if the policy hadn’t come 60 years after it was first shown to be necessary.&lt;/p&gt;
&lt;h1 id=&#34;works-cited&#34;&gt;Works Cited&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;The Wartime Prices and Trade Board Order No. 200, Respecting Housing Accommodation in Congested Areas&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;From &lt;a href=&#34;https://archive.org/stream/canadianwarorde1942v1cana#page/176/mode/2up&#34;&gt;Canadian war orders and regulations, page 177&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Houses for All: The Struggle for Social Housing in Vancouver, 1919-50&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Jill Wade. &lt;a href=&#34;https://books.google.ca/books?id=z-5PwY6CkGgC&amp;amp;pg=PA208&amp;amp;lpg=PA208&amp;amp;dq=order+200+war+housing+canada&amp;amp;source=bl&amp;amp;ots=BSCWm5XxzB&amp;amp;sig=5q2xCh9rjwYljd3FAg1XLSCiRAc&amp;amp;hl=en&amp;amp;sa=X&amp;amp;ved=0ahUKEwjXiMelytDWAhWmxVQKHQ89CiA4ChDoAQgqMAE#v=snippet&amp;amp;q=Shaughnessy&amp;amp;f=false&#34;&gt;Page 117&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Role of Secondary Suites&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://vancouver.ca/docs/policy/housing-secondary-suites.pdf&#34;&gt;City of Vancouver 2009&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vancouver Sun articles&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(Available on microfiche at the Vancouver Public Library central branch, reproduced here for convenience)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1943_Bylaw_Ordered.jpg&#34;&gt;Bylaw Ordered On Plea of Town Planning Board, August 10 1943&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1960_Boarding_Houses_Irk.jpg&#34;&gt;Boarding Houses Irk Homeowners, May 12 1960&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1966_Council_Rejects_UBC.jpg&#34;&gt;Council Rejects UBC Suite Plea, November 23 1966&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://s3-us-west-2.amazonaws.com/ahv-website-content/secondary-suites/1968_UBC_Housing_Crisis.jpg&#34;&gt;UBC Housing Crisis: City Won’t Okay Illegal Suites, August 21 1968&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
    <item>
      <title>What Motivated Vancouver&#39;s First Zoning Codes?</title>
      <link>https://www.reillywood.com/blog/vancouvers-first-zoning/</link>
      <pubDate>Sat, 17 Jun 2017 09:07:48 -0700</pubDate>
      
      <guid>https://www.reillywood.com/blog/vancouvers-first-zoning/</guid>
      <description>&lt;div class=&#39;carousel border border-gray-border&#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/APlanForVancouver_hu_44421a0222a9d034.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Ask a random person what the purpose of zoning is, and they’ll probably mention that it keeps unpleasant or dangerous things away from homes. You wouldn’t want to live next to a garbage dump or concrete factory, right?&lt;/p&gt;
&lt;p&gt;Most people agree that that’s a good thing, but zoning codes do a lot more than that. If you could ask the people who wrote our first zoning codes, you’d quickly learn that separating industry from homes was very far down their list of goals. Separating certain &lt;em&gt;homes&lt;/em&gt; – specifically apartment homes – from other homes was the primary motivation for Vancouver&amp;rsquo;s first zoning code.&lt;/p&gt;
&lt;h2 id=&#34;point-grey&#34;&gt;Point Grey&lt;/h2&gt;
&lt;p&gt;Our story starts in the 1920s, with 3 municipalities where Vancouver lies today: The City of Vancouver, Point Grey, and South Vancouver. Point Grey encompassed not just modern-day Point Grey, but most of the West Side. Point Grey was the first municipality in Canada to introduce zoning in 1922. The chairman of the Point Grey Town Planning Commission was eager to boast of the zoning bylaw’s “successes,” specifically, how it excluded lower-cost housing:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Such by-laws as these served, in no uncertain way, to implement the ideals held by the residents that their municipality was to be one in which the best type of home could not only be built, but also adequately safeguarded from the encroachments of undesirable types of development. That the quality and type of dwelling within the municipality at the present time is of a very high order is indicated by the study of the &amp;ldquo;dwelling permits&amp;rdquo; over a five-year period. &lt;strong&gt;The average cost of residences over such a period, these &amp;ldquo;permits&amp;rdquo; show, was in excess of $4,100. At the present time over ninety per cent, of the municipality is zoned for one-family dwelling districts.&lt;/strong&gt; Point Grey has no slum district. (A Plan for the City of Vancouver, p. 297)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;vancouvers-interim-zoning-bylaw&#34;&gt;Vancouver’s Interim Zoning Bylaw&lt;/h2&gt;
&lt;p&gt;By 1927, the City of Vancouver was seriously considering a zoning bylaw of their own. They had hired Harland Bartholomew, a prominent urban planning consultant, to devise a comprehensive plan for Vancouver. But these things take time, and time was in short supply. Well before Bartholomew could finish his official plan, Vancouver commissioned him to write a temporary bylaw to stop the most pressing danger to the city: apartment buildings. His instructions were unambiguous:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;When Bartholomew asked what abuses he should consider in the interim zoning by-law of 1927 he was preparing, &lt;strong&gt;the chairman replied that &amp;rsquo;the only serious abuse… is the intrusion of undesirable apartment houses into residential districts&amp;rsquo;&lt;/strong&gt;&amp;rdquo; (Zoning and the Single-Family Landscape, p. 60)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bartholomew was happy to state the motivation for the interim bylaw in official documents:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;…largely to prevent the intrusion of apartment houses in single or two-family residential areas&lt;/strong&gt;, an interim zoning by-law was prepared and approved by the Town Planning Commission, recommended to the Council, and became law on 5th February, 1927. (A Plan for the City of Vancouver, p. 211)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;vancouvers-first-comprehensive-plan&#34;&gt;Vancouver’s First Comprehensive Plan&lt;/h2&gt;
&lt;p&gt;Finally in 1930 the three municipalities had been amalgamated into one and Bartholomew’s official Plan for the City of Vancouver was released. Bartholomew’s plan was remarkably comprehensive, spanning 310 pages. Thankfully for us, many of those pages were used to describe the intent of the plan and not just the regulatory details. Town Planning Commission chairman Arthur Smith set the tone by praising Point Grey’s early bylaw, explicitly segregating Vancouver by class, and noting the promotion of single-family homes as a major goal:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The wise foresight which Point Grey has used in planning at an early stage of its growth should provide Vancouver with one of the most desirable residential districts possessed by any city on the Continent, and those who have to gain their livelihood by manual labor should find in Hastings Townsite, and in a replanned South Vancouver, a place where they can build up modest homes which should differ only in size from that of the more opulent employers. &lt;strong&gt;The retention of Vancouver as a city of single family homes has always been close to the heart of those engaged in the preparation of this plan.&lt;/strong&gt; (A Plan for the City of Vancouver, p. 26)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bartholomew later clarified his preference for single-family homes in an appeal to authority:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As has been mentioned, Vancouver is largely a city of one-family homes and is surrounded by similar development in the adjoining municipalities. Large areas are now available for such development, though a considerable proportion has yet to be served by utilities. &lt;strong&gt;That the one-family dwelling is the desirable unit for happy living is the general concensus (sic) of opinion of all authorities.&lt;/strong&gt; (A Plan for the City of Vancouver, p. 233-234)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;neighbourhood-stores&#34;&gt;Neighbourhood stores&lt;/h2&gt;
&lt;p&gt;Bartholomew was also keen to keep stores out of residential neighbourhoods:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The scattering of stores promiscuously throughout residence districts has done considerable damage to the city&amp;rsquo;s appearance. The nearly universal custom of building stores out to the street line has hurt the appearance of a good many residence streets and at the same time has injured adjoining lots by making them less desirable for living purposes and reducing their saleable value. The zoning by-law will remedy this condition and tend to prevent residence districts from becoming blighted. (A Plan for the City of Vancouver, p. 247)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The contrast with modern-day Vancouver is remarkable, given that neighbourhood stores built before Bartholomew’s Plan are now many neighbourhoods’ most cherished jewels. Who would prefer the East Side without the Marché St. George, or the West Side without Arbutus Coffee? Bartholomew sought to completely eradicate shops and meeting places from residential neighbourhoods, without asking whether people might &lt;em&gt;want&lt;/em&gt; to live near them.&lt;/p&gt;

&lt;div class=&#39;carousel &#39;&gt;





&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/marche-st-george_hu_cf029ce8e071e7dd.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        The Marché St. George in East Van
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/wilderSnail_hu_e221031867e3b9f2.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        The Wilder Snail in Strathcona
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;






&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-small&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/westEndCafe_hu_ef80ae61d5c70634.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Cardero Bottega in the West End
                &lt;/figcaption&gt;
        &lt;/figure&gt;
&lt;/div&gt;

&lt;/div&gt;
&lt;p&gt;Neighbourhood stores might seem like a small nuisance at worst, but no: in a later report for the City of Vancouver, Bartholomew clarified his belief that apartments and stores were the “worst offenders” motivating his plan:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Had it not been for the widespread intrusion of commercial and industrial uses and the building of multiple dwellings in single-family residential districts just after World War I, the institution of Town Planning in Vancouver would have been delayed or retarded for several years. &lt;strong&gt;The encroachment of apartments and of retail stores, the latter almost always extending to the street line, were the worst offenders.&lt;/strong&gt; (A Preliminary Report upon Zoning, p. 1)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If only Bartholomew had stopped there. Even though apartments and stores were his focus, the plan is littered with other “menaces” that he intended to keep out of Vancouver. Unfortunately, some of those are now recognized as aspects of Vancouver’s best pre-zoning urbanism.&lt;/p&gt;
&lt;h2 id=&#34;small-lots&#34;&gt;Small Lots&lt;/h2&gt;
&lt;p&gt;Small residential lots were also verboten in Bartholomew’s Vancouver:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The minimum dimensions for lots shall be forty (40) feet for width and one hundred and twenty (120) feet for depth, and in no case shall a rectangular or irregular shaped lot contain less than forty-eight hundred (4800) square feet. The custom of laying out 25-foot lots is productive of building conditions which are not a credit to the city. (A Plan for the City of Vancouver, p. 276)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Small residential lots were to be shunned, because they might attract the kind of builder who couldn’t afford a bigger lot. Nowadays, that sort of affordability is more appreciated and many people enjoy the fine-grained urbanism that small lots bring to neighbourhoods. Fortunately, Vancouver’s Strathcona neighbourhood was developed with narrow lots before Bartholomew’s ideas were adopted:&lt;/p&gt;
&lt;div class=&#34;grid grid-cols-2 gap-1 items-end&#34;&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/strathcona1_hu_5487cdc1b5fa3675.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Strathcona apartment buildings on 25-foot lots
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/strathcona2_hu_6bfd8d8feca537eb.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Strathcona 4-plex on a 25-foot lot
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;h2 id=&#34;front-yards&#34;&gt;Front yards&lt;/h2&gt;
&lt;p&gt;Bartholomew’s plan also mandated very large yards for residential buildings. Previously, many buildings were built fairly close to the sidewalk – but now houses and low-rise apartment buildings were required to have a front yard 20 feet deep. This might seem like a small change but its effects cannot be understated. Many low-rise buildings from before this rule take up much less land per household – and many later buildings are saddled with incredibly underused yards.&lt;/p&gt;
&lt;div class=&#34;grid grid-cols-2 gap-1 items-end&#34;&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/rileyParkGood_hu_c223cc181698e4d8.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Pre-zoning homes in Riley Park
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image &#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/rileyParkBad_hu_a47ba6701bbf9eb8.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Post-zoning homes in Riley Park
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/mountPleasantGood_hu_c3eb4134ab856617.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Pre-zoning Mount Pleasant apartments
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/mountPleasantBad_hu_7c776f0696d57a22.webp&#39; /&gt;
                
                &lt;figcaption
                        class=&#34;carousel-image-caption absolute bottom-0 left-0 right-0 text-solarized-base2 font-sans text-xs p-1&#34;
                        style=&#34;background-color: hsla(0, 0%, 0%, 0.5)&#34;&gt;
                        Post-zoning apartments in Mount Pleasant. About half as dense (by &lt;a href=&#34;https://en.wikipedia.org/wiki/Floor_area_ratio&#34;&gt;FAR&lt;/a&gt;)!
                &lt;/figcaption&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;h2 id=&#34;legacy&#34;&gt;Legacy&lt;/h2&gt;
&lt;p&gt;Bartholomew’s plan was not adopted in full, but the aspects mentioned above were. Even though it was written nearly 90 years ago, its outline is still clearly visible in Vancouver’s modern zoning code. Some change has occurred and mostly for the better: the “single-family” zones have been revised and now permit one basement suite and a (very small) laneway house in addition to the main suite. But that “Great House Reserve” still takes up the vast majority of our residential land, still forbids apartment buildings and townhouses, and its boundaries still remain mostly unchanged:&lt;/p&gt;
&lt;div class=&#34;grid grid-cols-2 gap-x-1 items-end&#34;&gt;






&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/1927Zoning_hu_a5807a65e48a4018.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;









&lt;div class=&#39;carousel &#39;&gt;
&lt;div class=&#34;carousel-cell&#34;&gt;
        &lt;figure class=&#34;relative&#34;&gt;
                &lt;img class=&#39;carousel-image height-medium&#39;
                        data-flickity-lazyload-src=&#39;https://www.reillywood.com/img/posts/vancouvers-first-zoning/modernZoningWeb_hu_2df8c596c981b2b5.webp&#39; /&gt;
                
        &lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The primary motivation for Vancouver’s first zoning code was to prevent the construction of apartment buildings and stores in residential neighbourhoods. Apartment buildings were seen by Bartholomew as an afterthought at best, and a major nuisance at worst. Stores in residential neighbourhoods were only seen as a blight. Even after ensuring the homogeneity of residential neighbourhoods, Bartholomew imposed extravagantly large minimum lot sizes and yards.&lt;/p&gt;
&lt;p&gt;Vancouver’s formative urban plan could be more accurately described as a &lt;em&gt;suburban&lt;/em&gt; plan, designed by men with a profoundly anti-urban bias. It would be amusing if we weren’t still living in its shadow.&lt;/p&gt;
&lt;h2 id=&#34;works-cited&#34;&gt;Works Cited&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://archive.org/details/planforcityofvan00vanc&#34;&gt;A Plan for the City of Vancouver including Point Grey and South Vancouver&lt;/a&gt;. Harland Bartholomew and Associates, 1930&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://archive.org/details/reportzoning00vanc&#34;&gt;A Preliminary Report Upon Zoning Vancouver, British Columbia&lt;/a&gt;. Harland Bartholomew and Associates, 1946&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://open.library.ubc.ca/cIRcle/collections/ubctheses/831/items/1.0086386&#34;&gt;Zoning and the single-family landscape: large new houses and neighbourhood change in Vancouver&lt;/a&gt;. Barbara A. Pettit, 1993&lt;/li&gt;
&lt;/ul&gt;</description>
    </item>
    
  </channel>
</rss>