<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Eli Perkins's Blog</title>
        <link>https://blog.eliperkins.com</link>
        <description>A bunch of ramblings from Eli Perkins</description>
        <lastBuildDate>Wed, 18 Mar 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Next.js+Feed</generator>
        <language>en</language>
        <copyright>All rights reserved 2024, Eli Perkins</copyright>
        <atom:link href="https://blog.eliperkins.com/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Tools I Love: Plannotator]]></title>
            <link>https://blog.eliperkins.com/tools-i-love-plannotator</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/tools-i-love-plannotator</guid>
            <pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Make working with your clanker feel more like collaborating in Google Docs.</p>]]></description>
            <content:encoded><![CDATA[<p>I've been using Claude Code a lot more over the last 6 months. The best outputs I've gotten from Claude Code have come from plans that are well thought out, explore a variety of edge cases or various strategies, and then consolidate on a plan of attack. The terminal is great, but the chat UI in general can leave me feeling less precise and more like I'm backseat driving a junior developer via Slack than writing my best code.</p>
<p><a href="https://plannotator.ai">Plannotator</a> is a tool that makes coming up with these plans feel more collaborative and give context or feedback in a much more precise way.</p>
<p>To me, Plannotator is one of the best examples of "<a href="https://danieldelaney.net/chat/">chat is a bad UI pattern</a>" since it does such a great job making what felt like typing context-less prose into a back-and-forth conversation, into what feels like reviewing an RFP Google Doc from a teammate.</p>
<figure>
    <img src="/images/touch-grass.png">
    <figcaption>Comment on specifics to get plans that fit <em>your</em> goals</figcaption>
</figure>
<p>Plannotator hooks into Claude Code's Plan Mode, so once a plan is devised by Claude Code, Plannotator pops open a localhost website for you. Markup, leave questions or comments, and accept or reject the plan. Any comment will reject the plan, giving your agent another go at creating its best work. Approving a plan sets it off and running.</p>
<p>Comments can be on individual words, sentences, paragraphs, sections or even on the whole plan itself. This context not only helps me from a human perspective (I hate when folks give me a list of feedback in a comment on a PR, rather than asking questions on a per-line basis), but also helps <a href="https://en.wikipedia.org/wiki/Clanker">your clanker</a> generate better plans that produce the results you want.</p>
<p>Getting started with Plannotator is about as easy as it comes too (although I wish it came in a Homebrew variant without piping into bash):</p>
<pre><code class="language-bash"><span class="pl-c"># Forgive me father, for I am about to sin.</span>
curl -fsSL https://plannotator.ai/install.sh <span class="pl-k">|</span> bash

<span class="pl-c"># In Claude Code:</span>
/plugin marketplace add backnotprop/plannotator
/plugin install plannotator@plannotator
</code></pre>
<p>Give it a go, and watch your plans become more refined, more precise, and your results look more like what you'd expect from your peers.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[I don't look at the code]]></title>
            <link>https://blog.eliperkins.com/i-dont-look-at-the-code</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/i-dont-look-at-the-code</guid>
            <pubDate>Tue, 17 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>It's not the flex you think it is.</p>]]></description>
            <content:encoded><![CDATA[<p>Saying "I don't look at the code" is not the flex you think it is.</p>
<p>I wouldn't want to fly on a plane where the pilot said "this plane has autopilot, so I don't look at the controls."</p>
<p>I wouldn't trust a doctor who said "I have no idea what's in this medicine, but it gets results."</p>
<p>I wouldn't hire a lawyer who said "I don't look at the arguments before I make them."</p>
<p>I wouldn't vote for a politician who said "I don't know why UBI works, but it makes people happy."</p>
<p>I wouldn't move into a building where the architect said "I don't look at the structural details. It has four walls and a roof so it's a functioning house."</p>
<p>If your business or side-project works without you needing to look at the code, great. I'm happy for you.</p>
<p>If you are directly responsible for a codebase or product as an engineer, I'd hope you'd be able to know how and why the code works the way it does. You don't need to write every line of it (I don't these days).</p>
<p>Understanding why code works the way it does doesn't require reading every line and memorizing it. Understanding the architecture and design principles does make you a better, more effective engineer, who can build and ship better products faster than the person who needs to have an LLM reinterpret the codebase for every prompt and bug fix needed.</p>
<p>The best makers I know are those who know <em>how and why</em> things work. They can think outside the box to come up with new innovations and solutions. They craft better products not by being first to market (that makes you an entrepreneur, not a maker), but by caring about the little things while the big things take care of themselves.</p>
<p>If you don't understand the code your LLM writes, take a few minutes to ask it to explain things for you. Change the code to understand what breaks when you make changes. I promise your next prompt will give you better results if your brain understands one percent more of how the code works.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Tools I Love: xcrun simctl io booted recordVideo]]></title>
            <link>https://blog.eliperkins.com/tools-i-love-xcrun-simctl-recordVideo</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/tools-i-love-xcrun-simctl-recordVideo</guid>
            <pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Record high quality videos of the iOS simulator right from the command line.</p>]]></description>
            <content:encoded><![CDATA[<p>I've used a million different ways to record videos of iOS apps that I've worked over the last many years. From recording with tools like <a href="https://cleanshot.com">CleanShot X</a> to macOS's built-in screen recorder, to QuickTime recordings of devices.</p>
<p>Even Simulator.app has a built-in way of recording videos via buttons in the UI now too:</p>
<figure>
    <img src="/images/record-video-sim.gif">
    <figcaption>Holding <kbd>option</kbd> swaps the button in Simulator.app from capturing a screenshot to recording a video.</figcaption>
</figure>
<p>However, one method of recording videos has really stuck with me: <code>xcrun simctl io booted recordVideo</code>.</p>
<p>I've got a lil function in my dotfiles for this command, since I use it so often:</p>
<pre><code class="language-bash"><span class="pl-k">function</span> <span class="pl-en">xcrecord()</span> {
    xcrun simctl io booted recordVideo <span class="pl-k">~</span>/Desktop/<span class="pl-s"><span class="pl-pds">$(</span>uuidgen<span class="pl-pds">)</span></span>.mp4
}
</code></pre>
<p>I usually use this alongside <code>git commit</code> and <code>git submitpr</code> so my flow looks something like:</p>
<pre><code class="language-bash"><span class="pl-c"># commit my changes</span>
$ git commit -m <span class="pl-s"><span class="pl-pds">'</span>Something new<span class="pl-pds">'</span></span>

<span class="pl-c"># create a PR</span>
$ git submitpr

<span class="pl-c"># record a video</span>
$ xcrecord
Recording started
^CRecording completed. Writing to disk.

Wrote video to: /Users/eliperkins/Desktop/EAB9665D-447E-4033-96B7-FC4D54B7B2D6.mp4
</code></pre>
<p>and then I'll drag-and-drop the video into the PR description that's waiting for me in the new tab in my browser.</p>
<p>The result is an MP4 with no artifacting or watermarks that clearly shows the change I've made in my PR.</p>
<p><video autoplay loop muted playsinline width="320px" src="/images/xcrecord-demo.mp4"></video></p>
<p>It's a little quality of life thing that's fit into my workflow as a way to create high-quality recordings from the simulator!</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[One Year with Kagi]]></title>
            <link>https://blog.eliperkins.com/one-year-with-kagi</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/one-year-with-kagi</guid>
            <pubDate>Wed, 28 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Restore your mental health with a quality search engine sans AI slop.</p>]]></description>
            <content:encoded><![CDATA[<p>One year ago, I started paying for a search engine. I've used Google Search since its advent. In the early 2000s, I texted Google via T9 to get SMS responses. I had used Google Search via Java Applets on my pretty-dumb phones. I used Google Search to help me learn programming and software engineering. I contemplated teaching a course to other aspiring programmers at iD Tech Camps called "How to Learn via Google Searches." I worked professionally to integrate with Google services up and down the board. But last year, something changed in my brain.</p>
<p>It wasn't Google's lack of privacy controls that did me in, although it was an influence.</p>
<p>It wasn't Google's ad placement that made me switch, although it was a factor.</p>
<p>It wasn't the massive amount of data collection that Google had about it, but I did consider it.</p>
<p>It was the AI Overviews that inserted themselves into every search I did. Instead of letting me be curious about the internet, Google decided it should force-feed me responses to my search query in text form, without sending me to another website instead.</p>
<p>My normal behavior when using a search engine was to search for some query like <a href="https://kagi.com/search?q=miami+hurricanes+football+roster&#x26;r=us&#x26;sh=OKWyh5_XR6R2L3s5bOyB5g"><code>miami hurricanes football roster</code></a> or <a href="https://kagi.com/search?q=swiftui+adjustedcontentinset+scrollview&#x26;r=us&#x26;sh=cxMfXtnaFrKRjJexywvHhg"><code>swiftui adjustedcontentinset scrollview</code></a>, and then scan the results for a reputable website that could give me a believable answer. I'd open the first few links in new tabs, scanning each tab for the results I was looking for. Over time, I'd learn which websites to trust for certain types of content: Stack Overflow for programming things, Reddit for advice while I shopping for <a href="https://www.reddit.com/r/BuyItForLife/">Buy It For Life</a> products, Serious Eats for recipes, Sports Reference for a deep dive on sports statistics. The list goes on.</p>
<p>By putting AI Overviews first and foremost on every search query, the first thing I would see would be some regurgitation of what an LLM decided matched by query stochastically. Instead of navigating across the internet, gaining preferences on how I wanted to learn about new things, Google decided to provide their own answer for me. A method I had for navigating the internet was trumped by a wall of text with its own bias. I felt like I was getting dumber every time I typed something into Google. I felt like I was asking ChatGPT for answers every time I googled something, which was very different from how I learned from my searches in the past.</p>
<figure>
    <img src="/images/cheese-not-sticking-to-pizza.jpg">
    <figcaption>Don't put glue on your pizza.</figcaption>
</figure>
<h2 id="switching-to-kagi" class="heading-group group"><a href="#switching-to-kagi"><span class="heading-link">#</span>Switching to Kagi</a></h2>
<p>I learned about Kagi from some tech-adjacent friends. It was pitched as a replacement for Google Search, with a focus on being fast and private. I figured I'd try it out as my default search engine for a day or week or so to see if it could become a full-time replacement.</p>
<p>One day using it became one week using it.</p>
<p>One week using it became one month using it.</p>
<p>One month using it became one year using it.</p>
<p>It felt like a breath of fresh air whenever I used it. I felt smarter again. Instead of feeling like I was being force-fed some LLM-generated response, I got to surf the web again. I learned about new websites, and rediscovered old ones. It stuck in a way I didn't expect. I suspect the <a href="https://help.kagi.com/kagi/getting-started/setting-default/safari-mac.html">macOS</a> and <a href="https://help.kagi.com/kagi/getting-started/setting-default/safari-iphone-ipad.html">iOS</a> Safari extensions had a lot to do with this.</p>
<p>I tried reskinning it with <a href="https://help.kagi.com/kagi/features/custom-css.html">Kagi's custom CSS</a> to make it look like Google again. After a few hours using it, something felt off, so I reverted back to the stock CSS. Kagi had replaced Google Search completely. Kagi has a million other features to differentiate them from Google that I've explored, from <a href="https://help.kagi.com/kagi/features/lenses.html">Lenses</a> and <a href="https://help.kagi.com/kagi/features/website-info-personalized-results.html#personalized-results">Personalized Results</a> to <a href="https://help.kagi.com/kagi/features/code.html">Code Search</a> and <a href="https://help.kagi.com/kagi/features/search-shortcuts.html">Search Shortcuts</a>. I barely scratch the surface of what Kagi offers, since <strong>Kagi's base offering is just that much better than the alternative.</strong></p>
<h2 id="surfing-the-web-again" class="heading-group group"><a href="#surfing-the-web-again"><span class="heading-link">#</span>Surfing the Web Again</a></h2>
<p>I've worked in computers long enough to know that Kagi is likely biased in some ways, just as Google or Bing or Yahoo! or AltaVista are. I know technology encodes the biases of those who build them, just as LLMs encode the biases of what they're trained on. I'd rather learn from an interconnected series of URLs shown to me on a webpage than to never go beyond a single paragraph response for information on a given topic.</p>
<p>Kagi recently introduced a similar feature to AI Overviews called "<a href="https://help.kagi.com/kagi/ai/quick-answer.html">Auto Quick Answer</a>". I'm thankful Kagi introduced a setting to turn this off. I did so immediately after seeing the first one.</p>
<p>I don't want people to see a Google AI Overview, a Kagi Auto Quick Answer, a chat response from ChatGPT as the cold, hard truth. Seeing a list of human-curated websites, understanding each website's spin or bias, and coming to your own truth should be something human brains continue to do. There's nothing learned from a single sentence or paragraph generated by an LLM, there's only laziness.</p>
<p>AI and ML are incredibly powerful tools. I currently use a lot of LLMs, ML and slew other stochastic tools at my job at <a href="https://particle.news">Particle</a>. I probably sit somewhere between an AI skeptic and an AI optimist, since I have seen first hand what these concepts and tools can do, both good and bad. There are ethically and informationally excellent applications of these tools, but I don't think Google's AI Overviews is either of those.</p>
<p>Here at Particle, we've toyed with a phrase to describe what our product can do for your news consumption:</p>
<blockquote>
<p>What are you feeding your mind? Clean up your news diet with Particle.</p>
</blockquote>
<p>Kagi has helped me clean up search engine diet to help feed my mind. Kagi helps me stay curious, and to choose my own diet, rather than being force-fed one. I would rather pay $10/month to stay mentally healthy than to succumb to a decaying brain from only relying on LLM responses for facts and learning.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Gardening]]></title>
            <link>https://blog.eliperkins.com/gardening</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/gardening</guid>
            <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>It's not only for your veggies and herbs.</p>]]></description>
            <content:encoded><![CDATA[<p>An old coworker of mine would spend an hour or two each week cleaning up warts or dead code in the codebase we worked on. Some weeks it would be taking on that refactor that would make the code more readable. Some weeks it would be fixing that silly edge case bug that only two users had reported over the last 12 months. <strong>He called it "gardening".</strong></p>
<p>Sometimes a garden fills up with weeds, and just needs an hour or two of someone pulling them up to make things pretty again. Other times it's applying fertilizer to the garden so that it can grow healthy and strong. In a codebase, gardening could be updating a dependency, or fixing that nagging warning that keeps popping up in your logs. Gardening could be taking a look at a flamegraph or stacktrace to figure out if there's a hot path that's worth optimizing.</p>
<p>Gardening is not to be confused with boyscouting in a codebase. Gardening is intentional, going in with the intent of pulling up weeds. Boyscouting is a by-product of some other change you're making, doing a refactor in favor of some feature you're building. I'm also happy to leave Uncle Bob and his terminology back in 2009.</p>
<p>Gardening means getting your hands at least a little bit dirty. It's shutting off Slack notifications for an hour or two, opening your IDE and working on some code in earnest. It's not something you need to ask for time to do, or something that your manager will ever tell you to do. It's about caring for your codebase, since you're living with it day in and day out.</p>
<p>Fridays are great days for gardening in your codebase. An hour or two to close out the week so that when you get back to your codebase on Monday, the weeds are a little less tall than they were last week. Maybe pull a few weeds from your garden today before the week is over.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Tools I Love: mise (mise-en-place)]]></title>
            <link>https://blog.eliperkins.com/tools-i-love-mise</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/tools-i-love-mise</guid>
            <pubDate>Thu, 15 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Use different versions of different languages and their toolchains with ease.</p>]]></description>
            <content:encoded><![CDATA[<div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>tl;dr</p>
<p>I love <a href="https://mise.jdx.dev">mise (aka mise-en-place)</a> for managing programming language versions.</p>
</div>
<p>I'm a bit of a <a href="https://eliperkins.com">polyglot engineer</a> these days. Most days I write Swift, but there's days I write JavaScript or Ruby or Go or whatever language du jour. Most programming languages and their toolchains share a similar pattern: there's some command line binary you'll need to run to compile/run/interpret your code.</p>
<pre><code class="language-csharp">$ <span class="pl-smi">node</span> <span class="pl-smi">someJavaScriptFile</span>.<span class="pl-smi">js</span>
$ <span class="pl-smi">ruby</span> <span class="pl-smi">some_ruby_file</span>.<span class="pl-smi">rb</span>
$ <span class="pl-smi">swift</span> <span class="pl-smi">run</span> <span class="pl-smi">SomeSwiftPackage</span>
</code></pre>
<p>These work just fine if you're working solo from your local developer environment. You do some <code>brew install</code> or download some installer for the toolchain from the official website, and you're off running.</p>
<h2 id="brew-install-my-toolv" class="heading-group group"><a href="#brew-install-my-toolv"><span class="heading-link">#</span><code>brew install my-tool@v???</code></a></h2>
<p>A new version of your toolchain is released, and you <code>brew upgrade my-tool</code> to get the latest version. This works great until you realize your CI/CD or deployment environment is on an older version, or some package of yours isn't ready yet, so you'll need to go back to the previous version.</p>
<p>Easy enough to do <code>brew install my-tool@v1</code>, right?</p>
<p>Except now <code>my-tool</code> is on version 1.0 <em>everywhere</em> across your computer, and your client's project is actually on <code>my-tool@v2</code> already.</p>
<p>You swap between versions by doing a <code>brew uninstall my-tool@v1 &#x26;&#x26; brew install my-tool@v2</code> in the morning to do your client work.</p>
<p>You swap back with a <code>brew uninstall my-tool@v2 &#x26;&#x26; brew install my-tool@v1</code> in the evening to do your personal work.</p>
<p><em>Pure madness</em>. 😩</p>
<h2 id="one-tool-to-rule-them-all" class="heading-group group"><a href="#one-tool-to-rule-them-all"><span class="heading-link">#</span>One tool to rule them all</a></h2>
<p><a href="https://mise.jdx.dev">mise</a> (aka mise-en-place) helps you manage multiple versions of your toolchains through <a href="https://mise.jdx.dev/configuration.html">a few mechanisms</a>:</p>
<ul>
<li><strong>Global versions</strong>: the default version for your development environment, regardless of current directory.</li>
<li><strong>Project versions</strong>: the resolved version for a given project, based on the existence of a <code>mise.toml</code> file in the directory tree.</li>
<li><strong><a href="https://mise.jdx.dev/configuration.html#idiomatic-version-files">Idiomatic version files</a></strong>: language-specific files like <code>.ruby-version</code>, <code>.nvmrc</code>, <code>.go-version</code>, etc.</li>
</ul>
<p>Each of these versions coalesce into a set of toolchain versions that are automatically available on your <code>$PATH</code> just by changing directories. It's magical.</p>
<h2 id="ive-used-asdfnvmrbenv-before-this-is-nothing-new" class="heading-group group"><a href="#ive-used-asdfnvmrbenv-before-this-is-nothing-new"><span class="heading-link">#</span>"I've used <code>asdf</code>/<code>nvm</code>/<code>rbenv</code> before, this is nothing new."</a></h2>
<p>Yes yes yes, of course. I used <code>asdf</code> for years too. But what's great about <code>mise</code> is that <strong>it Just Works™ for nearly every language or toolchain that I've encountered</strong>, without needing to install Yet Another Version Manager.</p>
<p>Over my career, I've used <code>mise</code> with everything from Go, Node, Ruby, .NET and C#, Java, and Rust. 99.9% of the time, mise works right out of the box. Even when my work project needed to have a different .NET SDK version versus the .NET runtime version, a handful of <a href="https://www.hanselman.com/blog/side-by-side-user-scoped-net-core-installations-on-linux-with-dotnetinstallsh">symlinks between mise's isolated installs</a> made it easy to jump between projects that had awkward setups.</p>
<hr>
<p>Of the tools I rely on in my development environment, <code>mise</code> has become one of the first I install, and one I can rely on out of the box.</p>
<p>Next time you go to <code>brew install</code> some programming language or toolchain, think twice, and <strong>use <a href="https://mise.jdx.dev"><code>mise</code></a> instead</strong>.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Stackless Stacked Pull Requests]]></title>
            <link>https://blog.eliperkins.com/stackless-stacked-prs</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/stackless-stacked-prs</guid>
            <pubDate>Thu, 24 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>How I've made my workflow more efficient with <code>git-pile</code> and gotten feedback on my work faster and easier.</p>]]></description>
            <content:encoded><![CDATA[<p>No one likes to review a <span class="font-mono text-sm"><span class="text-green-700">+4,490</span>/<span class="text-red-700">-903</span></span> pull request. It's far too much code for one reviewer to hold in their head, and results in worse feedback and slower review times. However, sometimes a large swath of code needs to be changed to close out an issue or to ship a new feature.</p>
<p>One of my favorite Kent Beck-isms is "<a href="https://x.com/KentBeck/status/250733358307500032">Make the change easy, then make the easy change</a>."</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">for each desired change, make the change easy (warning: this may be hard), then make the easy change</p>&mdash; Kent Beck 🌻 (@KentBeck) <a href="https://twitter.com/KentBeck/status/250733358307500032?ref_src=twsrc%5Etfw">September 25, 2012</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I'm sure my team is tired of hearing me say it, but it's a great way to break down changes into smaller chunks that are easier to reason about and review. <a href="https://martinfowler.com/articles/preparatory-refactoring-example.html">Martin Fowler calls this "prepartory refactoring"</a>, where changes are made in preparation for the larger change, making it trivial to make the final change.</p>
<p>One way to do this is to use a technique called "stacked pull requests."</p>
<p><a href="/great-contributions-to-a-codebase#use-stacked-pull-requests-to-break-down-a-large-diff-or-set-of-featuresfunctionality">Stacked pull requests is something I've talked about before</a> as part of my "Great Contributions to a Codebase" post. The idea is to break down on monolithic pull request into smaller chunks, each on a separate branch, and where each PR points at the branch ahead of it. Each of these PRs can be reviewed independently, and then merged from the first PR to the last, rebasing along the way as each PR is merged. This daisy-chaining of PRs (<a href="https://github.blog/developer-skills/github/github-protips-tips-tricks-hacks-and-secrets-from-sarah-vessels/">as my colleague, Sarah Vessels, calls it</a>) is a pattern of organizing branches where PRs for each piece of functionality make the change easy and then make the easy change.</p>
<p>However, this still involves some human overhead, along with the additional <code>git rebase</code> needed to keep merge history clean as each PR is merged. While it's not a ton of work, compounding with CI time, it can make it tough to keep stacked PRs up-to-date. There's tools that have come out like <a href="https://graphite.dev/blog/stacked-prs">Graphite's Stacked PRs</a> and <a href="https://www.modular.com/blog/announcing-stack-pr-an-open-source-tool-for-managing-stacked-prs-on-github">Modular's Stack PR tool</a> that help automate this process, but I've found a simpler way to manage stacked PRs that doesn't require any additional tooling.</p>
<p>Earlier this year, I started using <a href="https://github.com/keith/git-pile"><code>git-pile</code></a> by Keith Smiley and Dave Lee, which is a Facebook-/Phabricator-influence way of creating stacked PRs. In this approach, instead of creating a branch for each commit and for each PR, as a developer, I commit directly to <code>main</code> on my local working copy, and use <code>git submitpr</code> to have <code>git-pile</code> take that commit and create a PR for it. Under the hood, <code>git-pile</code> uses a separate git worktree of my repo, creating a new branch for each invocation of <code>git submitpr</code>, and then pushing that branch to GitHub to create a PR. My main working copy is always in a state I can anticipate, and without the need for an additional set of metadata from a service like Graphite or to daisy-chain PR after PR to keep developing locally.</p>
<p>This involves a bit of a change in my mental model of how I work with git and my codebase. Instead of having a feature-branch-ish approach with daisy-chained stacked PRs, where my functionality lives in a set of <code>n</code> branches, all which depend on each other, instead, I anticipate that my <code>main</code> branch in my local working copy will <em>eventually</em> be the same as what's on <code>origin/main</code>, after responding to feedback from PR review and working with my teammates. However, <strong>I'm never blocked from merging my stacked PRs into <code>main</code></strong> because my local <code>main</code> is already up-to-date! No rebasing needed, other than <code>git pull</code>-ing changes from <code>origin/main</code> to my local <code>main</code>.</p>
<p>This has also changed how I stack my pull requests too. I've found ways to break down my PRs into idempotent pieces, rather than daisy-chained ones, where I can set myself up by making the change easy, and then finally making the easy change once the other PRs have been merged.</p>
<p>Let's look at a contrived example. Say I'm working on a feature that requires:</p>
<ol>
<li>Changing legacy code to add a new <code>isFeatureXYZEnabled</code> property</li>
<li>Creating a new controller that's used when <code>isFeatureXYZEnabled == true</code></li>
<li>Changing the behavior within the legacy code to use the new controller if the feature is enabled</li>
</ol>
<p>In a stacked PR model, I'd have three PRs, each dependent on the one before it. So something like:</p>
<ol>
<li><strong>[1/3] Add a new <code>isFeatureXYZEnabled</code> property</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+400</span>/<span class="text-red-700">-90</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-xyz-feature</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">main</span> • 1 commit</li>
<li><strong>[2/3] Add <code>ModernXYZController</code> for the new feature</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+215</span>/<span class="text-red-700">-33</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-modern-xyzcontroller</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-xyz-feature</span> • 1 commit</li>
<li><strong>[3/3] Use <code>ModernXYZController</code> if enabled</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+10</span>/<span class="text-red-700">-30</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/enable-xyz</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-modern-xyzcontroller</span> • 1 commit</li>
</ol>
<p><img src="/images/daisy-chained-prs.png" alt="git branches showing the daisy-chained pull requests visually"></p>
<p>However, with a "stackless" stacked PR model with <code>git-pile</code>, I can break this down into two PRs that are idempotent where PR #1 and #2 can be merged in any order (<a href="https://github.blog/news-insights/product-news/github-merge-queue-is-generally-available/">or even simulatenously with a merge queue</a>), and where the third PR can either be stacked on the first two, or wait until I've gotten feedback on the first two and have them merged into <code>main</code>. So something like:</p>
<ol>
<li><strong>Add a new <code>isFeatureXYZEnabled</code> property</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+400</span>/<span class="text-red-700">-90</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-xyz-feature</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">main</span> • 1 commit</li>
<li><strong>Add <code>ModernXYZController</code> for the new feature</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+215</span>/<span class="text-red-700">-33</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-modern-xyzcontroller</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">main</span> • 1 commit</li>
<li><strong>[Draft] Use <code>ModernXYZController</code> if enabled</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+625</span>/<span class="text-red-700">-123</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/enable-xyz</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">main</span> • 3 commits</li>
</ol>
<p><img src="/images/stackless-stacked-prs.png" alt="git branches showing the stackless stacked pull requests visually"></p>
<p>This has sped up my personal development loop a ton, where I can <em>assume</em> that PRs #1 and #2 will get feedback from my team and get merged into <code>origin/main</code> at any point, <em>just like I already have in my local working copy of <code>main</code></em>! No more branch-of-a-branch-of-a-branch while I get feedback from my team, or needing to rebase daisy-chained PRs. Yes, there are totally times when stacked PRs are needed, and <code>git-pile</code> even supports that flow with the <code>--onto</code> flag, however, this change in my workflow has led me personally to making the change easy first, and then making the easy change. So after merging my PRs in upstream, upstream looks very similar to my local <code>main</code> branch:</p>
<p><img src="/images/after-merge.png" alt="git branches showing the end result after merging with linear history"></p>
<p>There's so much more that <code>git-pile</code> does, and I feel like I've only personally become comfortable with a portion of it yet. However, this notion of stackless stacked PRs is something that can be adopted even without <code>git-pile</code> (even though it does make it <em>so</em> much easier).</p>
<p>Strive to make Kent Beck happy: make the change easy, then make the easy change. And do it with small, reviewable PRs. 🚀</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Writing Great Release Notes Doesn't Need to Be Hard]]></title>
            <link>https://blog.eliperkins.com/great-release-notes</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/great-release-notes</guid>
            <pubDate>Fri, 23 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Passing on the opportunity to engage with your customers means giving up on showing your biggest fans that you're putting work into polishing and improving your app.</p>]]></description>
            <content:encoded><![CDATA[<p>Communicating with your app's customers is an art. Large companies and lazy businesses choose the lazy way out, writing the infamous "Bug fixes and performance improvements" week after week for their app's release notes. <strong>Writing great release notes doesn't need to be hard</strong>. Passing on the opportunity to engage with your customers means giving up on showing your biggest fans that you're putting work into polishing and improving your app.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Companies don’t care about release-notes.<br><br>And Apple isn’t setting a great example 😂 <a href="https://t.co/hQ6ngAPM7a">pic.twitter.com/hQ6ngAPM7a</a></p>&mdash; Jelle Prins (@jelleprins) <a href="https://twitter.com/jelleprins/status/1746441317452759477?ref_src=twsrc%5Etfw">January 14, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>When <a href="https://github.com/mobile">GitHub Mobile</a> began to scale from monthly releases to biweekly releases to now weekly releases, we needed to empower our release captains with the ability to write release notes that were not only on-brand, but informative and consistent. I worked with <a href="https://brianlovin.com">Brian Lovin</a> and the team to put together a set of guidelines to write release notes that focused on the customer, rather than on the company or bragging about the app itself. Every release, that week's release captain would get a list of the changes going into the app, drafted up a set of release notes, and had them approved not by copywriters, but by the team itself, empowering individual contributors, engineering managers, product managers and designers to speak with the same voice.</p>
<div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
<p>Brian Lovin is one of the greatest at pitching products that I've ever worked with. Checkout <a href="https://www.campsite.co/changelog">Campsite's Changelog</a> or <a href="https://x.com/brian_lovin">how he builds in public on his social media</a> 🏕️.</p>
<p>Much of what's in this document is from his mentorship and guidance!</p>
</div>
<p>These are the guidelines we put together for writing great release notes for GitHub Mobile. We hope you can use them to delight your app's superfans with your next release.</p>
<hr>
<p>Every week we ship new software to our users. The way we talk about our changes matters: release notes build excitement for our new features, inform people about what's fixed or improved, and remind people that our apps are constantly getting better with bug fixes.</p>
<p>It's important for our release notes to communicate clearly and consistently. We want our release notes to have "one voice," be grammatically correct, be ordered by impact on the user, and when applicable, instruct the user what actions they should take next. Also, checkout GitHub's overall <a href="https://brand.github.com/brand-identity/voice-and-tone">Voice &#x26; tone guidelines</a>.</p>
<h2 id="guidelines" class="heading-group group"><a href="#guidelines"><span class="heading-link">#</span>Guidelines</a></h2>
<h3 id="naming--capitalization" class="heading-group group"><a href="#naming--capitalization"><span class="heading-link">#</span>Naming &#x26; Capitalization</a></h3>
<p>Follow <a href="https://brand.github.com/brand-identity/attributes">GitHub's Brand Content guidelines</a> for naming and capitalizing features and products within release notes.</p>
<p>We reserve capitalization for the products and programs that define the GitHub experience and where it’s needed for clarity. We use consistent capitalization across marketing pages, docs, blog posts, and wherever else the name appears at launch and subsequently.</p>
<h4 id="rule-of-thumb" class="heading-group group"><a href="#rule-of-thumb"><span class="heading-link">#</span>Rule of thumb</a></h4>
<p>When first introduced, primary GitHub owned projects, products, and features are capitalized, like <code>GitHub Codespaces</code>. After that, you can drop the <code>GitHub</code> and simply refer to the feature by its name, <code>Codespaces</code>. When referring to the feature in the product, it’s sentence cased (e.g., <code>New codespace</code> for button text or <code>Codespaces</code> as a navigation link).</p>
<h4 id="lowercase" class="heading-group group"><a href="#lowercase"><span class="heading-link">#</span>Lowercase</a></h4>
<p>Use lowercase names for features and products that aren’t distinct to or ownable by GitHub.</p>
<p><strong>Examples</strong>: insights, accounts, payments, integrations, pull requests, security alerts, search</p>
<h4 id="capitalized" class="heading-group group"><a href="#capitalized"><span class="heading-link">#</span>Capitalized</a></h4>
<p>Use capitalized names for features and products that are distinct to or ownable by GitHub.</p>
<p><strong>Examples</strong>: Octocat, Pulse, Enterprise Server</p>
<p>Some features and products can introduce confusion into an experience if not capitalized:</p>
<p><strong>Examples</strong>: GitHub Pages, GitHub Projects</p>
<p>Note: Use GitHub Projects on the first instance it is used in writing. After capitalize Projects when you are referring to the capability (feature) but this is not necessary if you are talking about it as a noun (i.e. You can share projects with your team.).</p>
<h3 id="tone-and-tense" class="heading-group group"><a href="#tone-and-tense"><span class="heading-link">#</span>Tone and tense</a></h3>
<h4 id="avoid-third-person-when-talking-about-github-or-our-team" class="heading-group group"><a href="#avoid-third-person-when-talking-about-github-or-our-team"><span class="heading-link">#</span>Avoid third-person when talking about GitHub or our team</a></h4>
<p>Avoid using the word <code>we</code> in release notes. Instead, focus attention on the features and the benefits themselves.</p>
<pre><code class="language-diff"><span class="pl-md">- We made it easier to do foo.</span>
<span class="pl-mi1">+ A new widget makes it easier to do foo.</span>
</code></pre>
<p>It is fine to address users using the personal <code>you</code>, as this still describes the benefit to the user: <code>Foo makes it easier to see the status of your bar.</code></p>
<h4 id="avoid-the-word-now-when-indicating-when-the-change-will-affect-the-user" class="heading-group group"><a href="#avoid-the-word-now-when-indicating-when-the-change-will-affect-the-user"><span class="heading-link">#</span>Avoid the word <code>now</code> when indicating when the change will affect the user</a></h4>
<p>In most cases, this word can be dropped to better indicate the fix and plays nicely with the <a href="#make-it-concise">"Make it concise"</a> principle of avoiding superfluous terms. If the user is reading the release notes about a given release, the timeframe is implicit based on installing the update.</p>
<p>Leverage the <a href="https://en.wikipedia.org/wiki/Imperative_mood">imperative mood</a> followed by the <a href="#focus-on-outcomes-and-behavior-changes">outcome for the user</a>.</p>
<pre><code class="language-diff"><span class="pl-md">- Nested task lists now indent properly in issue comments.</span>
<span class="pl-mi1">+ Nested task lists indent properly in issue comments.</span>
</code></pre>
<pre><code class="language-diff"><span class="pl-md">- You can now view threaded comments on pull requests.</span>
<span class="pl-mi1">+ View threaded comments on pull requests to see what others are saying.</span>
</code></pre>
<pre><code class="language-diff"><span class="pl-md">- Your scroll-position in code views is now correctly maintained when rotating your device.</span>
<span class="pl-mi1">+ Rotate your device without losing your scroll position in code views.</span>
</code></pre>
<h3 id="whats-new" class="heading-group group"><a href="#whats-new"><span class="heading-link">#</span>What's New</a></h3>
<p>New features, or noticeable improvements to existing features, should always be in the <code>What's new</code> section of the release notes.</p>
<h4 id="focus-on-outcomes-and-behavior-changes" class="heading-group group"><a href="#focus-on-outcomes-and-behavior-changes"><span class="heading-link">#</span>Focus on outcomes and behavior changes</a></h4>
<p>Users want to know how changes or fixes affect their day to day lives. Does it make something easier? Is a new action possible? Is a flow faster? Rather than plainly describing a change, try to emphasize the outcome or benefits for a user.</p>
<pre><code class="language-diff"><span class="pl-md">- Support quote reply for discussions comments</span>
<span class="pl-mi1">+ Quote reply a comment in a discussion to keep the conversation going.</span>
</code></pre>
<h4 id="write-like-a-human" class="heading-group group"><a href="#write-like-a-human"><span class="heading-link">#</span>Write like a human</a></h4>
<p>Write like a human. Explain what's new in a professional, but not stuffy way. We're talking to real people, and they expect honest and clear communication.</p>
<pre><code class="language-diff"><span class="pl-md">- Added profile tab to make it easier to navigate.</span>
<span class="pl-mi1">+ A new Profile tab makes it easier to get to your profile from anywhere in the app.</span>
</code></pre>
<h4 id="use-full-sentences" class="heading-group group"><a href="#use-full-sentences"><span class="heading-link">#</span>Use full sentences</a></h4>
<p>Use complete thoughts to clearly communicate an idea or feature.</p>
<pre><code class="language-diff"><span class="pl-md">- Viewing repositories list on iPad shows split view</span>
<span class="pl-mi1">+ Viewing a list of repositories on iPad shows the list and repository details in a split view.</span>
</code></pre>
<h4 id="milestone-ships-are-introduced" class="heading-group group"><a href="#milestone-ships-are-introduced"><span class="heading-link">#</span>Milestone ships are "introduced"</a></h4>
<p>For our milestone ships – like Discussions – we "introduce" the new functionality with a more excited announcement-like voice. In these cases, use a few sentences to describe the core functionality, and guide users to the place where they can try the new feature.</p>
<pre><code class="language-diff"><span class="pl-md">- Discussions are now supported. Create, edit, and delete discussions in repositories where Discussions are enabled.</span>
<span class="pl-mi1">+ Introducing Discussions, a new way to connect with your community, team, and collaborators on GitHub! Share new ideas, ask questions, and find inspiration within your repository.</span>
</code></pre>
<h4 id="use-an-active-voice" class="heading-group group"><a href="#use-an-active-voice"><span class="heading-link">#</span>Use an active voice</a></h4>
<p>Use active verbs like "viewing" and "tapping" and "browsing" instead of their passive variants like "When foo is viewed" or "When foo is tapped".</p>
<pre><code class="language-diff"><span class="pl-md">- When a new issue is being created, there is now a loading spinner.</span>
<span class="pl-mi1">+ Creating a new issue shows a loading spinner.</span>
</code></pre>
<h3 id="bug-fixes" class="heading-group group"><a href="#bug-fixes"><span class="heading-link">#</span>Bug Fixes</a></h3>
<p>People may scan bug fixes to see if an issue they reported or experienced is now resolved. Because people are scanning this list, we must make our documentation concise and front load the fix in our copywriting.</p>
<h4 id="focus-on-outcomes-and-behavior-changes-1" class="heading-group group"><a href="#focus-on-outcomes-and-behavior-changes-1"><span class="heading-link">#</span>Focus on outcomes and behavior changes</a></h4>
<p>Users want to know how bug fixes actually affect them. Are they now unblocked? Is their specific use case now supported? Focusing on behaviors creates incentive for users to poke around and try things in the apps.</p>
<pre><code class="language-diff"><span class="pl-md">- Tapping on a notification no longer selects the wrong notification</span>
<span class="pl-mi1">+ Tapping on a notification selects the correct notification.</span>
</code></pre>
<h4 id="make-it-concise" class="heading-group group"><a href="#make-it-concise"><span class="heading-link">#</span>Make it concise</a></h4>
<pre><code class="language-diff"><span class="pl-md">- We discovered that when people were navigating through the file tree, if they long-pressed on the back button, there would be cases that the app could crash. This is now fixed.</span>
<span class="pl-mi1">+ Fixed a crash that occurred when navigating files and long-pressing the back button.</span>
</code></pre>
<h4 id="front-load-the-fix" class="heading-group group"><a href="#front-load-the-fix"><span class="heading-link">#</span>Front load the fix</a></h4>
<p>Quickly communicate the feature or surface where the bug fix occurred so that people can quickly decide if it's important to them.</p>
<pre><code class="language-diff"><span class="pl-md">- When there is expandable content, and when dark mode is enabled, user profile READMEs render correctly.</span>
<span class="pl-mi1">+ User profile READMEs with expandable content render correctly in light and dark mode.</span>
</code></pre>
<h4 id="use-active-voice-when-possible" class="heading-group group"><a href="#use-active-voice-when-possible"><span class="heading-link">#</span>Use active voice when possible</a></h4>
<p>This isn't always possible for certain kinds of bug fixes, but when possible we should maintain an active voice when describing fixes.</p>
<pre><code class="language-diff"><span class="pl-md">- Fix nested task list indentation</span>
<span class="pl-mi1">+ Nested task lists indent properly in issue comments.</span>
</code></pre>
<h3 id="what-if-we-have-a-week-with-no-user-facing-changes" class="heading-group group"><a href="#what-if-we-have-a-week-with-no-user-facing-changes"><span class="heading-link">#</span>What if we have a week with no user-facing changes?</a></h3>
<p>Has the team been heads down on new features behind feature flags? Is the changelog full of only feature-flagged changes? <strong>That's okay!</strong></p>
<p>Release notes are one of the few points of interactions that we have to communicate <em>directly</em> with our customers. By using release notes like "Bug fixes and performance improvements," we've given away an opportunity to tell our customers what great features we've been hard at work on over previous weeks as well. Additionally, "Bug fixes and performance improvements" is an opaque phrase which doesn't indicate to our customers where those fixes can be found or what performance has been improved. Let's do better than this!</p>
<p>As release captain, if that week's release notes have no user-facing changes in release notes:</p>
<ol>
<li><strong>Use the previous release's release notes.</strong></li>
<li>If last week's release notes include an "Introducing" change, <strong>check in with that team to see how to best rephrase that line!</strong></li>
</ol>
<p>This gives us a second chance to communicate the changes that our customers <em>will</em> get in that build, as well as giving the previous week's release notes another week to get in front of our customers.</p>
<p>If a feature has been <a href="#milestone-ships-are-introduced">"Introduced"</a>, feel free to work with the epic team to adjust the copy such that "Introducing..." can be adjusted to the benefit of that feature. For example:</p>
<pre><code class="language-diff"><span class="pl-md">- "Introducing Actions! View workflow run details and manage your pull request checks on the go."</span>
<span class="pl-mi1">+ "View workflow run details and manage your pull request checks on the go with GitHub Actions."</span>
</code></pre>
<p>To re-iterate, <em>it's okay</em> to have weeks where we have no user-facing changes. When that happens, use the previous week's release notes for this week's release notes.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[What makes a great contribution to a codebase?]]></title>
            <link>https://blog.eliperkins.com/great-contributions-to-a-codebase</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/great-contributions-to-a-codebase</guid>
            <pubDate>Wed, 05 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Guidelines and principles learned in going from two to seven ICs.</p>]]></description>
            <content:encoded><![CDATA[<p>Some years ago, my team at GitHub grew from 2 full time individual iOS contributors (and one <a href="https://github.com/rnystrom">manager who could still code</a>), to 7 of us ICs. Growing a team by 3.5x meant that certain behaviors, concepts or principles that were easy enough to agree upon with just two folks now needed to scale up to many independent people.</p>
<p>I wrote up this document for our repo's <code>CONTRIBUTING.md</code> to try to capture our growing team's guiding light in what made a great pull request or the building blocks of a great feature. A good chunk of it still holds true today. Here's <code>github/mobile-ios</code>'s guidelines for great contributions.</p>
<hr>
<h2 id="development-principles" class="heading-group group"><a href="#development-principles"><span class="heading-link">#</span>Development Principles</a></h2>
<h3 id="testing" class="heading-group group"><a href="#testing"><span class="heading-link">#</span>Testing</a></h3>
<h4 id="leverage-unit-tests-to-prevent-bugs-regressions" class="heading-group group"><a href="#leverage-unit-tests-to-prevent-bugs-regressions"><span class="heading-link">#</span>Leverage unit tests to prevent bugs, regressions</a></h4>
<p>Caught a bug and found a way to fix it? Great, you're halfway there!</p>
<p>One way to prevent a bug from recurring is to write a unit test around the bug. Writing a failing test that isolates the bug and refactoring using <a href="https://www.codecademy.com/articles/tdd-red-green-refactor">"red, green, refactor"</a> will not only fix the bug but give the codebase a way to ensure other changes don't break it again in the future.</p>
<h4 id="prefer-unit-tests-before-snapshot-tests" class="heading-group group"><a href="#prefer-unit-tests-before-snapshot-tests"><span class="heading-link">#</span>Prefer unit tests before snapshot tests</a></h4>
<p>Prefer unit testing behaviors on view models to assert behaviors. View models allow for testing at a more granular level without involving UIKit.</p>
<h4 id="prefer-snapshot-testing-an-individual-view-before-snapshot-testing-a-view-controller" class="heading-group group"><a href="#prefer-snapshot-testing-an-individual-view-before-snapshot-testing-a-view-controller"><span class="heading-link">#</span>Prefer snapshot testing an individual view before snapshot testing a view controller</a></h4>
<p>If a snapshot test is a useful way to test a behavior (e.g., asserting correct rendering of view in light mode and dark mode), prefer testing an individual view in isolation, rather than testing the whole view controller.</p>
<h4 id="snapshot-tests-as-a-tool-in-the-toolbox-not-a-hammer-for-all-tests" class="heading-group group"><a href="#snapshot-tests-as-a-tool-in-the-toolbox-not-a-hammer-for-all-tests"><span class="heading-link">#</span>Snapshot tests as a tool in the toolbox, not a hammer for all tests</a></h4>
<p>Remember, snapshot tests are only one tool that you can use to write tests. Before jumping to writing a snapshot test, ask yourself what behavior is trying to be tested. What assertions can be run on the view model before we test the view layer? What assertions can we run on an instance of a view controller, rather than rendering the view controller in its entirety.</p>
<p>All snapshot tests will involve UIKit as part of the system under test, meaning changes to UIKit will affect the test itself. We've seen snapshot tests be invalidated between iOS versions, Xcode versions, operating systems and more, so ensure that these variables are acceptable to the test you're writing.</p>
<h3 id="core-before-more" class="heading-group group"><a href="#core-before-more"><span class="heading-link">#</span>"Core before more"</a></h3>
<h4 id="prefer-well-tested-features-over-rushed-corner-cutting-solutions" class="heading-group group"><a href="#prefer-well-tested-features-over-rushed-corner-cutting-solutions"><span class="heading-link">#</span>Prefer well-tested features over rushed corner-cutting solutions</a></h4>
<p>Got that new mockup in Figma that you're ready to crank on? Great! Take a moment to think about how to best land it into the codebase and what tests you might write as a part of your PR.</p>
<p>If you feel like landing a particular feature might take twice as long to do properly, that's totally okay! Let your team know, talk to your engineering manager about it, and ensure that writing tests is part of estimates on your project.</p>
<h4 id="use-todos-and-linked-issues-to-track-follow-ups" class="heading-group group"><a href="#use-todos-and-linked-issues-to-track-follow-ups"><span class="heading-link">#</span>Use <code>TODO</code>s and linked issues to track follow-ups</a></h4>
<h4 id="leverage-feature-flags-to-roll-features-out-while-still-landing-code-into-main" class="heading-group group"><a href="#leverage-feature-flags-to-roll-features-out-while-still-landing-code-into-main"><span class="heading-link">#</span>Leverage feature flags to roll features out while still landing code into <code>main</code></a></h4>
<p>One great way to move at a higher velocity while still writing well-tested code is to leverage feature flags to hide your feature in production until it's ready for prime time.</p>
<p>Feature flags can also help you deploy features to certain environments or "rings", like only to TestFlight or only staff users. Use this to get feedback from a specific group of folks, rather than rushing out to production.</p>
<h4 id="refactor-existing-code-in-place" class="heading-group group"><a href="#refactor-existing-code-in-place"><span class="heading-link">#</span>Refactor existing code in-place</a></h4>
<p>Rather than rewriting code anew, prefer refactoring existing code in-place with a series of changes.</p>
<p>If it's tough to refactor a certain pattern in-place, try using a feature flag to allow for toggling between the new and old code.</p>
<h4 id="prefer-consistency-in-patterns-code-style" class="heading-group group"><a href="#prefer-consistency-in-patterns-code-style"><span class="heading-link">#</span>Prefer consistency in patterns, code style</a></h4>
<p>Follow established patterns within the codebase by default.</p>
<p>Have a new pattern that feels like it'd work out well? Great! Put the pull request up and start a discussion with your teammates.</p>
<p><strong><em>Need</em></strong> a new pattern to accomplish a task within the codebase? First, ask yourself if there's a way to do this without a new pattern first. If not, cool! Put the PR up and discuss it with your teammates.</p>
<h3 id="small-atomic-pull-requests" class="heading-group group"><a href="#small-atomic-pull-requests"><span class="heading-link">#</span>Small atomic pull requests</a></h3>
<p>Prefer pull requests that accomplish a single, focused change. Consider how easily your pull request could be reverted if needed.</p>
<h4 id="use-stacked-pull-requests-to-break-down-a-large-diff-or-set-of-featuresfunctionality" class="heading-group group"><a href="#use-stacked-pull-requests-to-break-down-a-large-diff-or-set-of-featuresfunctionality"><span class="heading-link">#</span>Use stacked pull requests to break down a large diff or set of features/functionality</a></h4>
<p>To keep code review as focused as possible, break up your large pull request into a series of pull requests.</p>
<p>Instead of one PR like:</p>
<ul>
<li><strong>Add XYZ feature</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+1,029</span>/<span class="text-red-700">-1,390</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-xyz-feature</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">main</span></li>
</ul>
<p>Prefer breaking up the PR into a series of pull requests like:</p>
<ol>
<li><strong>[1/3] Add GraphQL module for XYZ</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+400</span>/<span class="text-red-700">-90</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-xyz-graphql</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">main</span></li>
<li><strong>[2/3] Create XYZ view controller</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+215</span>/<span class="text-red-700">-33</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/create-xyz-viewcontroller</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/add-xyz-graphql</span></li>
<li><strong>[3/3] Use custom XYZ control throughout the app</strong><br /><span class="font-mono text-sm"><span class="text-green-700">+325</span>/<span class="text-red-700">-333</span></span> merging <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/custom-xyz-control</span> into <span class="font-mono text-sm bg-sky-50 text-sky-800 border border-sky-800/24 px-1 rounded-md">ep/create-xyz-viewcontroller</span></li>
</ol>
<p>This will create three discrete pull requests for your teammates to focus on, allowing for a better review of the code you wrote and for better code to be landed in piecemeal.</p>
<p>Further reading:</p>
<ul>
<li><a href="https://github.com/keith/git-pile"><code>git-pile</code></a></li>
<li><a href="https://www.michaelagreiler.com/stacked-pull-requests/">"Stacked pull requests: make code reviews faster, easier, and more effective"</a></li>
</ul>
<h3 id="code-review" class="heading-group group"><a href="#code-review"><span class="heading-link">#</span>Code Review</a></h3>
<h4 id="feel-free-to-review-pull-requests-from-other-projects" class="heading-group group"><a href="#feel-free-to-review-pull-requests-from-other-projects"><span class="heading-link">#</span>Feel free to review pull requests from other projects</a></h4>
<p>Leverage code review as a place to ask how another feature is being built. Dig deeper into a feature to learn about it's architecture or the product itself.</p>
<p>Keep in mind that informed decisions may have been made by other folks on the team, so ask questions and get curious!</p>
<h4 id="ask-for-tests-during-code-review" class="heading-group group"><a href="#ask-for-tests-during-code-review"><span class="heading-link">#</span>Ask for tests during code review</a></h4>
<p>Do you see some code that might have complex behavior that isn't obvious or is potentially brittle? <strong>Asking the author for some tests is okay!</strong> Tests are not just for the author but also for Future Us™ as we change the code around it.</p>
<p>Code review is a great time to ask how certain parts of a pull request could be tested. Tests can also provide clarification on certain behaviors, allowing for complex states to be modeled and asserted against.</p>
<h2 id="submitting-pull-requests" class="heading-group group"><a href="#submitting-pull-requests"><span class="heading-link">#</span>Submitting Pull Requests</a></h2>
<h3 id="leverage-pull-request-templates" class="heading-group group"><a href="#leverage-pull-request-templates"><span class="heading-link">#</span>Leverage pull request templates</a></h3>
<p>Add further description about what the change that is being made does. Clarify questions that you think reviewers might have. Add steps on how to test the change out locally. Indicate if a change might be risky or requires other changes as well.</p>
<p>Pull requests currently have a template to help you answer some of these questions while also providing a consistent format for reviewers to learn more about the change you're proposing.</p>
<h3 id="git-commit-style" class="heading-group group"><a href="#git-commit-style"><span class="heading-link">#</span>Git commit style</a></h3>
<ul>
<li>Use the present tense ("Add feature" not "Added feature").</li>
<li>Use the imperative mood ("Move cursor to..." not "Moves cursor to...").</li>
<li>Try to limit the first line to 80 characters or less.</li>
<li>Focus on "why" within the body/message of the commit. Let your code focus on "what" changed.
<ul>
<li>e.g., "This refactor allows the app to send a query that works on all GitHub Enterprise instances" not "This changes the query by adding the <code>foo</code> field and removing the <code>bar</code> field"</li>
</ul>
</li>
<li>Reference issues and pull requests liberally after the first line.
<ul>
<li>Use phrases like <code>Closes #1039</code> to automate closing issues when your pull request is merged.</li>
</ul>
</li>
<li>Avoid writing commits like <code>Fix bug</code>, preferring more description
<ul>
<li>Remember that the git commit log will be helpful to Future Us™ and Future You™ too! Running <code>git blame</code> on code is a great way to get context as to why code is written a certain way.</li>
</ul>
</li>
</ul>
<h2 id="adrs" class="heading-group group"><a href="#adrs"><span class="heading-link">#</span>ADRs</a></h2>
<p>ADRs (or architecture decision records) are a great way to capture a snapshot of what decisions led to a certain architecture within the codebase.</p>
<p>Have a question about how different parts of the codebase work together? Curious as to how a certain module is architected? Check out the ADRs for it!</p>
<p>Writing a new module with a bunch of surface area? Have a proposal for a new architecture or pattern to follow? Write an ADR for it!</p>
<p>Our ADRs are stored within this repo at <code>docs/adrs</code>, but you can also find more ADRs within <code>github/mobile</code> for ADRs that affect both iOS and Android.</p>
<p>Further reading:</p>
<ul>
<li><a href="https://github.blog/2020-08-13-why-write-adrs/">GitHub Blog: "Why Write ADRs"</a></li>
</ul>
<h2 id="project-planning" class="heading-group group"><a href="#project-planning"><span class="heading-link">#</span>Project Planning</a></h2>
<h3 id="estimate-time-needed-to-write-testable-code-as-part-of-project-planning" class="heading-group group"><a href="#estimate-time-needed-to-write-testable-code-as-part-of-project-planning"><span class="heading-link">#</span>Estimate time needed to write testable code as part of project planning</a></h3>
<p>Even if the smallest possible change to accomplish your feature could be made within a day, ensure that you're also accounting for the time it takes to test the feature. "Testable code" could mean writing unit tests, refactoring to allow for writing tests, or spinning up a development environment to test the feature itself.</p>
<p>It's okay (and encouraged!) to advocate as much time as needed to write and test a new feature. Work with your team to ensure that estimates match what all folks on the team expect.</p>
<h3 id="break-down-epics-into-smaller-milestones-to-ship-more-often" class="heading-group group"><a href="#break-down-epics-into-smaller-milestones-to-ship-more-often"><span class="heading-link">#</span>Break down epics into smaller milestones to ship more often</a></h3>
<p>Talk with your team about sequencing a series of changes that ship to production rather than bundling a ton of code behind a single feature flag.</p>
<p>By shipping incrementally, we can gain more feedback and iterate towards correctness. This will let you focus on smaller diffs and changes as well!</p>
<h2 id="submitting-bug-reports" class="heading-group group"><a href="#submitting-bug-reports"><span class="heading-link">#</span>Submitting Bug Reports</a></h2>
<p>Bugs are tracked as GitHub issues. When you've come across a bug within GitHub for iOS, create an issue in <code>github/mobile-ios</code> and provide the following information by filling in the "Bug Report" template.</p>
<p>Explain the problem and include additional details to help us reproduce the problem:</p>
<ul>
<li><strong>Use a clear and descriptive title</strong> for the issue to identify the problem.</li>
<li><strong>Describe the exact steps which reproduce the problem</strong> in as many details as possible. For example, start by explaining how you opened the app and navigated to that screen, where you tapped, how you were able to get into a fixed or different state.</li>
<li><strong>Provide specific examples to demonstrate the steps</strong>. Include links to relevant content on GitHub.com to help us reproduce or navigate back to that screen within the app.</li>
<li><strong>Describe the behavior you observed after following the steps</strong> and point out what exactly is the problem with that behavior.</li>
<li><strong>Explain which behavior you expected to see instead and why.</strong></li>
<li><strong>Include screenshots and animated GIFs</strong> which show you following the described steps and clearly demonstrate the problem.</li>
<li><strong>If you're reporting that GitHub for iOS crashed</strong>, include your device info (what model iPhone/iPad? cellular or wifi?), operating system information, and version and build of the app that you're using (you can find this in the footer of Settings within the app).</li>
<li><strong>If the problem wasn't triggered by a specific action</strong>, describe what you were doing before the problem happened and share more information using the guidelines below.</li>
</ul>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Migrating from Gatsby to Next.js]]></title>
            <link>https://blog.eliperkins.com/migrating-from-gatsby-to-next</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/migrating-from-gatsby-to-next</guid>
            <pubDate>Fri, 24 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Sometimes paying off tech debt comes at the cost of a rewrite</p>]]></description>
            <content:encoded><![CDATA[<p>Some years ago, I migrated both my personal site and my blog to <a href="https://www.gatsbyjs.com">Gatsby</a>. At the time, Gatsby was the new hotness, using GraphQL for data fetching and with, at the time, a rich ecosystem of developers and plugins. The site rendered out to some static JavaScript and HTML, and ran completely statically on S3 and Cloudfront. At the cost of a dollar a month, both were completely functional for what I wanted them to be: a home for me on the internet.</p>
<p>As most developers do, I let my blog languish, and only really updated my personal site on occasion, like when I got a new job.</p>
<p>Last summer (2023), I got married, and as is a developer's rite of passage, I built <a href="http://eliandgeorgia.com">my own wedding website</a>. I really wanted to learn <a href="https://nextjs.org">Next.js</a>, and play around with technologies that were absolutely overkill for a website that would be used for a single event. In the process, I learned what a joy Next.js is to use, along with how productive I can be with <a href="https://tailwindcss.com">Tailwind CSS</a>. While I've written JavaScript professionally before, it's been many years since I've written JavaScript in earnest, so it was refreshing to once again experience how great the tooling can be when your developer tools aren't a black box built by one company (looking at you, Apple).</p>
<p>Since then, I've been looking at my Dependabot warnings growing in my personal website and blog, and also feeling like their design was rotting nearly as fast as the code instead the repo was. So as a fun educational side project, and knowing what it'd take to create a new Next.js app from my experience with my wedding website, I took the plunge to rewrite both my personal site and blog in Next.js.</p>
<p>After a few days, I had tossed all the old Gatsby code out the window, and had a prettier, more modern, more performant personal site and blog that I feel proud of again. Instead of having a dated look and legacy code as my footprint on the internet, I had a site that I can easily update with a quick <code>git push</code>.</p>
<p>Here's what I learned:</p>
<ul>
<li>Tailwind slaps, and makes me feel like a designer. 🎨 I can bring the design ideas that I know from mobile development, and apply a loose design system to my sites with just some HTML class names.</li>
<li>There's no need for GraphQL in a static site which doesn't fetch data from the internet.</li>
<li>Bringing <a href="https://github.com/eliperkins/eliperkins-blog/tree/df1f44435169efb1310f06e44ab5d72402458976/posts">my old blog content</a> over from Gatsby to Next.js meant <a href="https://github.com/eliperkins/eliperkins-blog/blob/df1f44435169efb1310f06e44ab5d72402458976/lib/posts.tsx">loading in and parsing Markdown files at build-time</a>.
<ul>
<li>I want to explore using MDX for Markdown in blog posts in the future.</li>
</ul>
</li>
<li><a href="https://yarnpkg.com/features/caching#zero-installs">Yarn PnP and zero-installs</a> are trivial to set up these days.
<ul>
<li>Do I need it for my personal site/blog? Definitely not.</li>
</ul>
</li>
<li>Next.js makes it easy to do code-splitting, SSR, and in general, create a modern performant site, without needing to think about the technical concepts. <em>I feel productive with it in a way that I don't feel with other tools</em>.</li>
<li>Having a site with continuous deployments on Actions, that I can just commit and push to makes it easier to make quick updates to content without needing to remember what credentials I need or what incantation of AWS APIs or button clicks are needed.</li>
<li>I can still write functioning JavaScript.</li>
</ul>
<p>For the curious, here's the tech stack overall:</p>
<ul>
<li>Framework: <a href="https://nextjs.org">Next.js</a> (with app directory)</li>
<li>Styling: <a href="https://tailwindcss.com">Tailwind CSS</a></li>
<li>Hosting: AWS <a href="https://aws.amazon.com/s3/">S3</a> + <a href="https://aws.amazon.com/cloudfront/">Cloudfront</a> + <a href="https://aws.amazon.com/route53/">Route 53</a></li>
<li>CI/CD: <a href="https://github.com/features/actions">GitHub Actions</a>
<ul>
<li><a href="https://github.com/brandonweiss/discharge"><code>@static/discharge</code></a> for easy deployments (which I should contribute back to!)</li>
</ul>
</li>
<li>Markdown parsing: <a href="https://remark.js.org">Remark</a></li>
<li>RSS: <a href="https://github.com/jpmonette/feed"><code>feed</code></a></li>
<li>Other: TypeScript, Yarn, Prettier, CodeQL + Dependabot</li>
</ul>
<p><em>Want to learn more? Reach out to Eli via <a href="mailto://eli.j.perkins@gmail.com">email</a> or on <a href="https://mastodon.online/@eliperkins">Mastodon</a>.</em></p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Validating Relay Queries With This One Weird Trick™]]></title>
            <link>https://blog.eliperkins.com/what-is-relay-validate</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/what-is-relay-validate</guid>
            <pubDate>Fri, 22 Mar 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>tl;dr: Use <code>relay --validate</code> to catch Relay validation errors in CI.</p>]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>tl;dr: Use <code>relay --validate</code> to catch Relay validation errors in CI!</p>
</blockquote>
<p>I'm a big fan of GraphQL (seriously, ask me about GraphQL). One tool I've been
absolutely loving lately has been <a href="https://facebook.github.io/relay/">Relay</a>. We
use Relay at <a href="https://clubhouse.io">Clubhouse</a> to build some really awesome
features in
<a href="https://itunes.apple.com/us/app/clubhouse/id1193784808?mt=8">Clubhouse for iOS</a>.
As of the writing of this blog post, we've got nearly 100 different components
using Relay in some way, shape or form!</p>
<h3 id="hold-up-whats-relay-again" class="heading-group group"><a href="#hold-up-whats-relay-again"><span class="heading-link">#</span>Hold up, what's Relay again?</a></h3>
<p>Relay is to GraphQL and data fetching what React is to DOM and UI. It's a
declarative way to dictate <em>what</em> data your components need, rather than <em>how
and when</em> to fetch it.</p>
<p>A typical use-case of Relay looks something like this. Say you've got an social
media app that shows a list of friends for the current user.</p>
<p>Each of these components gets data from a different part of our tree. We can
<strong>colocate</strong> our queries of how to fetch the data for each component using
Relay. A common way to do this is using Relay's <code>createFragmentContainer</code>
(there's other methods to do this too, to handle pagination, refetching, etc. ,
but for the sake brevity in this post, let's focus on fragment containers).</p>
<p>Some of the components may look like this:</p>
<pre><code class="language-jsx"><span class="pl-c">// in src/Avatar.js</span>
<span class="pl-k">const</span> <span class="pl-c1">Avatar</span> <span class="pl-k">=</span> ({ user }) <span class="pl-k">=></span> <span class="pl-k">&#x3C;</span>Image src<span class="pl-k">=</span>{<span class="pl-smi">user</span>.<span class="pl-smi">thumbnailImgSrc</span>} <span class="pl-k">/></span>;

<span class="pl-k">const</span> <span class="pl-c1">AvatarContainer</span> <span class="pl-k">=</span> <span class="pl-en">createFragmentContainer</span>(
  Avatar,
  graphql<span class="pl-s"><span class="pl-pds">`</span></span>
<span class="pl-s">    fragment AvatarContainer_user on User {</span>
<span class="pl-s">      thumbnailImgSrc</span>
<span class="pl-s">    }</span>
<span class="pl-s">  <span class="pl-pds">`</span></span>,
);

<span class="pl-c">// in src/FriendListItem.js</span>
<span class="pl-k">const</span> <span class="pl-c1">FriendListItem</span> <span class="pl-k">=</span> ({ user }) <span class="pl-k">=></span> (
  <span class="pl-k">&#x3C;</span>View<span class="pl-k">></span>
    <span class="pl-k">&#x3C;</span>Avatar user<span class="pl-k">=</span>{<span class="pl-smi">user</span>.<span class="pl-smi">thumbnailImgSrc</span>} <span class="pl-k">/></span>
    <span class="pl-k">&#x3C;</span><span class="pl-c1">Text</span><span class="pl-k">></span>{<span class="pl-smi">user</span>.<span class="pl-c1">name</span>}<span class="pl-k">&#x3C;/</span><span class="pl-c1">Text</span><span class="pl-k">></span>
  <span class="pl-k">&#x3C;/</span>View<span class="pl-k">></span>
);

<span class="pl-k">const</span> <span class="pl-c1">FriendListItemContainer</span> <span class="pl-k">=</span> <span class="pl-en">createFragmentContainer</span>(
  FriendListItem,
  graphql<span class="pl-s"><span class="pl-pds">`</span></span>
<span class="pl-s">    fragment FriendListItemContainer_user on User {</span>
<span class="pl-s">      name</span>
<span class="pl-s">      ...AvatarContainer_user</span>
<span class="pl-s">    }</span>
<span class="pl-s">  <span class="pl-pds">`</span></span>,
);

<span class="pl-c">// in src/FriendsList.js</span>
<span class="pl-k">const</span> <span class="pl-c1">FriendsList</span> <span class="pl-k">=</span> ({ currentUser }) <span class="pl-k">=></span> (
  <span class="pl-k">&#x3C;</span>FlatList
    data<span class="pl-k">=</span>{<span class="pl-smi">currentUser</span>.<span class="pl-smi">friends</span>}
    renderItem<span class="pl-k">=</span>{({ item }) <span class="pl-k">=></span> <span class="pl-k">&#x3C;</span>FriendListItem user<span class="pl-k">=</span>{item} <span class="pl-k">/></span>}
  <span class="pl-k">/></span>
);

<span class="pl-k">const</span> <span class="pl-c1">FriendListItemContainer</span> <span class="pl-k">=</span> <span class="pl-en">createFragmentContainer</span>(
  FriendListItem,
  graphql<span class="pl-s"><span class="pl-pds">`</span></span>
<span class="pl-s">    fragment FriendListItemContainer_currentUser on CurrentUser {</span>
<span class="pl-s">      friends {</span>
<span class="pl-s">        ...FriendListItemContainer_user</span>
<span class="pl-s">      }</span>
<span class="pl-s">    }</span>
<span class="pl-s">  <span class="pl-pds">`</span></span>,
);
</code></pre>
<p>Now each of our components <strong>composes</strong> together both is UI components and it's
data-requirements, declaratively!</p>
<h3 id="dope-relay-seems-cool-but-whats-this-__generated__-directory-ive-got-here" class="heading-group group"><a href="#dope-relay-seems-cool-but-whats-this-__generated__-directory-ive-got-here"><span class="heading-link">#</span>Dope. Relay seems cool. But what's this <code>__generated__</code> directory I've got here?</a></h3>
<p>The
<a href="https://facebook.github.io/relay/docs/en/compiler-architecture.html">Relay Compiler</a>
will read through our source code to find Relay components and their colocated
GraphQL queries to generate artifacts that will be used by the Relay runtime.</p>
<p>These artifacts look something like this (abbreviated here):</p>
<pre><code class="language-jsx"><span class="pl-c">// from src/__generated__/AvatarContainer_user.graphql.js</span>
<span class="pl-k">const</span> <span class="pl-c1">node</span> <span class="pl-c">/*: ReaderFragment*/</span> <span class="pl-k">=</span> {
  kind<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>Fragment<span class="pl-pds">"</span></span>,
  name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>AvatarContainer_user<span class="pl-pds">"</span></span>,
  type<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>User<span class="pl-pds">"</span></span>,
  metadata<span class="pl-k">:</span> <span class="pl-c1">null</span>,
  argumentDefinitions<span class="pl-k">:</span> [],
  selections<span class="pl-k">:</span> [
    {
      kind<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>ScalarField<span class="pl-pds">"</span></span>,
      alias<span class="pl-k">:</span> <span class="pl-c1">null</span>,
      name<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>thumbnailImgSrc<span class="pl-pds">"</span></span>,
      args<span class="pl-k">:</span> <span class="pl-c1">null</span>,
      storageKey<span class="pl-k">:</span> <span class="pl-c1">null</span>,
    },
  ],
};
(node<span class="pl-c">/*: any*/</span>).<span class="pl-c1">hash</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>693ff4889bc9965ae9f6512d628b7292<span class="pl-pds">'</span></span>; <span class="pl-c">// prettier-ignore</span>
<span class="pl-c1">module</span>.<span class="pl-smi">exports</span> <span class="pl-k">=</span> node;
</code></pre>
<p>We can see that for the <code>Avatar</code> component, the compiler generates a static set
of data for the GraphQL fragment it needs to fetch the data.</p>
<p>Relay recommends checking in these artifacts from the Relay Compiler into your
source control, as they're crucial and necessary to run your app.</p>
<p>As you work through your app, you'll likely run <code>yarn relay --watch</code> to run the
compiler in watch mode to automatically generate these artifacts and as you
build new components, pages, features and so on. You'll see that as your change
your GraphQL queries, the Relay Compiler will indicate what is changed, and you
can see the changes in your <code>git diff</code> as well.</p>
<pre><code class="language-bash">❯ yarn relay
yarn run v1.15.2
$ relay-compiler --src ./src --schema ./schema.graphql --watch

Writing js
Updated:
 - AvatarContainer_user.graphql.js
Unchanged: 2 files

❯ git status
On branch master
Changes not staged <span class="pl-k">for</span> commit:
  (use <span class="pl-s"><span class="pl-pds">"</span>git add &#x3C;file>...<span class="pl-pds">"</span></span> to update what will be committed)
  (use <span class="pl-s"><span class="pl-pds">"</span>git checkout -- &#x3C;file>...<span class="pl-pds">"</span></span> to discard changes <span class="pl-k">in</span> working directory)

	modified:   src/Avatar.js
	modified:   src/__generated__/AvatarContainer_user.graphql.js
</code></pre>
<p>However, <em>these artifacts are only autogenerated if you run the Relay Compiler
while you're working</em>! If a team member (or even yourself) forgets to run
<code>yarn relay</code> while you're working, and a GraphQL query changes, your generated
artifacts will be out of sync with your product code! 😰</p>
<p>🤔 <strong>So how do we fix this?</strong> How do we prevent our product code's queries from
coming out of sync with our autogenerated Relay artifacts?</p>
<p>Luckily, <strong>Relay makes this easy</strong>.</p>
<p>The Relay Compiler includes
<a href="https://github.com/facebook/relay/blob/0ec46fd8c52bd2f4128a55dae5d59cf8ecb5d633/packages/relay-compiler/bin/RelayCompilerBin.js#L76-L81">a flag called <code>--validate</code></a>.
This flag will run the Relay Compiler and if there are any autogenerated
artifacts that will be overwritten based on the GraphQL queries, the compiler
will indicate that an artifact is our of date and exit with an error.</p>
<pre><code class="language-bash">❯ yarn relay --validate
yarn run v1.15.2
$ relay-compiler --src ./src --schema ./schema.graphql --validate

Writing js
Out of date:
 - AvatarContainer_user.graphql.js
error Command failed with <span class="pl-c1">exit</span> code 101.
</code></pre>
<p>This makes it really easy to validate your Relay codebase in CI, just as you
might run your tests in CI! Add <code>relay --validate</code> to your CI flow today and
catch changes in GraphQL queries before they land on master.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[SE-0117, API Design, and You]]></title>
            <link>https://blog.eliperkins.com/se0117-has-the-power-to-change-sdk-design</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/se0117-has-the-power-to-change-sdk-design</guid>
            <pubDate>Tue, 19 Jul 2016 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Use composition over inheritance. Pass values into functions.</p>]]></description>
            <content:encoded><![CDATA[<p>A lot of conversation has been going around about
<a href="https://github.com/apple/swift-evolution/blob/991e901f3ac9bb8d0d070b618a77bdb0aab00fd5/proposals/0117-non-public-subclassable-by-default.md">SE-0117</a>
this week, as the proposal has entered its Active Review stage.</p>
<p>The tl;dr on this proposal is that all classes that are marked as <code>public</code> will
not be able to be subclassed unless also marked <code>open</code>. Additionally, it’s
functions, variables, and subscripts would also not be overridden unless
explicitly marked as <code>open</code>. This means that classes will end up having behavior
that some have dubbed "<code>final</code> by default" for any publicly available class.
This behavior would eliminate the need for the compiler to generate dynamic
dispatching for methods and properties since their implementation cannot be
changed after compiletime. While the elimination of dynamic dispatch is a
performance boost, this means extra care and attention will be required to turn
dynamic dispatch back on for those who <em>want</em> consumers to override the
functionality of of their publicly exposed classes.</p>
<p>The changes here are quite interesting. People have taken to blogs and Twitter
to express their opinions about how this will affect their development and
workflow.</p>
<p>One argument against the proposal that I’ve often heard has been that been that
this reduces, if not entirely eliminates, the dynamism of a language like
Objective-C. There’s no getting around that elephant in the room; this is a
major change.</p>
<p>However, I do think there are some resulting behaviors that have the power to
push some core values forward in a way that will make Swift an even better
language to work in.</p>
<h2 id="lean-on-protocols-to-define-the-behavior-you-expect" class="heading-group group"><a href="#lean-on-protocols-to-define-the-behavior-you-expect"><span class="heading-link">#</span>Lean on Protocols to Define the Behavior You Expect</a></h2>
<p>APIs that expose functions that take in <em>protocols</em> rather than classes or
subclasses will flourish. We don’t need to wrap up our intended behavior or
properties into a <code>class</code> just to get the code we need run, but rather we can
make the decision to use a struct or class, choosing value vs reference
semantics.</p>
<p>This means that no matter what type we pass in, we’ll be able to get some
property or call some function, regardless of what class, subclass, or
subsubsubclass with its subsubclasses pass in.</p>
<p>This will lead to SDKs that don’t make you subclass <code>XYZModel</code> for their
behavior and will allow you to create beautiful, testable, understandable,
comprehensible code. The extraction of <code>MTLModel</code> in
<a href="https://github.com/Mantle/Mantle">Mantle</a> from a class to a protocol
<a href="https://github.com/Mantle/Mantle/pull/219">in this PR</a> is almost a case study
in the benefits of this type of API design. By moving the behaviors of
<code>MTLModel</code> off to protocol implementations, consumers of Mantle were no longer
forced into making their types inherit from <code>MTLModel</code>, but let them choose
their type that implemented the behavior. Craziest part of all? This
conversation was happening in Objective-C-land, not Swiftopia.</p>
<p>Arguments against this proposal that circle around “subclassing lets me write
code that I can test” are immediately moot if the API that they are trying to
test takes in a protocol rather than a class.</p>
<p>For more about this, watch me wax poetic about
<a href="http://blog.eliperkins.me/mocks-in-swift-via-protocols">Mocks in Swift via Protocols</a>.</p>
<h2 id="encourage-composition-over-inheritance" class="heading-group group"><a href="#encourage-composition-over-inheritance"><span class="heading-link">#</span>Encourage Composition over Inheritance</a></h2>
<p>Clamping down on how classes’ behaviors can be modified or extended means that
developers will need to find other ways to get the results they need.</p>
<p>Creating types that compose other types will be one opportunity Subclassers™
can use to create types that give the behavior they’re looking for. Packaging
their <code>final class</code> into another type that executes the <code>final class</code>’s behavior
and then returns its own behavior based on the results of that.</p>
<p>This will lead us towards a clearer business logic in types, that are more
flexible and have less responsibilities. Gone will be the
god/<a href="http://adventuretime.wikia.com/wiki/Grob_Gob_Glob_Grod">Grob Gob Glob Grod</a>
objects that have subclasses on subclasses just to get the behaviors they want.
A series of classes that compose together can more clearly define intention and
responsibility.</p>
<h2 id="alternatives-considered" class="heading-group group"><a href="#alternatives-considered"><span class="heading-link">#</span>Alternatives Considered</a></h2>
<p>I would have loved to seen this implemented as an opt-in compiler-time flag,
rather than a syntactical level keyword. As a language feature, some may want
it, and benefit from it, while others may shy away or not understand it’s
consequences. By having the compiler determine at compile time, with a flag such
as <code>Disable subclassing on public classes</code>, we could still write the code we
want, while being flexible enough to generate code and interfaces that we need.</p>
<p>While I may not be a ?? on the semantic implementation, I’m ?x? on the value
that <code>final</code> by default would bring.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Mocks in Swift via Protocols]]></title>
            <link>https://blog.eliperkins.com/mocks-in-swift-via-protocols</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/mocks-in-swift-via-protocols</guid>
            <pubDate>Thu, 01 Oct 2015 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>How to use protocols to mock types in Swift to test your iOS apps without relying on Objective-C runtime hacks and swizzles.</p>]]></description>
            <content:encoded><![CDATA[<p>Testing the nitty gritty dirty innards of a lot of iOS apps has been difficult
in the past. A variety of tools and methodologies have been developed over time,
including a couple mocking frameworks like <a href="http://ocmock.org/">OCMock</a> or
<a href="https://github.com/jonreid/OCMockito">OCMockito</a>. With the advent of Swift,
these frameworks have seen their implementations rendered useless, since they
depend heavily on the Objective-C runtime.</p>
<p>But with Swift, there might be a better way to setup our programs that don't
require Objective-C <del>funtime</del> runtime hacks and swizzles.</p>
<h2 id="mocking-uiapplication" class="heading-group group"><a href="#mocking-uiapplication"><span class="heading-link">#</span>Mocking <code>UIApplication</code></a></h2>
<p>Let's take a look at an example which mocks out our favorite hard-to-test class,
<code>UIApplication</code>.</p>
<p>In our example, let's work on a type that handles push notifications.</p>
<pre><code class="language-swift"><span class="pl-k">struct</span> <span class="pl-en">PushNotificationController</span> {
}
</code></pre>
<p>The goal of this type will be to have some function which we can call to ask the
user for the permission to send push notifications, via <code>UIApplication</code>'s
<code>registerUserNotificationSettings(_:)</code>, maybe handle some pushes and delegate
calls for device tokens and such, etc.</p>
<p>We want to trigger push registration to be off of some state, say maybe once our
user logs in, so that we don't bombard our new users with alerts without
actually using our app first. A naïve approach might be to just create some
function which calls
<code>UIApplication.sharedApplication().registerUserNotificationSettings(_:)</code>.</p>
<pre><code class="language-swift"><span class="pl-k">struct</span> <span class="pl-en">PushNotificationController</span> {
    <span class="pl-k">var</span> user: User {
        <span class="pl-k">didSet</span> {
            <span class="pl-k">let</span> application <span class="pl-k">=</span> UIApplication.<span class="pl-c1">sharedApplication</span>()
            application.<span class="pl-en">registerUserNotificationSettings</span>(_:)
        }
    }
}
</code></pre>
<p>Easy, right? Okay, now let's go test this functionality so we know that we can
write some unit test around <code>PushNotificationController</code> to know that we
actually do register for push notifications when we give it a user.</p>
<pre><code class="language-swift"><span class="pl-k">import</span> <span class="pl-en">XCTest</span>

<span class="pl-k">class</span> <span class="pl-en">PushNotificationControllerTests</span>: <span class="pl-e">XCTestCase </span>{
    <span class="pl-k">func</span> <span class="pl-en">testControllerRegistersForPushesAfterSettingAUser</span>() {
        <span class="pl-k">let</span> controller <span class="pl-k">=</span> <span class="pl-c1">PushNotificationController</span>()
        controller.<span class="pl-smi">user</span> <span class="pl-k">=</span> <span class="pl-c1">User</span>()
        <span class="pl-c">// uhhh... now what?</span>
    }
}
</code></pre>
<p><img src="https://i.imgur.com/yS9zFJK.gif" alt=""></p>
<h2 id="whats-in-a-test" class="heading-group group"><a href="#whats-in-a-test"><span class="heading-link">#</span>What's in a Test?</a></h2>
<p>Let's take a step back here and figure out what has made our code untestable.
There's a couple things that we're fighting against. We don't own
<code>UIApplication</code> or it's <code>sharedApplication()</code> method, so it's a bit difficult to
substitute our own functionality into these. Additionally, we don't have a way
to know if calling
<code>UIApplication.sharedApplication().registerUserNotificationSettings(_:)</code> really
does anything in our unit test. We can't assert that the screen has the alert
view on it now.</p>
<p>What are we <em>really</em> trying to test here? We shouldn't be testing UIKit, there's
a few Apple engineers out there whose job it is to do that. We really just want
to test that our type asks the relevant party to register for push
notifications, and in this case it <em>just so happens that</em> the relevant party is
a <code>UIApplication</code>.</p>
<h2 id="protocol-oriented-programming" class="heading-group group"><a href="#protocol-oriented-programming"><span class="heading-link">#</span>Protocol-Oriented Programming</a></h2>
<p>So how can we get a bit of flexibility here? How can we verify our type?</p>
<p><strong>I propose that protocols are the best way to mock types in Swift.</strong></p>
<p>Let's create a protocol here to stand in for <code>UIApplication</code>.</p>
<pre><code class="language-swift"><span class="pl-k">protocol</span> <span class="pl-en">PushNotificationRegistrar</span> {
    <span class="pl-k">func</span> <span class="pl-en">registerUserNotificationSettings</span>(<span class="pl-en">_</span>:)
}
</code></pre>
<p>Super simple. A <code>PushNotificationRegistrar</code> is any type that has a function
<code>registerUserNotificationSettings(_:)</code> to call.</p>
<p>Next, let's dependency inject a <code>PushNotificationRegistrar</code> into our
<code>PushNotificationController</code>.</p>
<pre><code class="language-swift"><span class="pl-k">struct</span> <span class="pl-en">PushNotificationController</span> {
    <span class="pl-k">let</span> registrar: PushNotificationRegistrar
    <span class="pl-k">init</span>(<span class="pl-en">registrar</span>: PushNotificationRegistrar) {
        <span class="pl-c1">self</span>.<span class="pl-smi">registrar</span> <span class="pl-k">=</span> registrar
    }
}
</code></pre>
<p>Perfect. Now instead of calling <code>registerUserNotificationSettings(_:)</code> on
<code>UIApplication.sharedApplication()</code>, let's instead call it on our <code>registrar</code>.</p>
<pre><code class="language-swift"><span class="pl-k">struct</span> <span class="pl-en">PushNotificationController</span> {
    <span class="pl-k">let</span> registrar: PushNotificationRegistrar
    <span class="pl-k">init</span>(<span class="pl-en">registrar</span>: PushNotificationRegistrar) {
        <span class="pl-c1">self</span>.<span class="pl-smi">registrar</span> <span class="pl-k">=</span> registrar
    }

    <span class="pl-k">var</span> user: User {
        <span class="pl-k">didSet</span> {
            registrar.<span class="pl-en">registerUserNotificationSettings</span>(_:)
        }
    }
}
</code></pre>
<p>Beautiful! Now there's no more <code>UIApplication.sharedApplication()</code> to be seen!
Less global state gives us a bit more wiggle room in our unit tests.</p>
<h2 id="hooking-up-uiapplication-with-pushnotificationregistrar" class="heading-group group"><a href="#hooking-up-uiapplication-with-pushnotificationregistrar"><span class="heading-link">#</span>Hooking up <code>UIApplication</code> with <code>PushNotificationRegistrar</code></a></h2>
<p>But if we don't have <code>UIApplication.sharedApplication()</code> ever, how do we get our
application to register, you ask? This is where an amazing part of Swift's
elegance comes into play.</p>
<p>We can conform any <code>UIApplication</code> type to <code>PushNotificationRegistrar</code> in one
line of code:</p>
<pre><code class="language-swift"><span class="pl-k">extension</span> <span class="pl-en">UIApplication</span>: <span class="pl-e">PushNotificationRegistrar </span>{}
</code></pre>
<p>Boom. Done. Since <code>UIApplication</code> already has a method named
<code>registerUserNotificationSettings(_:)</code>, it's already implementing our protocol.
In our application, we can simply just create a <code>PushNotificationController</code> in
our application delegate, say in
<code>application(_:didFinishLaunchingWithOptions:)</code>, like so:</p>
<pre><code class="language-swift"><span class="pl-k">extension</span> <span class="pl-en">AppDelegate</span>: <span class="pl-e">UIApplicationDelegate </span>{
    <span class="pl-k">func</span> <span class="pl-en">application</span>(<span class="pl-en">application</span>: UIApplication, <span class="pl-en">didFinishLaunchingWithOptions</span> <span class="pl-smi">launchOptions</span>: [NSObject : <span class="pl-c1">AnyObject</span>]<span class="pl-k">?</span>) <span class="pl-k">-></span> <span class="pl-c1">Bool</span> {
        <span class="pl-k">let</span> controller <span class="pl-k">=</span> <span class="pl-c1">PushNotificationController</span>(<span class="pl-c1">application</span>: application)
        controller.<span class="pl-smi">user</span> <span class="pl-k">=</span> <span class="pl-c1">User</span>()
    }
}
</code></pre>
<p>and just like that, we're on our way to registering!</p>
<h2 id="testing-with-mocks-via-protocols" class="heading-group group"><a href="#testing-with-mocks-via-protocols"><span class="heading-link">#</span>Testing with Mocks via Protocols</a></h2>
<p>Now let's write a test around it. In this case, we don't want to give it a
<code>UIApplication</code> because it's hard for us to get one and it's simply not
testable, like we mentioned before. So instead, let's put together a quick type
that does some faux registration for us.</p>
<pre><code class="language-swift"><span class="pl-k">import</span> <span class="pl-en">XCTest</span>

<span class="pl-k">class</span> <span class="pl-en">PushNotificationControllerTests</span>: <span class="pl-e">XCTestCase </span>{
    <span class="pl-k">func</span> <span class="pl-en">testControllerRegistersForPushesAfterSettingAUser</span>() {
        <span class="pl-k">class</span> <span class="pl-en">FauxRegistrar</span>: <span class="pl-e">PushNotificationRegistrar </span>{
            <span class="pl-k">var</span> registered <span class="pl-k">=</span> <span class="pl-c1">false</span>
            <span class="pl-k">func</span> <span class="pl-en">registerUserNotificationSettings</span>(<span class="pl-en">notificationSettings</span>: UIUserNotificationSettings) {
                registered <span class="pl-k">=</span> <span class="pl-c1">true</span>
            }
        }

        <span class="pl-k">let</span> registrar <span class="pl-k">=</span> <span class="pl-c1">FauxRegistrar</span>()
        <span class="pl-k">var</span> controller <span class="pl-k">=</span> <span class="pl-c1">PushNotificationController</span>(<span class="pl-c1">registrar</span>: registrar)
        controller.<span class="pl-smi">user</span> <span class="pl-k">=</span> <span class="pl-c1">User</span>()
        <span class="pl-c1">XCTAssertTrue</span>(registrar.<span class="pl-smi">registered</span>)
    }
}
</code></pre>
<p>Et volia! We just created a test that our application should register for push
notifications after setting a user!</p>
<h2 id="what-would-crusty-do" class="heading-group group"><a href="#what-would-crusty-do"><span class="heading-link">#</span>What Would Crusty Do?</a></h2>
<p>Creating mocks via protocols in Swift is great for reasons beyond just unit
testing <code>UIApplication</code> methods. Protocols contribute greatly in creating
<a href="https://www.destroyallsoftware.com/talks/boundaries">boundaries</a> around pieces
of your architecture and can make sure your make sure your software doesn't
become too <a href="https://developer.apple.com/videos/wwdc/2015/?id=408">crusty</a>.</p>
<p>This isn't anything new to Swift, but rather certainly a pattern that is very
trivial to implement in many places now. Protocol extensions with default
implementations might even push this method forward more as Swift 2 develops
more.</p>
<p>You can find a playground demoing some functionality of this here:
<a href="https://gist.github.com/eliperkins/8f4115151497dc1953ea">https://gist.github.com/eliperkins/8f4115151497dc1953ea</a></p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[WWDC 2014 Predictions]]></title>
            <link>https://blog.eliperkins.com/wwdc-2014-predictions</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/wwdc-2014-predictions</guid>
            <pubDate>Wed, 28 May 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Boy, was I wrong... maybe?</p>]]></description>
            <content:encoded><![CDATA[<p>I figured I'd put my hat in the ring here.</p>
<p><img src="https://9to5mac.files.wordpress.com/2014/04/wwdc14-home-branding-v2.png?w=655" alt=""></p>
<h1 id="predictions" class="heading-group group"><a href="#predictions"><span class="heading-link">#</span>Predictions</a></h1>
<ul>
<li>iOS 8
<ul>
<li>Lots of performance polish, a la Snow Leopard</li>
<li>UIMotionEffect improvements (full under-the-hood rewrite?)</li>
<li>Touch ID APIs</li>
<li>Some unifying API to talk with BLE-, WiFi-, etc based devices</li>
<li>Tint color becomes über important and defining, even more so than iOS 7</li>
<li>Fast, fast, fast and fast</li>
</ul>
</li>
<li>OS X.10 aka Huntington (shot in the dark here)
<ul>
<li>New UI
<a href="https://dribbble.com/shots/1465948-Mac-OS-X-Syrah-Concept/">similar to this</a></li>
<li>UIKit for OS X</li>
<li>Siri integration</li>
</ul>
</li>
<li>iOS ↔ OS X AirDrop</li>
<li>Xcode 6
<ul>
<li>Overhaul of Instruments</li>
</ul>
</li>
<li>Some sort of new platform for iOS and OS X development with JavaScript roots</li>
<li>Apple Beta Program
<ul>
<li>Beta testing of iOS and OS X apps a la Testflight</li>
<li>No limit to beta testers (won't count against developer's 100 devices)</li>
<li>Better crash reporting</li>
</ul>
</li>
<li>App Store app and music gifiting</li>
<li>MacBook spec bumps across the line, end-of-line for non-Retina MacBooks</li>
<li>A very average game demo from some company who has been trying iOS 8</li>
<li>Craig Federighi stealing the show as per usual</li>
</ul>
<h1 id="wants" class="heading-group group"><a href="#wants"><span class="heading-link">#</span>Wants</a></h1>
<ul>
<li>Objective-C 3.0 ^^^Just ^^^to ^^^make ^^^all ^^^the ^^^haters ^^^stop ^^^their
^^^bitching.</li>
<li>XPC on iOS (what I really just want is Android-like intents...)</li>
<li>Better permissions on iOS</li>
<li>Siri API of some sort</li>
<li>OS X does away with sandboxing</li>
<li>OS X Safari notifications to become just OS X notifications</li>
</ul>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[Get Off My Lawn]]></title>
            <link>https://blog.eliperkins.com/get-off-my-lawn</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/get-off-my-lawn</guid>
            <pubDate>Thu, 13 Feb 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Respect each other's opinions in the iOS community and let's focus on creating cool things with the tools we like.</p>]]></description>
            <content:encoded><![CDATA[<p>There's been a lot of drama among the iOS community these days relating to a
variety of topics. From
<a href="http://www.quora.com/Facebook-Paper-product/What-was-it-like-to-help-develop-Paper">how Xcode can't handle my scale</a>,
to
<a href="http://ashfurrow.com/blog/we-need-to-replace-objective-c">Objective-C needing a replacement</a>,
to usage of Cocoapods.</p>
<p>Almost every tweet, every blog post, every Quora answer I would read really only
showed me one thing: the entitlement that many of these opinions carried, as if
their opinion was not just an opinion, but rather fact.</p>
<p>Everyone is entitled to their opinion, sure. But to bash others for their
opinion is just wrong.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">This type of blatant disrespect /against others/ literally keeps me up at night: <a href="http://t.co/S6deW2xlZP">http://t.co/S6deW2xlZP</a></p>&mdash; @alloy@mastodon.social (@alloy) <a href="https://twitter.com/alloy/status/433989599107751936?ref_src=twsrc%5Etfw">February 13, 2014</a></blockquote>
<p>The way I've noticed a lot of iOS dev's acting lately has been a lot like an old
man telling everyone to get off his lawn. Different things work for different
people. There's no need to force your own opinions upon others. Listen to others
and accept what they have to say. Form your own opinion based on it. But <strong>stop
with the petty subtweets, stop with the bashing of the work of others, stop with
the outcry of how shitty our tools are.</strong></p>
<p>If you don't like something, don't use it. Simple as that. Facebook doesn't like
Xcode, so they don't use it.
<a href="https://twitter.com/jeff_lamarche/status/433720595314794498">Jeff Lamarche doesn't like CocoaPods</a>,
so he doesn't use it. Ash Furrow doesn't like the current state of Objective-C,
...so I guess he's stuck with it (I kid, there's alternatives out there that
<a href="https://twitter.com/ashfurrow/status/433595981138235392">he's trying like Eero</a>,
<a href="https://twitter.com/ashfurrow/status/434009653895499776">RubyMotion, Xamarin</a>
etc., too). My point is, everyone is different.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Throwing some positivism into the twitter-sphere. I liked <a href="https://twitter.com/CocoaPods?ref_src=twsrc%5Etfw">@CocoaPods</a> a lot, but it didn’t do what I wanted. So I fixed it &amp; helped. OSS wins</p>&mdash; @orta@webtoo.ls --leave-this-site (@orta) <a href="https://twitter.com/orta/status/433723905082392576?ref_src=twsrc%5Etfw">February 12, 2014</a></blockquote>
<p>I've been watching a lot of the Olympics this week and an entertaining bit for
me has been Johnny Weir.
<a href="http://www.sbnation.com/lookit/2014/2/11/5401156/johnny-weir-olympic-outfit-tracker-sochi-2014">Johnny Weir's outfits</a>
everyday have been off the wall, flamboyant and attention-grabbing. Watching him
as he attempts to make a statement in Russia, voicing his opinion, his beliefs,
in a way that doesn't harm others, has been awesome to follow.</p>
<p>We could take a note from Johnny Weir, and voice our opinions in a way that
doesn't attack others. Start making cool things with the tools you like. Start
making cool tools that help others make cool things in the way that you work.
<strong>Speak with your apps and code instead of 140 characters.</strong></p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
        <item>
            <title><![CDATA[How We Learn]]></title>
            <link>https://blog.eliperkins.com/how-we-learn</link>
            <guid isPermaLink="false">https://blog.eliperkins.com/how-we-learn</guid>
            <pubDate>Tue, 04 Feb 2014 00:00:00 GMT</pubDate>
            <description><![CDATA[<p>Find success by seeking to learn more, shaping one's own personal opinion, and changing one's mind.</p>]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>He said people who were right a lot of the time <em>were people who often changed
their minds</em>.</p>
</blockquote>
<p>--
<strong><a href="http://37signals.com/svn/posts/3289-some-advice-from-jeff-bezos">Jason Fried</a></strong>
on Jeff Bezos</p>
<p>Tonight, Bill Nye and Ken Ham put on a bit of a show in Kentucky
<a href="http://www.youtube.com/watch?v=z6kgvhG3AkI">and on YouTube</a>. Those who know me
may know that I'm in no way religious, but I don't shun those who are. I do,
however, believe religion is a necessary piece to a functioning society, but
that's a piece for a whole 'nother blog post.</p>
<p>The part that struck me most about the debate, however, was listening to
<a href="http://en.wikipedia.org/wiki/Ken_Ham">Ken Ham</a> discuss the difference between
"observational science" and "historical science". Recalling my grade school days
(fuck, I'm old now that I've said that), I can only ever really remember being
taught that some smart guy named Darwin did some studies on finches and this
other guy named Mendel did some study on peas and in the end, it's survival of
the fittest... or something like that.</p>
<p>But this isn't about me debating who was right and who was wrong. This isn't
about proving Nye's or Ham's opinions on evolution.</p>
<p>What really struck me was that Ham chose to accept a belief that may not have
been the popular opinion, that may not have been what was taught to him in
school, and chose rather to question a specific piece of science
(<a href="http://en.wikipedia.org/wiki/Radiometric_dating">radiometric dating</a>) and
question science's reliance on this method. From here, he draws his own personal
opinions on our creations based on his beliefs in Genesis (what he sees as
<strong>historical science</strong>) and what he can readily prove in a lab or on paper
(<strong>observational science</strong>).</p>
<p>While I may not stand for everything Ham stands for, I am all for learning by
questioning what already exists. I am all for digging deeper into what we think
we may know, with the sole purpose of learning more. And importantly, I am all
for listening to what others say and drawing my own personal opinion from that.</p>
<hr>
<p>Last year, I read a blurb from Jeff Bezos on
<a href="http://37signals.com/svn/posts/3289-some-advice-from-jeff-bezos">a Signal vs. Noise post</a>
that really resonated with me.</p>
<blockquote>
<p>He said people who were right a lot of the time <em>were people who often changed
their minds</em>.</p>
</blockquote>
<p>--
<a href="http://37signals.com/svn/posts/3289-some-advice-from-jeff-bezos">Jason Fried</a></p>
<p>People who seek to learn more, who seek to shape their own personal opinion, are
those who succeed.</p>
<blockquote>
<p><strong>It's really tough to find malleable opinions in the tech and start-up
industry.</strong></p>
</blockquote>
<p>People are full of opinions and hold them to a high grade when it comes to
discussing programming languages or frameworks or hell, even programming
paradigms. In the last few months, I've spent my spare time learning
<a href="https://github.com/ReactiveCocoa/ReactiveCocoa">ReactiveCocoa</a>, not because I
think it's the best and only way to craft Objective-C bits and pieces, but
because I wanted to learn.</p>
<p>I wanted to learn why <a href="https://github.com/jspahrsummers">Justin Spahr-Summers</a>,
a GitHubber I looked up to, spent a considerable amount of his time doing this.
I wanted to learn what existed out there besides the same old OOP that my
college professors raved about to my young, supple brain. I wanted to see if it
would change my mind and the way I think. And <strong>it did.</strong></p>
<p>Which brings me back to the whole reason that started this post. How do we
enable ourselves to learn more? How can we help enable others to learn in these
same ways? How can we help create productive conversations between ourselves,
rather than the relentless shitstorm that follows every opinion posted on Hacker
News? How can we promote drawing new opinions from a combination of observable
and historical science?</p>
<p>Because I will damn well follow those who will change their opinion based on
their own knowledge over the immovable stump any day.</p>]]></content:encoded>
            <author>eli.j.perkins@gmail.com (Eli Perkins)</author>
        </item>
    </channel>
</rss>