<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
  <title>Beambloggers Webring</title>
  <link>https://beambloggers.com/feed</link>
  <description>A collection of our webring members posts in an aggregated RSS feed. Please check out their blog posts :)</description>
  <lastBuildDate>Sun, 24 Nov 2024 07:23:40 +0000</lastBuildDate>
  <language>en-us</language>
<item>
  <title>Injecting tracing the hot way</title>
  <description>From Underjord.io: I mostly program in Elixir. So tracing is an overloaded term. This post relates to both of them and how we can use BEAM and Erlang fundamentals to do wild things for observability that wouldn&amp;rsquo;t be practical in most runtimes. Let&amp;rsquo;s load some hot code.
First is a set of (experimental) updates I&amp;rsquo;ve done to my &amp;ldquo;Erlang tracing&amp;rdquo; library Entrace. Erlang&amp;rsquo;s concept of tracing is a mechanism in which you can tell the system to send you information about execution of functions that match some pattern.</description>
  <pubDate>Wed, 25 Mar 2026 04:00:00 +0000</pubDate>
  <link>https://underjord.io/injecting-tracing-the-hot-way.html</link>
  <guid>https://underjord.io/injecting-tracing-the-hot-way.html---https://underjord.io/injecting-tracing-the-hot-way.html</guid>
</item>
<item>
  <title>Hacking on the Nerves Starter Kit in Malaga</title>
  <description>From Underjord.io: I&amp;rsquo;m going to be speaking at ElixirConf EU in Malaga. When I got to Elixir conferences I relatively often also make sure to run a little something-something for the real heads. A Nerves thing. Touch keyboard, touch hardware, get to know people.
The biggest reason for this is to make an ice-breaker for people that are attending the conf. People who would enjoy a way to engage with others up front in a practical workshop kind of way.</description>
  <pubDate>Wed, 11 Mar 2026 04:00:00 +0000</pubDate>
  <link>https://underjord.io/malaga-nerves-event.html</link>
  <guid>https://underjord.io/malaga-nerves-event.html---https://underjord.io/malaga-nerves-event.html</guid>
</item>
<item>
  <title>Shipping grayscale photos at small scale</title>
  <description>From Underjord.io: In September of 2025 Tigris sponsored an odd effort at an unusual conference. They contributed the money for manufacturing hardware devices to be used and hacked on during Goatmire Elixir in Varberg, Sweden. These devices also used Tigris for some features. Everything about it is open source. Let&amp;rsquo;s see what it turned into.
Goatmire Elixir is my (Lars') brainchild. I like the idea of a quirky event, smaller scope and people who are deeply enthusiastic about related things.</description>
  <pubDate>Sun, 08 Mar 2026 04:00:00 +0000</pubDate>
  <link>https://underjord.io/shipping-grayscale-photos-at-small-scale.html</link>
  <guid>https://underjord.io/shipping-grayscale-photos-at-small-scale.html---https://underjord.io/shipping-grayscale-photos-at-small-scale.html</guid>
</item>
<item>
  <title>Goatmire 2, announced</title>
  <description>From Underjord.io: Goatmire Elixir 2025 was a very dense success. The second one just got announced.
I say dense. Because massive success would make it sound large. And it wasn&amp;rsquo;t a large event. 150 attendees, with speakers and volunteers we were around 200 in total. And it went incredibly well. The feedback, the surveys, the outpouring of love and appreciation were all way above what I dared hope for. People have been graciously giving me space to consider a second one but it has also been the single most requested thing ever.</description>
  <pubDate>Wed, 14 Jan 2026 04:00:00 +0000</pubDate>
  <link>https://underjord.io/goatmire-2-announced.html</link>
  <guid>https://underjord.io/goatmire-2-announced.html---https://underjord.io/goatmire-2-announced.html</guid>
</item>
<item>
  <title>Buying the Kinesis Advantage 360 keyboard was a mistake</title>
  <description>From Angelika.me: Why this beast of a keyboard did not work for me and what I ended up using instead.</description>
  <pubDate>Thu, 04 Dec 2025 06:55:00 GMT</pubDate>
  <link>https://angelika.me/2025/12/04/buying-kinesis-advantage-was-a-mistake/</link>
  <guid>https://angelika.me/2025/12/04/buying-kinesis-advantage-was-a-mistake/---https://angelika.me/2025/12/04/buying-kinesis-advantage-was-a-mistake/</guid>
</item>
<item>
  <title>Conference Report: Goatmire Elixir 2025</title>
  <description>From Underjord.io: The quiet was unsettling. The lack of concrete things to do was stressful. Me and my wife had been fretting and flitting around the house for most of the day making sure everything was staged, that we had the various prints, checklists were ready, lots of bags and boxes were packed. The event wasn&amp;rsquo;t even starting today, not even fully tomorrow. The quiet after intense preparation.
Snap to today. The event has now happened.</description>
  <pubDate>Mon, 22 Sep 2025 04:00:00 +0000</pubDate>
  <link>https://underjord.io/conference-report-goatmire-elixir-2025.html</link>
  <guid>https://underjord.io/conference-report-goatmire-elixir-2025.html---https://underjord.io/conference-report-goatmire-elixir-2025.html</guid>
</item>
<item>
  <title>Booting 5000 Erlangs on Ampere One 192-core</title>
  <description>From Underjord.io: In the previous post on 500 virtual linux devices on ARM64 I hinted that I expected serious improvements if we got KVM working. Well. We&amp;rsquo;re there. Let&amp;rsquo;s see what we got going on.
Disclosure: I am running a conference called Goatmire Elixir which Ampere is a sponsor of. This post is not part of the sponsorship exchange as such. It is prep for my talk for the conference which uses the hardware they lent me.</description>
  <pubDate>Tue, 05 Aug 2025 04:00:00 +0000</pubDate>
  <link>https://underjord.io/booting-5000-erlangs-on-ampere-one.html</link>
  <guid>https://underjord.io/booting-5000-erlangs-on-ampere-one.html---https://underjord.io/booting-5000-erlangs-on-ampere-one.html</guid>
</item>
<item>
  <title>500 virtual Linux devices on ARM 64</title>
  <description>From Underjord.io: This is the first part of an experimental journey as I explore how many instances of my favorite IoT framework I can run on the 192 core Ampere One.
Background I work on the Nerves project which is an IoT framework providing best-practice underpinnings and support so that you can build your IoT hubs, smart thermostats and the like with a safe and productive high-level language on a runtime known for reliability, resilience and consistent performance.</description>
  <pubDate>Wed, 30 Jul 2025 04:00:00 +0000</pubDate>
  <link>https://underjord.io/500-virtual-linux-devices-on-arm64.html</link>
  <guid>https://underjord.io/500-virtual-linux-devices-on-arm64.html---https://underjord.io/500-virtual-linux-devices-on-arm64.html</guid>
</item>
<item>
  <title>Delta support - A tale of two firmware versions</title>
  <description>From Underjord.io: Binary deltas of firmware via fwup has been a supported feature for a while. There has also been a kind of rudimentary support for it in NervesHub. We can now remove the word rudimentary. There is now proper delta support in NervesHub. It is not in a tagged release quite yet but NervesCloud runs off of main and so can you if you would benefit from this.
(I initially wrote this up for the Nerves Newsletter but wanted it shareable in some other way)</description>
  <pubDate>Mon, 07 Jul 2025 04:00:00 +0000</pubDate>
  <link>https://underjord.io/delta-support-a-tale-of-two-firmwares.html</link>
  <guid>https://underjord.io/delta-support-a-tale-of-two-firmwares.html---https://underjord.io/delta-support-a-tale-of-two-firmwares.html</guid>
</item>
<item>
  <title>Making of an Elixir conference</title>
  <description>From Underjord.io: It all started when I visited Gig City Elixir. Or maybe it started when I did a workshop in preparation for a Code BEAM in Stockholm. But that actually started from reading Priya Parker&amp;rsquo;s book The Art of Gathering. Maybe this has been coming for a while.
I&amp;rsquo;m making an Elixir conference and it happens 10-12th of September in Varberg, Sweden. Tickets are still available at the time of writing. I figured it might be interesting to hear one data point of pulling something like this together.</description>
  <pubDate>Fri, 04 Jul 2025 04:00:00 +0000</pubDate>
  <link>https://underjord.io/making-of-an-elixir-conference.html</link>
  <guid>https://underjord.io/making-of-an-elixir-conference.html---https://underjord.io/making-of-an-elixir-conference.html</guid>
</item>
<item>
  <title>How to work with XML documents in Elixir using xmerl</title>
  <description>From Angelika.me: I want to parse an XML document, find an element by id, add an attribute, and export the document back to a string.</description>
  <pubDate>Sun, 15 Jun 2025 17:14:00 GMT</pubDate>
  <link>https://angelika.me/2025/06/15/how-to-work-with-xml-in-elixir-with-xmerl/</link>
  <guid>https://angelika.me/2025/06/15/how-to-work-with-xml-in-elixir-with-xmerl/---https://angelika.me/2025/06/15/how-to-work-with-xml-in-elixir-with-xmerl/</guid>
</item>
<item>
  <title>Elixir is not owned by Big Tech</title>
  <description>From Underjord.io: We all have varying degrees of exposure to Big Tech. Some of it seems fine, stable and can be relied on. Some of it feels like shifting sand under your feet. React seems to move a lot on whims, I don&amp;rsquo;t envy tracking that. Go seems like it might be fairly stable? With the current geo-political climate I don&amp;rsquo;t find massive corporations to be a guarantee for stability. People may be getting fired even though they chose IBM.</description>
  <pubDate>Tue, 29 Apr 2025 04:00:00 +0000</pubDate>
  <link>https://underjord.io/elixir-is-not-owned-by-big-tech.html</link>
  <guid>https://underjord.io/elixir-is-not-owned-by-big-tech.html---https://underjord.io/elixir-is-not-owned-by-big-tech.html</guid>
</item>
<item>
  <title>Keep this in mind when changing scrollbar colors</title>
  <description>From Angelika.me: TL;DR: always choose a scrollbar thumb color that makes it visible both against the container background and the scrollbar track.</description>
  <pubDate>Sat, 26 Apr 2025 15:16:00 GMT</pubDate>
  <link>https://angelika.me/2025/04/26/keep-this-in-mind-when-changing-scrollbar-colors/</link>
  <guid>https://angelika.me/2025/04/26/keep-this-in-mind-when-changing-scrollbar-colors/---https://angelika.me/2025/04/26/keep-this-in-mind-when-changing-scrollbar-colors/</guid>
</item>
<item>
  <title>The implications of Astro's HTML streaming</title>
  <description>From Angelika.me: Read this if you care about accurate HTTP response status codes.</description>
  <pubDate>Sun, 16 Mar 2025 14:17:00 GMT</pubDate>
  <link>https://angelika.me/2025/03/16/implications-of-astro-html-streaming/</link>
  <guid>https://angelika.me/2025/03/16/implications-of-astro-html-streaming/---https://angelika.me/2025/03/16/implications-of-astro-html-streaming/</guid>
</item>
<item>
  <title>Code BEAM America 2025</title>
  <description>From Mitchell Hanberg's Blog: My first conference of 2025 was [Code BEAM America](https://codebeamamerica.com) and I had the honor of attending with a great crew from DraftKings ([we're hiring!](https://careers.draftkings.com/jobs/jr10888/lead-software-engineer-elixir/)) and to give my second ever conference talk!

## On the Elixir community

Reflecting on this year's conference, I realized how comfortable I was the entire time. After 7 years in the Elixir community, I've accumulated a number for friends and acquaintences, something I really never expected from a work adjacent interest like a programming language.

At my first Elixir conference, [Lonestar Elixir 2020](https://web.archive.org/web/20200308234856/https://lonestarelixir.com/) (rough year, but also the year I "went pro" with Elixir), I had a couple of acquaintences from my new job at [Bleacher Report](https://bleacherreport.com/), but I hadn't really me them yet. I'm a pretty social guy, but traveling across the country to hang out with a bunch of strangers isn't really my comfort zone.

Luckily, I met folks like [Todd Resudek](https://supersimple.org/) who go out of their way to be friendly and introduce you to people they know. I've tried to embody this spirit myself at social gatherings ever since, and Code BEAM America 2025 was no exception.

## My talk

This year I gave an update on our progress with the [unified language server project](/ive-joined-the-official-elixir-lsp-team/).

We are a little more than 6 months into the project and it felt great to finally share how it's going and what to expect.

I spoke to a number of folks on what they're excited about with the project and had a blast exploring the possibilities of the next generation of Elixir tooling.

I'll update this with a link to the talk once it's available.

## The talks

I thoroughly enjoyed the talks I attended this year (I can admit I do enjoy the hallway track as well...) with a couple of notable talks.

### Jason Axelson - Choosing an effective testing structure

[Jason Axelson](https://www.linkedin.com/in/jasonaxelson/) gave a presentation on testing practices from which I almost broke neck from aggressively nodding the entire time.

One of his suggestions I believe is the most powerful: "Make your tests async" (paraphrased, I didn't write it down because of the aforementioned head banging).

The rationale is basically that by making your tests async, you are forced to iron out a lot of the concurrency kinks in your programs and it helps you really understand your codebase from the bottom up. Improving your own understanding of your codebases will pay dividends when it comes time to handle the unfortunate production incident or training juniors/new teammates.

Later on, I shared with Jason an [example repository](https://github.com/mhanberg/sandrabbit) I had made which demonstrates how to structure a Phoenix application that deals with singleton GenServers (global state) and still maintain async tests.

PS: Isn't it cool that at a conference you can watch a presentation and then later eat lunch (or drink beer) next to them and discuss it??

### Jeffrey Matthias and Aidan Obley - Crafting Fully Custom Code Generators

In this talk, [Jeffrey](https://www.linkedin.com/in/jeffreymatthias/) and [Aidan](https://www.linkedin.com/in/adobley/) cover how they use custom generators at [Mechanical Orchard](https://www.mechanical-orchard.com/) to help rebuild legacy programs in Elixir.

In their talk, Jeffrey notes (paraphrased) "Remember, this is _your_ app, you can name the folders what you want and place the files where you'd like".

I think this is an important thing to realize. I love that our community and frameworks have conventions, but sometimes I feel that folks become blinded to what they're "allowed" to do.

The default Phoenix generators might have a certain naming scheme or directory structure, but that doesn't mean it's the ordained way to structure your program (it's mostly superficial anyway.)

## See ya next time

I had a blast and I hope to catch ya at the next Code BEAM 👋</description>
  <pubDate>Tue, 11 Mar 2025 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/code-beam-america-2025/</link>
  <guid>https://www.mitchellhanberg.com/code-beam-america-2025/---https://www.mitchellhanberg.com/code-beam-america-2025/</guid>
</item>
<item>
  <title>How to set up unit tests for Astro components</title>
  <description>From Angelika.me: Improve your Astro unit testing experience by adding happy-dom and DOM Testing Library to your setup.</description>
  <pubDate>Sat, 01 Feb 2025 11:12:00 GMT</pubDate>
  <link>https://angelika.me/2025/02/01/astro-component-unit-tests/</link>
  <guid>https://angelika.me/2025/02/01/astro-component-unit-tests/---https://angelika.me/2025/02/01/astro-component-unit-tests/</guid>
</item>
<item>
  <title>The Elixir Shirt</title>
  <description>From Underjord.io: I wanted there to be an Elixir shirt you could buy. I talked to some people. Made some calculations. Took some debatable photos. And now you can pre-order The Elixir Shirt.
The Officialness This is an officially approved use of the Elixir logo trademark, by explicit permission from the Elixir core team. Usually the trademark can&amp;rsquo;t be used commercially. I was granted permission to make and sell products with the brand.</description>
  <pubDate>Fri, 13 Dec 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/the-elixir-shirt.html</link>
  <guid>https://underjord.io/the-elixir-shirt.html---https://underjord.io/the-elixir-shirt.html</guid>
</item>
<item>
  <title>From big Kubers down to small GenServers</title>
  <description>From Underjord.io: Me and the team had a meeting which we dedicated to talking about architecture. Sort of an ad-hoc exploration of patterns and concepts. How systems usually fit together. Which is mostly through various types of queues. And a bunch of common patterns like request/response, publish/subscribe and workers on a queue. And most request/response is essentially workers on a queue going in two directions.
This post was originally written as part of my newsletter which is a weekly thing that I&amp;rsquo;ve done for probably 5 years now.</description>
  <pubDate>Fri, 06 Dec 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/from-big-kubers-down-to-small-genservers.html</link>
  <guid>https://underjord.io/from-big-kubers-down-to-small-genservers.html---https://underjord.io/from-big-kubers-down-to-small-genservers.html</guid>
</item>
<item>
  <title>How to clamp the lightness of a relative color in CSS</title>
  <description>From Angelika.me: Let's say we have a color in a CSS variable and want to modify its lightness, but only if it's too dark or too light.</description>
  <pubDate>Sun, 01 Dec 2024 17:55:00 GMT</pubDate>
  <link>https://angelika.me/2024/12/01/how-to-clamp-relative-color-lightness/</link>
  <guid>https://angelika.me/2024/12/01/how-to-clamp-relative-color-lightness/---https://angelika.me/2024/12/01/how-to-clamp-relative-color-lightness/</guid>
</item>
<item>
  <title>Voice Activity Detection in Elixir and Membrane</title>
  <description>From Underjord.io: I hacked on something quite useful in the last few weeks, off and on. Voice Activity Detection in Elixir with Silero VAD through ONNX. I&amp;rsquo;ll show what I did and try to give an idea of what it is and why it is useful.
It boiled down to this gist as a proof of concept. Should work on most Elixir installs. These days Membrane will even try to pull pre-compiled dependencies for the libraries it wants.</description>
  <pubDate>Wed, 27 Nov 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/voice-activity-detection-elixir-membrane.html</link>
  <guid>https://underjord.io/voice-activity-detection-elixir-membrane.html---https://underjord.io/voice-activity-detection-elixir-membrane.html</guid>
</item>
<item>
  <title>Bodging GenServers Together</title>
  <description>From Underjord.io: What feels like forever ago but what was probably a year and a half I gave a talk about Lively LiveView with Membrane. Video is available for the curious. It was a stunt talk but also a talk about creativity and how Elixir let&amp;rsquo;s me plug things together and try things that feel magical. That feeling has never left me.
Some of the magic is a goddamn pain. Take the Membrane framework.</description>
  <pubDate>Fri, 22 Nov 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/bodging-genservers-together.html</link>
  <guid>https://underjord.io/bodging-genservers-together.html---https://underjord.io/bodging-genservers-together.html</guid>
</item>
<item>
  <title>How I use Erlang Hot Code Updates</title>
  <description>From Underjord.io: One of the Erlang ecosystem&amp;rsquo;s spiciest nerd snipes are hot code updates. Because it can do it. In ways that almost no other runtime can.
I use Elixir which builds on Erlang and has the same capabilities.
The standard way of doing Elixir releases via mix release does not support Erlang hot code updates. As in, it will not generate the necessary files for you. And if you do want to do it there are several blog posts you need to stitch together or you need to use the Erlang docs in great detail.</description>
  <pubDate>Tue, 19 Nov 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/how-i-use-erlang-hot-code-updates.html</link>
  <guid>https://underjord.io/how-i-use-erlang-hot-code-updates.html---https://underjord.io/how-i-use-erlang-hot-code-updates.html</guid>
</item>
<item>
  <title>Dependencies vs. devDependencies for JavaScript apps</title>
  <description>From Angelika.me: Whenever I'm adding a new dependency to a JavaScript app, or setting up the build process of a new app, I ask myself: what exactly are devDependencies? It's time to answer that question in depth.</description>
  <pubDate>Mon, 11 Nov 2024 19:15:00 GMT</pubDate>
  <link>https://angelika.me/2024/11/11/dependencies-vs-dev-dependencies-javascript-apps/</link>
  <guid>https://angelika.me/2024/11/11/dependencies-vs-dev-dependencies-javascript-apps/---https://angelika.me/2024/11/11/dependencies-vs-dev-dependencies-javascript-apps/</guid>
</item>
<item>
  <title>A Brief History of LLMs</title>
  <description>From Sayan Chakraborty | Blog | Research | AI | Engineering | Tech: A post where we recap and think about current state of language models</description>
  <pubDate>Mon, 07 Oct 2024 15:05:50 GMT</pubDate>
  <link>https://sayanc93.github.iobrief-history-of-llms</link>
  <guid>https://sayanc93.github.iobrief-history-of-llms---https://sayanc93.github.iobrief-history-of-llms</guid>
</item>
<item>
  <title>Anatomy of Embedded Elixir</title>
  <description>From Underjord.io: Working on the Nerves project, the Embedded framework for Elixir, has given me an increased appreciation for how Frank Hunleth and his collaborators through the years have structured things. And while I&amp;rsquo;ve found crossing into the Linux-heavy part of it difficult and frustrating there has been reasonable steps to take all the way from building the application layer in Elixir all the way back to fighting the bootloader. I&amp;rsquo;ll try to detail how a Nerves system is built up in this post.</description>
  <pubDate>Mon, 07 Oct 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/anatomy-of-embedded-elixir.html</link>
  <guid>https://underjord.io/anatomy-of-embedded-elixir.html---https://underjord.io/anatomy-of-embedded-elixir.html</guid>
</item>
<item>
  <title>Putting code on a Nerves device</title>
  <description>From Underjord.io: The Nerves project is a way of building embedded Linux devices where the BEAM virtual machine takes care of running things. This does not constrain what you can run in any significant way.
The easiest way to think of it is as a replacement for systemd. Though it is not only that. I will attempt to explain the different ways you can run things via the BEAM and consequently, on Nerves.</description>
  <pubDate>Mon, 16 Sep 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/putting-code-on-a-nerves-device.html</link>
  <guid>https://underjord.io/putting-code-on-a-nerves-device.html---https://underjord.io/putting-code-on-a-nerves-device.html</guid>
</item>
<item>
  <title>To Nerves from Elixir</title>
  <description>From Underjord.io: I adore Nerves. I recently joined the core team. And I&amp;rsquo;ll be doing my best to help people get along with this lovely way to co-mingle hardware and massively concurrent reliable software.
Getting started Assuming you have a comfortable Elixir &amp;amp; Erlang installed, preferrably the most recent. Most conveniently through asdf/mise, but I don&amp;rsquo;t judge. Instructions are lifted from the official docs but I will try to keep it leaner as I assume you are comfortable with Elixir.</description>
  <pubDate>Fri, 30 Aug 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/to-nerves-from-elixir.html</link>
  <guid>https://underjord.io/to-nerves-from-elixir.html---https://underjord.io/to-nerves-from-elixir.html</guid>
</item>
<item>
  <title>Catching up with Elixir</title>
  <description>From Underjord.io: I&amp;rsquo;ve kept very busy recently and as I look at what I published last it has clearly kept me from blogging. I don&amp;rsquo;t love that. I like having a blog and I like tending to it. I&amp;rsquo;ve not missed a beat on the newsletter&amp;rsquo;s weekly cadence but that might not be your jam. Let&amp;rsquo;s catch up. I&amp;rsquo;ve been diving into embedded Linux with Elixir.
My latest non-newsletter issue was about The Future Stack.</description>
  <pubDate>Wed, 28 Aug 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/catching-up-with-elixir.html</link>
  <guid>https://underjord.io/catching-up-with-elixir.html---https://underjord.io/catching-up-with-elixir.html</guid>
</item>
<item>
  <title>I've Joined the Official Elixir LSP Team</title>
  <description>From Mitchell Hanberg's Blog: I've joined the official Elixir language server team!

This is the culmination of the efforts from [Jonatan Kłosko](https://github.com/jonatanklosko) (of LiveBook), [Steve Cohen](https://github.com/scohen) (of Lexical), [Łukasz Samson](https://github.com/lukaszsamson) (of ElixirLS), and myself ([Mitchell Hanberg](https://github.com/mhanberg) of Next LS). 

You can read more about it on [elixir-lang.org](https://elixir-lang.org/blog/2024/08/15/welcome-elixir-language-server-team/).

We are currently working on merging the three projects (Lexical, ElixirLS, and Next LS) into a single official project, so in the mean time, you can continue to use 
the language server you are currently using.

If you would like to support my efforts on this project, please sponsor me on GitHub! I have a goal of reaching 100 sponsors, and as of writing I'm a little more than halfway there.

**Sponsor link:** https://github.com/sponsors/mhanberg

If you sponsor and would like an elixir-tools sticker, please email me your name and address and I'll send one your way (even internationally!)</description>
  <pubDate>Wed, 21 Aug 2024 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/ive-joined-the-official-elixir-lsp-team/</link>
  <guid>https://www.mitchellhanberg.com/ive-joined-the-official-elixir-lsp-team/---https://www.mitchellhanberg.com/ive-joined-the-official-elixir-lsp-team/</guid>
</item>
<item>
  <title>How to add CSS rules only in the test env in your Phoenix app</title>
  <description>From Angelika.me: Certain CSS styles might cause problems in your browser-based tests, so you might want to add some CSS rules that turn off those styles, but only in the test environment.</description>
  <pubDate>Mon, 12 Aug 2024 15:29:00 GMT</pubDate>
  <link>https://angelika.me/2024/08/12/how-to-add-css-rules-only-in-test-env-phoenix-app/</link>
  <guid>https://angelika.me/2024/08/12/how-to-add-css-rules-only-in-test-env-phoenix-app/---https://angelika.me/2024/08/12/how-to-add-css-rules-only-in-test-env-phoenix-app/</guid>
</item>
<item>
  <title>Rewriting a Vue app to Astro</title>
  <description>From Angelika.me: I rewrote my hobby project to Astro and its JavaScript bundle size shrank 3x.</description>
  <pubDate>Sun, 04 Aug 2024 08:54:00 GMT</pubDate>
  <link>https://angelika.me/2024/08/04/rewriting-vue-app-to-astro/</link>
  <guid>https://angelika.me/2024/08/04/rewriting-vue-app-to-astro/---https://angelika.me/2024/08/04/rewriting-vue-app-to-astro/</guid>
</item>
<item>
  <title>Newsletter issue, adept adoption</title>
  <description>From Underjord.io: My newsletter-provider Campaign Monitor (not a recommendation, they are .. fine I guess) are having an outage. And I&amp;rsquo;ve been on an unbroken publishing streak for the newsletter for a significant number of years. Probably 4, I could check but can&amp;rsquo;t log into Campaign Monitor. So this goes on the website until I can send it properly. Dangit.
 Building up the Nerve(s) As we are doing NervesCloud we are thinking a lot about adoption for Nerves.</description>
  <pubDate>Fri, 19 Jul 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/newsletter-building-up-the-nerves.html</link>
  <guid>https://underjord.io/newsletter-building-up-the-nerves.html---https://underjord.io/newsletter-building-up-the-nerves.html</guid>
</item>
<item>
  <title>Validate and Transform Your Data with Schematic</title>
  <description>From Mitchell Hanberg's Blog: I've recently seen talk of similar libraries to [Schematic](https://github.com/mhanberg/schematic) so I figured I'd share my take on the problem space!

## Schematic

Schematic is a library for validating and transforming data in Elixir, similar to [Ecto Changesets](https://hexdocs.pm/ecto/3.11.2/Ecto.Changeset.html#field_missing?/2?utm_source=thinkingelixir&amp;utm_medium=shownotes) and [Norm](https://github.com/elixir-toniq/norm).

I created Schematic early in 2023 while developing the [GenLSP](https://github.com/elixir-tools/gen_lsp) library, which I developed to build [Next LS](https://www.elixir-tools.dev/docs/next-ls/quickstart/). I needed to be able to consume and produce data structures described by the Language Server Protocol JSON Schema specification, which describes data in terms of basic scalar types and certain compound types, similar to some algebraic data types.

I wanted a library that lends itself to expressibility, composition, and code generation.

## Basic schematics

Schematic provides... _schematics_ for basic primitive types in Elixir.

```elixir
import Schematic

unify(int(), 1)
#=&gt; {:ok, 1}

unify(int(), "one")
#=&gt; {:error, "expected an integer"}
```

These are just functions, so you can bind them to variables and return them from functions.

```elixir
defmodule Numbers do
  import Schematic

  def integer(), do: int()

  def validate(value), do: unify(integer(), value)
end

Numbers.validate(1)
#=&gt; {:ok, 1}

Numbers.validate("one")
#=&gt; {:error, "expected an integer"}
```

## List and Tuple schematics

We can also define schematics for lists and tuples.


```elixir
defmodule Hobbies do
  import Schematic
    
  def validate(values), do: unify(list(str()), values)
end

Hobbies.validate(["foosball", "cooking"])
#=&gt; {:ok, ["foosball", "cooking"]}

Hobbies.validate([1, "cooking"])
#=&gt; {:error, [error: "expected a string", ok: "cooking"]}

Hobbies.validate("foosball")
#=&gt; {:error, "expected a list"}

```

## Map and Struct schematics

Map and struct (or `schema` in Schematic parlance) schematics are versatile and extendable.

```elixir
defmodule Animals do
  import Schematic

  def schematic() do
    map(%{
      species: str(),
      genus: str(),
      color: str()
    })
  end

  defmodule Cat do
    def schematic() do
      map(Animals.schematic(), %{
        breed: str(),
        declawed: bool()
      })
    end
  end
end

Schematic.unify(Animals.schematic(), %{})
#=&gt; {:error,
# %{
#   color: "expected a string",
#   species: "expected a string",
#   genus: "expected a string"
# }}

Schematic.unify(Animals.schematic(), %{color: "black"})
#=&gt; {:error, %{species: "expected a string", genus: "expected a string"}}

Schematic.unify(Animals.schematic(), %{color: "black", species: "lupus", genus: "canis"})
#=&gt; {:ok, %{color: "black", species: "lupus", genus: "canis"}}

Schematic.unify(Animals.Cat.schematic(), %{color: "orange", species: "catus", genus: "felis"})
#=&gt; {:error, %{breed: "expected a string", declawed: "expected a boolean"}}

Schematic.unify(Animals.Cat.schematic(), %{
  color: "orange",
  species: "catus",
  genus: "felis",
  breed: "shorthair",
  declawed: false
})
#=&gt; {:ok,
# %{
#   color: "orange",
#   species: "catus",
#   genus: "felis",
#   breed: "shorthair",
#   declawed: false
# }}
```

Structs can be created from plain maps using the `schema` schematic. By default it looks for string keys and converts them to atom keys, but that can be disabled using the `convert: false` option.

```elixir
defmodule Animals do
  import Schematic

  defstruct [:species, :genus, :color]

  def schematic() do
    schema(__MODULE__, %{
      species: str(),
      genus: str(),
      color: str()
    })
  end
end

Schematic.unify(Animals.schematic(), %{"species" =&gt; "lupus", "genus" =&gt; "canis", "color" =&gt; "grey"})
#=&gt; {:ok, %Animals{species: "lupus", genus: "canis", color: "grey"}}
```

### Optional keys and nullable fields

Optional keys and nullable values can be specified using the `optional` and `nullable` schematics. Combining this with a struct, you can create default values for certain keys.

`optional` keys do not have to be present, but if they are, the value must unify with the given schematic. 

```elixir
defmodule Animals do
  import Schematic

  defstruct [:species, :genus, color: "brown"]

  def schematic() do
    schema(__MODULE__, %{
      optional(:color) =&gt; str(),
      species: str(),
      genus: nullable(str())
    })
  end
end

Schematic.unify(Animals.schematic(), %{
  "species" =&gt; "lupus",
  "genus" =&gt; nil,
})
#=&gt; {:ok, %Animals{species: "lupus", genus: nil, color: "brown"}}
```

### Tranforming keys

While `schema` will automatically convert string to atom keys, you can also use a tuple for the key specification for key transformation like camelCase to snake_case.


```elixir
defmodule JobPosting do
  import Schematic

  def schematic() do
    map(%{
      {"startDate", :start_date} =&gt; str(),
      optional({"salaryRange", :salary_range}) =&gt; str(),
      {"title", :title} =&gt; str()
    })
  end
end

Schematic.unify(JobPosting.schematic(), %{
  "startDate" =&gt; "Jan 1, 2025",
  "title" =&gt; "Chicken Tender Engineer"
})
#=&gt; {:ok, %{title: "Chicken Tender Engineer", start_date: "Jan 1, 2025"}}
```

You can also use the `dump` function to transform keys in reverse.

```elixir
Schematic.dump(JobPosting.schematic(), %{
  title: "Chicken Tender Engineer",
  start_date: "Jan 1, 2025"
})
#=&gt; {:ok, %{"startDate" =&gt; "Jan 1, 2025", "title" =&gt; "Chicken Tender Engineer"}}
```

## Advanced schematics

Most of your data is rather complex and can be specified further than just a "string", you might have an enumeration, you might actually say a value can be either a `Mammal` or a `Reptile`, or convert an ISO timestamp into an Elixir DateTime struct.

### `oneof`

If you want to say a value is "one of" a list of schematics, you can use the `oneof` schematic. I believe the semantics are similar to an enum or union type from other languages. 

In this example, we also demonstrate using literals as schematics.

```elixir
defmodule HousePet do
  import Schematic

  def schematic() do
    map(%{
      name: str(),
      type:
        oneof([
          "Dog",
          "Cat",
          "Hamster",
          "Fish"
        ])
    })
  end
end
```

Making an enum of strings is nice, but for a proper union type style schematic, we can use other schematics, even map schematics.

```elixir
defmodule HousePet do
  import Schematic

  def dog, do: map(%{type: "dog"})
  def cat, do: map(%{type: "cat"})
  def hamster, do: map(%{type: "hamster"})
  def fish, do: map(%{type: "fish"})

  def schematic() do
    oneof([
      dog(),
      cat(),
      hamster(),
      fish()
    ])
  end
end

Schematic.unify(HousePet.schematic(), %{type: "cat"})
#=&gt; {:ok, %{type: "cat"}}
```

Unfortunately the error message for these kind of failure case is not very good, but will be improved in a future version Schematic.

```elixir
Schematic.unify(HousePet.schematic(), %{type: "snake"})
#=&gt; {:error, "expected either a map, a map, a map, or a map"}
```

## Value based validations

So far we've covered more _structural_ style of data validation, but we can also do more value based validations that you are probably used to in your `Ecto.Changeset` code.

We can use the `all` and `raw` schematics to accomplish this!

```elixir
defmodule SpecialNumber do
  def schematic do
    all([
      int(),
      raw(&amp;Kernel.&lt;(&amp;1, 10), message: "must be less than 10"),
      raw(&amp;(Kernel.rem(&amp;1, 2) == 0), message: "must be divisible by 2")
    ])
  end
end

Schematic.unify(SpecialNumber.schematic(), 8)
#=&gt; {:ok, 8}

Schematic.unify(SpecialNumber.schematic(), 15)
#=&gt; {:error, ["must be less than 10", "must be divisible by 2"]}

Schematic.unify(SpecialNumber.schematic(), "15")
#=&gt; {:error, ["expected an integer", "must be less than 10", "is invalid"]}
```

## Transforming Data

You can also use the `raw` schematic to transform the data as you parse and validate it. Here we read a ISO8601 timestamp and turn it into a `DateTime` struct.

```elixir
defmodule Datetime do
  import Schematic

  def schematic() do
    raw(
      fn
        i, :to -&gt; is_binary(i) and match?({:ok, _, _}, DateTime.from_iso8601(i))
        i, :from -&gt; match?(%DateTime{}, i)
      end,
      transform: fn
        i, :to -&gt;
          {:ok, dt, _} = DateTime.from_iso8601(i)
          dt

        i, :from -&gt;
          DateTime.to_iso8601(i)
      end
    )
  end
end

Schematic.unify(Datetime.schematic(), "2024-07-11T17:48:41.972851Z")
#=&gt; {:ok, ~U[2024-07-11 17:48:41.972851Z]}
```

## Dumping Data

Not only can you parse and validate external data into your internal format, you can also _dump_ that data back into the external format.

This will respect any map key transformations that you've declared and as seen above, you can use a `raw` schematic to arbitrarily control how the data is transformed in each direction.

```elixir
Schematic.dump(Datetime.schematic(), ~U[2024-07-11 17:48:41.972851Z])
#=&gt; {:ok, "2024-07-11T17:48:41.972851Z"}

Schematic.dump(Animals.schematic(), %Animals{
  species: "lupus",
  genus: "canis",
  color: "grey"
})
#=&gt; {:ok, %{"color" =&gt; "grey", "genus" =&gt; "canis", "species" =&gt; "lupus"}}
```

## Future features

While the `oneof` schematic handles "union" types (typically represented like `Dog | Cat`), I would like to add "intersection" types (represented sometimes like `Dog &amp; Cat`).

## Conclusion

I am pretty happy with what I've come up with, and it works for my use cases very well!

You can see Schematic in action in the [gen_lsp](https://github.com/elixir-tools/gen_lsp/blob/main/lib/gen_lsp/protocol/requests/initialize.ex) code base and for an example of how it works with code generation you can check out the [lsp_codegen](https://github.com/elixir-tools/lsp_codegen) project.

Happy hacking!</description>
  <pubDate>Fri, 12 Jul 2024 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/validate-and-transform-your-data-with-schematic/</link>
  <guid>https://www.mitchellhanberg.com/validate-and-transform-your-data-with-schematic/---https://www.mitchellhanberg.com/validate-and-transform-your-data-with-schematic/</guid>
</item>
<item>
  <title>Pet Peeves</title>
  <description>From Mitchell Hanberg's Blog: &gt; **noun** _informal_
&gt;
&gt; **something that a particular person finds especially annoying:** _one of my biggest pet peeves is poor customer service._

Here's a list of minor things I find irritating. I'll attempt to keep this page updated as more irritating things inevitably cross my path.

- Calling software "dead" or "abandoned"
- Complaining about too many choices, especially when C=2
- Complaining about what other people are doing when it doesn't affect you at all
- Judgement without context
- Speaking in a way that implies you have a ton of experience with something (saying "I do this in all my apps") when really you've done it two times
- The word "just"</description>
  <pubDate>Wed, 19 Jun 2024 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/pet-peeves/</link>
  <guid>https://www.mitchellhanberg.com/pet-peeves/---https://www.mitchellhanberg.com/pet-peeves/</guid>
</item>
<item>
  <title>Automated accessibility testing for Elixir web applications</title>
  <description>From Angelika.me: Integrate Axe into your Elixir browser tests with my new library, A11yAudit.</description>
  <pubDate>Mon, 10 Jun 2024 04:15:00 GMT</pubDate>
  <link>https://angelika.me/2024/06/10/automated-accessibility-testing-for-elixir-web-apps/</link>
  <guid>https://angelika.me/2024/06/10/automated-accessibility-testing-for-elixir-web-apps/---https://angelika.me/2024/06/10/automated-accessibility-testing-for-elixir-web-apps/</guid>
</item>
<item>
  <title>Create Your Own Neovim Distribution</title>
  <description>From Mitchell Hanberg's Blog: Gotcha! The click bait worked!

We're not really in the market to create an actual "distribution", but we are going to explore how to extract your Neovim configuration into it's own plugin.

Other real distributions actually employ this trick themselves like [LazyVim](https://github.com/LazyVim/LazyVim) and [AstroVim](https://github.com/astronvim/astronvim), the main requirement is that you use the [lazy.nvim](https://github.com/folke/lazy.nvim) package manager.

Let's get into the code!

## Converting your current configuration

Converting your configuration into a plugin is mostly just renaming some files/directories and moving them into their own repo.

### Original Configuration

Your original configuration might look something like this, you have a folder that handles your lazy.nvim plugin specs, some custom modules, some ftplugins, and a normal `init.lua`.

```
$HOME/.config/nvim/
└── lua/
    ├── custom/
    │   ├── plugins/
    │   │   ├── init.lua
    │   │   ├── elixir.lua
    │   │   └── lsp.lua
    │   ├── terminal.lua
    │   └── treesitter.lua
    ├── ftplugin/
    │   ├── elixir.lua
    │   └── javascript.lua
    └── init.lua
```

### Plugin based Configuration

The steps I took to extract my distribution was to:

- move the whole thing to a new git repository.
- rename `custom` to `mydistro` and resolve any necessary changes.
- rename `init.lua` to `plugin/mydistro.lua`.

Your new configuration structure should look like this:

```
$HOME/
├── .config/nvim/
│   └── init.lua
└── mydistro/
    └── lua/
        ├── custom/
        │   ├── plugins/
        │   │   ├── init.lua
        │   │   ├── elixir.lua
        │   │   └── lsp.lua
        │   ├── terminal.lua
        │   └── treesitter.lua
        ├── ftplugin/
        │   ├── elixir.lua
        │   └── javascript.lua
        └── plugin/
            └── mydistro.lua
```

### init.lua

The `init.lua` file in your dotfiles should look like this:

```lua
-- bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazypath) then
  vim
    .system({
      "git",
      "clone",
      "--filter=blob:none",
      "https://github.com/folke/lazy.nvim.git",
      "--branch=stable", -- latest stable release
      lazypath,
    })
    :wait()
end

vim.opt.rtp:prepend(lazypath)
require("lazy").setup {
  spec = {
    {
      "myname/mydistro", -- the location on GitHub for our distro
      dev = true, -- tells lazy.nvim to actually load a local copy
      import = "mydistro.plugins" -- the path to your plugins lazy plugin spec
    },
  },
  dev = { path = "~" }, -- the path to where `dev = true` looks for local plugins
  install = {
    missing = true,
  },
}
```

And we're done!

## Why?

Well, one: for fun!

And two: I personally use [home-manager](https://github.com/nix-community/home-manager) to manage my dotfiles, but that requires running home-manager anytime you change them.

This is very annoying when it comes to tweaking your Neovim configuration, so moving my configuration to a plugin that lives outside of home-manager means I can iterate quicker.

Another hypothetical use case is making it easier for someone to try out your Neovim configuration. This could be for someone getting into Neovim for the first time, or perhaps a plugin author trying to help debug an issue you're having.

Happy hacking!</description>
  <pubDate>Wed, 05 Jun 2024 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/create-your-own-neovim-distribution/</link>
  <guid>https://www.mitchellhanberg.com/create-your-own-neovim-distribution/---https://www.mitchellhanberg.com/create-your-own-neovim-distribution/</guid>
</item>
<item>
  <title>The Future Stack</title>
  <description>From Underjord.io: Fly.io is a highly visible cloud provider in the Elixir ecosystem and they put forward an interesting promise. They don&amp;rsquo;t deliver on that promise currently but I think it would be very compelling if they get there. Especially for Elixir. Let&amp;rsquo;s dig in.
This work is sponsored and supported by Tigris. Object Storage that works like a CDN on Fly. They are also part of the subject of the post. You&amp;rsquo;ll see.</description>
  <pubDate>Tue, 28 May 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/the-future-stack.html</link>
  <guid>https://underjord.io/the-future-stack.html---https://underjord.io/the-future-stack.html</guid>
</item>
<item>
  <title>How to change your local timezone on a mac to debug date-related JavaScript tests</title>
  <description>From Angelika.me: My date-related JavaScript tests were passing locally, but failing on CI. Changing my local timezone helped me find the bugs.</description>
  <pubDate>Sat, 25 May 2024 14:09:00 GMT</pubDate>
  <link>https://angelika.me/2024/05/25/change-your-local-timezone-on-a-mac-to-debug-javascript-tests/</link>
  <guid>https://angelika.me/2024/05/25/change-your-local-timezone-on-a-mac-to-debug-javascript-tests/---https://angelika.me/2024/05/25/change-your-local-timezone-on-a-mac-to-debug-javascript-tests/</guid>
</item>
<item>
  <title>Chatting, Sharing (&amp; Shots) in Chattanooga - GigCity 2024 report</title>
  <description>From Underjord.io: I went to Chattanooga in Tennessee from Sweden to hang out with the Elixir community. I&amp;rsquo;ve been part of Elixir in various ways for 6-7 years now and this is the first time things have lined up and I could go to the US for a conference: I had a sponsor for the trip. I was offered to not just speak but keynote. The conference is put on by friends, Bruce and Maggie Tate, who&amp;rsquo;ve supported a lot of great stuff in the Elixir community for a long time.</description>
  <pubDate>Thu, 23 May 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/chattanooga-gigcityelixir-nervesconf-2024.html</link>
  <guid>https://underjord.io/chattanooga-gigcityelixir-nervesconf-2024.html---https://underjord.io/chattanooga-gigcityelixir-nervesconf-2024.html</guid>
</item>
<item>
  <title>Unpacking Elixir - IoT &amp; Embedded with Nerves</title>
  <description>From Underjord.io: In this part of the series we dive into one of the frameworks and toolsets that really got me going with both Elixir as a hobby and the community as a space full of helpful people. This will be all about The Nerves Project, oh, and touch on some related stuff.
Before we dig in I should shout out some other BEAM, Elixir &amp;amp; Erlang-related things in the embedded space. AtomVM seems super cool and lets you do Erlang and Elixir on microcontrollers.</description>
  <pubDate>Fri, 26 Apr 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-iot-embedded-nerves.html</link>
  <guid>https://underjord.io/unpacking-elixir-iot-embedded-nerves.html---https://underjord.io/unpacking-elixir-iot-embedded-nerves.html</guid>
</item>
<item>
  <title>9 signs your frontend code has quality issues that affect your users</title>
  <description>From Angelika.me: Check if your project is displaying any of them.</description>
  <pubDate>Sat, 13 Apr 2024 16:47:00 GMT</pubDate>
  <link>https://angelika.me/2024/04/13/9-signs-your-frontend-code-has-quality-issues/</link>
  <guid>https://angelika.me/2024/04/13/9-signs-your-frontend-code-has-quality-issues/---https://angelika.me/2024/04/13/9-signs-your-frontend-code-has-quality-issues/</guid>
</item>
<item>
  <title>How to prevent WebStorm from removing trailing spaces from one specific file?</title>
  <description>From Angelika.me: I love WebStorm's "on save" formatting options, but from time to time, they get in my way.</description>
  <pubDate>Fri, 29 Mar 2024 12:32:00 GMT</pubDate>
  <link>https://angelika.me/2024/03/29/how-to-prevent-webstorm-from-removing-trailing-spaces-from-one-file/</link>
  <guid>https://angelika.me/2024/03/29/how-to-prevent-webstorm-from-removing-trailing-spaces-from-one-file/---https://angelika.me/2024/03/29/how-to-prevent-webstorm-from-removing-trailing-spaces-from-one-file/</guid>
</item>
<item>
  <title>Fundamentals of Object Storage</title>
  <description>From Underjord.io: I did a livestream where I talked about Object Storage. The how and the why. The Bad Old Days. And also the neat and interesting stuff just beyond the basics. I figured I&amp;rsquo;d cover that in text as well.
You can watch the stream here.
Transparency notice: The livestream and this blog post have been work supported by Tigris. We collaborate on the topics, they provide resources and pay for some of my time so I can do this stuff instead of writing software for clients.</description>
  <pubDate>Tue, 19 Mar 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/fundamentals-of-object-storage.html</link>
  <guid>https://underjord.io/fundamentals-of-object-storage.html---https://underjord.io/fundamentals-of-object-storage.html</guid>
</item>
<item>
  <title>Modern Format on Save in Neovim</title>
  <description>From Mitchell Hanberg's Blog: Formatting on save is a popular workflow and is builtin to many text editors and IDEs.

In Neovim, you must create this manually, but it is very easy using `autocmds`.

```lua
-- 1
vim.api.nvim_create_autocmd("LspAttach", {
  group = vim.api.nvim_create_augroup("lsp", { clear = true }),
  callback = function(args)
    -- 2
    vim.api.nvim_create_autocmd("BufWritePre", {
      -- 3
      buffer = args.buf,
      callback = function()
        -- 4 + 5
        vim.lsp.buf.format {async = false, id = args.data.client_id }
      end,
    })
  end
})
```

1. We create a new `autocmd` that fires on the `LspAttach` event. This event is fired when an LSP client attaches to a buffer. In this `autocmd`, you can easily set configuration that is specific to that buffer and LSP client.
2. We create another `autcmd` inside the `LspAttach` callback, this time for the `BufWritePre` event. This fires when you save the buffer but before it flushes anything to disk. This gives you a chance to manipulate the buffer first.
3. We configure this `autocmd` to only execute for the current buffer. This is a little more straight forward rather than setting an `autocmd` that executes for any range of file types.
4. In the callback, we run `vim.lsp.buf.format` to format the buffer, with the flag `async = false`. This ensures that the formatting request will block until it completes, so that it completely finishes formatting before flushing the file to disk.
5. We also set the `id = args.data.client` flag so that the formatting request is only sent to the LSP client that is related to the outer `LspAttach` request. This ensures that we aren't running the formatting request multiple times in case the buffer is attached to multiple LSPs.

And there we have it, modern format on save for those who want it.</description>
  <pubDate>Thu, 14 Mar 2024 08:00:00 -05</pubDate>
  <link>https://www.mitchellhanberg.com/modern-format-on-save-in-neovim/</link>
  <guid>https://www.mitchellhanberg.com/modern-format-on-save-in-neovim/---https://www.mitchellhanberg.com/modern-format-on-save-in-neovim/</guid>
</item>
<item>
  <title>A great music system</title>
  <description>From Posts on Claudio Ortolina: &lt;img src="https://claudio-ortolina.org/img/a-great-music-system/cover.png"/&gt;
&lt;p&gt;Over the last few months I had the opportunity to spend some time ironing out a music system that would marry analog and digital listening, log everything I play for recommendations and tracking of future releases, and collect all physical releases I own along with accompanying notes and reflections.&lt;/p&gt;
&lt;h2 id="music-library"&gt;
Music library
&lt;a href="#music-library" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I own music in different formats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vinyl records&lt;/li&gt;
&lt;li&gt;CDs&lt;/li&gt;
&lt;li&gt;High-resolution files (usually in &lt;a href="https://en.wikipedia.org/wiki/FLAC"&gt;FLAC&lt;/a&gt; format)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why 3 formats?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vinyl is usually the best format for old records before the 1980s, as it was the dominant medium. For a lot of music, there’s literally no other available physical format. The format had a massive revival in the last few years, and many artists release high-quality, 180g vinyls which sound amazing.&lt;/li&gt;
&lt;li&gt;CDs tend to be the target format for music produced in the late 1980s up to the 2010s, before streaming took off. A FLAC digital file is absolutely fine as a replacement for a CD quality-wise, but I’m a sucker for booklets and limited edition goodies. Particularly in the UK, second-hand CDs are sold in pretty much any charity shop, and more often than not you can get very good deals.&lt;/li&gt;
&lt;li&gt;Digital files are convenient for listening on the go, and are also sometimes the only option for bands who don’t produce physical releases. Both record labels and websites like &lt;a href="https://bandcamp.com"&gt;Bandcamp&lt;/a&gt; give the option to purchase in FLAC format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What’s crucial here is that for music I really love, I do wanna own it for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For new releases, I want to compensate the artist or band, and the best way is to buy their music (and attend their gigs).&lt;/li&gt;
&lt;li&gt;Streaming platforms can pull content anytime, and in many cases they don’t have all releases I want to listen to.&lt;/li&gt;
&lt;li&gt;Once I have albums in my library, I can organise them how I prefer (both physically and digitally).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A priceless extra is that my 2-year old daughter loves CDs because they look like little books but they make music, and vinyls because they’re big and rotate.&lt;/p&gt;
&lt;h2 id="hardware"&gt;
Hardware
&lt;a href="#hardware" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;img src="https://claudio-ortolina.org/img/a-great-music-system/music-system.png" alt="A photo of the music system I use showing the R5 player unit and the Pro-ject E1 turntable" class="left" /&gt;
&lt;p&gt;There are a few moving parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://www.synology.com/en-uk"&gt;Synology NAS&lt;/a&gt; storing all digital files.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.ruarkaudio.com/products/r5-high-fidelity-music-system"&gt;Ruark R5&lt;/a&gt; music system (which can play CDs directly, and that has an integrated amplifier for the turntable). It’s been recently discontinued, which does make a bit uneasy, but the manufacturer still supports it and I reckon I can go on for a few years before having to evaluate a replacement.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.project-audio.com/en/product/e1/"&gt;Pro-Ject E1&lt;/a&gt; turntable, playing through the R5. Pricy, but it really just works.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-4-model-b/"&gt;Raspberry Pi 4&lt;/a&gt; with a &lt;a href="https://www.hifiberry.com/shop/boards/hifiberry-dac2-pro/"&gt;HifiBerry DAC2 Pro&lt;/a&gt; hat, which allows the PI to play audio with great quality via the R5.&lt;/li&gt;
&lt;/ul&gt;
&lt;img src="https://claudio-ortolina.org/img/a-great-music-system/raspberry-pi.png" alt="A photo of the Raspberry Pi 4 with the HifiBerry DAC2 Pro hat" class="left" /&gt;
&lt;p&gt;None of this is cheap and there’s certainly more affordable options particularly if you’re able to source a different speakers/amp/turntable combo. The big advantage of using a Raspberry Pi (or an equivalent off the shelf solution) is that you separate “brain” from “muscle” (and the Pi + DAC hat are relatively cost-effective).&lt;/p&gt;
&lt;h2 id="playing-music"&gt;
Playing music
&lt;a href="#playing-music" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The whole system revolves around Plex: the NAS runs &lt;a href="https://www.plex.tv/en-gb/personal-media-server/"&gt;Plex Media Server&lt;/a&gt;, while the Raspberry Pi runs &lt;a href="https://www.plex.tv/plexamp/"&gt;PlexAmp&lt;/a&gt; headless. My laptop and phone run PlexAmp as well.&lt;/p&gt;
&lt;p&gt;Setting up PlexAmp headless on the Pi required minimal work thanks to the excellent &lt;a href="https://github.com/odinb/bash-plexamp-installer"&gt;bash-plexamp-installer&lt;/a&gt; project, which makes it a breeze to both install and update the software and correctly configure the DAC2 Pro hat. Couple of reboots and everything worked like a charm (and still does).&lt;/p&gt;
&lt;p&gt;With this setup, I can use any device to access my music collection, and play it through the Pi if I’m at home, or on-device if I’m out.&lt;/p&gt;
&lt;p&gt;The Plex Media Server instance is configured to scrobble to &lt;a href="https://last.fm"&gt;Last.fm&lt;/a&gt;, so everything gets logged automatically no matter where I play it from.&lt;/p&gt;
&lt;p&gt;When I play physical releases, I use the excellent &lt;a href="https://openscrobbler.com"&gt;OpenScrobbler.com&lt;/a&gt; web application to search for the album I’m playing, and scrobble it.&lt;/p&gt;
&lt;p&gt;I’m now trying out &lt;a href="https://tidal.com"&gt;Tidal&lt;/a&gt; as a way to listen before buying, as it provides hi-fi quality, pays artists better than other platforms, and it’s deeply integrated into Plex (which means I don’t need to use a separate app, and can rely just on PlexAmp).&lt;/p&gt;
&lt;p&gt;If the Tidal experiment is a success, I’ll stop my subscription to Apple Music.&lt;/p&gt;
&lt;p&gt;When music is played by the Pi, the R5 system has no information on what&amp;rsquo;s currently playing. I can read that on my phone, but for other people in the house I programmed a small automation that can be run with Siri, so that anyone can ask &amp;ldquo;What&amp;rsquo;s playing?&amp;rdquo; and get a good answer. This took some lightweight reverse engineering of the PlexAmp web application, and I&amp;rsquo;m not expecting it to be rock solid as it&amp;rsquo;s based on private APIs.&lt;/p&gt;
&lt;p&gt;In short, the PlexAmp web application polls an endpoint that returns a fairly comprehensive playing status with metadata information about the artist being played, both for local music in the Plex Server and on Tidal. As PlexAmp is written in Node.js, I wrote another small Node.js application to poll the same endpoint, and parse the information I need. I then created an iOS shortcut to hit my Node.js application, and read out the response in a human readable format. The shortcut is automatically available on all devices, and can be run via Siri by its name.&lt;/p&gt;
&lt;h2 id="discovery"&gt;
Discovery
&lt;a href="#discovery" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Algorithmic recommendations are now very precise - no matter the platform. I’ve come to find them &lt;em&gt;too precise&lt;/em&gt; in the sense that they don’t stray off the beaten path.&lt;/p&gt;
&lt;p&gt;I prefer to monitor different sources, particularly when it comes to my core preference, progressive rock:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.loudersound.com/prog"&gt;Prog Magazine&lt;/a&gt;, which is an old-school monthly zine (both printed and digital) that covers pretty much anything happening in the progressive scene. I have a digital subscription, and read it on the iPad where I can take notes and add albums to a queue.&lt;/li&gt;
&lt;li&gt;The reviews RSS feed for &lt;a href="https://progarchives.com"&gt;progarchives.com&lt;/a&gt;, which is a fairly old progressive rock community website where people publish reviews of whatever album they can think of. The RSS format is great because I can read the review, see the cover art (which does a lot for me), and from that decide if I’m interested to explore more.&lt;/li&gt;
&lt;li&gt;The occasional visit to a few subreddits, just to get a feel of the community is looking at.&lt;/li&gt;
&lt;li&gt;Newsletters from specific record labels that tend to publish music I like (e.g. &lt;a href="https://kscopemusic.com"&gt;Kscope&lt;/a&gt;) or &lt;a href="https://www.karismarecords.no/"&gt;Karisma Records&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thealbumyears.com/"&gt;The Album Years&lt;/a&gt; podcast with Steven Wilson and Tim Bowness, which provides a &lt;strong&gt;very opinionated&lt;/strong&gt; list of significant music releases roughly from the 70s till the 90s. Only downside is that it’s very UK-centric, and I wish there were similar lists for other parts of the world.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once I’ve listened to something, and the artist is scrobbled to Last.fm, I’m able to monitor new releases via the excellent &lt;a href="https://apps.apple.com/us/app/musicharbor-track-new-music/id1440405750"&gt;MusicHarbor&lt;/a&gt; iOS app. The app imports the list of artists I have on Last.fm, and keeps track of all releases by these artists. The import process is easily done manually via a few button presses in the app, and I do that once a month.&lt;/p&gt;
&lt;p&gt;Each Friday (release day!) I get a new batch of albums to check out, and I add whatever picks my attention to my queue.&lt;/p&gt;
&lt;h2 id="physical-collection-management-and-notes"&gt;
Physical collection management and notes
&lt;a href="#physical-collection-management-and-notes" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Managing a physical collection is a relatively solved problem: with &lt;a href="https://discogs.com"&gt;Discogs&lt;/a&gt;, one can simply scan or search for the correct release, and add it to their own account.&lt;/p&gt;
&lt;p&gt;While this is great to track value and conditions of the item, it&amp;rsquo;s not geared towards managing a collection with associated notes about the artist(s), or lyrics. It&amp;rsquo;s also not possible to arbitrarily draw connections between albums.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m currently trying out &lt;a href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt; with the &lt;a href="https://github.com/mProjectsCode/obsidian-media-db-plugin"&gt;MediaDB&lt;/a&gt; plugin. This lets me quickly add every album via the &lt;a href="https://musicbrainz.org/"&gt;MusicBrainz&lt;/a&gt; search API (without matching to an exact release, i.e. you can&amp;rsquo;t pick the correct year/label/country/limited edition).&lt;/p&gt;
&lt;p&gt;Each album becomes its own file with a set of metadata information I can query through a couple of plugins. On top of that, I can write notes and associate albums with simple internal links.&lt;/p&gt;
&lt;p&gt;As the data is stored as markdown files with a YAML front matter, there’s no risk of lock-in, and if I ever need to process the data for further analysis or visualization, I can write my own program that does that.&lt;/p&gt;
&lt;p&gt;Obsidian is available on all platforms I use, and syncs both data and settings without having to do anything special.&lt;/p&gt;
&lt;h2 id="too-much"&gt;
Too much?
&lt;a href="#too-much" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I often ask myself if this system is too complicated, but I personally find that the experience of using it is simple at the expense of a reasonable amount of hidden complexity. I’m also aware that my requirements are many, and that’s because music is pretty much my only significant hobby. It still gives me the same joy I felt as a teenager, and still manages to surprise me even when I think I heard it all.&lt;/p&gt;</description>
  <pubDate>Fri, 08 Mar 2024 09:00:00 +0100</pubDate>
  <link>https://claudio-ortolina.org/posts/a-great-music-system/</link>
  <guid>https://claudio-ortolina.org/posts/a-great-music-system/---https://claudio-ortolina.org/posts/a-great-music-system/</guid>
</item>
<item>
  <title>Challenges of local-first LLMs</title>
  <description>From Underjord.io: TLDR: the tricky part is &amp;ldquo;Large&amp;rdquo;. &amp;ldquo;Language&amp;rdquo; and &amp;ldquo;Model&amp;rdquo; seems manageable. But largeness has all sorts of trouble associated with it for mobile devices. Not insurmountable, but challenging. Also makes for finicky development.
To start off, I am not an AI expert. I am not an ML engineer. I&amp;rsquo;m a web and systems dev that mostly works in Elixir these days. Through the Elixir community I got connected with ElectricSQL and have done some collaboration with them.</description>
  <pubDate>Fri, 08 Mar 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/challenges-of-local-first-llms.html</link>
  <guid>https://underjord.io/challenges-of-local-first-llms.html---https://underjord.io/challenges-of-local-first-llms.html</guid>
</item>
<item>
  <title>How to format Elixir doctests?</title>
  <description>From Angelika.me: I wrote a doctest formatter plugin to help you with that.</description>
  <pubDate>Sat, 02 Mar 2024 14:33:00 GMT</pubDate>
  <link>https://angelika.me/2024/03/02/format-elixir-doctests/</link>
  <guid>https://angelika.me/2024/03/02/format-elixir-doctests/---https://angelika.me/2024/03/02/format-elixir-doctests/</guid>
</item>
<item>
  <title>How to format Elixir code in Markdown code blocks?</title>
  <description>From Angelika.me: I wrote a Markdown code block formatter plugin to help you with that.</description>
  <pubDate>Sat, 27 Jan 2024 15:03:00 GMT</pubDate>
  <link>https://angelika.me/2024/01/27/format-elixir-code-blocks-in-markdown/</link>
  <guid>https://angelika.me/2024/01/27/format-elixir-code-blocks-in-markdown/---https://angelika.me/2024/01/27/format-elixir-code-blocks-in-markdown/</guid>
</item>
<item>
  <title>Unpacking Elixir: Phoenix</title>
  <description>From Underjord.io: In this series I&amp;rsquo;ve been unpacking various facets of Elixir. Mostly this has meant trying to explain Erlang and the BEAM through the lens of Elixir. Now we are moving into the domain of the web framework. This is where I dare say that Elixir has much more to say than Erlang. Erlang has to my understanding never landed fully on a canonical preferred web framework. Elixir has Phoenix and this post will be unpacking Phoenix.</description>
  <pubDate>Mon, 22 Jan 2024 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-phoenix.html</link>
  <guid>https://underjord.io/unpacking-elixir-phoenix.html---https://underjord.io/unpacking-elixir-phoenix.html</guid>
</item>
<item>
  <title>Do not run `mix test || mix test --failed`</title>
  <description>From Angelika.me: This popular way of retrying flaky tests on CI is a trap.</description>
  <pubDate>Mon, 08 Jan 2024 19:46:00 +0100</pubDate>
  <link>https://angelika.me/2024/01/08/do-not-run-mix-test-failed/</link>
  <guid>https://angelika.me/2024/01/08/do-not-run-mix-test-failed/---https://angelika.me/2024/01/08/do-not-run-mix-test-failed/</guid>
</item>
<item>
  <title>Unpacking Elixir: The Actor Model</title>
  <description>From Underjord.io: This series covers a lot of fundamentals about the underlying BEAM VM and Erlang as consequence of covering Elixir fundamentals. A lot has been said about The Actor Model when it comes to Erlang. That&amp;rsquo;s kind of funny. Because the only thing that as a matter of terminology should definitely be called actors in the Erlang world are Bjarne Däcker, Joe Armstrong, Mike Williams and Robert Virding as featured in Erlang: The Movie.</description>
  <pubDate>Thu, 16 Nov 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-the-actor-model.html</link>
  <guid>https://underjord.io/unpacking-elixir-the-actor-model.html---https://underjord.io/unpacking-elixir-the-actor-model.html</guid>
</item>
<item>
  <title>Ergonomic Remote Development</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;I recently built a new PC (you can see the specs on my &lt;a href="/uses"&gt;/uses&lt;/a&gt; page!) and installed Linux on it.&lt;/p&gt;
&lt;p&gt;The way I have been using it mostly has been through an SSH connection, even though its sitting underneath my desk, plugged into my monitor, and all my peripherals plugged into a nifty USB hub splitter thing!&lt;/p&gt;
&lt;p&gt;I simply couldn't live without the macOS desktop environment. I have so much muscle memory for all the shortcuts and have so many apps that I use that enhance my workflow.&lt;/p&gt;
&lt;p&gt;I have never remotely developed for this long before, so I quickly ran into a bunch of paper cuts for which I was able to easily find bandaids.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#tailscale" aria-hidden="true" class="anchor" id="tailscale"&gt;&lt;/a&gt;Tailscale&lt;/h2&gt;
&lt;p&gt;When you are going to be SSHing into a remote computer, you need to know the IP address of the computer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt; makes this super easy. You install Tailscale on your local computer and on the remote computer, and they both join your "tailnet".&lt;/p&gt;
&lt;p&gt;Now, you have a static IP address that only other computers on your tailnet can access!&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-bash" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;ssh&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;mitchell@&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;remote&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;ip&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #8cf8f7;"&gt;Welcome&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;to&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;Ubuntu&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;23.10&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;GNU/Linux&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;6.5.0-10-generic&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;x86_64&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt; &lt;span style="color: #8cf8f7;"&gt;*&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Documentation:&lt;/span&gt;  &lt;span style="color: #e0e2ea;"&gt;https://help.ubuntu.com&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt; &lt;span style="color: #8cf8f7;"&gt;*&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Management:&lt;/span&gt;     &lt;span style="color: #e0e2ea;"&gt;https://landscape.canonical.com&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt; &lt;span style="color: #8cf8f7;"&gt;*&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Support:&lt;/span&gt;        &lt;span style="color: #e0e2ea;"&gt;https://ubuntu.com/advantage&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;&lt;span style="color: #e0e2ea;"&gt;6&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;updates&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;can&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;be&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;applied&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;immediately.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;&lt;span style="color: #8cf8f7;"&gt;To&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;see&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;these&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;additional&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;updates&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;run:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;apt&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;list&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;--upgradable&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;&lt;span style="color: #8cf8f7;"&gt;***&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;System&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;restart&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;required&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;***&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;&lt;span style="color: #8cf8f7;"&gt;Last&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;login:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Tue&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Nov&lt;/span&gt;  &lt;span style="color: #e0e2ea;"&gt;7&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;07:24:00&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;2023&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;from&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;local&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;ip&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#clipboard" aria-hidden="true" class="anchor" id="clipboard"&gt;&lt;/a&gt;Clipboard&lt;/h2&gt;
&lt;p&gt;You'll want to make sure that you can still copy/paste to/from your host computer's clipboard (or 'pasteboard' as macOS calls it) from within TUI applications like Vim/Tmux and in shell scripts.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#tui-apps" aria-hidden="true" class="anchor" id="tui-apps"&gt;&lt;/a&gt;TUI Apps&lt;/h3&gt;
&lt;p&gt;If you are using a modern terminal emulator (I use &lt;a href="https://mitchellh.com/ghostty"&gt;Ghostty&lt;/a&gt;), you most likely have clipboard working already for TUI applications, as long as they support the appropriate terminal features.&lt;/p&gt;
&lt;p&gt;I use &lt;a href="https://neovim.io/"&gt;Neovim&lt;/a&gt;, which does not &lt;a href="https://github.com/neovim/neovim/pull/25872"&gt;currently&lt;/a&gt; support the OSC-52 terminal feature that enables system clipboard communication, but I use Neovim inside &lt;a href="https://github.com/tmux/tmux"&gt;tmux&lt;/a&gt;, which does!&lt;/p&gt;
&lt;p&gt;And luckily, Neovim has a tmux 'clipboard provider' which is how Neovim actually communicates to the "outside" clipboard.&lt;/p&gt;
&lt;p&gt;If you don't use tmux and use Vim/Neovim, there are plenty of &lt;a href="https://github.com/ojroques/vim-oscyank"&gt;plugins&lt;/a&gt; that implement it!&lt;/p&gt;
&lt;h3&gt;&lt;a href="#scripting" aria-hidden="true" class="anchor" id="scripting"&gt;&lt;/a&gt;Scripting&lt;/h3&gt;
&lt;p&gt;The way that I have implemented scripting to my local clipboard is to again use SSH!&lt;/p&gt;
&lt;p&gt;Since your computers are using Tailscale and are on the same tailnet, this means that your local computer also has a nice and static IP address that you can use.&lt;/p&gt;
&lt;p&gt;Here is an example:&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-bash" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;ssh&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;mitchell@&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;local&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;ip&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;pbpaste&lt;/span&gt; \
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  &lt;span style="color: #e0e2ea;"&gt;|&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;some-local-command&lt;/span&gt; \
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;  &lt;span style="color: #e0e2ea;"&gt;|&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;ssh&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;mitchell@&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;local&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;ip&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;pbcopy&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we remotely run the &lt;code&gt;pbpaste&lt;/code&gt; command (which, along with &lt;code&gt;pbpaste&lt;/code&gt;, are the macOS scripting utilities for using the system clipboard), pipe the ouput into a local command, and then pipe it into &lt;code&gt;ssh&lt;/code&gt;, which will send it to the &lt;code&gt;pbcopy&lt;/code&gt; command on our local computer.&lt;/p&gt;
&lt;p&gt;Now, you can easily wrap that up in an alias or a shell script.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#opening-your-web-browser" aria-hidden="true" class="anchor" id="opening-your-web-browser"&gt;&lt;/a&gt;Opening your web browser&lt;/h2&gt;
&lt;p&gt;If you are used to opening web pages with utilities like the GitHub CLI &lt;a href="https://cli.github.com/"&gt;gh&lt;/a&gt;, you'll notice that those don't work anymore. This is because (on Linux), the tool is (most likely) running &lt;code&gt;xdg-open&lt;/code&gt;, which is similar to the &lt;code&gt;open&lt;/code&gt; command on macOS.&lt;/p&gt;
&lt;p&gt;You are running this on the remote computer, which is not logged into a desktop environment, and has no web browser.&lt;/p&gt;
&lt;p&gt;Well, we can easily fix this again with SSH!&lt;/p&gt;
&lt;p&gt;What I did was write a shell script called &lt;code&gt;xdg-open&lt;/code&gt; and put it in my &lt;code&gt;$PATH&lt;/code&gt;. This way, tools like &lt;code&gt;gh&lt;/code&gt; will actually call my script instead of the built-in &lt;code&gt;xdg-open&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;My script looks like this:&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-bash" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;function&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;main&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;	&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;local&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;ip&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;	&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;local&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;uri&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;	&lt;span style="color: #e0e2ea;"&gt;uri&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;	&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;[[&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;!&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-z&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;SSH_CONNECTION&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;]]&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;		&lt;span style="color: #e0e2ea;"&gt;ip&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;SSH_CONNECTION&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;|&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;awk&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;&amp;lbrace; print $1 &amp;rbrace;&amp;#39;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;)&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;		&lt;span style="color: #8cf8f7;"&gt;ssh&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;mitchell@&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;ip&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;open&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;uri&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;	&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;		&lt;span style="color: #8cf8f7;"&gt;xdg-open&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;uri&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;	&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;&lt;span style="color: #8cf8f7;"&gt;main&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We check the &lt;code&gt;$SSH_CONNECTION&lt;/code&gt; environment variable to see if we are in an SSH session.&lt;/p&gt;
&lt;p&gt;If we are &lt;em&gt;not&lt;/em&gt;, we just call &lt;code&gt;xdg-open&lt;/code&gt; as usual. This is helpful in case we log onto our PC like a normal person.&lt;/p&gt;
&lt;p&gt;If we &lt;em&gt;are&lt;/em&gt;, then we parse our local IP address out of the &lt;code&gt;$SSH_CONNECTION&lt;/code&gt; variable and run the &lt;code&gt;open&lt;/code&gt; command remotely using &lt;code&gt;ssh&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#web-development" aria-hidden="true" class="anchor" id="web-development"&gt;&lt;/a&gt;Web Development&lt;/h2&gt;
&lt;p&gt;Now, if you are a web developer, you typically will be starting a local web server and previewing your site or app in the browser. This gets a little tricky when your browser and web server are not on the same computer!&lt;/p&gt;
&lt;h3&gt;&lt;a href="#tailscale-1" aria-hidden="true" class="anchor" id="tailscale-1"&gt;&lt;/a&gt;Tailscale&lt;/h3&gt;
&lt;p&gt;Luckily, tailscale comes to the rescue again!&lt;/p&gt;
&lt;p&gt;With tailscale, you can create a reverse proxy served over &lt;code&gt;https&lt;/code&gt; with your existing free plan.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-bash" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;tailscale&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;serve&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;https&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;/&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;http://localhost:4000&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a reverse proxy that is only available inside your tailnet!&lt;/p&gt;
&lt;p&gt;If you want to show off your beautiful website to a client or coworker, you can run &lt;code&gt;tailscale funnel 443 on&lt;/code&gt;, which will make your reverse proxy available outside of your tailnet.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#ssh-port-forwarding" aria-hidden="true" class="anchor" id="ssh-port-forwarding"&gt;&lt;/a&gt;SSH Port Forwarding&lt;/h3&gt;
&lt;p&gt;You can also just tack on some options to the &lt;code&gt;ssh&lt;/code&gt; command instead of using Tailscale&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-bash" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;ssh&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;mitchell@&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;remote&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;ip&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;-L&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;4999:localhost:4999&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#signing-git-commits" aria-hidden="true" class="anchor" id="signing-git-commits"&gt;&lt;/a&gt;Signing Git Commits&lt;/h2&gt;
&lt;p&gt;I sign my &lt;code&gt;git&lt;/code&gt; commits using the &lt;a href="https://1password.com/"&gt;1Password&lt;/a&gt; SSH agent. This allows me to use an SSH key (instead of a GPG key) and to authorize the usage of the key with Touch ID on my Mac.&lt;/p&gt;
&lt;p&gt;As you can imagine, when I went commit my first code on my new PC, I thought I had found a roadblock kill this new workflow.&lt;/p&gt;
&lt;p&gt;Luckily, you can actually &lt;em&gt;forward&lt;/em&gt; your SSH agent when you make and SSH connection. This is typically for when you are machine hopping and have to &lt;code&gt;ssh&lt;/code&gt; to a remote machine from inside an &lt;code&gt;ssh&lt;/code&gt; connection, so that you don't have to install your keys on your local computer and the first remote computer.&lt;/p&gt;
&lt;p&gt;And, this also works for 1Password Git Commit signing!&lt;/p&gt;
&lt;p&gt;Just add an entry in your &lt;code&gt;~/.ssh/config&lt;/code&gt; for your PC to allow forwarding on your local computer:&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;Host &amp;lt;remote ip&amp;gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  ForwardAgent yes
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And on the PC, configure the IdentityAgent to use 1Password. I don't actually remember if this is necessary, but when writing this post, I found this config and I don't remember writing it, so it must be important!&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;Host *
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  IdentityAgent ~/.1password/agent.sock
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;And there we have it!&lt;/p&gt;
&lt;p&gt;Now you should be able to develop remotely and not even be able to tell the difference, except for the sheer speed of your overpowered new PC!&lt;/p&gt;</description>
  <pubDate>Tue, 07 Nov 2023 08:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/ergonomic-remote-development/</link>
  <guid>https://www.mitchellhanberg.com/ergonomic-remote-development/---https://www.mitchellhanberg.com/ergonomic-remote-development/</guid>
</item>
<item>
  <title>Unpacking Elixir: Observability</title>
  <description>From Underjord.io: Elixir supports the usual supects of observability. Open Telemetry (OTel), log handlers, capturing metrics. And it does it well. This post will mostly focus on the observability you have on the BEAM that is either incredibly rare to see elsewhere or possibly entirely unique.
The previous posts on concurrency and resilience might give useful context around how processes work and how supervision trees are structured. I will try not to lean too heavily on them but if you feel the need to understand more, consider reading them.</description>
  <pubDate>Mon, 02 Oct 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-observability.html</link>
  <guid>https://underjord.io/unpacking-elixir-observability.html---https://underjord.io/unpacking-elixir-observability.html</guid>
</item>
<item>
  <title>Unpacking Elixir: Resilience</title>
  <description>From Underjord.io: The nine nines. 99.9999999% of uptime. Whether the AXD301 actually deserves to be held up as a system of nine nines seems debatable. I am not particularly interested in that debate. Erlang has a strong record for reliability and a design intended to help you as a developer and operator achieve your nines. Maybe just five of them. Up to you really.
Previous episodes of this article series has unpacked Concurrency, Real-time &amp;amp; Latency and the syntax.</description>
  <pubDate>Sun, 24 Sep 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-resilience.html</link>
  <guid>https://underjord.io/unpacking-elixir-resilience.html---https://underjord.io/unpacking-elixir-resilience.html</guid>
</item>
<item>
  <title>Create a sorted Index in Ecto</title>
  <description>From ⚡ Maqbool Khan: defmodule MyApp.Repo.Migrations.AddMessagesInsertedAtIndex do
  use Ecto.Migration

  @disable_ddl_transaction true
  @disable_migration_lock true

  def change do
    create index("messages", ["inserted_at DESC"], concurrently: true)
  end
end

if y...</description>
  <pubDate>Sat, 09 Sep 2023 11:45:03 GMT</pubDate>
  <link>https://www.maqbool.net/create-a-sorted-index-in-ecto</link>
  <guid>https://www.maqbool.net/create-a-sorted-index-in-ecto---https://www.maqbool.net/create-a-sorted-index-in-ecto</guid>
</item>
<item>
  <title>Start async task under our application Supervision tree</title>
  <description>From ⚡ Maqbool Khan: Inside your application.ex file we will add our task supervisor with the name MyApp.TaskSupervisor
MyApp.Application is the module where we essentially describe in what order BEAM should start our elixir application and it's the place where you descr...</description>
  <pubDate>Sat, 09 Sep 2023 11:31:04 GMT</pubDate>
  <link>https://www.maqbool.net/start-async-task-under-our-application-supervision-tree</link>
  <guid>https://www.maqbool.net/start-async-task-under-our-application-supervision-tree---https://www.maqbool.net/start-async-task-under-our-application-supervision-tree</guid>
</item>
<item>
  <title>Unpacking Elixir: Real-time &amp; Latency</title>
  <description>From Underjord.io: Elixir was built on Erlang. Erlang was built to provide &amp;ldquo;consistently low latency&amp;rdquo; and a few other audacious goals. Note, this is not a hard realtime constraint. It is soft, squishy and yet, important and real. It makes Erlang unusually suitable to systems where latency matters and where a near-realtime experience is necessary.
Soft realtime. This part of the Unpacking Elixir series will really benefit from having read Unpacking Elixir - Concurrency as that post covers a lot about how concurrency works in Erlang and with that it covers Processes and Schedulers.</description>
  <pubDate>Fri, 08 Sep 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-realtime-latency.html</link>
  <guid>https://underjord.io/unpacking-elixir-realtime-latency.html---https://underjord.io/unpacking-elixir-realtime-latency.html</guid>
</item>
<item>
  <title>Unpacking Elixir: Syntax</title>
  <description>From Underjord.io: Elixir is a language with syntactical roots in Ruby. It also carries the Erlang legacy. Legacy used here as in &amp;ldquo;a great legacy&amp;rdquo; and not as in &amp;ldquo;system you don&amp;rsquo;t like anymore&amp;rdquo;. Ruby is an object-oriented language. Elixir is functional language. The Erlang part has an impact as Elixir was designed to provide strong interoperability with Erlang. Like Ruby and Erlang, Elixir offers a high-level of abstraction and is a very dynamic language.</description>
  <pubDate>Fri, 01 Sep 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-syntax.html</link>
  <guid>https://underjord.io/unpacking-elixir-syntax.html---https://underjord.io/unpacking-elixir-syntax.html</guid>
</item>
<item>
  <title>Unpacking Elixir: Concurrency</title>
  <description>From Underjord.io: Elixir is the thing I do most of my public writing and speaking about. It is my default programming language for the last 5-6 years. It suits my brain. Performs well for the kind of work I typically do. And using it I have experienced very few drawbacks. Rather than writing yet another post trying to widely summarize what I think is beneficial about the language I want to try and go a bit deeper on one particular aspect I like.</description>
  <pubDate>Fri, 25 Aug 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/unpacking-elixir-concurrency.html</link>
  <guid>https://underjord.io/unpacking-elixir-concurrency.html---https://underjord.io/unpacking-elixir-concurrency.html</guid>
</item>
<item>
  <title>Arriving Somewhere</title>
  <description>From Posts on Claudio Ortolina: &lt;img src="https://claudio-ortolina.org/img/arriving-somewhere/cover.jpg"/&gt;
&lt;p&gt;I&amp;rsquo;m almost 40 - and while my life has somehow the shape that I envisioned as I grew up, there are definitely big differences. Think about the same picture, but in different colors.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s music that accompanied me for the last 20 years, pretty much since I became an adult. It&amp;rsquo;s been long enough that I don&amp;rsquo;t exactly remember when it entered my life, and in some ways it feels like it&amp;rsquo;s always been there.&lt;/p&gt;
&lt;p&gt;Of all that music, thousands of songs, there&amp;rsquo;s a handful that made a deep mark that I still carry with me. More than anything else, one song stands above any other: &lt;em&gt;Arriving Somewhere, But Not Here&lt;/em&gt; by Porcupine Tree.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Kpeip2B8l2E?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;I turned 39 a few days ago and my wife got me as a gift a very nice edition of &lt;em&gt;Deadwing&lt;/em&gt;, the album the song is included in. It&amp;rsquo;s a 2018 remaster, which I&amp;rsquo;m listening to on a high-quality system.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a 12 minute song, with long instrumental sections, and I know every single little twist - yet this new version has some little extra details, some of the ambient parts are crisper, some of the guitar sounds pop a little bit more. I love it.&lt;/p&gt;
&lt;p&gt;It also dawned on me that it is a song about how life turns out different than you think - with fairly tragic images - but with a growing sense of peace as notes go by.&lt;/p&gt;
&lt;p&gt;There is relief in the understanding that some pieces of someone&amp;rsquo;s life are set - that some of the choices made are points of no return. That some dreams are over, and that&amp;rsquo;s ok.&lt;/p&gt;
&lt;p&gt;I do have the feeling of having arrived somewhere, but not here. It is different, but it&amp;rsquo;s equally beautiful. And I&amp;rsquo;m finally ok with that.&lt;/p&gt;
&lt;p&gt;And through the twists and turns of the last 20 years, I have a song that feels like an old friend that grew up with me, or even that was a little bit ahead, and I finally caught up.&lt;/p&gt;
&lt;p&gt;To the next 20 years - hopefully I&amp;rsquo;ll get somewhere.&lt;/p&gt;</description>
  <pubDate>Fri, 23 Jun 2023 17:34:25 +0300</pubDate>
  <link>https://claudio-ortolina.org/posts/arriving-somewhere/</link>
  <guid>https://claudio-ortolina.org/posts/arriving-somewhere/---https://claudio-ortolina.org/posts/arriving-somewhere/</guid>
</item>
<item>
  <title>The many states of Elixir</title>
  <description>From Underjord.io: We of the blessed church of Functional Programming pride ourselves on our immutability, purity and lack of noxious side-effects. We do not mutate the state. We only produce new and better state. Deterministic state. Correct state. The best state.
Except that I work in Elixir. Elixir is built on Erlang. I like to think Erlang being FP is mostly an accidental side-effect of trying to solve a bunch of complex requirements around distributed systems, fault tolerance and consistently low latency (this predates the concurrency story, another kind-of-accident).</description>
  <pubDate>Tue, 13 Jun 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/the-many-states-of-elixir.html</link>
  <guid>https://underjord.io/the-many-states-of-elixir.html---https://underjord.io/the-many-states-of-elixir.html</guid>
</item>
<item>
  <title>Scripting with Elixir</title>
  <description>From Underjord.io: I was a Python developer for some time and one great joy of Python is that you have an expressive language that you can use for your serious apps as well as for your hacky little one-off script or bespoke pieces of automation. By expressive I mean that typing very little can give you a lot of progress towards your result. I&amp;rsquo;ve scripted a fair bit in Python. Now I do it with Elixir.</description>
  <pubDate>Mon, 12 Jun 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/scripting-with-elixir.html</link>
  <guid>https://underjord.io/scripting-with-elixir.html---https://underjord.io/scripting-with-elixir.html</guid>
</item>
<item>
  <title>Why do ML on the Erlang VM?</title>
  <description>From Underjord.io: Another question might be; why do machine learning at all? I&amp;rsquo;m not big into ML/AI though I&amp;rsquo;ve been poking it more recently as the open models, such as Stable Diffusion and the more practical Whisper, caught my curiosity. I have a very decent GPU (3090 Ti) and I&amp;rsquo;ve poked around a bit. I don&amp;rsquo;t consider ML a super exciting solution to all the problems or a harbinger of general artificial intelligence.</description>
  <pubDate>Fri, 09 Jun 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/why-ml-on-erlang.html</link>
  <guid>https://underjord.io/why-ml-on-erlang.html---https://underjord.io/why-ml-on-erlang.html</guid>
</item>
<item>
  <title>OTP Process Abstractions with proc_lib</title>
  <description>From Mitchell Hanberg's Blog: In November of 2022, I had the privilege to speak at [Code BEAM America](https://codebeamamerica.com/).

You can check out the video below, let me know what you think!

&amp;nbsp;

&lt;div class="flex justify-center"&gt;
  &lt;iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Ug-SEozyG1A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;</description>
  <pubDate>Mon, 22 May 2023 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/otp-process-abstractions-with-proc-lib/</link>
  <guid>https://www.mitchellhanberg.com/otp-process-abstractions-with-proc-lib/---https://www.mitchellhanberg.com/otp-process-abstractions-with-proc-lib/</guid>
</item>
<item>
  <title>ElixirConf EU 2023, Lisbon report</title>
  <description>From Underjord.io: Last week I attended ElixirConf in Portugal and had a lovely time. I&amp;rsquo;ll try to capture my experience of the conference here in this post. For me and for you.
The last ElixirConf I visited was Prague in 2019. Pre-pandemic. I had just gotten into Elixir and kind of decided that I should actually really engage and try to do the whole community thing with this language as it seemed like it had a community of a comprehendable size.</description>
  <pubDate>Thu, 27 Apr 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/elixirconf-eu-2023-lisbon.html</link>
  <guid>https://underjord.io/elixirconf-eu-2023-lisbon.html---https://underjord.io/elixirconf-eu-2023-lisbon.html</guid>
</item>
<item>
  <title>Credo Language Server and the birth of elixir-tools</title>
  <description>From Mitchell Hanberg's Blog: Last year I started working on [gen_lsp](https://github.com/mhanberg/gen_lsp), an abstraction for writing [language servers](https://microsoft.github.io/language-server-protocol/) in Elixir.

&gt; I gave a presentation on gen_lsp and writing OTP process abstractions at CodeBEAM America 2022. You can watch my talk on [YouTube](https://www.youtube.com/watch?v=Ug-SEozyG1A).

Today I'd like to announce the release of the first language server built with `gen_lsp`.

## Credo Language Server

[Credo Language Server](https://github.com/elixir-tools/credo-language-server) is a persistent server that communicates with text editors via the Language Server Protocol.

This initial release comes with project wide diagnostics and a code action to add a `# credo:disable-for-next-line` magic comment above any Credo warning.

You can install it today using one of the `elixir-tools` family of editor extensions.

## elixir-tools

[elixir-tools](https://github.com/elixir-tools) is the new home for Credo Language Server and the aforementioned editor extensions.

### elixir-tools.nvim

If you write Elixir and use [Neovim](https://neovim.io), there is a chance you are already using my plugin, [elixir-tools.nvim](https://github.com/elixir-tools/elixir-tools.nvim) (formerly known as elixir.nvim).

The Neovim plugin has been renamed to help it better fit into it's place in the ecosystem and has given rise to the new elixir-tools family.

### elixir-tools.vscode

The elixir-tools family keeps on growing!

The release of Credo Language Server naturally mean that there would need to a way to use it with Visual Studio Code, so [elixir-tools.vscode](https://marketplace.visualstudio.com/items?itemName=elixir-tools.elixir-tools) was born.

This initial release brings support for Credo Language Server and Elixir filetype and highlighting support.

[ElixirLS](https://github.com/elixir-lsp/elixir-ls) support has been omitted since it can happily coexist along side elixir-tools.vscode. If there is demand to add ElixirLS support, that can be done, but at this time there is no need.

## Slow and Steady Wins the Race

Over the last 10 months I have been slowly chipping away at this project, making sure that every part is built with excellence in mind.

As I built out gen_lsp, I realized that the best way to achieve correctness was to generate most of the code from the official specification.

So, I built the [lsp_codegen](https://github.com/mhanberg/lsp_codegen) library.

It includes a handwritten generator that conforms to the LSP specification's [JSON Schema](https://json-schema.org) and is used to read the LSP metamodel and to generate Elixir code that includes typespecs and structs for all of the data structures.

While building the code generator, I realized that the specification's "Or" type was easy to represent using typespecs, but was actually hard to deserialize from a JSON payload into the Elixir data structures.

Inspired by [norm](https://github.com/elixir-toniq/norm) and my friend [Chris Keathley](https://keathley.io), I built [schematic](https://github.com/mhanberg/schematic). This allows me to fully validate, serialize, and deserialize complex data structures.

So, to build Credo Language Server, it just took 3 libraries, 2 editor extensions, and a conference talk. 😅

I will be writing more about gen_lsp and schematic in the future.

## What's Next

This is just the beginning! 🤗

I have some exciting ideas planned and can't wait to be able to share them with the Elixir community. If you'd like to stay on the bleeding edge of elixir-tools, feel free to join the [Discord](https://discord.gg/6XdGnxVA2A).</description>
  <pubDate>Tue, 18 Apr 2023 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/credo-language-server-and-the-birth-of-elixir-tools/</link>
  <guid>https://www.mitchellhanberg.com/credo-language-server-and-the-birth-of-elixir-tools/---https://www.mitchellhanberg.com/credo-language-server-and-the-birth-of-elixir-tools/</guid>
</item>
<item>
  <title>Introducing lazyasdf: An Elixir-based TUI for the asdf version manager</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;&lt;img src="https://res.cloudinary.com/mhanberg/image/upload/v1678034860/CleanShot_2023-03-04_at_14.44.18_2x.png" alt="lazyasdf" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/mhanberg/lazyasdf"&gt;lazyasdf&lt;/a&gt; is my first real&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-1" id="fnref-1" data-footnote-ref&gt;1&lt;/a&gt;&lt;/sup&gt; attempt at making a TUI with &lt;a href="https://elixir-lang.org"&gt;Elixir&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://asdf-vm.com/"&gt;asdf&lt;/a&gt; is normally used through a &lt;em&gt;command line interface&lt;/em&gt; (CLI), &lt;code&gt;lazyasdf&lt;/code&gt; presents you with a &lt;em&gt;terminal user interface&lt;/em&gt; (TUI) for working with &lt;code&gt;asdf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I recently fell in love with &lt;a href="https://github.com/jesseduffield/lazygit"&gt;lazygit&lt;/a&gt; and have since dreamed of writing my own TUI programs, but with Elixir.&lt;/p&gt;
&lt;p&gt;The TUI provides a quick and intuitive interface for those familiar with the terminal and for those who prefer a graphical application, but the TUI is so much more approachable in my humble opinion when it comes to making your own 😄.&lt;/p&gt;
&lt;p&gt;While I find &lt;code&gt;lazyasdf&lt;/code&gt; to be an amazing achievement for myself, it isn't &lt;em&gt;super&lt;/em&gt; interesting on its own. Let's dive into the specifics of how I was able to build and distribute a TUI application with Elixir.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#ratatouille" aria-hidden="true" class="anchor" id="ratatouille"&gt;&lt;/a&gt;Ratatouille&lt;/h2&gt;
&lt;p&gt;None of this would be possible if it weren't for the library &lt;a href="https://github.com/ndreynolds/ratatouille"&gt;ratatouille&lt;/a&gt; by &lt;a href="https://ndreynolds.com/"&gt;Nick Reynolds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I am not some genius when it comes to terminals or laying out text, this all comes from Ratatouille, which builds off of &lt;a href="https://github.com/nsf/termbox"&gt;termbox&lt;/a&gt;, which is a [n]curses alternative.&lt;/p&gt;
&lt;p&gt;Ratatouille leverages the &lt;a href="https://guide.elm-lang.org/architecture/"&gt;Elm Architecture&lt;/a&gt; of which many of us have grown familiar. Let's take a look at a small Ratatouille program that showcases most of its features.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://res.cloudinary.com/mhanberg/image/upload/v1678037282/CleanShot_2023-03-05_at_12.27.43_2x.png" alt="Tuido" /&gt;&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;#!/usr/bin/env elixir&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea;"&gt;Mix&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;install&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:ratatouille&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defmodule&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Todos&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;behaviour &lt;span style="color: #e0e2ea;"&gt;Ratatouille.App&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Ratatouille.View&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Ratatouille.Constants&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;only: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;color: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;key: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;  &lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;style_selected &lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;    &lt;span style="color: #8cf8f7;"&gt;color: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;color&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:black&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;    &lt;span style="color: #8cf8f7;"&gt;background: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;color&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:white&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;  &lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;  &lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;space &lt;span style="color: #8cf8f7;"&gt;key&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:space&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;  &lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;impl &lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;init&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #9b9ea4;"&gt;_&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;    &lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;      &lt;span style="color: #8cf8f7;"&gt;todo: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;        &lt;span style="color: #8cf8f7;"&gt;items: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;buy eggs&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;false&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;mow the lawn&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;get a haircut&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;false&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;        &lt;span style="color: #8cf8f7;"&gt;cursor_y: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;      &lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="25"&gt;    &lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="26"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="27"&gt;
&lt;/div&gt;&lt;div class="line" data-line="28"&gt;  &lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;impl &lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="29"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;update&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;msg&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="30"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;case&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;msg&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="31"&gt;      &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:event&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;key: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #e0e2ea;"&gt;space&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="32"&gt;        &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #9b9ea4;"&gt;_done&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Enum&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;at&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;items&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor_y&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="33"&gt;
&lt;/div&gt;&lt;div class="line" data-line="34"&gt;        &lt;span style="color: #8cf8f7;"&gt;update_in&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;items&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;!&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="35"&gt;
&lt;/div&gt;&lt;div class="line" data-line="36"&gt;      &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:event&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;ch: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;?j&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="37"&gt;        &lt;span style="color: #8cf8f7;"&gt;update_in&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor_y&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;cursor_down&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;items&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="38"&gt;
&lt;/div&gt;&lt;div class="line" data-line="39"&gt;      &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:event&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;ch: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;?k&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="40"&gt;        &lt;span style="color: #8cf8f7;"&gt;update_in&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor_y&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;cursor_up&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;/&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="41"&gt;
&lt;/div&gt;&lt;div class="line" data-line="42"&gt;      &lt;span style="color: #9b9ea4;"&gt;_&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="43"&gt;        &lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="44"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="45"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="46"&gt;
&lt;/div&gt;&lt;div class="line" data-line="47"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defp&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;cursor_down&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;rows&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="48"&gt;    &lt;span style="color: #8cf8f7;"&gt;min&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;+&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Enum&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;count&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;rows&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="49"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="50"&gt;
&lt;/div&gt;&lt;div class="line" data-line="51"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defp&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;cursor_up&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="52"&gt;    &lt;span style="color: #8cf8f7;"&gt;max&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;0&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="53"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="54"&gt;
&lt;/div&gt;&lt;div class="line" data-line="55"&gt;  &lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #8cf8f7;"&gt;&lt;span style="color: #e0e2ea;"&gt;impl &lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="56"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;render&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="57"&gt;    &lt;span style="color: #8cf8f7;"&gt;view&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="58"&gt;      &lt;span style="color: #8cf8f7;"&gt;panel&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;title: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;TODO&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="59"&gt;        &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;for&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;t&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;done&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;idx&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Enum&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;with_index&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;items&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="60"&gt;          &lt;span style="color: #8cf8f7;"&gt;row&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="61"&gt;            &lt;span style="color: #8cf8f7;"&gt;column&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;size: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;12&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="62"&gt;              &lt;span style="color: #8cf8f7;"&gt;label&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;idx&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;==&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;model&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;todo&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;cursor_y&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;do: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&lt;span style="color: #e0e2ea;"&gt;@&lt;span style="color: #e0e2ea;"&gt;style_selected&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;else: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="63"&gt;                &lt;span style="color: #8cf8f7;"&gt;text&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;content: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;- [&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="64"&gt;                &lt;span style="color: #8cf8f7;"&gt;done&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;done&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="65"&gt;                &lt;span style="color: #8cf8f7;"&gt;text&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;content: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;] &lt;span style="color: #8cf8f7;"&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;t&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;rbrace;&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="66"&gt;              &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="67"&gt;            &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="68"&gt;          &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="69"&gt;        &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="70"&gt;      &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="71"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="72"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="73"&gt;
&lt;/div&gt;&lt;div class="line" data-line="74"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defp&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;done&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;do: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;text&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;content: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="75"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defp&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;done&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;false&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;do: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;text&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;content: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="76"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="77"&gt;
&lt;/div&gt;&lt;div class="line" data-line="78"&gt;&lt;span style="color: #e0e2ea;"&gt;Ratatouille&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;run&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;Todos&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should be able to copy the above snippet into a file, make it executable (&lt;code&gt;chmod + x&lt;/code&gt;) and run it!&lt;/p&gt;
&lt;p&gt;Ratatouille calls for 3 callbacks in your TUI program, &lt;code&gt;init/1&lt;/code&gt;, &lt;code&gt;update/2&lt;/code&gt;, and &lt;code&gt;render/1&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When the program boots up, the &lt;code&gt;init/1&lt;/code&gt; callback is called and the return value becomes your initial model state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Whenever the TUI receives user input, the &lt;code&gt;update/2&lt;/code&gt; callback is executed with the message and your current model state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When that returns, the runtime will call the &lt;code&gt;render/1&lt;/code&gt; callback with the new model state.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;render/1&lt;/code&gt; callback is full of macros which translate to element structs, so it's just an ergonomic DSL. Typing out many structs by hand would be a PITA!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;a href="#notes" aria-hidden="true" class="anchor" id="notes"&gt;&lt;/a&gt;Notes&lt;/h3&gt;
&lt;p&gt;You have probably observed that, while it is high level compared to raw &lt;code&gt;termbox&lt;/code&gt;, Ratatouille is still sort of "low level" as an application framework.&lt;/p&gt;
&lt;p&gt;We still have to manually track and move our cursor position, as well as index into our data structures to pull out the right data for that position.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#burrito" aria-hidden="true" class="anchor" id="burrito"&gt;&lt;/a&gt;Burrito&lt;/h2&gt;
&lt;p&gt;Now the normal problem with Elixir apps is that you have to have Elixir and Erlang on your machine to run them, as well as keep track of the version of them you have installed to make sure they are compatible, as well as write aliases to run escripts and Mix tasks, yada yada.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href="https://github.com/burrito-elixir/burrito"&gt;Burrito&lt;/a&gt; comes in!&lt;/p&gt;
&lt;p&gt;Burrito utilizes &lt;a href="https://ziglang.org/"&gt;Zig&lt;/a&gt; to bundle up your application, the BEAM, and the Runtime all into one tidy executable that you can distribute at your leisure!&lt;/p&gt;
&lt;p&gt;In the end, once we run &lt;code&gt;MIX_ENV=prod mix release&lt;/code&gt;, Burrito will create binaries for each of our specified target platforms, and you can just copy those onto your computer and run them&lt;/p&gt;
&lt;p&gt;The Burrito project is lead by &lt;a href="https://twitter.com/doawoo/"&gt;Digit&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#homebrew" aria-hidden="true" class="anchor" id="homebrew"&gt;&lt;/a&gt;Homebrew&lt;/h2&gt;
&lt;p&gt;To make any program useful, it is help to be able to install it easily.&lt;/p&gt;
&lt;p&gt;Homebrew is the primary way of accomplishing this on MacOS (my preferred operating system) and you can easily host your own collection of Homebrew packages with your own Tap!&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;lazyasdf&lt;/code&gt; has some quirky dependencies, the formula (what Homebrew calls a package definition) is a little interesting.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-ruby" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;class&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Lazyasdf&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Formula&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  &lt;span style="color: #8cf8f7;"&gt;desc&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;TUI for the asdf version manager&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;  &lt;span style="color: #8cf8f7;"&gt;homepage&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;https://github.com/mhanberg/lazyasdf&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;  &lt;span style="color: #8cf8f7;"&gt;url&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;https://github.com/mhanberg/lazyasdf/archive/refs/tags/v0.1.1.tar.gz&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;  &lt;span style="color: #8cf8f7;"&gt;sha256&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;787da19809ed714c569c8bd7df58d55d7389b69efdf1859e57f713d18e3d2d05&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #8cf8f7;"&gt;license&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;MIT&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #8cf8f7;"&gt;bottle&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;    &lt;span style="color: #8cf8f7;"&gt;root_url&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;https://github.com/mhanberg/homebrew-tap/releases/download/lazyasdf-0.1.1&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;    &lt;span style="color: #8cf8f7;"&gt;sha256&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;cellar&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:any_skip_relocation&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;monterey&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;f489e328c19954d62284a7154fbc8da4e7a1df61dc963930d291361a7b2ca751&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;  &lt;span style="color: #8cf8f7;"&gt;depends_on&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;elixir&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:build&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;  &lt;span style="color: #8cf8f7;"&gt;depends_on&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;erlang&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:build&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;  &lt;span style="color: #8cf8f7;"&gt;depends_on&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;gcc&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:build&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;  &lt;span style="color: #8cf8f7;"&gt;depends_on&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;make&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:build&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;  &lt;span style="color: #8cf8f7;"&gt;depends_on&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;python@3.9&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:build&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;  &lt;span style="color: #8cf8f7;"&gt;depends_on&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;xz&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:build&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;  &lt;span style="color: #8cf8f7;"&gt;depends_on&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;asdf&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;  &lt;span style="color: #8cf8f7;"&gt;on_macos&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;    &lt;span style="color: #8cf8f7;"&gt;on_arm&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;      &lt;span style="color: #8cf8f7;"&gt;resource&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;zig&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="25"&gt;        &lt;span style="color: #8cf8f7;"&gt;url&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;https://ziglang.org/download/0.10.0/zig-macos-aarch64-0.10.0.tar.xz&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="26"&gt;        &lt;span style="color: #8cf8f7;"&gt;sha256&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;02f7a7839b6a1e127eeae22ea72c87603fb7298c58bc35822a951479d53c7557&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="27"&gt;      &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="28"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="29"&gt;
&lt;/div&gt;&lt;div class="line" data-line="30"&gt;    &lt;span style="color: #8cf8f7;"&gt;on_intel&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="31"&gt;      &lt;span style="color: #8cf8f7;"&gt;resource&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;zig&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="32"&gt;        &lt;span style="color: #8cf8f7;"&gt;url&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;https://ziglang.org/download/0.10.0/zig-macos-x86_64-0.10.0.tar.xz&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="33"&gt;        &lt;span style="color: #8cf8f7;"&gt;sha256&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;3a22cb6c4749884156a94ea9b60f3a28cf4e098a69f08c18fbca81c733ebfeda&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="34"&gt;      &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="35"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="36"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="37"&gt;
&lt;/div&gt;&lt;div class="line" data-line="38"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;install&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="39"&gt;    &lt;span style="color: #e0e2ea;"&gt;zig_install_dir&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;buildpath&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;/&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;zig&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="40"&gt;    &lt;span style="color: #8cf8f7;"&gt;mkdir&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;zig_install_dir&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="41"&gt;    &lt;span style="color: #e0e2ea;"&gt;resources&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;each&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;|&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;r&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="42"&gt;      &lt;span style="color: #e0e2ea;"&gt;r&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;fetch&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="43"&gt;
&lt;/div&gt;&lt;div class="line" data-line="44"&gt;      &lt;span style="color: #8cf8f7;"&gt;system&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;tar&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;xvC&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;zig_install_dir&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;-f&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;r&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;cached_download&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="45"&gt;      &lt;span style="color: #e0e2ea;"&gt;zig_dir&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="46"&gt;        &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Hardware&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;::&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;CPU&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;arm?&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="47"&gt;          &lt;span style="color: #e0e2ea;"&gt;zig_install_dir&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;/&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;zig-macos-aarch64-0.10.0&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="48"&gt;        &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="49"&gt;          &lt;span style="color: #e0e2ea;"&gt;zig_install_dir&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;/&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;zig-macos-x86_64-0.10.0&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="50"&gt;        &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="51"&gt;
&lt;/div&gt;&lt;div class="line" data-line="52"&gt;      &lt;span style="color: #e0e2ea;"&gt;ENV&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;PATH&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;zig_dir&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;:&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;+&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;ENV&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;PATH&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="53"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="54"&gt;
&lt;/div&gt;&lt;div class="line" data-line="55"&gt;    &lt;span style="color: #e0e2ea;"&gt;ENV&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;PATH&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;Formula&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;python@3.9&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;opt_libexec&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;/&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;bin:&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;+&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;ENV&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;PATH&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="56"&gt;
&lt;/div&gt;&lt;div class="line" data-line="57"&gt;    &lt;span style="color: #8cf8f7;"&gt;system&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;mix&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;local.hex&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;--force&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="58"&gt;    &lt;span style="color: #8cf8f7;"&gt;system&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;mix&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;local.rebar&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;--force&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="59"&gt;
&lt;/div&gt;&lt;div class="line" data-line="60"&gt;    &lt;span style="color: #e0e2ea;"&gt;ENV&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;BURRITO_TARGET&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Hardware&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;::&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;CPU&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;arm?&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="61"&gt;      &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;macos_m1&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="62"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="63"&gt;      &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;macos&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="64"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="65"&gt;
&lt;/div&gt;&lt;div class="line" data-line="66"&gt;    &lt;span style="color: #e0e2ea;"&gt;ENV&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;MIX_ENV&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;prod&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="67"&gt;    &lt;span style="color: #8cf8f7;"&gt;system&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;mix&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;deps.get&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="68"&gt;    &lt;span style="color: #8cf8f7;"&gt;system&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;mix&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;release&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="69"&gt;
&lt;/div&gt;&lt;div class="line" data-line="70"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;OS&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;mac?&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="71"&gt;      &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Hardware&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;::&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;CPU&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;arm?&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="72"&gt;        &lt;span style="color: #e0e2ea;"&gt;bin&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;install&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;burrito_out/lazyasdf_macos_m1&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;lazyasdf&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="73"&gt;      &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="74"&gt;        &lt;span style="color: #e0e2ea;"&gt;bin&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;install&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;burrito_out/lazyasdf_macos&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;lazyasdf&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="75"&gt;      &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="76"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="77"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="78"&gt;
&lt;/div&gt;&lt;div class="line" data-line="79"&gt;  &lt;span style="color: #8cf8f7;"&gt;test&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="80"&gt;    &lt;span style="color: #9b9ea4;"&gt;# this is required for homebrew-core&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="81"&gt;    &lt;span style="color: #8cf8f7;"&gt;system&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;true&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="82"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="83"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we can see all of &lt;code&gt;lazyasdf&lt;/code&gt;'s dependencies.&lt;/p&gt;
&lt;p&gt;It requires&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Elixir/Erlang: self-explanatory&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;asdf: self-explanatory&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;gcc, make: used to compile the termbox NIF bindings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python 3.9: The termbox NIF uses a python script. For some reason it works with 3.9 and not 3.11, so I pinned it at 3.9 🤷‍♂️.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;zig,xz: Burrito uses these two.&lt;/p&gt;
&lt;p&gt;Burrito specifically uses Zig 0.10.0, not 0.10.1, so we have to specify it as a resource and download it from the Zig website. Luckily, they provide pre-compiled binaries for both of our target platforms, so we can just download, untar, and add them to our PATH!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Python dependency is even more quirky. The termbox scripts use the unversioned &lt;code&gt;python&lt;/code&gt; executable, but Homebrew does not link those by default, so we have to manually add the unversioned one to our PATH for it to work.&lt;/p&gt;
&lt;p&gt;Voilà!&lt;/p&gt;
&lt;h3&gt;&lt;a href="#notes-1" aria-hidden="true" class="anchor" id="notes-1"&gt;&lt;/a&gt;Notes&lt;/h3&gt;
&lt;p&gt;Since this is a 3rd party Tap, the bottles that are generated are for an older version of Intel Mac, so those won't be very useful to anybody.&lt;/p&gt;
&lt;p&gt;But if I were to merge this formula into homebrew-core, they would be bottled using the secret Homebrew GitHub Actions runners that can bottle it for all the OS's and architectures.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#the-future" aria-hidden="true" class="anchor" id="the-future"&gt;&lt;/a&gt;The Future&lt;/h2&gt;
&lt;p&gt;Ratatouille is incredible as it is today, but there is a lot of room for improvement.&lt;/p&gt;
&lt;p&gt;As time allows, I hope to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Contribute to Ratatouille to allow more complex UI features like scrollbars and dynamic size information for elements.&lt;/li&gt;
&lt;li&gt;Create bindings for termbox2 (the next iteration of termbox).&lt;/li&gt;
&lt;li&gt;Create a higher level toolkit for building TUIs with Ratatouille, including menus, inputs, dialogs, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;hr /&gt;
&lt;section class="footnotes" data-footnotes&gt;
&lt;ol&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;Previously, I have made a &lt;a href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt; clone using Ratatouille. You can find it in my &lt;a href="https://github.com/mhanberg/.dotfiles/blob/ab07f27041780d1b54704ad4799382f58548468e/bin/fxf"&gt;dotfiles&lt;/a&gt;. &lt;a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description>
  <pubDate>Mon, 06 Mar 2023 01:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/introducing-lazyasdf-a-tui-for-the-asdf-version-manager/</link>
  <guid>https://www.mitchellhanberg.com/introducing-lazyasdf-a-tui-for-the-asdf-version-manager/---https://www.mitchellhanberg.com/introducing-lazyasdf-a-tui-for-the-asdf-version-manager/</guid>
</item>
<item>
  <title>Team Retrospectives: A How-To Guide for Team Building</title>
  <description>From Binary Noggin: &lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/team-retrospectives-a-how-to-guide-for-team-buildling/"&gt;Team Retrospectives: A How-To Guide for Team Building&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Thu, 02 Mar 2023 20:25:16 +0000</pubDate>
  <link>https://binarynoggin.com/blog/team-retrospectives-a-how-to-guide-for-team-buildling/</link>
  <guid>https://binarynoggin.com/blog/team-retrospectives-a-how-to-guide-for-team-buildling/---https://binarynoggin.com/?p=3121</guid>
</item>
<item>
  <title>Physical knobs &amp; userspace drivers in Elixir</title>
  <description>From Underjord.io: I like production gear. Audio, video and .. miscellaneous. I like things that seem like they&amp;rsquo;ll make me oh-so-very productive. I&amp;rsquo;m a sucker prosumer as a hobby. Let&amp;rsquo;s talk about Elgato, Elixir and why I never actually get anything done. This is unfortunately not sponsored by a prosumer brand, or anyone, that&amp;rsquo;d have been something.
Ad-style pitch for a business-thing: I help companies find competent, capable or promising Elixir developers. If you build in Elixir and want to spend less time finding great candidates, reach out.</description>
  <pubDate>Wed, 01 Mar 2023 04:00:00 +0000</pubDate>
  <link>https://underjord.io/userspace-drivers-in-elixir.html</link>
  <guid>https://underjord.io/userspace-drivers-in-elixir.html---https://underjord.io/userspace-drivers-in-elixir.html</guid>
</item>
<item>
  <title>TIL - Implementing Encoder for NifStructs</title>
  <description>From Maarten van Vliet: I am currently using Rustler, a tool that allows for Elixir to interact with the Rust ecosystem. As a newcomer to Rust, working on Rust-Elixir crossover libraries helps me understand its quirks.
Today, I was trying to use a SQL parser in Rust and convert the results to Elixir terms. Rustler&amp;rsquo;s NifStructs feature made this process convenient. However, I encountered an issue with a data structure defined as follows:
#[derive(NifStruct)] #[module = &amp;#34;SqlParser.</description>
  <pubDate>Tue, 27 Dec 2022 00:08:54 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2022/12/27/til-rustler-encoder/</link>
  <guid>https://maartenvanvliet.nl/2022/12/27/til-rustler-encoder/---https://maartenvanvliet.nl/2022/12/27/til-rustler-encoder/</guid>
</item>
<item>
  <title>ElixirConf 2022 Productivity Takeaways</title>
  <description>From Binary Noggin: &lt;p&gt;I am a huge fan of improving development workflows. The less I have to think about incidental things, the more I can concentrate on the problems that matter. One talk from ElixirConf 2022 helped improve my development workflow. Jason Axelson doled out some essential tips to speed up Elixir development. A few quick wins for [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/elixirconf-2022-productivity-takeaways/"&gt;ElixirConf 2022 Productivity Takeaways&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Fri, 09 Dec 2022 18:29:23 +0000</pubDate>
  <link>https://binarynoggin.com/blog/elixirconf-2022-productivity-takeaways/</link>
  <guid>https://binarynoggin.com/blog/elixirconf-2022-productivity-takeaways/---https://binarynoggin.com/?p=2994</guid>
</item>
<item>
  <title>Guarantees with Ecto.Repo.update_all/3</title>
  <description>From Binary Noggin: &lt;p&gt;We all want guarantees. We can get guarantees on our automobiles, homes, phones, shoes, food, and even our pets. We’ll take a guarantee in, on, or about anything. As software developers, our clients want guarantees for a whole host of things, from delivery dates to service uptime. Some of those are difficult to promise. What [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/guarantees-with-ecto-repo-update_all-3/"&gt;Guarantees with Ecto.Repo.update_all/3&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Fri, 09 Dec 2022 18:19:27 +0000</pubDate>
  <link>https://binarynoggin.com/blog/guarantees-with-ecto-repo-update_all-3/</link>
  <guid>https://binarynoggin.com/blog/guarantees-with-ecto-repo-update_all-3/---https://binarynoggin.com/?p=2984</guid>
</item>
<item>
  <title>The Lifecycle of a Phoenix LiveView</title>
  <description>From Binary Noggin: &lt;p&gt;The LiveView request lifecycle runs twice when a connection is first made to your application. It runs once to render static content for web crawlers, search engines, and other non-javascript-enabled clients. The second pass occurs when the browser establishes the websocket connection that sends events and data back and forth between the application and the [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/the-lifecycle-of-a-phoenix-liveview/"&gt;The Lifecycle of a Phoenix LiveView&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Wed, 30 Nov 2022 21:36:13 +0000</pubDate>
  <link>https://binarynoggin.com/blog/the-lifecycle-of-a-phoenix-liveview/</link>
  <guid>https://binarynoggin.com/blog/the-lifecycle-of-a-phoenix-liveview/---https://binarynoggin.com/?p=2966</guid>
</item>
<item>
  <title>Video - Why use a database?</title>
  <description>From Underjord.io: A short video on databases.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-why-use-a-database.html</link>
  <guid>https://underjord.io/video-why-use-a-database.html---https://underjord.io/video-why-use-a-database.html</guid>
</item>
<item>
  <title>Video - Sonic Pi</title>
  <description>From Underjord.io: Code has always been about creative pursuit for me.
This video showcases Sonic Pi. It is an open source project that I really appreciate. Sonic Pi enables live coding of music. If you are already a programmer it can turn your hard-earned, often dry, programming capabilities into musical potential. If you know music, you might just learn programming. If you know neither it is a fantastically fun place to start.</description>
  <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-sonic-pi.html</link>
  <guid>https://underjord.io/video-sonic-pi.html---https://underjord.io/video-sonic-pi.html</guid>
</item>
<item>
  <title>Video - Reacting to the Phoenix Keynote 2022</title>
  <description>From Underjord.io: I share my reactions to the Phoenix keynote at ElixirConf 2022.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-reacting-to-phoenix-keynote.html</link>
  <guid>https://underjord.io/video-reacting-to-phoenix-keynote.html---https://underjord.io/video-reacting-to-phoenix-keynote.html</guid>
</item>
<item>
  <title>Video - Is it all queues?</title>
  <description>From Underjord.io: Are computers really all about queues?
At some level. Probably.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-is-it-all-queues.html</link>
  <guid>https://underjord.io/video-is-it-all-queues.html---https://underjord.io/video-is-it-all-queues.html</guid>
</item>
<item>
  <title>Video - Chat bots as UI at ElixirConf Africa</title>
  <description>From Underjord.io: My presentation from ElixirConf Africa.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-elixirconf-africa-chatbots.html</link>
  <guid>https://underjord.io/video-elixirconf-africa-chatbots.html---https://underjord.io/video-elixirconf-africa-chatbots.html</guid>
</item>
<item>
  <title>Video - Does terminology matter?</title>
  <description>From Underjord.io: A short video on the importance of terminology in computing.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 04 Nov 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-does-terminology-matter.html</link>
  <guid>https://underjord.io/video-does-terminology-matter.html---https://underjord.io/video-does-terminology-matter.html</guid>
</item>
<item>
  <title>Complex Systems All The Way Down</title>
  <description>From Underjord.io: As part of running Underjord I&amp;rsquo;ve done both mentorship and some teaching. Both in the form of a working relationship where I have seniority and am responsible for supporting their learning as well as mentoring entirely external people of varying experience levels. I find it incredibly hard to transmit everything I think people need to get familiar with when they want to do development. Why?
Well, I learned it all incrementally.</description>
  <pubDate>Mon, 24 Oct 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/complex-systems-all-the-way-down.html</link>
  <guid>https://underjord.io/complex-systems-all-the-way-down.html---https://underjord.io/complex-systems-all-the-way-down.html</guid>
</item>
<item>
  <title>Building Embedded Systems in the Modern Era</title>
  <description>From Binary Noggin: &lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/building-embedded-systems-in-the-modern-era/"&gt;Building Embedded Systems in the Modern Era&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Wed, 12 Oct 2022 00:00:13 +0000</pubDate>
  <link>https://binarynoggin.com/blog/building-embedded-systems-in-the-modern-era/</link>
  <guid>https://binarynoggin.com/blog/building-embedded-systems-in-the-modern-era/---https://binarynoggin.com/?p=2880</guid>
</item>
<item>
  <title>Fear-driven development</title>
  <description>From Underjord.io: Twice burnt, getting the hang of this fire thing.
There’s a thing I’m wary of with both companies and developers: when they seem to operate mostly on fear. In the case of companies, fear can lead to heavy screening during the hiring process, heavy-handed, detail-oriented project management or distrust towards employees. To my mind, these things either come from the basic mode of operation of some founding member or from cultural scars caused by a bad hire, a runaway project or very anxious management.</description>
  <pubDate>Fri, 07 Oct 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/fear-driven-development.html</link>
  <guid>https://underjord.io/fear-driven-development.html---https://underjord.io/fear-driven-development.html</guid>
</item>
<item>
  <title>Why can't I find a CMS?</title>
  <description>From Underjord.io: I&amp;rsquo;ve been trying to address this particular topic and I find it challenging. It is a topic that&amp;rsquo;s very easy to turn into a negative rant and that&amp;rsquo;s not what I like to do. If you enjoy any of the stacks I dismiss in this post, please know that is not a jab at you or your work. I have worked in these stacks and I&amp;rsquo;ve mostly transitioned away from them where they aren&amp;rsquo;t necessary.</description>
  <pubDate>Mon, 26 Sep 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/cant-find-a-cms.html</link>
  <guid>https://underjord.io/cant-find-a-cms.html---https://underjord.io/cant-find-a-cms.html</guid>
</item>
<item>
  <title>Video - Self-hosting: Mattermost</title>
  <description>From Underjord.io: Slack, Teams, teamchat. We all use them in our jobs. It is unclear whether that&amp;rsquo;s even legal. I wanted to present one of the more well-known open source options and how to get started with it. This video is a collaboration with GleSYS. Keep them in mind for your cloud and data center needs in the Nordics.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.</description>
  <pubDate>Fri, 16 Sep 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-self-hosting-mattermost.html</link>
  <guid>https://underjord.io/video-self-hosting-mattermost.html---https://underjord.io/video-self-hosting-mattermost.html</guid>
</item>
<item>
  <title>Tradition and Ritual in Software, or The Unwritten Code of Code</title>
  <description>From Underjord.io: I’ve been thinking about shared experiences: culture and tradition. As the seed of this was written close to the holidays, it might be that they are to blame. But this post isn’t about Christmas or the new year; it’s about my reflections on these human matters and how they apply in the software development world.
How we run dangerous commands Have you ever had to run a potentially dangerous command on a production server?</description>
  <pubDate>Mon, 12 Sep 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/tradition-and-ritual-in-software.html</link>
  <guid>https://underjord.io/tradition-and-ritual-in-software.html---https://underjord.io/tradition-and-ritual-in-software.html</guid>
</item>
<item>
  <title>Video - My Tools, the current product stack</title>
  <description>From Underjord.io: In this very serious video I will tell you exactly how you must build your products.
Actually, I&amp;rsquo;m just outlining the tools I&amp;rsquo;d typically look at and consider right now. I hope you enjoy. I rather like the intro personally.
Also on YouTube.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 26 Aug 2022 05:00:00 +0000</pubDate>
  <link>https://underjord.io/video-my-tools.html</link>
  <guid>https://underjord.io/video-my-tools.html---https://underjord.io/video-my-tools.html</guid>
</item>
<item>
  <title>Video - LiveView on Nerves</title>
  <description>From Underjord.io: A video on doing LiveView with Nerves and having fun with input devices. Published as I finish up my backlog of publishing all my videos to my site. It should calm down now.
This was also more thoroughly covered in text in this post.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 26 Aug 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-liveview-on-nerves.html</link>
  <guid>https://underjord.io/video-liveview-on-nerves.html---https://underjord.io/video-liveview-on-nerves.html</guid>
</item>
<item>
  <title>Video - Self-hosting: Plausible Analytics</title>
  <description>From Underjord.io: This video covers the steps to set up Plausible Analytics. Previously published on YouTube and part of me catching up on site publishing :)
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Thu, 25 Aug 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-self-hosting-plausible-analytics.html</link>
  <guid>https://underjord.io/video-self-hosting-plausible-analytics.html---https://underjord.io/video-self-hosting-plausible-analytics.html</guid>
</item>
<item>
  <title>Dynamic Form Inputs in Elixir LiveView</title>
  <description>From Binary Noggin: &lt;p&gt;I recently found myself addressing a product requirement involving a form with input fields generated from a list of data we retrieved from a third-party provider. This requirement proved challenging as the prescribed user experience stretched a little beyond the familiar tools for form building in Elixir. Even more frustrating, the wireframes presented the data [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/dynamic-form-inputs-in-elixir-liveview/"&gt;Dynamic Form Inputs in Elixir LiveView&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Thu, 25 Aug 2022 00:00:04 +0000</pubDate>
  <link>https://binarynoggin.com/blog/dynamic-form-inputs-in-elixir-liveview/</link>
  <guid>https://binarynoggin.com/blog/dynamic-form-inputs-in-elixir-liveview/---https://binarynoggin.com/?p=2692</guid>
</item>
<item>
  <title>Video - Reacting to React</title>
  <description>From Underjord.io: Out of freak fortune I haven&amp;rsquo;t significantly used React. This was an experiment in just diving in and trying to use it. Be warned, if you know React it might hurt. Let me know you like it.
This video has been on the YouTube channel for some time but I&amp;rsquo;m catching up on the transcoding and publishing here.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.</description>
  <pubDate>Sun, 21 Aug 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-reacting-to-react.html</link>
  <guid>https://underjord.io/video-reacting-to-react.html---https://underjord.io/video-reacting-to-react.html</guid>
</item>
<item>
  <title>Video - Why Self-Host</title>
  <description>From Underjord.io: A video on why you should consider hosting your own services and data. Especially in the current EU legal landscape. Created in collaboration with GleSYS, a swedish data centre and cloud provider.
This video lays the explanatory groundwork for a series of guides for setting up some self-hosted services.
It went up on YouTube a while back, I&amp;rsquo;m catching up on the website publishing side :)
 function switch_video(element) { var src = element.</description>
  <pubDate>Sat, 20 Aug 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-why-self-host.html</link>
  <guid>https://underjord.io/video-why-self-host.html---https://underjord.io/video-why-self-host.html</guid>
</item>
<item>
  <title>Video - What Is: Phoenix LiveView</title>
  <description>From Underjord.io: A video explainer on what Phoenix LiveView is. Published late to this site, has been up on YouTube for some time. I&amp;rsquo;m catching up :)
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Sat, 20 Aug 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-what-is-phoenix-liveview.html</link>
  <guid>https://underjord.io/video-what-is-phoenix-liveview.html---https://underjord.io/video-what-is-phoenix-liveview.html</guid>
</item>
<item>
  <title>An iPad retrospective</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;I’ve been working on iPad full-time for a while now, and it’s always a source of surprise when I talk about it with other people. This post aims at summarising the &lt;em&gt;why&lt;/em&gt; behind this preference of mine.&lt;/p&gt;
&lt;h2 id="planting-a-seed"&gt;
Planting a seed
&lt;a href="#planting-a-seed" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I spent 2019 on the Turkish West Coast, in a small town near Izmir. It’s a lovely place where everyone knows each other, and where we now have friends and people we care about.&lt;/p&gt;
&lt;p&gt;I was working as a backend engineer on &lt;a href="https://pspdfkit.com/guides/server/pspdfkit-server/overview/"&gt;PSPDFKit Server&lt;/a&gt;, a product written in Elixir and packaged via Docker, on my trusty old 2014 11” Macbook Air. A wonderful machine - powerful for its size, and featherweight.&lt;/p&gt;
&lt;p&gt;Around April, a few things happened at the same time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring came: warm, sunny, and ideal to start working outside in the shade of a café.&lt;/li&gt;
&lt;li&gt;A set of requirements that involved way more work with Docker, and consequently a much larger use of CPU, battery, and internet bandwidth.&lt;/li&gt;
&lt;li&gt;The need to finally increase my editor font size to 14pt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It quickly became apparent that I was asking too much out of my laptop.&lt;/p&gt;
&lt;p&gt;As I’ve been writing software in the shell for years, I switched to a remote setup using a powerful remote server. 32 cores, very large bandwidth, almost the same experience as working locally (if you take into account the occasional latency problem, well mitigated by mosh).&lt;/p&gt;
&lt;p&gt;Screen size, however, became an issue: 14pt on a 11” screen is crammed, and difficult to use.&lt;/p&gt;
&lt;p&gt;I thought about purchasing a new laptop, but doing that from Turkey proved to be difficult, because of the different keyboard layout.&lt;/p&gt;
&lt;p&gt;I decided I would try a week long experiment using the other device I had, a 3rd generation 12.9 iPad Pro (with its keyboard) which I mainly used for entertainment. I figured that given my remote development setup, I could still get things done.&lt;/p&gt;
&lt;p&gt;The experience was great: I loved the screen size and ratio, and the retina screen made text very easy to read (to the point that I could go back to 12pt, which worked really well in terms of information density). I could work the entire day without worrying about the battery, and enjoy the spring/summer season.&lt;/p&gt;
&lt;p&gt;While coding was (surprisingly) pleasant considering the fiddly setup, other activities were proving to be more difficult: apps like Slack and Google Docs were (and in some cases still are) cumbersome to use, with very little support for keyboard shortcuts, and of substantial inferior quality compared to their desktop counterparts. I got by, occasionally dusting off the Macbook Air for specific tasks that wouldn’t work at all on the iPad (e.g. work that required using the Chrome Web Inspector).&lt;/p&gt;
&lt;p&gt;Then February 2020 came, we moved back to England and the pandemic started. Like many others, we spent most of our time at home, where I could use a decent Mac Mini with a nice keyboard and external screen.&lt;/p&gt;
&lt;p&gt;Despite this change, a seed had been planted in my head: could an iPad eventually replace a desktop computer altogether for a “hardcore” user like me?&lt;/p&gt;
&lt;h2 id="unexpected-changes"&gt;
Unexpected changes
&lt;a href="#unexpected-changes" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Around spring 2020, my role changed, and I started managing a team. Coding became a past-time activity that I kept maintaining for fun, and 99% of my work now involved planning, communication with people, and some form of documents and task lists.&lt;/p&gt;
&lt;p&gt;In December 2020, a family situation forced us to quickly pack our bags and fly to Turkey again. I ended up taking with me all devices (which sounds ridiculous - &lt;em&gt;I know&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;The following months are a blur - I remember working from all sorts of places: the car, parking lots, hospital cafés, the balcony table, hotel rooms. I once had a meeting in a service station.&lt;/p&gt;
&lt;p&gt;That’s how the iPad came back in full force: always on, always charged, with data always in sync. Pair it with the Apple Pencil, and a pair of earphones, and it gave me the ability to adapt work to a complicated personal life.&lt;/p&gt;
&lt;p&gt;I was happy to see that software had gotten better: Safari gained a desktop mode which supported all sorts of complex web apps, and all other apps improved to the point of being &lt;em&gt;at least&lt;/em&gt; usable (Google Docs, I’m looking at you). Notable mentions go to &lt;a href="https://www.agenda.com"&gt;Agenda&lt;/a&gt;, &lt;a href="https://www.mindnode.com"&gt;Mindnode&lt;/a&gt;, and &lt;a href="https://flexibits.com/fantastical"&gt;Fantastical&lt;/a&gt;, which set a very high bar for quality software in general.&lt;/p&gt;
&lt;p&gt;Later in the year, things started to calm down and even if I could use the Mac Mini, I would prefer the iPad. I invested in a desk stand which would let me prop it to a decent height, connect it to a good keyboard and mouse, and happily went about my work day.&lt;/p&gt;
&lt;img src="https://claudio-ortolina.org/img/an-ipad-retrospective/my-desk.jpg" alt="Photo of my desk showing the iPad with its stand, keyboard, and trackball" class="left" /&gt;
&lt;h2 id="introspection"&gt;
Introspection
&lt;a href="#introspection" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I started asking myself what is it about working on the iPad that I love. What started as a challenge motivated by specific constraints evolved now into a solid preference.&lt;/p&gt;
&lt;p&gt;I don’t think I’m making a point - as in trying to be clever or different by not using a traditional computer. There’s plenty of people who do that, and if you search on YouTube you will find all sorts of tutorials on how to try and work around iPadOS’s limitations (e.g. having a Raspberry PI as a sidecar mini-server) to achieve things that are indeed way easier with a laptop.&lt;/p&gt;
&lt;p&gt;The iPad fits my mental model of getting things done (tasks instead of files), and it doesn’t get in the way. It’s got great security defaults, it’s encrypted, and automatically backed up. If I buy a new one, I can easily transfer all my data and be up and running in a couple of hours.&lt;/p&gt;
&lt;p&gt;The iPad lets me focus on what I want to achieve, as opposed to losing myself in endless maintenance.&lt;/p&gt;
&lt;p&gt;I take notes of points of friction, and every once in a while, I review my workflows: if I wanna change anything, I’ll test a different application (after making sure my data is portable), and move on.&lt;/p&gt;</description>
  <pubDate>Thu, 11 Aug 2022 06:34:25 +0300</pubDate>
  <link>https://claudio-ortolina.org/posts/ipad-retrospective/</link>
  <guid>https://claudio-ortolina.org/posts/ipad-retrospective/---https://claudio-ortolina.org/posts/ipad-retrospective/</guid>
</item>
<item>
  <title>Binary Noggin Ranks #20 on Kansas City Business Journal Fast 50 List</title>
  <description>From Binary Noggin: &lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/binary-noggin-ranks-20-on-kansas-city-business-journal-fast-50-list/"&gt;Binary Noggin Ranks #20 on Kansas City Business Journal Fast 50 List&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Thu, 28 Jul 2022 00:00:24 +0000</pubDate>
  <link>https://binarynoggin.com/blog/binary-noggin-ranks-20-on-kansas-city-business-journal-fast-50-list/</link>
  <guid>https://binarynoggin.com/blog/binary-noggin-ranks-20-on-kansas-city-business-journal-fast-50-list/---https://binarynoggin.com/?p=2648</guid>
</item>
<item>
  <title>Front-End Testing in Phoenix With Wallaby</title>
  <description>From Binary Noggin: &lt;p&gt;Why is front-end testing even a thing? We have LiveView, right? I can run tests on my components and everything is magical. What is Wallaby? You may have heard of Wallaby at the Big Elixir when Britton Broderick gave a talk about using it with LiveView, or maybe you knew of it before that. If [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/front-end-testing-in-phoenix-with-wallaby/"&gt;Front-End Testing in Phoenix With Wallaby&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Wed, 29 Jun 2022 00:00:26 +0000</pubDate>
  <link>https://binarynoggin.com/blog/front-end-testing-in-phoenix-with-wallaby/</link>
  <guid>https://binarynoggin.com/blog/front-end-testing-in-phoenix-with-wallaby/---https://binarynoggin.com/?p=2616</guid>
</item>
<item>
  <title>The Binary Noggin Breakdown: Why Is Collaboration Important in Software Development?</title>
  <description>From Binary Noggin: &lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/the-binary-noggin-breakdown-why-is-collaboration-important-in-software-development/"&gt;The Binary Noggin Breakdown: Why Is Collaboration Important in Software Development?&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Tue, 14 Jun 2022 00:00:30 +0000</pubDate>
  <link>https://binarynoggin.com/blog/the-binary-noggin-breakdown-why-is-collaboration-important-in-software-development/</link>
  <guid>https://binarynoggin.com/blog/the-binary-noggin-breakdown-why-is-collaboration-important-in-software-development/---https://binarynoggin.com/?p=2600</guid>
</item>
<item>
  <title>What ID3v2 could have been</title>
  <description>From Underjord.io: Speculations and specifications. If you were a Winamp user back in the day, or curate an MP3 collection currently, you might recognize the humble ID3 tag. It is what the metadata in the MP3 file is made up of. First it was pretty limited in the version later dubbed ID3v1. Like any good 2.0 they added a ton more fields, features, removed character limits and it was suddenly ID3v2. The latest spec is ID3v2.</description>
  <pubDate>Tue, 07 Jun 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/id3-specification-and-speculation.html</link>
  <guid>https://underjord.io/id3-specification-and-speculation.html---https://underjord.io/id3-specification-and-speculation.html</guid>
</item>
<item>
  <title>The Comprehensive Guide to Elixir's List Comprehension</title>
  <description>From Mitchell Hanberg's Blog: ## What is it?

The `for` special form, also known as a list comprehension, is a construct designed for concise and powerful enumerable transformation in Elixir.

It looks very similar to a "for loop" in other languages like JavaScript and C, but rather than being a language construct, it is an expression (like everything else in Elixir). This means that it evaluates to a value that can be bound to a variable. You may have heard this before as "statement vs expression".

```elixir
last_names = 
  for friend &lt;- friends do
    friend.last_name
  end
```

Before reading this rest of this article, I suggest you read the list comprehension guide on [elixir-lang.org](https://elixir-lang.org/getting-started/comprehensions.html). I will go over some of the same details, but hopefully will go much more in depth.

## Generators

The primary ingredient in a comprehension is the generator. The only other place you will see this "left arrow" syntax (`lhs &lt;- rhs`) is in the `with` special form.

The right hand side is the enumerable you want to loop over and the left hand side is the intermediate pattern you want to match on during each iteration. This is a normal pattern, so you can pattern match like you would anywhere else.

```elixir
friends = [
  %{first_name: "Joe", last_name: "Swanson"},
  %{first_name: "Greg", last_name: "Mefford"},
  %{first_name: "Erik", last_name: "Plostins"},
]

for %{last_name: last_name} &lt;- friends do
  last_name
end

# ["Swanson", "Mefford", "Plostins"]
```

### Multiple Generators

Comprehensions are very concise in that you can declare multiple generators, allowing you to generate every permutation of both enumerables. A great example is generating a list of coordinate pairs from a range of `x` values and `y` values.

A key detail to recognize is that the both values are yielded to the same block, so the result is a flat list.

```elixir
for x &lt;- 0..99, y &lt;- 0..99 do
  {x, y}
end

# [{0, 0}, {0, 1}, {0, 2}, ...]
```

The counter example using [`Enum.map/2`](https://hexdocs.pm/elixir/Enum.html#map/2) is not nearly as readable and demonstrates that you need to remember to flatten the outer loop, or else you'll get a list of lists.

```elixir
Enum.flat_map(0..99, fn x -&gt;
  Enum.map(0..99, fn y -&gt;
    {x, y}
  end)
end)

# [{0, 0}, {0, 1}, {0, 2}, ...]
```

### Generators work with maps too

The comprehension works with anything that implements the `Enumerable` protocol, so you can iterate through a map as well. The generator will yield each key/value pair of the given map.

```elixir
dictionary = %{
  "low-latency" =&gt; "short delay before a transfer of data begins following an instruction for its transfer",
  "distributed" =&gt; "(of a computer system) spread over several machines, especially over a network",
  "fault-tolerant" =&gt; "relating to or being a computer or program with a self-contained backup system that allows continued operation when major components fail"
}

for {word, definition} &lt;- dictionary do
  IO.puts "#{word}: #{definition}"
end
```

As seen above, we can iterate through a `Range` as well.

```elixir
for x &lt;- 0..99, y &lt;- 0..99 do
  {x, y}
end

# [{0, 0}, {0, 1}, {0, 2}, ...]
```

### Bitstring generators

The generators we've seen so far have been "list generators". The other type of generator is the "bitstring generator". The bitstring generator allows you to easily loop over a bitstring while correctly parsing bytes.

For a primer on bitstrings, please see the fantastic guide on [elixir-lang.org](https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#bitstrings).

This is often very useful when parsing binary protocols like database protocols. Below is an example that demonstrates that each iteration of the comprehension reads the message length and then uses it to know how much more of the bitstring to read next. While the previous examples could be translated into various form of `Enum.map/2`, this example can only be achieved with normal recursion.

```elixir
bitstring = &lt;&lt;1, "I", 6, "really", 4, "love", 4, "list", 14, "comprehensions", 1, "!"&gt;&gt;

# &lt;&lt;1, 73, 6, 114, 101, 97, 108, 108, 121, 4, 108, 111, 118, 101, 4, 108, 105,
#  115, 116, 14, 99, 111, 109, 112, 114, 101, 104, 101, 110, 115, 105, 111, 110,
#  115, 1, 33&gt;&gt;

for &lt;&lt;message_length::integer, message::binary-size(message_length) &lt;- bitstring&gt;&gt; do
  message
end

# ["I", "really", "love", "list", "comprehensions", "!"]
```

The example using recursion looks like:

```elixir
defmodule For do
  def loop(""), do: []

  def loop(&lt;&lt;message_length::integer, message::binary-size(message_length), rest::binary&gt;&gt;) do
    [message | loop(rest)]
  end
end

bitstring = &lt;&lt;1, "I", 6, "really", 4, "love", 4, "list", 14, "comprehensions", 1, "!"&gt;&gt;

For.loop(bitstring)

# ["I", "really", "love", "list", "comprehensions", "!"]
```

To compare the two styles, let's look at the syntax of the generator in the comprehension example and the second function head of `loop` in the function example.

In the function example, you'll see that we pattern match on the bitstring in a similar manner to who you pattern match on a list. We pull several individual items off the beginning of the bitstring (`message_length::integer, message::binary-size(message_length)`) and we pattern match on the "rest" of the bitstring with `rest::binary`.

The list equivalent looks like:

```elixir
defmodule For do
  def loop([]), do: []

  def loop([_length, message | rest]) do
    [message | loop(rest)]
  end
end

list = [1, "I", 6, "really", 4, "love", 4, "list", 14, "comprehensions", 1, "!"]

For.loop(list)

# ["I", "really", "love", "list", "comprehensions", "!"]
```

Now if we compare this to the bitstring generator, we can see the similarity (_**with a caveat**_). The `lhs` of the bitstring generator is the pattern match on the individual items and the `rhs` is pattern matching on the "rest". The caveat is that `bitstring` here is not actually the "rest" on each iteration, it is still the entire bitstring. I find this the best analogy to describe how the bitstring generator works since it is different enough from the "normal" list generator.

```elixir
#   👇 individual item         👇 individual item                      👇rest
for &lt;&lt;message_length::integer, message::binary-size(message_length) &lt;- bitstring&gt;&gt; do
  # 👇 this will print the same thing every time
  IO.inspect(bitstring)

  message
end
```

### Chaining generators

List comprehensions allow you to chain generators together by using the `lhs` value from a generator in the `rhs` of a subsequent generator.

Here's an example that demonstrates getting a list of all of your friends hobbies:

```elixir
friends = [
  %{name: "Derek", hobbies: ["Movies", "Hot Sauce"]},
  %{name: "Joe", hobbies: ["Yu-Gi-Oh!", "Tattoos"]},
  %{name: "Andres", hobbies: ["Photoshop", "Oreos", "Cereal"]},
]

for %{hobbies: hobbies} &lt;- friends, hobby &lt;- hobbies do
  hobby
end

# ["Movies", "Hot Sauce", "Yu-Gi-Oh!", "Tattoos", "Photoshop", "Oreos", "Cereal"]
```

### Generators filter non-matching lhs values

If the match expression in the `lhs` of a generator does not match on the value yielded from the `rhs`, it will be rejected, and the list comprehension will move on to the next element in the enumerable.

This is slightly surprising behavior at first and should be kept in mind when using list comprehensions. The following example might lead to a bug in your program.

```elixir
friends = [
    %{"name" =&gt; "Derek"},
    %{name: "Joe"}
  ]

for %{name: name} &lt;- friends do
  name
end

# ["Joe"]
```

If you were to have written this program using `Enum.map/2`, you would have ran into a function clause error.

```elixir
friends = [
    %{"name" =&gt; "Derek"},
    %{name: "Joe"}
  ]

Enum.map(friends, fn %{name: name} -&gt;
  name
end)

# ** (FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/1
# 
#    The following arguments were given to :erl_eval."-inside-an-interpreted-fun-"/1:
# 
#        # 1
#        %{"name" =&gt; "Derek"}
# 
#    (stdlib 3.17.1) :erl_eval."-inside-an-interpreted-fun-"/1
#    (stdlib 3.17.1) erl_eval.erl:834: :erl_eval.eval_fun/6
#    (elixir 1.13.3) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
```

This behaviour can be useful! If you only wanted to iterate over configuration options that are enabled, you could write something like this:

```elixir
configs = [
  %{name: :feature_a, enabled: true},
  %{name: :feature_b, enabled: false},
  %{name: :feature_c, enabled: true}
]

for %{enabled: true} = config &lt;- configs do
  config
end

# [%{name: :feature_a, enabled: true}, %{name: :feature_c, enabled: true}]
```

## Filters

Now that we've talked about _generator filtering_, let's talk about _Filters_.

So far we have seen one type of "argument" that can be passed to the comprehension, the generator. Another "argument" is the filter! Let's look at a quick example.

In this example, we iterate over a list of employees, filter based on the employees status, and return a list of the employee's names

```elixir
employees = [
  %{name: "Eric", status: :active},
  %{name: "Mitch", status: :former},
  %{name: "Greg", status: :active}
]

for employee &lt;- employees, employee.status == :active do
  employee.name
end

# ["Eric", "Greg"]
```

As with generators, filters can use values bound in a previous step of the comprehension. And like generators, you can use multiple filters as well. You can even mix and match them!

```elixir
employees = [
  %{
    name: "Eric",
    status: :active,
    hobbies: [%{name: "Text Adventures", type: :gaming}, %{name: "Chickens", type: :animals}]
  },
  %{
    name: "Mitch",
    status: :former,
    hobbies: [%{name: "Woodworking", type: :making}, %{name: "Homebrewing", type: :making}]
  },
  %{
    name: "Greg",
    status: :active,
    hobbies: [
      %{name: "Dungeons &amp; Dragons", type: :gaming},
      %{name: "Woodworking", type: :making}
    ]
  }
]

for employee &lt;- employees,
    employee.status == :active,
    hobby &lt;- employee.hobbies,
    hobby.type == :gaming do
  {employee.name, hobby}
end

# [
#   {"Eric", %{name: "Text Adventures", type: :gaming}},
#   {"Greg", %{name: "Dungeons &amp; Dragons", type: :gaming}}
# ]

```

At this point we can recognize that the list comprehension has the characteristics of a function with [variadic arguments](https://en.wikipedia.org/wiki/Variadic_function). If we were to write our own `for` using plain functions, we'd have to pass it a list of callbacks to evaluate and a final callback to do the mapping. While we aren't necessarily concerned with how we'd implement `for` as a plain function, it's important to recognize aspects that are "different" from "normal" constructs in the language.

One of the great things about the list comprehension is that it allows you to operate on `Enumerable` data structures in fewer passes (usually 1) than when using the `Enum` module.

We can write our previous example using functions like so:

```elixir
employees = [
  %{name: "Eric", status: :active},
  %{name: "Mitch", status: :former},
  %{name: "Greg", status: :active}
]

employees
|&gt; Enum.filter(fn employee -&gt; employee.status == :active end)
|&gt; Enum.map(fn employee -&gt; employee.name end)

# ["Eric", "Greg"]

employees
|&gt; Enum.reduce([], fn employee, acc -&gt; 
  if employee.status == :active do
    [employee.name | acc]
  else
    acc
  end
end)
|&gt; Enum.reverse()

# ["Eric", "Greg"]

:lists.filtermap(
  fn employee -&gt;
    if employee.status == :active do
      {true, employee.name}
    else
      false
    end
  end,
  employees
)

# ["Eric", "Greg"]
```

You can see benchmarks of all of these styles of "filter map" [here](https://github.com/mhanberg/notebooks/blob/c0c0c710fd5ca7a6f36d5acdca9043911d49044d/notebooks/list_benchmarks.livemd).

## Options

Now that we've covered the basic principles of the list comprehension, we can explore the various options that can be passed to augment it's behavior. The default behavior is to act more or less like `Enum.map/2` with regard to the return type.

As of this writing, there are three options available: `:uniq`, `:into`, and `:reduce`

### :uniq

`:uniq` is the least interesting of the available options, but still quite powerful.

It simply ensures that the return result will only contain unique values.

```elixir
employees = [
  %{
    name: "Eric",
    status: :active,
    hobbies: [%{name: "Text Adventures", type: :gaming}, %{name: "Chickens", type: :animals}]
  },
  %{
    name: "Mitch",
    status: :former,
    hobbies: [%{name: "Woodworking", type: :making}, %{name: "Homebrewing", type: :making}]
  },
  %{
    name: "Greg",
    status: :active,
    hobbies: [
      %{name: "Dungeons &amp; Dragons", type: :gaming},
      %{name: "Woodworking", type: :making}
    ]
  }
]

for employee &lt;- employees, hobby &lt;- employee.hobbies, uniq: true do
  hobby.name
end

# ["Text Adventures", "Chickens", "Woodworking", "Homebrewing", "Dungeons &amp; Dragons"]
```

You can see benchmarks of all of these styles of "map uniq" [here](https://github.com/mhanberg/notebooks/blob/ae5362fded45465e22e74b9d310715e7852502d8/notebooks/list_benchmarks.livemd#results-mapuniq).

### :into

`:into` is where things start to get interesting.

The default behavior for a list comprehension behaves more or less like a "map" operation, meaning that the expression evaluates to a list.

The `:into` option allows you to instead push the value returned by each iteration into a _collectable_. A data structure is collectable if it implements the [Collectable](https://hexdocs.pm/elixir/Collectable.html) protocol.

If you aren't familiar with protocols, you have already been using them! The `Enum` module is a set of functions that operate on data structures that implement the [Enumerable](https://hexdocs.pm/elixir/Enumerable.html) protocol. The builtin data structures that implement the `Enumerable` protocol are the `List`, `Range`, `Map`, and `MapSet` types.

The builtin data structures that implement the `Collectable` protocol are `List`, `Map`, `MapSet`, and `BitString`. The `Enum` function that you would use to take advantage of this protocol is [Enum.into/2](https://hexdocs.pm/elixir/Enum.html#into/3).

Let's take a look at some examples.

#### List

Using a list as the `:into` actually doesn't change the behavior at all!


```elixir
employees = [
  %{
    name: "Eric",
    status: :active,
    hobbies: [%{name: "Text Adventures", type: :gaming}, %{name: "Chickens", type: :animals}]
  },
  %{
    name: "Mitch",
    status: :former,
    hobbies: [%{name: "Woodworking", type: :making}, %{name: "Homebrewing", type: :making}]
  },
  %{
    name: "Greg",
    status: :active,
    hobbies: [
      %{name: "Dungeons &amp; Dragons", type: :gaming},
      %{name: "Woodworking", type: :making}
    ]
  }
]

for employee &lt;- employees,
    employee.status == :active,
    hobby &lt;- employee.hobbies,
    hobby.type == :gaming,
    into: [] do
  {employee.name, hobby}
end

# [
#   {"Eric", %{name: "Text Adventures", type: :gaming}},
#   {"Greg", %{name: "Dungeons &amp; Dragons", type: :gaming}}
# ]
```

#### Map

But if we use a map, we can see that it pushes each key/value pair into the map that you pass for the option. Usually I use this with an empty map (`%{}` or `Map.new()`), but let's look at an example using a non-empty map.

```elixir
employees = [
  %{
    name: "Eric",
    status: :active,
    hobbies: [%{name: "Text Adventures", type: :gaming}, %{name: "Chickens", type: :animals}]
  },
  %{
    name: "Mitch",
    status: :former,
    hobbies: [%{name: "Woodworking", type: :making}, %{name: "Homebrewing", type: :making}]
  },
  %{
    name: "Greg",
    status: :active,
    hobbies: [
      %{name: "Dungeons &amp; Dragons", type: :gaming},
      %{name: "Woodworking", type: :making}
    ]
  }
]

base_map = %{
  "Mitch" =&gt; %{
    name: "Reading",
    type: :learning
  },
  "Greg" =&gt; %{
    name: "Traveling",
    type: :expensive
  }
}

for employee &lt;- employees,
    employee.status == :active,
    hobby &lt;- employee.hobbies,
    hobby.type == :gaming,
    into: base_map do
  {employee.name, hobby}
end

# %{
#   "Eric" =&gt; %{name: "Text Adventures", type: :gaming},
#   "Greg" =&gt; %{name: "Dungeons &amp; Dragons", type: :gaming},
#   "Mitch" =&gt; %{name: "Reading", type: :learning}
# }
```

Here we can observe three things.

- The comprehension evaluates to a map.
- The `"Mitch"` key and its value were preserved in the final output.
- The `"Greg"` key's value in the `base_map` was overwritten by the value yielded during the comprehension with the same key. If our comprehension were to have returned multiple key/value pairs with identical keys, the last one would have won.

This option is very useful for transforming maps. Since iterating over a map with an `Enum` function turns it into a list of 2-tuples, you always need to pipe the return value into `Enum.into/2` or `Map.new/1`.

```elixir
employees = [
  %{
    name: "Eric",
    status: :active,
    hobbies: [%{name: "Text Adventures", type: :gaming}, %{name: "Chickens", type: :animals}]
  },
  %{
    name: "Mitch",
    status: :former,
    hobbies: [%{name: "Woodworking", type: :making}, %{name: "Homebrewing", type: :making}]
  },
  %{
    name: "Greg",
    status: :active,
    hobbies: [
      %{name: "Dungeons &amp; Dragons", type: :gaming},
      %{name: "Woodworking", type: :making}
    ]
  }
]

base_map = %{
  "Mitch" =&gt; %{
    name: "Reading",
    type: :learning
  },
  "Greg" =&gt; %{
    name: "Traveling",
    type: :expensive
  }
}

employees
|&gt; Enum.filter(fn employee -&gt; employee.status == :active end)
|&gt; Enum.flat_map(fn employee -&gt;
  employee.hobbies
  |&gt; Enum.filter(fn hobby -&gt; hobby.type == :gaming end)
  |&gt; Enum.map(fn hobby -&gt;
    {employee.name, hobby}
  end)
end)
|&gt; Enum.into(base_map)

# %{
#   "Eric" =&gt; %{name: "Text Adventures", type: :gaming},
#   "Greg" =&gt; %{name: "Dungeons &amp; Dragons", type: :gaming},
#   "Mitch" =&gt; %{name: "Reading", type: :learning}
# }
```

#### Strings and BitStrings

You can build strings and bitstrings with the `:into` option as well!

This is useful when you want to build a string or a binary out of a list or map all in one pass. Let's take a look at an example of creating an "attribute string" for use with HTML.

```elixir
attributes = [
  class: "font-bold text-red-500 underline",
  id: "error-text",
  data_controller: "error-controller"
]

for {property, value} &lt;- attributes, into: "" do
  property =
    property
    |&gt; to_string()
    |&gt; String.replace("_", "-")

  ~s| #{property}="#{value}"|
end

# " class=\"font-bold text-red-500 underline\" id=\"error-text\" data-controller=\"error-controller\""
```

### :reduce

My favorite option to the comprehension is `:reduce`!

Reduce allows us to change the comprehension from behaving like a "map" operation to a "reduce" operation. This means that it will loop over an enumerable, but collect an "accumulator" instead.

Let's take a look at the first example in the `Enum.reduce/3` documentation and then convert it to a comprehension. This example produces the sum of a list of integers.

```elixir
Enum.reduce([1, 2, 3], 0, fn x, acc -&gt;
  x + acc
end)

# 6
```

We can express this as a comprehension like so:

```elixir
for x &lt;- [1, 2, 3], reduce: 0 do
  acc -&gt;
    x + acc
end

# 6
```

There are two immediate things we can observe.

First, the `:reduce` option takes a value that is to be used as the first value of the accumulator.

Second, the comprehension in this mode includes a slightly different syntax. Here the inside of the block includes the "arg(s) and right arrow" syntax that you see in anonymous functions and case expressions. This is the syntax that allows the comprehensions to yield the accumulator to the block on every iteration.

The additional syntax is the same as the other places you have probably seen it; you can pattern match and pass additional clauses!

```elixir
directions = [
  left: 2,
  up: 1,
  down: 5,
  right: 6
]

# You can't move below or to the left of 0.
starting_position = {0, 0}

for {dir, movement} &lt;- directions, reduce: starting_position  do
  {x, y} when dir == :left and x - movement &gt; 0 -&gt;
    {x - movement, y}

  {x, y} when dir == :down and y - movement &gt; 0 -&gt;
    {x, y - movement}

  {x, y} when dir == :up -&gt;
    {x, y + movement}

  {x, y} when dir == :right -&gt;
    {x + movement, y}

  position -&gt;
    IO.puts("Not possible to move #{dir} by #{movement} when you care located at #{inspect(position)}")

    position
end

# {6, 1}
```

Above we can observe that we've written 5 different clauses, pattern matched on the shape of the data, as well as added guard clauses that capture the data in the generator and the accumulator.

The beauty of using a comprehension as a reducer is the ability to use multiple generators and act on them as if they are one level of iteration.

```elixir
friends = [
  %{name: "Derek", hobbies: ["Movies", "Hot Sauce"]},
  %{name: "Joe", hobbies: ["Yu-Gi-Oh!", "Tattoos"]},
  %{name: "Andres", hobbies: ["Photoshop", "Oreos", "Cereal"]},
]

for %{hobbies: hobbies, name: name} &lt;- friends, hobby &lt;- hobbies, reduce: [] do
  tagged_hobbies -&gt;
    [{name, hobby} | tagged_hobbies]
end

# [
#   {"Andres", "Cereal"},
#   {"Andres", "Oreos"},
#   {"Andres", "Photoshop"},
#   {"Joe", "Tattoos"},
#   {"Joe", "Yu-Gi-Oh!"},
#   {"Derek", "Hot Sauce"},
#   {"Derek", "Movies"}
# ]
```

To write this without a comprehension it would look something like:

```elixir
Enum.reduce(friends, [], fn %{name: name, hobbies: hobbies}, tagged_hobbies -&gt;
  new_hobbies = Enum.map(hobbies, fn hobby -&gt; {name, hobby} end)

  new_hobbies ++ tagged_hobbies
end)
```

## Conclusion

If you've made it this far, congrats! The comprehension packs a lot of features into a tiny programming construct and demonstrating all of them is a lot of work!

The comprehension is one of my favorite features of the Elixir programming language, and it was a pleasure to write about every feature in as much depth as I could.

If you have any questions about comprehensions or want to suggest examples or features that I've missed, feel free to reach out on [Twitter](https://twitter.com/mitchhanberg) or [email](mailto:contact@mitchellhanberg.com).

## Supplementary Information

### List Comprehensions in Erlang

It would be remiss to not mention that the list comprehension also exists in Erlang. I am not personally familiar with them, so I won't explain them very much, but I I'll provide an example and a link to learn more about them on your own.

The following examples are from the official Erlang/OTP documentation and can be found [here](https://erlang.org/doc/programming_examples/list_comprehensions.html).

```erlang
[X || X &lt;- [1, 2, a, 3, 4, b, 5, 6], is_integer(X), X &gt; 3].

% [4, 5, 6]

[{X, Y} || X &lt;- [1, 2, 3],  Y &lt;- [a, b]].

% [{1, a}, {1, b}, {2, a}, {2, b}, {3, a}, {3, b}]
```</description>
  <pubDate>Thu, 02 Jun 2022 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/the-comprehensive-guide-to-elixirs-for-comprehension/</link>
  <guid>https://www.mitchellhanberg.com/the-comprehensive-guide-to-elixirs-for-comprehension/---https://www.mitchellhanberg.com/the-comprehensive-guide-to-elixirs-for-comprehension/</guid>
</item>
<item>
  <title>Boost your test coverage with Elixir</title>
  <description>From Bits of Code - Pieces of Writing: In this article, I will help you build the appropriate tooling to track and measure your test coverage, and hopefully improve it.</description>
  <pubDate>Mon, 30 May 2022 08:53:43 GMT</pubDate>
  <link>https://www.christianblavier.com/boost-your-test-coverage-with-elixir/</link>
  <guid>https://www.christianblavier.com/boost-your-test-coverage-with-elixir/---629603e163e1200001252ac5</guid>
</item>
<item>
  <title>Code BEAM, Stockholm 2022</title>
  <description>From Underjord.io: I started writing this on the train home from Code BEAM. It&amp;rsquo;s an Erlang, Elixir (and BEAM in general) conference put on by Code Sync which is part of Erlang Solutions. It was a hybrid conference, I was there entirely in person. It was a very good time.
&amp;ldquo;Isn&amp;rsquo;t Underjord Just You?&amp;rdquo; Having gone recently from being a single-person consulting and contracting shop to having a small team I took a rather different approach to this conference.</description>
  <pubDate>Sun, 29 May 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/code-beam-sto-2022.html</link>
  <guid>https://underjord.io/code-beam-sto-2022.html---https://underjord.io/code-beam-sto-2022.html</guid>
</item>
<item>
  <title>LiveView on Nerves</title>
  <description>From Underjord.io: I&amp;rsquo;ve played with Nerves for almost as long as I&amp;rsquo;ve been learning and using Elixir. Nerves is a fantastic way of working with hardware along with the BEAM virtual machine and it is great fun for hobbyist projects like Raspberry Pis. Phoenix LiveView is currently my favorite way of making full-stack web development cohesive and keeping the complexity as low as possible for it. I haven&amp;rsquo;t run into any short compelling demos of getting these started together.</description>
  <pubDate>Thu, 26 May 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/liveview-on-nerves.html</link>
  <guid>https://underjord.io/liveview-on-nerves.html---https://underjord.io/liveview-on-nerves.html</guid>
</item>
<item>
  <title>Questions to Consider When Choosing a Software Development Consultant</title>
  <description>From Binary Noggin: &lt;p&gt;The post &lt;a href="https://binarynoggin.com/blog/questions-to-consider-when-choosing-a-software-development-consultant/"&gt;Questions to Consider When Choosing a Software Development Consultant&lt;/a&gt; appeared first on &lt;a href="https://binarynoggin.com"&gt;Binary Noggin&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Wed, 04 May 2022 00:00:39 +0000</pubDate>
  <link>https://binarynoggin.com/blog/questions-to-consider-when-choosing-a-software-development-consultant/</link>
  <guid>https://binarynoggin.com/blog/questions-to-consider-when-choosing-a-software-development-consultant/---https://binarynoggin.com/?p=2532</guid>
</item>
<item>
  <title>The Big Elixir 2022 - Building a Note taking App using LiveView, OTP, and friends - Mohammad Maqbool Alam</title>
  <description>From ⚡ Maqbool Khan: In this talk, we will start by building the Backend while taking advantage of OTP primitives such as GenServer and friends to build a simple cron to fetch messages from Telegram API and then we build using Phoenix LiveView and Alpine.js to build the ...</description>
  <pubDate>Sat, 30 Apr 2022 05:44:10 GMT</pubDate>
  <link>https://www.maqbool.net/the-big-elixir-2022-building-a-note-taking-app-with-telegram-liveview-maqbool-alam</link>
  <guid>https://www.maqbool.net/the-big-elixir-2022-building-a-note-taking-app-with-telegram-liveview-maqbool-alam---https://www.maqbool.net/the-big-elixir-2022-building-a-note-taking-app-with-telegram-liveview-maqbool-alam</guid>
</item>
<item>
  <title>Creating an Input Union Type System Directive in Absinthe</title>
  <description>From Maarten van Vliet: In this post I will explain how to create a type system directive in Absinthe to implement Input Unions in Graphql. The code for this post can be found on Github
A common issue with GraphQL is the lack of input unions. This means an input object that lets you choose between a number of potential input types.
For example (taken from a spec discussion):
input PetInput { cat: CatInput dog: DogInput fish: FishInput } input CatInput { name: String!</description>
  <pubDate>Thu, 28 Apr 2022 11:08:54 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2022/04/28/absinthe_input_union/</link>
  <guid>https://maartenvanvliet.nl/2022/04/28/absinthe_input_union/---https://maartenvanvliet.nl/2022/04/28/absinthe_input_union/</guid>
</item>
<item>
  <title>How EEx Turns Your Template Into HTML</title>
  <description>From Mitchell Hanberg's Blog: EEx is a templating language and module built into the [Elixir](https://hexdocs.pm/eex/EEx.html) standard library, often used by web servers to render dynamic content for a web application. The same concept exists in other language ecosystems, ERB for Ruby, Blade for Laravel/PHP, Pug/Jade for JavaScript.

A couple features of EEx that make it stand out are:

- Templates can be compiled into functions ahead of time, avoiding needing to read from the file system at runtime. 
- EEx has the concept of an "Engine" allowing you to compile an existing template into something different than what the builtin engine, [`EEx.SmartEngine`](https://hexdocs.pm/eex/EEx.SmartEngine.html), does. This is what [Phoenix LiveView](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html) does.

Recently, [José Valim tweeted me some advice on how to use the EEx Engine](https://twitter.com/josevalim/status/1511348149323014155?s=20&amp;t=sFyaicmJApbs5yQO-n_CIA) in my library [Temple](https://github.com/mhanberg/temple) (an alternative HTML library, that uses an Elixir DSL). At first I was very confused at José's suggestion; I don't want to write EEx at all! I want Temple to go from DSL straight to `iolist` or `%LiveView.Rendered{}` structs.

Luckily, my coworker [Marlus Saraiva](https://twitter.com/MarlusSaraiva) messaged me and gave a bit more context on José's suggestion, and suddenly it all clicked!. (Hopefully at the end of this post, you'll understand how José's suggestion makes perfect sense and was extremely clear.)

This conversation led me to ask myself, how does EEx work anyway? I decided to source dive EEx, LiveView, [Surface](https://surface-ui.org/), and [HEEx](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#sigil_H/2-syntax) and will share with you what I learned!

## The Source Code

Technically EEx is just a templating syntax that can handle any plaintext format. It doesn't matter if its HTML, JavaScript, or even Elixir (this is how the Phoenix generators work).

EEx templates allow you to execute arbitrary Elixir code inside of special tags and inject the runtime result into the return value. You can use single expressions or even expressions that take blocks. The `EEx.SmartEngine` also allows you to ergonomically access data passed to the template with the `@` syntax.

```eex
&lt;!-- single line expression --&gt;
&lt;span&gt;&lt;%= @user.name %&gt;&lt;/span&gt;

&lt;!-- block expression --&gt;
&lt;ul&gt;
  &lt;%= for user &lt;- @users do %&gt;
    &lt;li&gt;&lt;%= user.name %&gt;&lt;/li&gt;
  &lt;% end %&gt;
&lt;/ul&gt;
```

## The Compiler

The public API for compiling an EEx file or string consists of the `EEx.compile_file/2` and `EEx.compile_string/2` functions. Internally, they will call into the `EEx.Compiler` module.

The `EEx.Compiler` module is where the coordination happens when compiling our source code. You can find the source code [here](https://github.com/elixir-lang/elixir/blob/v1.13.3/lib/eex/lib/eex/compiler.ex).

This module's `compile/2` function is responsible for calling the tokenizer, traversing them, and passing them to the appropriate callbacks on the engine. By default, EEx uses the builtin `EEx.SmartEngine`.

Essentially there are two steps:

1. Turn the source into tokens.
2. Turn the tokens into Elixir AST by passing them to the relevant callback on the given `EEx.Engine`.

## The Tokenizer

The first thing that the compiler will do is tokenize the content. You can find the source code [here](https://github.com/elixir-lang/elixir/blob/v1.13.3/lib/eex/lib/eex/tokenizer.ex).

This translates the source code into a list of Elixir terms that describe the tokens found in the document. The above example will be tokenized into the following.

```elixir
{:ok,
 [
   {:text, 0, 0, '&lt;!-- single line expression --&gt;\n&lt;span&gt;'},
   {:expr, 1, 7, '=', ' @user.name '},
   {:text, 1, 24, '&lt;/span&gt;\n\n&lt;!-- block expression --&gt;\n&lt;ul&gt;\n'},
   {:start_expr, 5, 3, '=', ' for user &lt;- @users do '},
   {:text, 5, 31, '\n    &lt;li&gt;'},
   {:expr, 6, 9, '=', ' user.name '},
   {:text, 6, 25, '&lt;/li&gt;\n'},
   {:end_expr, 7, 3, [], ' end '},
   {:text, 7, 12, '\n&lt;/ul&gt;\n'},
   {:eof, 9, 1}
 ]}
```

The list of tokens are fairly self explanatory. We can see `:text` tokens for static ranges of plain text, `:expr` tokens for single line expressions, and `:start_expr`/`:end_expr` tokens for the multi-line do/end block expression.

The tokens also include some row/column metadata, which is helpful later on for attributing error messages to the origin point in the source code.

Now that we have the tokens, we need to traverse over them and pass them into the engine.

## The Engine

The engine is where the magic happens! It's responsible for building the tokens into usable Elixir AST, which can then be injected into a function.

An `EEx.Engine` has the following callbacks that it must implement and that your compiler must call appropriately. I'll copy/paste some of the callback documentation here, copyright belonging to the elixir-lang/elixir project.

```elixir
@doc """
Called at the beginning of every template.
It must return the initial state.
"""
@callback init(opts :: keyword) :: state

@doc """
Called at the end of every template.
It must return Elixir's quoted expressions for the template.
"""
@callback handle_body(state) :: Macro.t()

@doc """
Called for the text/static parts of a template.
It must return the updated state.
"""
@callback handle_text(state, [line: pos_integer, column: pos_integer], text :: String.t()) ::
            state

@doc """
Called for the dynamic/code parts of a template.
The marker is what follows exactly after `&lt;%`. For example,
`&lt;% foo %&gt;` has an empty marker, but `&lt;%= foo %&gt;` has `"="`
as marker. The allowed markers so far are:
  * `""`
  * `"="`
  * `"/"`
  * `"|"`
Markers `"/"` and `"|"` are only for use in custom EEx engines
and are not implemented by default. Using them without an
appropriate implementation raises `EEx.SyntaxError`.
It must return the updated state.
"""
@callback handle_expr(state, marker :: String.t(), expr :: Macro.t()) :: state

@doc """
Invoked at the beginning of every nesting.
It must return a new state that is used only inside the nesting.
Once the nesting terminates, the current `state` is resumed.
"""
@callback handle_begin(state) :: state

@doc """
Invokes at the end of a nesting.
It must return Elixir's quoted expressions for the nesting.
"""
@callback handle_end(state) :: Macro.t()
```

The compiler will start by calling the `c:init` callback to initialize the engine's state. It will pass this state to the various other callbacks as required. The state is an implementation detail to the Engine but will usually collect the static and dynamic pieces of the template as it traverses the tokens.

Whenever the compiler encounters a "text" token, it will pass the state, the metadata, and the text to he `c:handle_text/3` callback.

Whenever the compiler encounters an `expression` token, it will pass the state, the "marker", and the expression AST to the `c:handle_expr/3` callback.

If the encountered expression is multi-line, it will call the `c:handle_begin/3` callback. This creates a new "nesting" and will return a brand new state to be used to collect the output for the "nested" tokens.

When the compiler encounters an `:eof` token, it knows the template has finished and passes the state to `c:handle_body`, which returns the Elixir AST for the entire template.

I built an app that allows you to step through the compilation of a template as it traverses the tokens to help visualize what is happening and the output that is produced.

![EEx Compiler Visualizer](https://user-images.githubusercontent.com/5523984/162604950-a682e940-2af2-4968-886b-7413f256020d.gif)

You should check it out [here](https://github.com/mhanberg/eex_compiler_visualizer).

Here is what the compiled output for the above example looks like.

The AST generated by the `EEx.SmartEngine` forms all dynamic parts of your template into variables, then at the end, creates a `bitstring` with the result.

```elixir
arg0 = String.Chars.to_string(EEx.Engine.fetch_assign!(var!(assigns), :user).name)

arg1 =
  String.Chars.to_string(
    for user &lt;- EEx.Engine.fetch_assign!(var!(assigns), :users) do
      arg1 = String.Chars.to_string(user.name)
      &lt;&lt;"\n    &lt;li&gt;", arg1::binary, "&lt;/li&gt;\n  "&gt;&gt;
    end
  )

&lt;&lt;"&lt;!-- single line expression --&gt;\n&lt;span&gt;", arg0::binary,
  "&lt;/span&gt;\n\n&lt;!-- block expression --&gt;\n&lt;ul&gt;\n  ", arg1::binary, "\n&lt;/ul&gt;"&gt;&gt;
```

Let's take a look at how the `Phoenix.HTML.Engine` compiles the same template.

We can observe some similarities: it has the same `@` assigns functionality and it groups dynamic pieces into variables to coordinate later into the final result.

It also packs in some extra features!

- It will escape dynamically rendered plaintext, so you won't accidentally get some JS injection.
- It can pass data through the `Phoenix.HTML.Safe` protocol, allowing you to make custom representation of your Elixir data structures.
- It builds the result into `iodata` instead of a `bitstring`.

```elixir
arg0 =
  case Phoenix.HTML.Engine.fetch_assign!(var!(assigns), :user).name do
    {:safe, data} -&gt; data
    bin when is_binary(bin) -&gt; Phoenix.HTML.Engine.html_escape(bin)
    other -&gt; Phoenix.HTML.Safe.to_iodata(other)
  end

arg1 =
  case (for user &lt;- Phoenix.HTML.Engine.fetch_assign!(var!(assigns), :users) do
          arg1 =
            case user.name do
              {:safe, data} -&gt; data
              bin when is_binary(bin) -&gt; Phoenix.HTML.Engine.html_escape(bin)
              other -&gt; Phoenix.HTML.Safe.to_iodata(other)
            end

          {:safe, ["\n    &lt;li&gt;", arg1, "&lt;/li&gt;\n  "]}
        end) do
    {:safe, data} -&gt; data
    bin when is_binary(bin) -&gt; Phoenix.HTML.Engine.html_escape(bin)
    other -&gt; Phoenix.HTML.Safe.to_iodata(other)
  end

{:safe,
 [
   "&lt;!-- single line expression --&gt;\n&lt;span&gt;",
   arg0,
   "&lt;/span&gt;\n\n&lt;!-- block expression --&gt;\n&lt;ul&gt;\n  ",
   arg1,
   "\n&lt;/ul&gt;"
 ]}
```

Let's also peek at the output of `Phoenix.LiveView.Engine`, as it will generate something fairly different than the previous two engines.

The LiveView engine will emit these `%Rendered{}` and `%Comprehension{}` structs that allow it to efficiently do its diff tracking.

```elixir
require Phoenix.LiveView.Engine

(
  dynamic = fn track_changes? -&gt;
    changed =
      case assigns do
        %{__changed__: changed} when track_changes? -&gt; changed
        _ -&gt; nil
      end

    (
      v0 =
        case Phoenix.LiveView.Engine.nested_changed_assign?(assigns, changed, :user, struct: :name) do
          true -&gt; Phoenix.LiveView.Engine.live_to_iodata(assigns.user.name)
          false -&gt; nil
        end

      v2 =
        case Phoenix.LiveView.Engine.changed_assign?(changed, :users) do
          true -&gt;
            %Phoenix.LiveView.Comprehension{
              static: ["\n    &lt;li&gt;", "&lt;/li&gt;\n  "],
              dynamics:
                for user &lt;- assigns.users do
                  v1 = Phoenix.LiveView.Engine.live_to_iodata(user.name)
                  [v1]
                end,
              fingerprint: 245_710_792_584_248_035_419_893_716_549_685_965_177
            }

          false -&gt;
            nil
        end
    )

    [v0, v2]
  end

  %Phoenix.LiveView.Rendered{
    static: [
      "&lt;!-- single line expression --&gt;\n&lt;span&gt;",
      "&lt;/span&gt;\n\n&lt;!-- block expression --&gt;\n&lt;ul&gt;\n  ",
      "\n&lt;/ul&gt;"
    ],
    dynamic: dynamic,
    fingerprint: 104_434_365_286_405_865_087_616_174_532_871_347_220,
    root: nil
  }
)
```

## How can this help Temple?

So far we've detailed how _Elixir_ will compile an EEx template. The `EEx.Tokenizer` and `EEx.Compiler` are not public modules and are essentially an implementation detail.

Surface and HEEx are not EEx compatible, so attempting to compile them with `EEx.compile_string/2` would raise a syntax error. So how do they work?

They both implement their own tokenizer and compiler but delegate to the engine! Temple can do the exact same thing.

The _Engine_ is the public API that Temple can leverage to efficiently output Elixir AST for the various compile targets: Non-Phoenix web apps (for use in Plug, [Aino](https://github.com/oestrich/aino), any non-Phoenix web framework), traditional Phoenix Apps, and Phoenix LiveView apps. This is the technique that José was explaining that HEEx and Surface use.

Here is a table comparing which parts of the "template to Elixir AST" lifestyle between the builtin EEx compiler, the Temple compiler (in a theoretical future when Temple leverages this technique), Surface, and HEEx. EEx and HEEx don't have their own AST, so the parser step is not applicable.

| Step       | EEx           | Temple                      | Surface                    | HEEx                           |
| ---        | ---           | ---                         | ---                        | ---                            |
| Tokenizer  | EEx.Tokenizer | Kernel.SpecialForms.quote/2 | Surface.Compiler.Tokenizer | Phoenix.LiveView.HTMLTokenizer |
| Parser     | -             | Temple.Parser               | Surface.Compiler.Parser    | -                              |
| "Compiler" | EEx.Compiler  | Temple.Renderer             | Surface.Compiler.EExEngine | Phoenix.LiveView.HTMLEngine    |
| Engine     | Chosen Engine | Chosen Engine               | Phoenix.LiveView.Engine    | Chosen Engine                  |

Prior to this revelation, I had planned on the monumental task of basically re-implementing the `EEx.SmartEngine`, `Phoenix.HTML.Engine`, and `Phoenix.LiveView.Engine` modules in Temple (probably with lots of bugs). Now that I understand how to utilize the existing engines properly, it should be a relatively small lift for Temple to be able to use all three of them!

🎉 Kudos to José and Marlus for the advice, I really appreciate it!</description>
  <pubDate>Mon, 11 Apr 2022 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/how-eex-turns-your-template-into-html/</link>
  <guid>https://www.mitchellhanberg.com/how-eex-turns-your-template-into-html/---https://www.mitchellhanberg.com/how-eex-turns-your-template-into-html/</guid>
</item>
<item>
  <title>Writing Shell Scripts Using Elixir</title>
  <description>From My.Thoughts v1: Intro First of all, my apologies for not having written a blog post since July of last year! Between maintaining all my open source libraries, publishing Build a Weather Station with Elixir and Nerves, starting Elixir Patterns with Hugo Barauna, running my consulting business and being a host on the BEAM Radio podcast&amp;hellip;something had to give and unfortunately it was my Elixir blog 😔. But alas, I have promised myself that I can (and will) write a new blog post every quarter as opposed to every month like I used to.</description>
  <pubDate>Mon, 11 Apr 2022 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/elixir-shell-scripts/</link>
  <guid>https://akoutmos.com/post/elixir-shell-scripts/---https://akoutmos.com/post/elixir-shell-scripts/</guid>
</item>
<item>
  <title>Fundamentals &amp; Deployment</title>
  <description>From Underjord.io: I&amp;rsquo;ve had Gerhard Lazu on my livestream once where he showed me a way of doing CI/CD with k3s and ArgoCD. The podcast Ship It! that he hosts on Changelog recently had Kelsey Hightower on. Kelsey is to many this guru of the cloud native and Kubernetes space. I&amp;rsquo;ve had good value from what I&amp;rsquo;ve heard him say in the past. In this episode he really spoke to me.
So he essentially said, I&amp;rsquo;m interpreting here, that when it comes to deploying software to servers the documented manual steps for deploying something need to be the canonical reference.</description>
  <pubDate>Mon, 21 Mar 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/fundamentals-and-deployment.html</link>
  <guid>https://underjord.io/fundamentals-and-deployment.html---https://underjord.io/fundamentals-and-deployment.html</guid>
</item>
<item>
  <title>Is LiveView Overhyped?</title>
  <description>From Underjord.io: To alleviate any sense of clickbait. No, it is not overhyped as a technology, in my opinion. Let&amp;rsquo;s get into the nuances.
In a recent tweet a while back a member of the Elixir community expressed some frustration with how everything is all about LiveView. It led to some interesting and useful discussion and as someone who writes a fair bit about Elixir, Phoenix and such I have thoughts. Following on that discussions other people expressed further concern about the unreserved enthusiasm with which some of us have pushed LiveView and maybe even more, the PETAL stack.</description>
  <pubDate>Thu, 17 Mar 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/is-liveview-overhyped.html</link>
  <guid>https://underjord.io/is-liveview-overhyped.html---https://underjord.io/is-liveview-overhyped.html</guid>
</item>
<item>
  <title>Nerves Quickstart</title>
  <description>From Underjord.io: This is a labor of love where I just wanted the Nerves project to have a nice promo video. Frank and crew put a lot of work into making Nerves approachable while all their day-to-day use and needs is in the nitty-gritty industry side of it. So what does all that effort get them? Well, they&amp;rsquo;ve gotten it to the point where a complete step by step can be a one minute, 38 seconds long video.</description>
  <pubDate>Fri, 04 Feb 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-nerves-quickstart.html</link>
  <guid>https://underjord.io/video-nerves-quickstart.html---https://underjord.io/video-nerves-quickstart.html</guid>
</item>
<item>
  <title>Video - What Is: Elixir</title>
  <description>From Underjord.io: Trying to put words to the technology that has been most important to me for the last 4-5 years. Elixir. A language I picked up and now work almost exclusively with and specialize in. It is not the mainstream and I think that has serious advantages.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Sun, 30 Jan 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-what-is-elixir.html</link>
  <guid>https://underjord.io/video-what-is-elixir.html---https://underjord.io/video-what-is-elixir.html</guid>
</item>
<item>
  <title>Video - How Do: Server</title>
  <description>From Underjord.io: This one has been a while at the back of my mind. A lot of my readers have mentioned wanting to learn Linux and servers. This is an attempt at just unleashing you on the very first steps of your journey to do server work.
It covers setting up a server with Linode (taken as an example) and then installing a webserver on it. I also cover a few bits about securing a server and potential next steps.</description>
  <pubDate>Sun, 30 Jan 2022 03:00:00 +0000</pubDate>
  <link>https://underjord.io/video-how-do-server.html</link>
  <guid>https://underjord.io/video-how-do-server.html---https://underjord.io/video-how-do-server.html</guid>
</item>
<item>
  <title>Video - What Is: Server</title>
  <description>From Underjord.io: A brief explanation of servers, the concept.
Next step should be to show you good folks some stuff about how I set one up.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Original 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Sun, 30 Jan 2022 02:00:00 +0000</pubDate>
  <link>https://underjord.io/video-what-is-server.html</link>
  <guid>https://underjord.io/video-what-is-server.html---https://underjord.io/video-what-is-server.html</guid>
</item>
<item>
  <title>Formatting GraphQL in Elixir projects</title>
  <description>From Maarten van Vliet: With the release of Elixir 1.13 it became possible to add plugins to the formatter. These plugins gives developers the option to hook into the Elixir formatter.
I&amp;rsquo;ve created an Elixir formatter plugin to Absinthe to format GraphQL documents. With the release of Absinthe 1.7 this is available for everyone to use.
To start, you&amp;rsquo;ll need Absinthe 1.7 or higher together with Elixir 1.13 or higher.
The Absinthe project comes with Absinthe.</description>
  <pubDate>Wed, 26 Jan 2022 11:08:54 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2022/01/26/absinthe_formatter/</link>
  <guid>https://maartenvanvliet.nl/2022/01/26/absinthe_formatter/---https://maartenvanvliet.nl/2022/01/26/absinthe_formatter/</guid>
</item>
<item>
  <title>My Elm Experience</title>
  <description>From Underjord.io: I&amp;rsquo;ve been using Elm since about one year back now. At the start of 2021 I began working with a client that had an existing Elm frontend code base and an early stage backend built in Elixir and Phoenix. This post will be about my experiences running face first into Elm. I&amp;rsquo;ll be vague on some details as I can&amp;rsquo;t share too much about the specific client and code base. I&amp;rsquo;ll be much less vague about Elm itself.</description>
  <pubDate>Thu, 20 Jan 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/my-elm-experience.html</link>
  <guid>https://underjord.io/my-elm-experience.html---https://underjord.io/my-elm-experience.html</guid>
</item>
<item>
  <title>Video: Fixing my LiveView app</title>
  <description>From Underjord.io: My note-taking web app build in LiveView needed some fixing. Also updated it to use the new heex templates.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 07 Jan 2022 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-noted-liveview-fixing.html</link>
  <guid>https://underjord.io/livestream-noted-liveview-fixing.html---https://underjord.io/livestream-noted-liveview-fixing.html</guid>
</item>
<item>
  <title>Video: Indie Livestreaming with Elixir &amp; Membrane</title>
  <description>From Underjord.io: So Software Mansion, the makers of Membrane Framework, have kindly conspired with me to get RTMP ready for Membrane. RTMP is the protcol used when streaming from popular open source tool OBS. The RTMP module is currently at a technical preview level so be warned, might have some sharp edges.
They made sure there was a chain of building blocks from RTMP to HLS (HTTP Live Streaming) and now I can actually set up a completely independent version of the Underjord livestreams.</description>
  <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-indie-livestreaming-with-membrane-framework.html</link>
  <guid>https://underjord.io/livestream-indie-livestreaming-with-membrane-framework.html---https://underjord.io/livestream-indie-livestreaming-with-membrane-framework.html</guid>
</item>
<item>
  <title>Video: Building Out Indie Livestreaming with LiveView and Membrane</title>
  <description>From Underjord.io: On a previous stream we tested out the library for RTMP in Membrane to achieve streaming on our own in Elixir. This time we make it more convenient, flexible and work our way towards using it.
Being able to prepare and start livestreams, setting them up with LiveView, giving meaningful progress to users. Things are fun when you have near real-time events :)
 function switch_video(element) { var src = element.</description>
  <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-indie-livestreaming-continued.html</link>
  <guid>https://underjord.io/livestream-indie-livestreaming-continued.html---https://underjord.io/livestream-indie-livestreaming-continued.html</guid>
</item>
<item>
  <title>Video: Improving a GenServer, My IoT Lightswitch</title>
  <description>From Underjord.io: GenServer is the typical Actor abstraction in Erlang and Elixir. I&amp;rsquo;ve used one for talking to my Elgato Keylights in the lightswitch project. I made it sort of dumb.
It was a quick job so I want to improve it in a few ways.
 Make it not need to pull the new state from lights before sending commands. Pull state in the background occasionally to keep relatively up-to-date. Always fetch status after sending commands.</description>
  <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-improving-a-genserver.html</link>
  <guid>https://underjord.io/livestream-improving-a-genserver.html---https://underjord.io/livestream-improving-a-genserver.html</guid>
</item>
<item>
  <title>Video: Elixir and Computer Vision with evision</title>
  <description>From Underjord.io: From Twitter I stumbled on this neat, still early, but working project for using the OpenCV library from Elixir. So I decided to give it a whirl!
The project is called evision and I had a great time on stream screwing around with it :)
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-elixir-and-computer-vision.html</link>
  <guid>https://underjord.io/livestream-elixir-and-computer-vision.html---https://underjord.io/livestream-elixir-and-computer-vision.html</guid>
</item>
<item>
  <title>Guest post: Cross-cutting Elixir in Teams</title>
  <description>From Underjord.io: Release day. Although the product team was used to a bit of a hustle to wrap up open features, perform final testing, and prepare for deployment, today presented some additional challenges. This morning Jerry, the team’s product manager, discovered that one of the major features slated for this release was implemented in a way that would cause problems for some key users on the platform. Jerry notified Mark, the backend engineer, who said he might be able to fix the issue by modifying the data sent to the frontend.</description>
  <pubDate>Tue, 21 Dec 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/guest-feature-amclain-cross-cutting-elixir.html</link>
  <guid>https://underjord.io/guest-feature-amclain-cross-cutting-elixir.html---https://underjord.io/guest-feature-amclain-cross-cutting-elixir.html</guid>
</item>
<item>
  <title>Winners - Manning book giveaway</title>
  <description>From Underjord.io: Just a brief note announcing the three winners of the giveaway that take home a copy of Elixir in Action by Saša Jurić. Thanks to Manning for providing those copies. If you want 35% off of their books you can use the discount code nlunderjord21 and you can find their collection via this link that definitely will track some statistics about click-through rates (you have been warned): Manning&amp;rsquo;s other books.</description>
  <pubDate>Mon, 20 Dec 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/giveaway-manning-winners-2021-11.html</link>
  <guid>https://underjord.io/giveaway-manning-winners-2021-11.html---https://underjord.io/giveaway-manning-winners-2021-11.html</guid>
</item>
<item>
  <title>Are you hiring Elixir developers?</title>
  <description>From Underjord.io: Is your company actively trying to hire Elixir developers? Or should you be?
I care about making good software happen. Even if I&amp;rsquo;m not writing it. And good software is formed from good teams.
To help this happen I want to connect companies and developers in the Elixir space. I know and connect with plenty of both.
This is the first time I mention this particular offer to the public. If the interest is at the level it seems this will also be the only chance to get in at the initial pricing.</description>
  <pubDate>Tue, 14 Dec 2021 12:00:00 +0000</pubDate>
  <link>https://underjord.io/are-you-hiring-elixir-developers.html</link>
  <guid>https://underjord.io/are-you-hiring-elixir-developers.html---https://underjord.io/are-you-hiring-elixir-developers.html</guid>
</item>
<item>
  <title>Are Contexts a Thing?</title>
  <description>From Underjord.io: The discussion of Contexts in Phoenix and their general usefulness feels like a common point of disagreement in Elixir. I&amp;rsquo;ve gathered that the discussions went high and low as it was becoming a thing in mainline Phoenix. I don&amp;rsquo;t really care for controversy but what I see is a topic which gets confusing to wrangle with and which I never know quite how to explain. So this will be an attempt to explain what Contexts as provided are, cover some common concerns around this and two rather opposing suggestions about how to deal with them.</description>
  <pubDate>Mon, 06 Dec 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/are-contexts-a-thing-in-phoenix-web-apps.html</link>
  <guid>https://underjord.io/are-contexts-a-thing-in-phoenix-web-apps.html---https://underjord.io/are-contexts-a-thing-in-phoenix-web-apps.html</guid>
</item>
<item>
  <title>Books</title>
  <description>From My.Thoughts v1: If you enjoy reading my blog posts, using my open source libraries, and my Elixir tips, be sure to checkout my book publications and show your support. It means a lot to me and I am sure you&amp;rsquo;ll learn something cool from my books :).
Build a Weather Station with Elixir and Nerves In this book, Bruce Tate, Frank Hunleth and I discuss how to build and program a Nerves based weather station.</description>
  <pubDate>Thu, 02 Dec 2021 16:56:55 -0400</pubDate>
  <link>https://akoutmos.com/top/books/</link>
  <guid>https://akoutmos.com/top/books/---https://akoutmos.com/top/books/</guid>
</item>
<item>
  <title>Giveaway: Elixir in Action</title>
  <description>From Underjord.io: The book Elixir in Action by Saša Jurić is widely recognized as one of the best books on this language I favor. I&amp;rsquo;ve been meaning to run some giveaways and this book was always on the agenda. It shouldn&amp;rsquo;t be the only one but I think it makes a very appropriate starting point. Especially as the book has been such a starting point for many Elixirists through the years.
Transparency note: This giveaway is supported by Manning, the publisher that published Elixir in Action.</description>
  <pubDate>Tue, 23 Nov 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/giveaway-manning-2021-11.html</link>
  <guid>https://underjord.io/giveaway-manning-2021-11.html---https://underjord.io/giveaway-manning-2021-11.html</guid>
</item>
<item>
  <title>Building a Startup on Elixir to a $50 mil round</title>
  <description>From Underjord.io: I became familiar with Marcin Kulik when I was brought in to work with a company called Vic.ai. A fintech startup. Marcin is the guy who started building their entire Elixir system and was the person to primarily onboard me to the work. When I was brought in they were a small team of 4 devs, looking to grow up to around 7 at the time.
Special thanks to DockYard for supporting my work on this piece.</description>
  <pubDate>Wed, 17 Nov 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/50-mil-round-on-elixir.html</link>
  <guid>https://underjord.io/50-mil-round-on-elixir.html---https://underjord.io/50-mil-round-on-elixir.html</guid>
</item>
<item>
  <title>Finally using your Raspberry Pi &amp; Elixir</title>
  <description>From Underjord.io: How many Raspberry Pi do you have sitting in a drawer. I think I still have about 4? And at least 2 on a shelf. I may be over the average there but the story itself is common. &amp;ldquo;Oh! This is neat, I can automate X with this! And learn Y tech doing it!&amp;rdquo; And yet it never gets quite done. For reasons.
No one needs to feel too bad about this.</description>
  <pubDate>Sat, 30 Oct 2021 12:00:00 +0000</pubDate>
  <link>https://underjord.io/finally-using-your-raspberry-pi-and-elixir.html</link>
  <guid>https://underjord.io/finally-using-your-raspberry-pi-and-elixir.html---https://underjord.io/finally-using-your-raspberry-pi-and-elixir.html</guid>
</item>
<item>
  <title>Video: Attempting to Control Elgato Keylights from Nerves</title>
  <description>From Underjord.io: This livestream we tried to control my Elgato Keylights from a Nerves device. We really tried gang, come on. A good attempt.
We ran into an issue with the expectations we had on what mDNS would give us. Those issues are still under investigation but they seem to be a change or regression in underlying parts of Nerves or Erlang. I&amp;rsquo;ve since gotten it to work by going back a few versions.</description>
  <pubDate>Sun, 24 Oct 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-keylights-and-keypads-feat-failure.html</link>
  <guid>https://underjord.io/livestream-keylights-and-keypads-feat-failure.html---https://underjord.io/livestream-keylights-and-keypads-feat-failure.html</guid>
</item>
<item>
  <title>Video: Automatically Clustering Nerves devices</title>
  <description>From Underjord.io: Sitting down to show some of the work I&amp;rsquo;ve done with Nerves and Erlang Distribution for clustering Raspberry Pis. In this case automatically, via mDNS. This is what I do when I have a cold and am not streaming I guess.
We also poke around with an HDMI screen on the Pi400. I want to run Scenic on that one.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.</description>
  <pubDate>Fri, 22 Oct 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-nerves-nodes-and-mdns.html</link>
  <guid>https://underjord.io/livestream-nerves-nodes-and-mdns.html---https://underjord.io/livestream-nerves-nodes-and-mdns.html</guid>
</item>
<item>
  <title>Video: Making a Calendar Gadget with Nerves and LiveView</title>
  <description>From Underjord.io: Cleaning up previous work and making the eInk display look fabulous by iterating in our LiveView-based simulator. We get this thing ready for small office production :)
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 15 Oct 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-liveview-nerves-eink-calendar-gadget.html</link>
  <guid>https://underjord.io/livestream-liveview-nerves-eink-calendar-gadget.html---https://underjord.io/livestream-liveview-nerves-eink-calendar-gadget.html</guid>
</item>
<item>
  <title>Absinthe subscriptions-transport-ws websocket</title>
  <description>From Maarten van Vliet: Absinthe subscriptions-transport-ws websocket Support subscriptions-transport-ws websocket protocol for Absinthe
https://github.com/maartenvanvliet/subscriptions-transport-ws</description>
  <pubDate>Sat, 02 Oct 2021 12:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/subscriptions_transport_ws/</link>
  <guid>https://maartenvanvliet.nl/project/subscriptions_transport_ws/---https://maartenvanvliet.nl/project/subscriptions_transport_ws/</guid>
</item>
<item>
  <title>Video: Forming an Erlang cluster of Pi Zeros</title>
  <description>From Underjord.io: I sat down with the stream and gently coaxed a couple of Raspberry Pi Zeros to cluster up with each other using the Nerves project.
With recent support for Erlang distribution in MdnsLite we got the power to help nodes find each other via mDNS instead of some other method. So I wanted to give it a whirl, and I did :)
Special thanks to Isaac for his help in the chat.</description>
  <pubDate>Thu, 30 Sep 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-pi0-erlang-cluster-with-nerves.html</link>
  <guid>https://underjord.io/livestream-pi0-erlang-cluster-with-nerves.html---https://underjord.io/livestream-pi0-erlang-cluster-with-nerves.html</guid>
</item>
<item>
  <title>Video: Unbox a Pi400 and put Nerves on it</title>
  <description>From Underjord.io: I&amp;rsquo;ve been enjoying some Nerves recently which prompted me to finally order the Pi400 to play around with. In this stream I unpack it and get Nerves running on it without having tried it before or hearing that anyone had tried it. I just trusted the hard work of the Nerves team.
Turns out that was a great call. It worked beautifully, almost confusingly well. Then we investigated some of the things we can do with a Pi with input devices but no output connected.</description>
  <pubDate>Mon, 20 Sep 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-unbox-pi400-nerves.html</link>
  <guid>https://underjord.io/livestream-unbox-pi400-nerves.html---https://underjord.io/livestream-unbox-pi400-nerves.html</guid>
</item>
<item>
  <title>Video: Nerves, Livebook &amp; small displays (eInk, OLED)</title>
  <description>From Underjord.io: The Inky eInk display was one of my first real things done with Elixir. And I&amp;rsquo;m apparently back on my bullshit. Because I just pulled that thing out and tried it with Nerves + Livebook. Had a really good time this stream and I think I want to do more things with hardware like this. Let me know if you&amp;rsquo;d be curious to see more of this.
This triggered a not-yet-working Nerves device project I&amp;rsquo;m calling my calendar gadget which will be more documented at some point I&amp;rsquo;m sure.</description>
  <pubDate>Thu, 16 Sep 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-nerves-displays-and-livebook.html</link>
  <guid>https://underjord.io/livestream-nerves-displays-and-livebook.html---https://underjord.io/livestream-nerves-displays-and-livebook.html</guid>
</item>
<item>
  <title>Video: Phoenix 1.6 RC, Preview</title>
  <description>From Underjord.io: There are some very exciting updates in Phoenix 1.6 that just recently hit RC. We go through and try them out. It all seems to work great. I consider every bit of it an improvement.
The official blog announcement for RC 0 has the details and I reference it through the video.
I particularly tried out the LiveView Heex template changes which was great. This seems much more ergonomic and it wasn&amp;rsquo;t bad to begin with at eex/leex.</description>
  <pubDate>Fri, 03 Sep 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-elixir-phoenix-1-6-preview-liveview.html</link>
  <guid>https://underjord.io/livestream-elixir-phoenix-1-6-preview-liveview.html---https://underjord.io/livestream-elixir-phoenix-1-6-preview-liveview.html</guid>
</item>
<item>
  <title>Video: Elixir &amp; Ecto, generating queries</title>
  <description>From Underjord.io: As part of some foundational work on a CMS concept I&amp;rsquo;ve been building out this fairly dynamic Ecto-based way of dealing with content types and content (we call it entity types and entities) in a database.
And I wanted this to be broadly compatible. That&amp;rsquo;s what most of this livestream ended up being about from what I remember. Trying things and swearing about internals. All in all a good time right?</description>
  <pubDate>Fri, 27 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-elixir-ecto-query-generation.html</link>
  <guid>https://underjord.io/livestream-elixir-ecto-query-generation.html---https://underjord.io/livestream-elixir-ecto-query-generation.html</guid>
</item>
<item>
  <title>Video Companion - Teaching Elixir - Strings</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, strings.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Mon, 23 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-strings.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-strings.html---https://underjord.io/video-companion-teaching-elixir-strings.html</guid>
</item>
<item>
  <title>Video: Elixir CMS Thoughts, and code</title>
  <description>From Underjord.io: I&amp;rsquo;ve had a lot of thoughts and a few failed runs at creating a CMS in Elixir. This isn&amp;rsquo;t because Elixir is particular hard to do it in. Rather the opposite. The reason I&amp;rsquo;ve abandoned those efforts have to do with what exactly I want solved with a CMS.
I&amp;rsquo;ve outlined some of that in The WordPress merging problem and WordPress &amp;amp; the gross inefficiencies. I&amp;rsquo;m trying to create a sound technical solution with a sound human solution on top of it.</description>
  <pubDate>Mon, 23 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-elixir-cms-thoughts.html</link>
  <guid>https://underjord.io/livestream-elixir-cms-thoughts.html---https://underjord.io/livestream-elixir-cms-thoughts.html</guid>
</item>
<item>
  <title>Video Companion - Teaching Elixir - Comprehensions</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, comprehensions, the most arcane.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Mon, 16 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-comprehensions.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-comprehensions.html---https://underjord.io/video-companion-teaching-elixir-comprehensions.html</guid>
</item>
<item>
  <title>Video: Attempting Operational Transformation .. again</title>
  <description>From Underjord.io: Getting deeper and farther into trying to do Operational Transformation with Erlang wxStyledTextCtrl, aka. Scintilla and TextDelta.
I end up doing almost no TextDelta or Operational Transformation in this one as I try to pin down whether I can reliably insert and delete text from the different editor sessions without causing endless loops of updates. Turns out that there is no great facility for handling that. We learn quite a bit.</description>
  <pubDate>Mon, 16 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-attempting-operational-transformation-again.html</link>
  <guid>https://underjord.io/livestream-attempting-operational-transformation-again.html---https://underjord.io/livestream-attempting-operational-transformation-again.html</guid>
</item>
<item>
  <title>Video Companion - Teaching Elixir - Sigils</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time, sigils, the most arcane.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 13 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-sigils.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-sigils.html---https://underjord.io/video-companion-teaching-elixir-sigils.html</guid>
</item>
<item>
  <title>Video: Attempting Operational Transformation</title>
  <description>From Underjord.io: In an act of hubris I try to tackle with wxStyledTextCtrl and Operational Transformation in an unholy alliance of native UI and offline-friendly collaboration. I get partway there but nothing actually works.
Hopefully the flailing is interesting :)
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Fri, 13 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-attempting-operational-transformation.html</link>
  <guid>https://underjord.io/livestream-attempting-operational-transformation.html---https://underjord.io/livestream-attempting-operational-transformation.html</guid>
</item>
<item>
  <title>Video Companion - Teaching Elixir - Modules</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover modules.
You can find the livebooks of the modified lessons in my fork of ElixirSchool.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Thu, 05 Aug 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-modules.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-modules.html---https://underjord.io/video-companion-teaching-elixir-modules.html</guid>
</item>
<item>
  <title>It is not about Elixir</title>
  <description>From Underjord.io: That&amp;rsquo;s right. It never has been.
Before you read this post with your teeth clenched and ready for some hot takes, chill. This isn&amp;rsquo;t where I throw Elixir under the bus. Rather it is where I try to put my finger on the values that makes Elixir a good fit for me and a language I consistently recommend to people.
So to summarize the values:
 Software should be efficient Software should be maintainable Software should be suitable for continuous development Developers should not burn out from the work Teams should be able to deliver consistently without stress Technically conservative but extremely progressive  So some of my values are fairly specific to software though they apply in other contexts as well.</description>
  <pubDate>Fri, 30 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/it-is-not-about-elixir.html</link>
  <guid>https://underjord.io/it-is-not-about-elixir.html---https://underjord.io/it-is-not-about-elixir.html</guid>
</item>
<item>
  <title>Video: Building with PETAL 4 - Authentication</title>
  <description>From Underjord.io: In this stream of the PETAL Photo project we work on authentication. We build a way to log in with a magic link through email.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 low Sorry, your browser does not support embedded videos.</description>
  <pubDate>Thu, 29 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-building-with-petal-4.html</link>
  <guid>https://underjord.io/livestream-building-with-petal-4.html---https://underjord.io/livestream-building-with-petal-4.html</guid>
</item>
<item>
  <title>GenServer from first principles: Mohammad Maqbool Alam // Elixir Wizards Conference 2021</title>
  <description>From ⚡ Maqbool Khan: GenServer from first principles: Maqbool Alam // Elixir Wizards Conference 2021</description>
  <pubDate>Tue, 20 Jul 2021 03:42:18 GMT</pubDate>
  <link>https://www.maqbool.net/genserver-from-first-principles-maqbool-alam-elixir-wizards-conference-2021</link>
  <guid>https://www.maqbool.net/genserver-from-first-principles-maqbool-alam-elixir-wizards-conference-2021---https://www.maqbool.net/genserver-from-first-principles-maqbool-alam-elixir-wizards-conference-2021</guid>
</item>
<item>
  <title>Video: Building with PETAL 3 - Thumbnails and Transcoding</title>
  <description>From Underjord.io: Getting a bit technical in this one we get into thumbnails and video transcoding in the middle of a heatwave. Watch me sweat .. the details with FFMPEG.
This is the third installment Part 2 and Part 1. I don&amp;rsquo;t think they are necessary context but it probably helps. There has also been a few hours of work before each stream so the system moves forward off screen a bit.</description>
  <pubDate>Fri, 16 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-building-with-petal-3.html</link>
  <guid>https://underjord.io/livestream-building-with-petal-3.html---https://underjord.io/livestream-building-with-petal-3.html</guid>
</item>
<item>
  <title>Video: Building with PETAL 2 - Mostly Tailwind</title>
  <description>From Underjord.io: It continues. After some off-stream work fixing the upload issues from last stream we are now mostly heading into making this visually sensible. We are using the PETAL stack and that means a lot of Tailwind CSS.
This is the second installment, consider watching Part 1 before or after this.
So if you enjoy watching people turn blank white nothing into something more visual, this might be for you, or if you want to see someone that is not a Tailwind expert wield it, also accurate.</description>
  <pubDate>Fri, 16 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-building-with-petal-2.html</link>
  <guid>https://underjord.io/livestream-building-with-petal-2.html---https://underjord.io/livestream-building-with-petal-2.html</guid>
</item>
<item>
  <title>Video: Building with PETAL 1 - Getting Started</title>
  <description>From Underjord.io: It begins. For my vacation work-days, a concept to explain soon, I wanted to try to scratch an itch that I feel have product potential. So I decided to do parts of the work on the stream. This is the first part.
To address the vacation work-days thing. I&amp;rsquo;m not being terrible at vacationing, or at least not as far as I can tell. We have a young child in the house, so some time on my own is quite nice and I have established some routines I really enjoy that I didn&amp;rsquo;t want a break from this summer staycation.</description>
  <pubDate>Fri, 16 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-building-with-petal-1.html</link>
  <guid>https://underjord.io/livestream-building-with-petal-1.html---https://underjord.io/livestream-building-with-petal-1.html</guid>
</item>
<item>
  <title>The Human Side of Elixir</title>
  <description>From My.Thoughts v1: Intro If you follow my blog, you have probably noticed that my articles usually revolve around some deep technical problems and how to go about solving these problems using the amazing Elixir programming language. These posts usually discuss the technical merits surrounding Elixir and the Erlang virtual machine, but rarely touch on the &amp;ldquo;human&amp;rdquo; aspects of Elixir.
The goal of today&amp;rsquo;s post will be to address some of the non-technical aspects of the Elixir programming language and talk about the profound impact they can have on your engineers and your business.</description>
  <pubDate>Mon, 12 Jul 2021 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/betting-on-elixir/</link>
  <guid>https://akoutmos.com/post/betting-on-elixir/---https://akoutmos.com/post/betting-on-elixir/</guid>
</item>
<item>
  <title>Trust in Software, an All Time Low</title>
  <description>From Underjord.io: I don&amp;rsquo;t think I&amp;rsquo;ve ever had more distrust and as a consequence distate for software than in recent years. I don&amp;rsquo;t think its just me as a tech-nerd with artisanal tech-carpentry aspirations. I want people to build well, treat their users right and generally exercise some actual restraint. I see it very clearly and I react more viscerally than anyone non-technical in my surroundings. However, I see the frustrations and the consequences everywhere.</description>
  <pubDate>Fri, 09 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/trust-in-software-an-all-time-low.html</link>
  <guid>https://underjord.io/trust-in-software-an-all-time-low.html---https://underjord.io/trust-in-software-an-all-time-low.html</guid>
</item>
<item>
  <title>Teaching Elixir</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;When I joined &lt;a href="https://pspdfkit.com"&gt;PSPDFKit&lt;/a&gt; in 2018, I inherited the development and maintenance of &lt;a href="https://pspdfkit.com/guides/server/pspdfkit-server/overview/"&gt;PSPDFKit Server&lt;/a&gt;, the server-side component of &lt;a href="https://pspdfkit.com/pdf-sdk/web/"&gt;PSPDFKit for Web&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Initially, I was the only person working on the project full-time. The team eventually expanded to a total of three people and I moved on to manage the Web Team.&lt;/p&gt;
&lt;p&gt;In addition, other developers occasionally contributed features with differing degrees of complexity (ranging from small, incremental changes to large features over a few weeks of work).&lt;/p&gt;
&lt;p&gt;Looking at the entire process, I can safely say that onboarding has been smooth and fast, especially for internal hires from different teams.&lt;/p&gt;
&lt;p&gt;There are, of course, lessons learnt from this process, which is exactly what this blog post is about.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This post was sponsored by digital product consultancy &lt;a href="dockyard.com"&gt;DockYard&lt;/a&gt; to support the Elixir community and to encourage its members to share their stories.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="the-organization"&gt;
The organization
&lt;a href="#the-organization" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;PSPDFKit is a distributed company where everyone works remotely. The PSPDFKit Server team includes people with overlapping time zones and no fixed working hours.&lt;/p&gt;
&lt;p&gt;The core business for the company is the sale of SDKs used to manipulate documents (predominantly in PDF format).&lt;/p&gt;
&lt;p&gt;SDKs support a variety of platforms (iOS, Android, Windows, and web) and are embedded in client applications written and maintained by our customers.&lt;/p&gt;
&lt;h2 id="the-project"&gt;
The Project
&lt;a href="#the-project" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;PSPDFKit Server’s codebase is more than five years old and has grown organically over time thanks to the work of people with different expertise and backgrounds.&lt;/p&gt;
&lt;p&gt;Domain-wise, the application manages the lifetime of a document, allowing our customers to build specific workflows on top of a small set of primitives: documents, content layers, annotations of different types, and permission sets. At a very high level, a user can open a document, perform an arbitrary set of actions (with the option of leveraging real-time collaboration with other users), and finish their session with changes automatically and efficiently stored.&lt;/p&gt;
&lt;p&gt;Due to the way the product is sold and operated, it has a few key properties that make it different from most projects I’ve ever worked on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It’s both CPU and memory intensive due to the amount of operations performed on PDF files. The component that interacts with PDF files is run as a binary daemon and maintained by a different team.&lt;/li&gt;
&lt;li&gt;The application has optional components that are activated depending on specific configuration options. For example, one can enable an entire caching layer based on Redis.&lt;/li&gt;
&lt;li&gt;Features can be turned on and off remotely via our licensing infrastructure.&lt;/li&gt;
&lt;li&gt;The release is shipped as a Docker image to be operated on premise by the customer. This property has two implications. First, it means that we don&amp;rsquo;t have direct access to performance monitoring or visibility over runtime issues. Second, it drives us to minimise the operational complexity of the end product; for example, we don&amp;rsquo;t leverage the native Erlang/Elixir distribution because that would complicate deployment for our customers.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="onboarding-results"&gt;
Onboarding results
&lt;a href="#onboarding-results" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;In the last two years, we managed to onboard a good number of people:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 external hire with extensive Elixir experience, now part of the team full time&lt;/li&gt;
&lt;li&gt;1 internal hire with no Elixir experience, now part of the team full time&lt;/li&gt;
&lt;li&gt;2 internal hires with no Elixir experience, in the process of joining the team full time&lt;/li&gt;
&lt;li&gt;1 person contributing a significant piece of work over a few weeks&lt;/li&gt;
&lt;li&gt;Occasional contributions by other people&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s great that we can facilitate different degrees of contribution, with a ramp-up time measure between a couple of days and a week.&lt;/p&gt;
&lt;p&gt;The people involved have completely different backgrounds: heavy frontend development, systems programming, and mobile development. Furthermore, apart from one person, everyone had very little functional programming experience.&lt;/p&gt;
&lt;h2 id="training-structure"&gt;
Training structure
&lt;a href="#training-structure" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="learning-styles"&gt;
Learning styles
&lt;a href="#learning-styles" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;An important aspect to consider is each person’s learning style. Some people prefer pair-programming, while others would rather go through tutorials in their own time and ask questions when needed.&lt;/p&gt;
&lt;p&gt;To accommodate such differences, we combine learning materials with targeted pairing sessions as well as follow-up calls to clarify where needed. We also keep the schedule pretty flexible, meaning that we don&amp;rsquo;t have a fixed curriculum people go through in a set period of time.&lt;/p&gt;
&lt;h3 id="tooling"&gt;
Tooling
&lt;a href="#tooling" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The company uses a diverse set of languages and associated development tools. This ranges from text editors to full-fledged IDEs, as well as running on MacOS, Windows, and Linux.&lt;/p&gt;
&lt;p&gt;Apart from work on platforms where there’s strong incentive to standardize on a specific tool (e.g. XCode for iOS development), people pick their tools based on personal preference.&lt;/p&gt;
&lt;p&gt;A quick informal survey of the Server Team reveals that people predominantly use MacOS and favor Vim, Emacs, VSCode, and JetBrains IntelliJ.&lt;/p&gt;
&lt;p&gt;We enforce code formatting at the CI level, but encourage people to set up their development environment to format on file save.&lt;/p&gt;
&lt;p&gt;For code intelligence, Elixir has a solid implementation of a &lt;a href="https://github.com/elixir-lsp/elixir-ls"&gt;Language Server&lt;/a&gt; that provides intelligent autocompletion, inline docs, and jump to definition. Especially for people coming from a Java background, this type of tooling helps provide a consistent experience.&lt;/p&gt;
&lt;p&gt;In terms of domain-specific tooling, we glue everything together with shell scripts and Docker-based workflows.&lt;/p&gt;
&lt;h3 id="productivity-ramp-up-time"&gt;
Productivity ramp-up time
&lt;a href="#productivity-ramp-up-time" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;People are usually productive in two to three days, where &amp;ldquo;being productive&amp;rdquo; means being able to set up the project, getting a basic understanding of its structure and architecture, taking on a small piece of work, and producing a relevant pull request containing implementation, tests, and documentation.&lt;/p&gt;
&lt;p&gt;You may have heard the mantra &amp;ldquo;Make a commit on your first day.&amp;rdquo; While I agree with the sentiment behind it, I find it puts too much pressure on the contributor.&lt;/p&gt;
&lt;p&gt;As part of the initial onboarding, it&amp;rsquo;s normal to find little issues with setup scripts (e.g. things that stop working between major OS versions) or unclear documentation. In most cases, fixes are cheap to implement and offer an opportunity to discuss the application architecture.&lt;/p&gt;
&lt;h3 id="proficiency"&gt;
Proficiency
&lt;a href="#proficiency" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;On average, it takes two to three months for an engineer to become a proficient contributor who has worked on most areas of the codebase, including some of the internal parts which are subject to very low churn over time.&lt;/p&gt;
&lt;h3 id="learning-materials"&gt;
Learning materials
&lt;a href="#learning-materials" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;We generally recommend going through the &lt;a href="https://elixir-lang.org/getting-started/introduction.html"&gt;Elixir Lang guides&lt;/a&gt;: they’re well written, accurate, and split in easy-to-digest chunks.&lt;/p&gt;
&lt;p&gt;For all libraries, making package documentation available on &lt;a href="https://hexdocs.pm"&gt;hexdocs&lt;/a&gt; tends to be sufficient.&lt;/p&gt;
&lt;p&gt;While we do have a few books available in the company digital library, they&amp;rsquo;re rarely consulted.&lt;/p&gt;
&lt;h3 id="scope"&gt;
Scope
&lt;a href="#scope" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;We start with small, incremental features, as they have a very high completion success rate. For internal hires, we try to choose something connected to a use case the person is already familiar with from previous work.&lt;/p&gt;
&lt;p&gt;A more experienced team member is available for general guidance, so that the resulting piece of work includes all necessary components: implementation, tests, and documentation.&lt;/p&gt;
&lt;p&gt;On a technical level, we try to avoid pieces of work that introduce more than one key concept. For example, if the person is not familiar with immutable data structures, we avoid assigning a feature that would also introduce concurrency patterns.&lt;/p&gt;
&lt;h3 id="example-projects"&gt;
Example projects
&lt;a href="#example-projects" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;There are cases where it’s worth starting from smaller, separate projects. For concurrency and distribution, it’s better to rely on smaller applications (e.g. the examples referenced in the Elixir Lang guides) to nail the basics.&lt;/p&gt;
&lt;p&gt;Once the basics are clear, we build up by looking at codebase components to address topics like error handling, logging, production hardening, and recovery.&lt;/p&gt;
&lt;p&gt;Learning is complemented by specific articles (e.g. an introduction to circuit breakers).&lt;/p&gt;
&lt;h3 id="providing-feedback"&gt;
Providing feedback
&lt;a href="#providing-feedback" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;When a person starts to submit code for review, we try to provide different layers of feedback:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At first, we focus only on functionality, so that things work as expected. At this stage, the purpose is to build confidence and get the person comfortable.&lt;/li&gt;
&lt;li&gt;We address code quality and language conventions in follow-up PRs, so that changes can be reviewed separately and it’s clearer why they’re necessary.&lt;/li&gt;
&lt;li&gt;Reviews include links for self-learning, so that the person can explore on their own.&lt;/li&gt;
&lt;li&gt;If a PR review becomes too long, we suggest a pairing session to discuss things directly.&lt;/li&gt;
&lt;li&gt;We try to use proper names for all concepts, but also be conservative about introducing too many ideas in the same feedback session.&lt;/li&gt;
&lt;li&gt;We make a clear distinction between company and community conventions, as they may differ. This way, when the developer reads other code on their own, they can compare and contrast approaches. This also often provides constructive criticism on the way we do things.&lt;/li&gt;
&lt;li&gt;We slow down as needed during pairing sessions focused on learning. We allow ourselves to diverge from the initial session goal if the person wants to drill into a different topic. Maintaining interest and enthusiasm is more important in the early stages than sticking to a predefined plan.&lt;/li&gt;
&lt;li&gt;We recently started recording short videos that document how to approach specific development tasks in order to facilitate independent learning.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="milestones"&gt;
Milestones
&lt;a href="#milestones" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Every person that learned Elixir in the company followed a different process, but it’s possible to identify common milestones.&lt;/p&gt;
&lt;h4 id="pattern-matching-and-control-flow"&gt;
Pattern matching and control flow
&lt;a href="#pattern-matching-and-control-flow" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Understanding pattern-matching comes in the first hour of reading existing code. It takes more time to develop a sensibility around specifics, such as when to use a single function head with an internal case statement versus multiple function heads with specific pattern matching clauses.&lt;/p&gt;
&lt;h4 id="immutable-data-and-functions"&gt;
Immutable data and functions
&lt;a href="#immutable-data-and-functions" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;For people who approach Elixir as their first functional language coming from object-oriented languages, the first adaptation step is to start reasoning in terms of data passed through functions.&lt;/p&gt;
&lt;p&gt;Our trainees quickly embraced immutability due perceived benefits in clarity and predictability of the program. For example, one of our engineers commented that at the beginning of his learning journey, he could sprinkle print statements all over the codebase to see values changing and visualize the application flow.&lt;/p&gt;
&lt;p&gt;In this phase, developers tended to write very explicit code, with plenty of intermediate bindings and little use of more advanced language features like the &lt;code&gt;|&amp;gt;&lt;/code&gt; operator or the &lt;code&gt;with&lt;/code&gt; special form.&lt;/p&gt;
&lt;h4 id="configuration"&gt;
Configuration
&lt;a href="#configuration" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Configuration usually triggers a few questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Should this value be configurable?&lt;/li&gt;
&lt;li&gt;What about changing the value at runtime?&lt;/li&gt;
&lt;li&gt;How does configuration interact with building a Docker image and, later on, the container runtime?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is usually a good opportunity to introduce compile-time vs. runtime configuration, along with the operational implications of relying on the &lt;code&gt;Application.*&lt;/code&gt; functions to get and set configuration values at runtime.&lt;/p&gt;
&lt;p&gt;Common issues include hitting some light race conditions, namely unexpected test results due to configuration values being changed non-deterministically. It&amp;rsquo;s a great opportunity to reinforce that we should strive to write deterministic code that reads configuration as early as possible and passes relevant values down the line.&lt;/p&gt;
&lt;h4 id="concurrency-and-otp"&gt;
Concurrency and OTP
&lt;a href="#concurrency-and-otp" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The application has a few concurrent components, but their structure is rarely modified and developers can use public APIs without much concern for the internals.&lt;/p&gt;
&lt;p&gt;This is a big advantage and it lets us plan when to look at the concurrency model. We normally suggest looking at the examples in the Elixir Lang guides, then scheduling a couple of pairing sessions to look at the most significant components included in our codebase.&lt;/p&gt;
&lt;p&gt;Once we&amp;rsquo;re done with the fundamentals, we approach concurrency patterns: pools, worker queues, tasks, etc. For such cases, we train only on our application codebase.&lt;/p&gt;
&lt;h4 id="state-management"&gt;
State management
&lt;a href="#state-management" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The application depends on a few stateful components: metadata store, assets store, and different layers of caching (both filesystem and memory).&lt;/p&gt;
&lt;p&gt;These components provide the opportunity to introduce ETS tables, process lifetimes and recovery patterns, OTP guarantees around process trees, and error-handling logic.&lt;/p&gt;
&lt;h4 id="metaprogramming"&gt;
Metaprogramming
&lt;a href="#metaprogramming" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;We found very little use for meta-programming, so we don&amp;rsquo;t make it an explicit part of the onboarding process. Most of the time, people look into it by themselves when they start exploring the internals of dependencies like Phoenix and Ecto.&lt;/p&gt;
&lt;h3 id="bumps-on-the-road"&gt;
Bumps on the road
&lt;a href="#bumps-on-the-road" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;What follows is a list of road bumps we encountered over time. Some of them are more tied to our application and the way we ship it, so they don&amp;rsquo;t necessarily represent issues with the language or the platform.&lt;/p&gt;
&lt;h4 id="dialyzer"&gt;
Dialyzer
&lt;a href="#dialyzer" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Especially for people with knowledge of programming languages with static types, having Dialyzer as a separate step from the compiler can be frustrating.&lt;/p&gt;
&lt;p&gt;Fortunately, the aforementioned Language Server and related projects make it possible to set up the editing environment to provide almost real-time type information with a reasonable level of accuracy.&lt;/p&gt;
&lt;h4 id="structuring-large-components"&gt;
Structuring large components
&lt;a href="#structuring-large-components" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;One common observation is that compared to other languages, Elixir applications are built on very few architectural patterns. This applies to both ours and others I&amp;rsquo;ve worked on in the past few years, and is especially noticeable outside concurrency-related areas (which very often implement explicit behaviours).&lt;/p&gt;
&lt;p&gt;Most of the time, we simply take care of separating modules that perform side effects (e.g. writing/reading files, http calls, database interaction) from modules which only manipulate data.&lt;/p&gt;
&lt;p&gt;Modules are grouped according to their main &amp;ldquo;topic,&amp;rdquo; which helps exploration.&lt;/p&gt;
&lt;p&gt;Having fewer patterns sometimes makes the conversation slightly more difficult, as you need to develop a shared, domain-specific vocabulary to quickly express more complex ideas. On the plus side, the effort of defining names helps onboarding immensely, because it forces everyone in the team to be precise and thorough in their explanations.&lt;/p&gt;
&lt;h4 id="error-handling-and-recovery"&gt;
Error handling and recovery
&lt;a href="#error-handling-and-recovery" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;As we don&amp;rsquo;t operate the software we write, we need to take extra care with error handling. As best we can, we want to provide error messages that are simple(r) to understand for our customers (who are not Elixir developers).&lt;/p&gt;
&lt;p&gt;This creates a very mild tension between our style of error handling and the literature and community examples around applications being run directly by the developers who write them.&lt;/p&gt;
&lt;p&gt;For example, it&amp;rsquo;s much better for us to produce a single line error log with a specific name we understand compared to a more verbose stack trace, because it facilitates communication with the customer.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
Conclusion
&lt;a href="#conclusion" class="h-anchor" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I often hear the argument, &amp;ldquo;it&amp;rsquo;s difficult to hire Elixir developers.&amp;rdquo; In my experience, once you have two reasonably knowledgeable engineers, you can stop searching for already-trained developers and expand the search to developers willing to learn the language. With some care around the onboarding process, it&amp;rsquo;s possible to successfully train a new developer every two to three months with a very reasonable impact on the rest of the team.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Thanks to &lt;a href="mailto:swatkinsyl@gmail.com"&gt;Susan Watkins&lt;/a&gt; for editing this article&lt;/em&gt;&lt;/p&gt;</description>
  <pubDate>Tue, 06 Jul 2021 08:34:25 +0300</pubDate>
  <link>https://claudio-ortolina.org/posts/teaching-elixir/</link>
  <guid>https://claudio-ortolina.org/posts/teaching-elixir/---https://claudio-ortolina.org/posts/teaching-elixir/</guid>
</item>
<item>
  <title>Deploying with k3s and ArgoCD</title>
  <description>From Underjord.io: I&amp;rsquo;m pretty skeptical of Kubernetes as a choice for small teams, small projects and any less complicated operation. Gerhard Lazu is quite enthusiastic about it on the other hand. After having me on his show Ship It!, a Changelog.com production, I expressed some curiosity about k3s, a lightweight variant of k8s. He offered to come on the livestream and show me how good life can be. Hope you enjoy the video.</description>
  <pubDate>Thu, 01 Jul 2021 10:00:00 +0000</pubDate>
  <link>https://underjord.io/k3s-argocd-livestream.html</link>
  <guid>https://underjord.io/k3s-argocd-livestream.html---https://underjord.io/k3s-argocd-livestream.html</guid>
</item>
<item>
  <title>Video Companion - Teaching Elixir - Pipe Operator</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover the pipe operator |&amp;gt;.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
 function switch_video(element) { var src = element.</description>
  <pubDate>Thu, 01 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-pipe-operator.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-pipe-operator.html---https://underjord.io/video-companion-teaching-elixir-pipe-operator.html</guid>
</item>
<item>
  <title>Systems Design &amp; Architecture livestream</title>
  <description>From Underjord.io: For this livestream I sat down with pen and (i)Pad and tried to hash out some of my design thinking for a product idea I&amp;rsquo;ve been turning over in my head.
Let me know if this type of thinky-talky streams are of interest and I just might do some more of them. The video is also on the YouTube channel if you prefer.
 function switch_video(element) { var src = element.</description>
  <pubDate>Thu, 01 Jul 2021 04:00:00 +0000</pubDate>
  <link>https://underjord.io/livestream-systems-design-architecture.html</link>
  <guid>https://underjord.io/livestream-systems-design-architecture.html---https://underjord.io/livestream-systems-design-architecture.html</guid>
</item>
<item>
  <title>An Elixir Adoption Success Story</title>
  <description>From The Great Code Adventure: How a team that was new to Elixir over-delivered a big project in just three months.</description>
  <pubDate>Tue, 22 Jun 2021 19:51:56 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/an-elixir-adoption-success-story/</link>
  <guid>https://www.thegreatcodeadventure.com/an-elixir-adoption-success-story/---60b90080e3c30a003baa885a</guid>
</item>
<item>
  <title>Onboarding to Elixir</title>
  <description>From Underjord.io: I&amp;rsquo;ve worked with a number of clients on Elixir projects and I&amp;rsquo;ve onboarded myself, I&amp;rsquo;ve been onboarded and I&amp;rsquo;ve onboarded others. And compared to my experiences with PHP/Python/Javascript and my limited experience with C#/.Net I have experienced quite a difference.
Elixir projects tend to be very consistently laid out. Especially Phoenix-based web projects. The basic Phoenix generators provide a fair number of opinions on where things go. Even without that the general shape of an Elixir project is surprisingly stable.</description>
  <pubDate>Tue, 22 Jun 2021 05:00:00 +0000</pubDate>
  <link>https://underjord.io/onboarding-to-elixir.html</link>
  <guid>https://underjord.io/onboarding-to-elixir.html---https://underjord.io/onboarding-to-elixir.html</guid>
</item>
<item>
  <title>Video companion - Teaching Elixir - Functions</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover functions.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
Sorry, your browser doesn't support embedded videos.   If you enjoy this or have questions you can reach me via email as lars@underjord.</description>
  <pubDate>Tue, 15 Jun 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-functions.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-functions.html---https://underjord.io/video-companion-teaching-elixir-functions.html</guid>
</item>
<item>
  <title>Video - Setting up Prometheus and Grafana with Elixir</title>
  <description>From Underjord.io: Alex Koutmos, creator of the PromEx library and buddy from the radio show gave us some of his time to help set up Grafana and Prometheus metrics for our application Noted. This was an extremely tight path to a lot of observability. PromEx is very kind to you as a developer. Leveraging the common telemetry API to make something really compelling. And then Alex has spent the effort so you get a whole bunch of useful default dashboards rather than needing to decide up front what you want.</description>
  <pubDate>Mon, 07 Jun 2021 10:00:00 +0000</pubDate>
  <link>https://underjord.io/promex-livestream.html</link>
  <guid>https://underjord.io/promex-livestream.html---https://underjord.io/promex-livestream.html</guid>
</item>
<item>
  <title>Video companion - Teaching Elixir - Control Structures</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover control structures.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
Sorry, your browser doesn't support embedded videos.   If you enjoy this or have questions you can reach me via email as lars@underjord.</description>
  <pubDate>Mon, 07 Jun 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-control-structures.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-control-structures.html---https://underjord.io/video-companion-teaching-elixir-control-structures.html</guid>
</item>
<item>
  <title>How I Handle Static Assets in my Phoenix apps</title>
  <description>From Mitchell Hanberg's Blog: I no longer use [Webpack](https://webpack.js.org/).

Now I use [esbuild](https://esbuild.github.io/), [postcss-cli](https://github.com/postcss/postcss-cli), and [cpx](https://github.com/mysticatea/cpx).

- esbuild bundles and transpiles my JavaScript files extremely fast.
- postcss-cli processes my CSS and can run the [TailwindCSS JIT](https://tailwindcss.com/docs/just-in-time-mode) mode without any problems.
- cpx copies other static files from the `assets` directory into the `priv` directory.

Running each of these tools in development is just as easy as running Webpack.

```elixir
# config/dev.exs

esbuild = Path.expand("../assets/node_modules/.bin/esbuild", __DIR__)
config :my_app, MyAppWeb.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  check_origin: false,
  watchers: [
    "#{esbuild}": [
      "./js/app.js",
      "--target=es2015",
      "--bundle",
      "--outdir=../priv/static/js",
      "--sourcemap",
      "--watch",
      cd: Path.expand("../assets", __DIR__)
    ],
    node: [
      "./node_modules/.bin/postcss",
      "./css/app.css",
      "--dir",
      "../priv/static/css",
      "-w",
      cd: Path.expand("../assets", __DIR__),
      env: [{"NODE_ENV", "development"}, {"TAILWIND_MODE", "watch"}]
    ],
    node: [
      "./node_modules/.bin/cpx",
      "'static/**/*'",
      "../priv/static",
      "--watch",
      cd: Path.expand("../assets", __DIR__)
    ]
  ]
```


## esbuild

![esbuild home page](https://res.cloudinary.com/mhanberg/image/upload/v1622864248/Screen_Shot_2021-06-04_at_11.36.48_PM.png)

esbuild is part of the newest generation of JavaScript tooling. It is built in [Go](https://golang.org/) and is _extremely_ fast. All I really need is my JavaScript to be bundled and transpiled to some degree, and esbuild does just that.

Since esbuild is not available in our path, we'll have to declare our watcher with the absolute path to the binary. To make this legible, I have pulled out the absolute path to a variable so it reads more like what we are used to.

## PostCSS

Since I'm are no longer using Webpack, I just use the `postcss-cli`.

The third highlighted line demonstrates that this configuration is really just a declarative way to run commands with [`System.cmd/3`](https://hexdocs.pm/elixir/System.html#cmd/2). This means I can use all the same options! I am using the new [TailwindCSS JIT](https://tailwindcss.com/docs/just-in-time-mode), which requires a couple of environment variables to be set, so I pass the `:env` option to include variables in the environment of PostCSS.

## cpx

Once I ditched Webpack, I realized that in addition to bundling my JS and CSS, it was responsible for copying my other static assets to the `priv` directory. Luckily there is this handy npm package called `cpx` that will watch a glob of directories and copy them somewhere else when it notices changes.

## Wrapping Up

The speed increase from switching from Webpack to esbuild is well worth the effort to make the switch.

Do you use something other than Webpack in your Phoenix applications? Let me know on [Twitter](https://twitter.com/mitchhanberg)!</description>
  <pubDate>Mon, 07 Jun 2021 01:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/how-i-handle-static-assets-in-my-phoenix-apps/</link>
  <guid>https://www.mitchellhanberg.com/how-i-handle-static-assets-in-my-phoenix-apps/---https://www.mitchellhanberg.com/how-i-handle-static-assets-in-my-phoenix-apps/</guid>
</item>
<item>
  <title>Video - Improving the crawler</title>
  <description>From Underjord.io: A good jam session on improving the crawler. A bunch of basic ETS usage, some bug hunting and fixing and in the end I think it is better than it was :)
The project repo I built up during the stream is on Github as lawik/caterpillar and the video is below.
Also available on the YouTube channel.
Sorry, your browser doesn't support embedded videos.   Enjoy the things I do?</description>
  <pubDate>Fri, 04 Jun 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/webcrawler-livestream-2.html</link>
  <guid>https://underjord.io/webcrawler-livestream-2.html---https://underjord.io/webcrawler-livestream-2.html</guid>
</item>
<item>
  <title>How to Set Up Neovim for Elixir Development</title>
  <description>From Mitchell Hanberg's Blog: &gt; [!IMPORTANT] Update
&gt; 4/12/22: I've started working on a dedicated Neovim plugin for Elixir called [elixir-tools.nvim](https://github.com/elixir-tools/elixir-tools.nvim). Please follow me on [Twitter](https://twitter.com/mitchhanberg) for more frequent updates!

This article is the spiritual successor to [How to use Elixir LS with Vim](/post/2018/10/18/how-to-use-elixir-ls-with-vim/).

Since then, I've switched from Vim to the nightly release of [Neovim](https://github.com/neovim/neovim) as well as how I integrate linters, formatters, and LSPs.

This article will cover:

- Installing Neovim
- Getting started with the builtin LSP client
- Setting up Elixir LS
- Integrating [Credo](https://github.com/rrrene/credo)

_If you run into any problems with this guide, feel free to shoot me an [email](mailto:contact@mitchellhanberg.com)._

Let's get started!

## Installing Neovim

You can install Neovim with your package manager of choice, or [asdf](https://asdf-vm.com).

### Homebrew

```shell
$ brew install neovim
```

### asdf

```shell
$ asdf plugin add neovim

$ asdf install neovim stable
```

### Nightly

&gt; With the release of 0.5, you no longer need to use Neovim nightly for this setup. I will leave these instructions in case you still want to live on the edge.

As of this writing, the builtin LSP client is only available on the nightly build of Neovim. Once 0.5 is released, you should be able to switch to a stable build, but for now, let's get nightly installed.

My preferred method for managing my installation of Neovim is to use [asdf](https://asdf-vm.com). You can install it with [Homebrew](https://brew.sh) as well, but I find asdf to be better.

For this article, I am going to assume you already have asdf installed, as it is the most prevalent way to manage Elixir and Erlang installations.

But we still need to install the Neovim asdf plugin.

```shell
$ asdf plugin add neovim
```

Listing the available versions demonstrates that we can install any previously released version of Neovim, as well as the nightly build. These versions are pre-built and downloaded as a GitHub release artifact. This makes installing them very fast.

```shell
$ asdf list all neovim
0.1.0
0.1.1
0.1.2
0.1.3
0.1.4
0.1.5
0.1.6
0.1.7
0.2.0
0.2.1
0.2.2
0.3.0
0.3.1
0.3.2
0.3.3
0.3.4
0.3.5
0.3.6
0.3.7
0.3.8
0.4.0
0.4.1
0.4.2
0.4.3
0.4.4
nightly
stable
```

If for some reason the nightly build cron job is on the fritz (as it sometimes is), you can also build form source with:

```shell
$ asdf install neovim ref:master
```

What we are going to stick with is:

```shell
$ asdf install neovim nightly
$ asdf global neovim nightly
```

Now, if you want to _update_ your nightly installation, all you have to do is uninstall and reinstall. I use the following as a convenient shell alias.

```shell
$ alias update-nvim-nightly='asdf uninstall neovim nightly &amp;&amp; asdf install neovim nightly'
```

So, just to tie this all together, these are the steps you will go through to get Neovim nightly installed.

```shell
$ asdf plugin add neovim
$ asdf install neovim nightly
$ asdf global neovim nightly
$ echo "alias update-nvim-nightly='asdf uninstall neovim nightly &amp;&amp; asdf install neovim nightly'" &gt;&gt; .zshrc # bash/fish/etc
```

## Getting started with the builtin LSP client

To help users get started with the LSP client, the Neovim team provides a plugin called [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) that contains configurations for many common language servers.

There are also a few other plugins revolving around autocomplete that you'll need to install to get the full LSP experience.

With your preferred plugin manager, install the following plugins. I'm using [packer.nvim](https://github.com/wbthomason/packer.nvim). If you don't use a package manager, I suggest learning more about them by checking out packer.nvim as well as [vim-plug](https://github.com/junegunn/vim-plug).

```lua
vim.cmd [[packadd packer.nvim]]

local startup = require("packer").startup

startup(function(use)
  -- language server configurations
  use "neovim/nvim-lspconfig"

  -- autocomplete and snippets
  use("hrsh7th/nvim-cmp")
  use("hrsh7th/cmp-nvim-lsp")
  use("hrsh7th/cmp-vsnip")
  use("hrsh7th/vim-vsnip")

  use("onsails/lspkind-nvim")
end)
```

Now that we have the required plugins installed, let's set them up so they get booted when we start Neovim. I am using the `init.lua` config file, but you can also add this to your `init.vim` if you use a lua heredoc.

```lua
local lspconfig = require("lspconfig")

-- Neovim doesn't support snippets out of the box, so we need to mutate the
-- capabilities we send to the language server to let them know we want snippets.
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true

-- Setup our autocompletion. These configuration options are the default ones
-- copied out of the documentation.
local cmp = require("cmp")

cmp.setup({
  snippet = {
    expand = function(args)
      -- For `vsnip` user.
      vim.fn["vsnip#anonymous"](args.body)
    end,
  },
  mapping = {
    ["&lt;C-b&gt;"] = cmp.mapping.scroll_docs(-4),
    ["&lt;C-f&gt;"] = cmp.mapping.scroll_docs(4),
    ["&lt;C-Space&gt;"] = cmp.mapping.complete(),
    ["&lt;C-e&gt;"] = cmp.mapping.close(),
    ["&lt;C-y&gt;"] = cmp.mapping.confirm({ select = true }),
  },
  sources = {
    { name = "nvim_lsp" },
    { name = "vsnip" },
  },
  formatting = {
    format = require("lspkind").cmp_format({
      with_text = true,
      menu = {
        nvim_lsp = "[LSP]",
      },
    }),
  },
})
```

That should be it for the basic LSP client configuration.

## Setting up Elixir LS

[Elixir LS](https://github.com/elixir-lsp/elixir-ls) is a tool that needs to be compiled from source, but it's pretty simple. Pick a directory to install and run the following commands.

```shell
$ git clone git@github.com:elixir-lsp/elixir-ls.git
$ cd elixir-ls &amp;&amp; mkdir rel

# checkout the latest release if you'd like
$ git checkout tags/v0.7.0

$ mix deps.get &amp;&amp; mix compile

$ mix elixir_ls.release -o release
```

Now that we have Elixir LS installed and compiled, let's get it set up in Neovim.

```lua
-- A callback that will get called when a buffer connects to the language server.
-- Here we create any key maps that we want to have on that buffer.
local on_attach = function(_, bufnr)
  local function map(...)
    vim.api.nvim_buf_set_keymap(bufnr, ...)
  end
  local map_opts = {noremap = true, silent = true}

  map("n", "df", "&lt;cmd&gt;lua vim.lsp.buf.formatting()&lt;cr&gt;", map_opts)
  map("n", "gd", "&lt;cmd&gt;lua vim.lsp.diagnostic.show_line_diagnostics()&lt;cr&gt;", map_opts)
  map("n", "dt", "&lt;cmd&gt;lua vim.lsp.buf.definition()&lt;cr&gt;", map_opts)
  map("n", "K", "&lt;cmd&gt;lua vim.lsp.buf.hover()&lt;cr&gt;", map_opts)
  map("n", "gD", "&lt;cmd&gt;lua vim.lsp.buf.implementation()&lt;cr&gt;", map_opts)
  map("n", "&lt;c-k&gt;", "&lt;cmd&gt;lua vim.lsp.buf.signature_help()&lt;cr&gt;", map_opts)
  map("n", "1gD", "&lt;cmd&gt;lua vim.lsp.buf.type_definition()&lt;cr&gt;", map_opts)

  -- These have a different style than above because I was fiddling
  -- around and never converted them. Instead of converting them
  -- now, I'm leaving them as they are for this article because this is
  -- what I actually use, and hey, it works ¯\_(ツ)_/¯.
  vim.cmd [[imap &lt;expr&gt; &lt;C-l&gt; vsnip#available(1) ? '&lt;Plug&gt;(vsnip-expand-or-jump)' : '&lt;C-l&gt;']]
  vim.cmd [[smap &lt;expr&gt; &lt;C-l&gt; vsnip#available(1) ? '&lt;Plug&gt;(vsnip-expand-or-jump)' : '&lt;C-l&gt;']]

  vim.cmd [[imap &lt;expr&gt; &lt;Tab&gt; vsnip#jumpable(1) ? '&lt;Plug&gt;(vsnip-jump-next)' : '&lt;Tab&gt;']]
  vim.cmd [[smap &lt;expr&gt; &lt;Tab&gt; vsnip#jumpable(1) ? '&lt;Plug&gt;(vsnip-jump-next)' : '&lt;Tab&gt;']]
  vim.cmd [[imap &lt;expr&gt; &lt;S-Tab&gt; vsnip#jumpable(-1) ? '&lt;Plug&gt;(vsnip-jump-prev)' : '&lt;S-Tab&gt;']]
  vim.cmd [[smap &lt;expr&gt; &lt;S-Tab&gt; vsnip#jumpable(-1) ? '&lt;Plug&gt;(vsnip-jump-prev)' : '&lt;S-Tab&gt;']]

  vim.cmd [[inoremap &lt;silent&gt;&lt;expr&gt; &lt;C-Space&gt; compe#complete()]]
  vim.cmd [[inoremap &lt;silent&gt;&lt;expr&gt; &lt;CR&gt; compe#confirm('&lt;CR&gt;')]]
  vim.cmd [[inoremap &lt;silent&gt;&lt;expr&gt; &lt;C-e&gt; compe#close('&lt;C-e&gt;')]]
  vim.cmd [[inoremap &lt;silent&gt;&lt;expr&gt; &lt;C-f&gt; compe#scroll({ 'delta': +4 })]]
  vim.cmd [[inoremap &lt;silent&gt;&lt;expr&gt; &lt;C-d&gt; compe#scroll({ 'delta': -4 })]]

  -- tell nvim-cmp about our desired capabilities
  require("cmp_nvim_lsp").update_capabilities(capabilities)
end

-- Finally, let's initialize the Elixir language server

-- Replace the following with the path to your installation
local path_to_elixirls = vim.fn.expand("~/.cache/nvim/lspconfig/elixirls/elixir-ls/release/language_server.sh")

lspconfig.elixirls.setup({
  cmd = {path_to_elixirls},
  capabilities = capabilities,
  on_attach = on_attach,
  settings = {
    elixirLS = {
      -- I choose to disable dialyzer for personal reasons, but
      -- I would suggest you also disable it unless you are well
      -- acquainted with dialzyer and know how to use it.
      dialyzerEnabled = false,
      -- I also choose to turn off the auto dep fetching feature.
      -- It often get's into a weird state that requires deleting
      -- the .elixir_ls directory and restarting your editor.
      fetchDeps = false
    }
  }
})
```

Elixir LS should be all set up now! Let's test it out by seeing if autocompletion, documentation on hover, and go to definition is working.

![Gif demonstration of autocomplete, documentation on hover, and go to definition](https://res.cloudinary.com/mhanberg/image/upload/v1621999434/elixir-ls-demo.gif)

## Integrating Credo

I decided to completely remove [ALE](https://github.com/dense-analysis/ale), so I was wondering how I might get linters and formatters like credo and prettier hooked back in.

Luckly, there are a few projects that implement a language server for the purpose of running these tools for you. I am currently using [efm-langserver](https://github.com/mattn/efm-langserver).

I install efm with brew.

```shell
$ brew install efm-langserver
```

Once that is installed, let's hook it up to Neovim.

```lua
lspconfig.efm.setup({
  capabilities = capabilities,
  on_attach = on_attach,
  filetypes = {"elixir"}
})
```

And last, we need to teach efm how to speak Credo. Create a new file at `~/.config/efm-langserver/config.yaml`. Please note that we need to run Credo with `MIX_ENV=test` or else it's going to mess with Phoenix code reloading.

```yaml
version: 2

tools:
  mix_credo: &amp;mix_credo
    lint-command: "MIX_ENV=test mix credo suggest --format=flycheck --read-from-stdin ${INPUT}"
    lint-stdin: true
    lint-formats:
      - '%f:%l:%c: %t: %m'
      - '%f:%l: %t: %m'
    lint-category-map:
      R: N
      D: I
      F: E
      W: W
    root-markers:
      - mix.lock
      - mix.exs

languages:
  elixir:
    - &lt;&lt;: *mix_credo
```

Now you should be seeing Credo checks showing up inside Neovim.

## My Setup

If you would like to check out my actual dotfiles, feel free to check them out on [GitHub](https://github.com/mhanberg/.dotfiles).

I've extracted quite a few helper modules and functions that make organizing my plugins a little easier.

Enjoy!</description>
  <pubDate>Wed, 02 Jun 2021 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/how-to-set-up-neovim-for-elixir-development/</link>
  <guid>https://www.mitchellhanberg.com/how-to-set-up-neovim-for-elixir-development/---https://www.mitchellhanberg.com/how-to-set-up-neovim-for-elixir-development/</guid>
</item>
<item>
  <title>A Brief History of Time</title>
  <description>From Sayan Chakraborty | Blog | Research | AI | Engineering | Tech: A post where we dive deeper into complexities of handling time in distributed systems</description>
  <pubDate>Wed, 02 Jun 2021 04:40:32 GMT</pubDate>
  <link>https://sayanc93.github.ioclocks-and-distributed-systems</link>
  <guid>https://sayanc93.github.ioclocks-and-distributed-systems---https://sayanc93.github.ioclocks-and-distributed-systems</guid>
</item>
<item>
  <title>Video companion - Teaching Elixir - Pattern Matching</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover pattern matching.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
Today&amp;rsquo;s goof is to call the whole thing Learning Elixir at the start. Also we had to start over once because I screwed up the entire Livebook.</description>
  <pubDate>Mon, 31 May 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-pattern-matching.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-pattern-matching.html---https://underjord.io/video-companion-teaching-elixir-pattern-matching.html</guid>
</item>
<item>
  <title>Announcing Temple v0.6!</title>
  <description>From Mitchell Hanberg's Blog: v0.6 of Temple has been released! 🎉

This release is the result of a few architecture changes that allows for faster rendering as well as many new features.

_If you'd like to follow along with the code in this article, it's available [here](https://github.com/mhanberg/temple_example)_.

## Compile time output of EEx

Temple now outputs EEx at compile time, allowing us to piggy back on the power of existing EEx Engines, such as the engines included by the [Phoenix Framework](https://phoenixframework.org). If you're using Temple with Phoenix, your templates should now be just as fast as the standard Phoenix templates, since the EEx output of Temple is piped right into the Phoenix template engine.

Compiling to EEx also means that Temple is now compatible with [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view)! This is made possible by the same process outlined above, without having to re-invent any wheels.

## Improved Component API

The current component API is module driven and comes with support for "slots".

Let's write a `Card` component to see how this all works.

A Temple component begins with importing the `Temple.Component` module, which brings us the `render/1` and `defcomp/2` macros.

### defcomp

`defcomp` allows us to create a basic component within any module. The only limitation is that you can't define any functions inside of it, as the body of the macro is the markup of the component. Here we are creating several more components using `defcomp`, but you can use this macro anywhere. Under the hood, it defines a Temple component module.

### render 

`render` is where your component markup will go. Since Temple components are basically the same as Phoenix views, this macro expands into a render function that takes assigns, which you can access like `@my_assign` just like you would do in a normal EEx template.

### slot

Inside each component definition, you'll see the usage of `slot :default`. This is the proper way to render the inner content of the component that is defined at the call site. `slot` is more like a _keyword_, as it is not a macro or a function.

```elixir
defmodule TempleExampleWeb.Components.Card do
  import Temple.Component

  defcomp Header do
    header class: "p-4 border-b border-gray-300 bg-gray-50 rounded-t-lg" do
      div class: "flex items-center space-x-4" do
        slot :default
      end
    end
  end

  defcomp Body do
    div class: "p-4 border-b border-gray-300" do
      slot :default
    end
  end

  defcomp Footer do
    footer class: "bg-gray-50 rounded-b-lg p-4" do
      ul class: "flex items-center" do
        slot :default
      end
    end
  end

  render do
    div class: "bg-white w-full flex flex-col rounded-lg shadow-lg" do
      slot :default
    end
  end
end
```

Here we've defined a `Card` component as well as `Card.Header`, `Card.Body`, and `Card.Footer` components. Now we can easily compose these components together to create a few cards.

### c

We render a component calling the `c` _keyword_ with a component module. I decided to keep this keyword as short as I could to make using components as ergonomic as possible. In the beginning, I wanted to be able to render them _without_ any keyword, but calling a module like a function is not valid Elixir syntax, so I couldn't do that.

```elixir
div class: "grid grid-cols-3 gap-4 p-4 items-start" do
  for user &lt;- @users do
    c Card do
      c Card.Header do
        div class: "w-10 rounded-full overflow-hidden flex-shrink-0" do
          div class: "aspect-w-1 aspect-h-1" do
            img class: "object-cover", src: user.avatar
          end
        end

        div do: user.name
      end

      c Card.Body do
        text_to_html(user.bio, attributes: [class: "first:mt-0 mt-2"])
      end

      unless Enum.empty?(user.socials) do
        c Card.Footer do
          c LinkList, socials: user.socials do
            slot :link, %{text: text, url: url} do
              c LinkList.Item, url: url do
                text
              end
            end
          end
        end
      end
    end
  end
end
```

![Screenshot of 3 cards created using the above code snippet](https://res.cloudinary.com/mhanberg/image/upload/v1621913949/Screen_Shot_2021-05-24_at_11.41.34_PM.png)

The `Card` related components all called `slot :default` to render their inner content, but as we can see inside our usage of `LinkList` within the `Card.Footer` component, we call `slot :link, %{text: text, url: url}`, why is that??

I'm glad you asked. The key feature of slots in my eyes is the ability for a component to pass data back into the scope of the caller.

Before, we were _rendering_ a slot, whereas now we are _defining_ a slot.

It's pretty easy to think of this in the context of a function that takes an anonymous function as an argument, like how you usually render a form in a Phoenix project.

```elixir
form_for(@user, Routes.user_path(@conn, :edit, @user.id), fn form -&gt;
  # markup and functions that use the form
end)
```

If we were to make a form _component_, we could use a slot to do the same thing. You can see in these examples that we can pattern match on the assigns that are passed to the slot, allowing us to easily rename a variable if we want.

Here's how we might make a form component that is compatible with how we're supposed to write forms in LiveView.

```elixir
defmodule TempleExampleWeb.Components.Form do
  import Temple.Component

  render do
    form_for(@changeset, @path)  

    slot :form, form: form_for(@changeset, @path)

    "&lt;/form&gt;"
  end
end

# usage

alias TempleExampleWeb.Components.Form

c Form, changeset: @user, path: Routes.user_path(@conn, :edit, @user.id) do
  slot :form, %{form: f} do
    # markup and functions that use the form
  end
end
```

The nice part of slots is that we can have more than one and control exactly where they are rendered inside the component. Let's take another look at our `Card.Header` component to see what I mean.

```elixir
defcomp Header do
  header class: "p-4 border-b border-gray-300 bg-gray-50 rounded-t-lg" do
    div class: "flex items-center justify-between" do
      div do
        slot :left
      end

      slot :default

      div do
        slot :right
      end
    end
  end
end
```

Now we can slot (😅) some markup into specific parts of the component.

```elixir
c Card.Header do
  slot :left do
    div class: "w-10 rounded-full overflow-hidden" do
      div class: "aspect-w-1 aspect-h-1" do
        img class: "object-cover", src: user.avatar
      end
    end
  end

  slot :right do
    SVG.verified_icon()
  end

  div do: user.name
end
```

## What's Next?

While Temple has certainly come a long way, there are still many improvements to be made! The ones that I can immediately think of include:

- [Fallback content for slots](https://github.com/mhanberg/temple/issues/129)
- [Allow multiple instances of a single slot name](https://github.com/mhanberg/temple/issues/130)
- [Use a component as a "typed" slot](https://github.com/mhanberg/temple/issues/128)
- Improve stacktraces. Currently if something goes wrong, you might see errors/warnings being rendered on the wrong lines.
- Writing guides. The hex documentation is alright, but doesn't really teach you how to use Temple.
- Write a "component library" a la something like [React Bootstrap](https://react-bootstrap.github.io/)
- New logo 👨‍🎨

---

## Related Reading

- [Temple, AST, and Protocols](/temple-ast-and-protocols)
- [Introducing Temple: An elegant HTML library for Elixir and Phoenix](/introducing-temple-an-elegant-html-library-for-elixir-and-phoenix/)
- [Release Notes](https://github.com/mhanberg/temple/releases/tag/v0.6.0)</description>
  <pubDate>Tue, 25 May 2021 10:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/announcing-temple-v06/</link>
  <guid>https://www.mitchellhanberg.com/announcing-temple-v06/---https://www.mitchellhanberg.com/announcing-temple-v06/</guid>
</item>
<item>
  <title>Video - Building a web crawler</title>
  <description>From Underjord.io: Had some great fun building on this webcrawler on the stream. Plan to continue with this in coming livestreams, so watch this to get up to speed on what we are doing.
We grab an HTTP client, and HTML parser and then we go hog wild spawning Task processes and fetching web pages.
The project repo I built up during the stream is on Github as lawik/caterpillar and the video is below.</description>
  <pubDate>Fri, 21 May 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/webcrawler-livestream-1.html</link>
  <guid>https://underjord.io/webcrawler-livestream-1.html---https://underjord.io/webcrawler-livestream-1.html</guid>
</item>
<item>
  <title>Video companion - Teaching Elixir - Enums</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover enum.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
There was an audio issue while recording that couldn&amp;rsquo;t be isolated and fixed where my friend was quite a bit lower in volume than I was.</description>
  <pubDate>Mon, 17 May 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-enum.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-enum.html---https://underjord.io/video-companion-teaching-elixir-enum.html</guid>
</item>
<item>
  <title>Video - Native UI with Elixir and wxWidgets</title>
  <description>From Underjord.io: This livestream turned out well. We got the Erlang wx module to work as intended in Elixir. We built a window, we made some controls, we made them interact. Good fun.
This stream connects to some previous thoughts I&amp;rsquo;ve expressed about taking shortcuts in software or prioritizing developer experience over end-user experience, efficiency, consistency and so on. I wrote about that as the idea of artisanal software. wxWidgets is a bit more pragmatic than that lofty post.</description>
  <pubDate>Fri, 14 May 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/native-ui-wx-elixir.html</link>
  <guid>https://underjord.io/native-ui-wx-elixir.html---https://underjord.io/native-ui-wx-elixir.html</guid>
</item>
<item>
  <title>Video - PETAL Stack Setup</title>
  <description>From Underjord.io: Last weeks livestream was spent mostly on setting up the PETAL stack basics for a project. So if you want the video version of setting up PETAL with my guide this is for you.
Then we also spent some time setting up Exqlite so we can use Sqlite instead of Postgres. That worked nicely, we tested it with phx.gen.auth. I will try to find some time to make a post on this later.</description>
  <pubDate>Tue, 11 May 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/petal-stack-setup-stream-vod.html</link>
  <guid>https://underjord.io/petal-stack-setup-stream-vod.html---https://underjord.io/petal-stack-setup-stream-vod.html</guid>
</item>
<item>
  <title>Video companion - Teaching Elixir - Collections</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover collections.
You can find the very simplistic Livebook version of the Elixir school content on my forked Elixir School repo for now.
The video is also available on YouTube.
Sorry, your browser doesn't support embedded videos.   If you enjoy this or have questions you can reach me via email as lars@underjord.</description>
  <pubDate>Mon, 10 May 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-collections.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-collections.html---https://underjord.io/video-companion-teaching-elixir-collections.html</guid>
</item>
<item>
  <title>Video companion - Teaching Elixir - Basics</title>
  <description>From Underjord.io: Me and an experienced programmer friend sit down and try to teach him Elixir using the Elixir School curriculum and materials. We are doing it in Livebook. This time we cover the very basics.
This is the first episode and we&amp;rsquo;ll definitely polish our process as we go. They will be much shorter and probably simpler than the live stream videos. The goal is to work through Elixir School and get my friend properly started with Elixir.</description>
  <pubDate>Mon, 03 May 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/video-companion-teaching-elixir-basics.html</link>
  <guid>https://underjord.io/video-companion-teaching-elixir-basics.html---https://underjord.io/video-companion-teaching-elixir-basics.html</guid>
</item>
<item>
  <title>Video - Stream Overlay with LiveView</title>
  <description>From Underjord.io: A livestream where I created some parts of an overlay for my stream. In this case it shows some indication of what we do during a coding stream by showing lines-of-code stats as we go. This is the view-at-your-leisure archival footage of that. This time in 1080.
Also available on the YouTube.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.</description>
  <pubDate>Fri, 30 Apr 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/stream-overlay-liveview-stream-vod.html</link>
  <guid>https://underjord.io/stream-overlay-liveview-stream-vod.html---https://underjord.io/stream-overlay-liveview-stream-vod.html</guid>
</item>
<item>
  <title>Membrane Media Processing &amp; LiveView</title>
  <description>From Underjord.io: Membrane Framework allows processing media streams in a very high-level compelling way with Elixir. I&amp;rsquo;ve been wanting to work with it for a while and finally did a small thing with it around my most recent livestream. When we did the live stream we didn&amp;rsquo;t get it all the way to where I wanted. This resolves that.
At the end of the post there is also a video showing you what the thing does.</description>
  <pubDate>Mon, 26 Apr 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/membrane-media-processing-and-liveview.html</link>
  <guid>https://underjord.io/membrane-media-processing-and-liveview.html---https://underjord.io/membrane-media-processing-and-liveview.html</guid>
</item>
<item>
  <title>Video - Membrane Framework, trying it out</title>
  <description>From Underjord.io: Another live stream. I&amp;rsquo;ve been very curious to try Membrane and I finally got to screw around with it some. Thanks to Marcel Fahle for being a patient and helpful guest.
The post about making a Twitch clone that was referenced is this one.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  Sorry, your browser doesn't support embedded videos.</description>
  <pubDate>Fri, 23 Apr 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/membrane-framework-stream-vod.html</link>
  <guid>https://underjord.io/membrane-framework-stream-vod.html---https://underjord.io/membrane-framework-stream-vod.html</guid>
</item>
<item>
  <title>Video - Livebook, trying it out</title>
  <description>From Underjord.io: Last friday I did my second live stream. A lot of nice people stopped by and I spent the time showing and getting more familiar with the newly released [Livebook](https://dashbit.co/blog/announcing-livebook). Wasn't planning on providing a VOD (Video-On-Demand) of it but a bunch of people asked so I did. But this means it is only available in 720p. I'll be experimenting with this going forward. Video below.
 function switch_video(element) { var src = element.</description>
  <pubDate>Mon, 19 Apr 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/trying-out-livebook-stream-vod.html</link>
  <guid>https://underjord.io/trying-out-livebook-stream-vod.html---https://underjord.io/trying-out-livebook-stream-vod.html</guid>
</item>
<item>
  <title>Temple, AST, and Protocols</title>
  <description>From Mitchell Hanberg's Blog: As [Temple](https://github.com/mhanberg/temple) has aged, my ambition for this little library has grown.

Temple started with the ability to produce HTML at runtime, but now includes:

- EEx output target
- LiveView support (it's just EEx after all!)
- Basic component functionality (essentially just partials)

If my goals for this project are going to evolve, so does the code base. So far I've been able to accomplish this with a rather naive and imperative compilation process.

I figured that writing an actual abstract syntax tree (AST) as an intermediate format (IF) would be the next step, and along that journey I also found a nice use case for a [protocol](https://elixir-lang.org/getting-started/protocols.html).

Before we look at the new AST, let's go over how things used to work.

The previous method would recursively traverse Elixir AST, storing the collected tokens in a global buffer (backed by an [Agent](https://hexdocs.pm/elixir/Agent.html)).

```elixir
buffer = Agent.start_link(fn -&gt; [] end)

Utils.traverse(ast, buffer)

markup =
  buffer
  |&gt; Agent.get(fn buf -&gt; buf end)
  |&gt; Enum.reverse()
  |&gt; Enum.join("\n")
```

The `Utils.traverse/2` function would call a certain parser based on the Elixir AST with which it was working. The parser that would be invoked for lines that include anonymous functions looked something like this.

```elixir
{_do_and_else, args} =
  args
  |&gt; Utils.split_args()

{args, func_arg, args2} = Utils.split_on_fn(args, {[], nil, []})

{func, _, [{arrow, _, [[{arg, _, _}], block]}]} = func_arg

Agent.update(buffer, fn buf -&gt;
  markup = "&lt;%= " &lt;&gt;
           to_string(name) &lt;&gt;
           " " &lt;&gt;
           (Enum.map(args, &amp;Macro.to_string(&amp;1)) |&gt; Enum.join(", ")) &lt;&gt;
           ", " &lt;&gt;
           to_string(func) &lt;&gt;
           " " &lt;&gt;
           to_string(arg) &lt;&gt;
           " " &lt;&gt;
           to_string(arrow) &lt;&gt;
           " %&gt;"

  [markup | buf]
end)

Agent.update(fn buf -&gt; ["\n" | buf] end)

Utils.traverse(buffer, block)

if Enum.any?(args2) do
  post_fn_args =
    args2
    |&gt; Enum.map(fn arg -&gt; Macro.to_string(arg) end)
    |&gt; Enum.join(", ")

  Agent.update(fn buf -&gt;
    ["&lt;% end, " &lt;&gt; post_fn_args &lt;&gt; " %&gt;" | buf]
  end)

  Agent.update(fn buf -&gt; ["\n" | buf] end)
else
  Agent.update(fn buf -&gt; ["&lt;% end %&gt;" | buf] end)
  Agent.update(fn buf -&gt; ["\n" | buf] end)
end
```

This code illustrates that I am compiling the Elixir AST into markup all in one pass and utilizing some global state to store the compiled markup.

Named Slots, the feature that I want to build before cutting the v0.6.0 release, would be extremely complex or impossible to write with the architecture I described above.

Let's discuss the AST and the benefits.

## Temple AST

The AST follows a basic tree structure. Below I've demonstrated how some code you've probably written before would be represented by the AST.

```elixir
form_for @conn, Routes.widget_path(@conn, :create), fn f-&gt;
  label f, :name do
    span class: "text-bold" do
      "Name:"
    end

    text_input f, :name, placeholder: "Name..."
  end
end

# parses into

%AnonymousFunctions{
  elixir_ast: # the quoted expression from above,
  children: [
    %DoExpressions{
      elixir_ast: {:label, [], [{:f, [], Elixir}, :name]},
      children: [
        %NonvoidElementsAliases{
          name: "span",
          attrs: [class: "text-bold"],
          children: [
            %Text{text: "Name:"}
          ]
        },
        %Default{
          elixir_ast:
            {:text_input, [], [{:f, [], Elixir}, :name, [placeholder: "Name..."]]}
        }
      ]
    }
  ]
}
```

The biggest benefit to the AST is its role as an _intermediate format_. Since we've explored the entire AST, we can now run it through another step before generating the final output. The goal is to target EEx, but now that we have the IF, we could write a generator that targets ANSI sequences for a CLI or maybe even [Scenic](https://github.com/boydm/scenic)!

This brings us to our next topic, protocols!

## Protocols

The EEX generator step utilizes a [protocol](https://elixir-lang.org/getting-started/protocols.html) to be able to compile Temple AST into an `iolist` that represents EEx.

Each AST module implements this protocol and this allows any protocol implementation to generate any child nodes it contains without concerning itself with the shape of the children.

The implementation for the `Text` node type is the easiest to understand.

```elixir
defmodule Temple.Parser.Text do
  # ...

  defimpl Temple.Generator do
    def to_eex(%{text: text}) do
      [text, "\n"]
    end
  end
end
```

The benefit of using a protocol becomes clear when we look at the `NonvoidElementsAliases` implementation. The highlighted line belows shows how the protocol makes recursively compiling the AST super easy.  

```elixir
defmodule Temple.Parser.NonvoidElementsAliases do
  # ...

  defimpl Temple.Generator do
    def to_eex(%{name: name, attrs: attrs, children: children}) do
      [
        "&lt;",
        name,
        Temple.Parser.Utils.compile_attrs(attrs),
        "&gt;\n",
        for(child &lt;- children, do: Temple.Generator.to_eex(child)),
        "\n&lt;/",
        name,
        "&gt;"
      ]
    end
  end
end
```

Since the implementation takes advantage of `iolist`s, we can easily compute the final markup without maintaining any state or dealing with cumbersome return values. Once `to_eex` returns, we just run that through `:erlang.iolist_to_binary/1` and we're good to go!

## What's Next

With a proper AST in place, I can now move forward with the Named Slots API, which is the missing piece of the puzzle to make the Component API _really_ slick.

Eventually, you should be able to write something like this. (The exact syntax is subject to change)

```elixir
c Card, data: @person do
  slot :header, %{data: person} do
    "Full name: #{person.first_name} #{person.last_name}" 
  end

  # some card body

  slot :footer, %{data: person} do
    "Find me on Twitter: "

    a href: "https:twitter.com/#{person.socials.twitter}" do
      "@" &lt;&gt; person.socials.twitter
    end
  end
end

c Card, data: @company do
  slot :header, %{data: company} do
    "Legal name: #{company.name}"
  end

  # some card body

  slot :footer, %{data: company} do
    "Contact support at:"

    a href: "tel:" &lt;&gt; company.phone_number do
      person.phone_number
    end
  end
end
```

See you next time!</description>
  <pubDate>Mon, 12 Apr 2021 10:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/temple-ast-and-protocols/</link>
  <guid>https://www.mitchellhanberg.com/temple-ast-and-protocols/---https://www.mitchellhanberg.com/temple-ast-and-protocols/</guid>
</item>
<item>
  <title>Lumen - Statically compiled Erlang for x86</title>
  <description>From Underjord.io: The Lumen Project is an ambitious compiler development effort to create a complimentary set of compilers and tools that allow developers to get the power of the Erlang VM, The BEAM, in places it does not traditionally fit. Such as the browser. Currently the project is at an early released stage as covered in this talk. It does not yet implement all of Erlang OTP and as such won&amp;rsquo;t handle most Erlang/Elixir you could throw at it.</description>
  <pubDate>Thu, 08 Apr 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/lumen-statically-compiled-erlang-for-x86.html</link>
  <guid>https://underjord.io/lumen-statically-compiled-erlang-for-x86.html---https://underjord.io/lumen-statically-compiled-erlang-for-x86.html</guid>
</item>
<item>
  <title>The Perils of Large Files</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;
I recently wrote a blog post about working with large files in Elixir and OTP at &lt;a href="https://pspdfkit.com/blog/2021/the-perils-of-large-files-in-elixir/"&gt;PSPDFKit&amp;#39;s blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
The post originated from a talk I gave last year for &lt;a href="https://www.elixirconf.eu"&gt;ElixirConf.eu&lt;/a&gt;, but it contains more precise analysis.&lt;/p&gt;</description>
  <pubDate>Mon, 05 Apr 2021 11:00:00 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/the-perils-of-large-files/</link>
  <guid>https://claudio-ortolina.org/posts/the-perils-of-large-files/---https://claudio-ortolina.org/posts/the-perils-of-large-files/</guid>
</item>
<item>
  <title>A Telegram Bot in Elixir feat. LiveView</title>
  <description>From Underjord.io: I asked my network on Twitter about noting ideas quickly and got a lot of good responses. One mentioned saving them in Telegram. I don&amp;rsquo;t think I want to do specifically that but I do want a minimum friction way of noting ideas for later review and refinement. And sending them to a Telegram chat would be quite nice. So I started on the path of something like a note-taking system using Telegram for ingesting quick notes.</description>
  <pubDate>Wed, 24 Feb 2021 07:00:00 +0000</pubDate>
  <link>https://underjord.io/a-telegram-bot-in-elixir.html</link>
  <guid>https://underjord.io/a-telegram-bot-in-elixir.html---https://underjord.io/a-telegram-bot-in-elixir.html</guid>
</item>
<item>
  <title>Small update, full content RSS</title>
  <description>From Underjord.io: This is a small update to let you know that the RSS feed contains full content now.
It also lets me verify that the feed has updated correctly and works fine. If it comes out weird in your feed reader, please let me know.
If you aren&amp;rsquo;t familiar with RSS. Consider it the open standard that drives podcasting and an exceptionally useful way of reading blogs, news, webcomics in a more intentional way via a dedicated, nice, newsreader application or web UI.</description>
  <pubDate>Mon, 22 Feb 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/small-update-full-content-rss.html</link>
  <guid>https://underjord.io/small-update-full-content-rss.html---https://underjord.io/small-update-full-content-rss.html</guid>
</item>
<item>
  <title>Post-mortem: 10 years in the vertical - Part 3</title>
  <description>From Underjord.io: Content warning: Contains poor technical decisions, inexperience and stories of a developer just starting out and up to roughly present day. Be kind to who I was, he gets enough shit from me.
This continues our dive from Part 2. Part 1 can be found here.
The better system 2.0 The big embarrassment of the system we&amp;rsquo;d previously built was performance, it wasn&amp;rsquo;t scaling with a growing customer base and was slowing down.</description>
  <pubDate>Fri, 12 Feb 2021 07:00:00 +0000</pubDate>
  <link>https://underjord.io/10-years-in-the-vertical-part-3.html</link>
  <guid>https://underjord.io/10-years-in-the-vertical-part-3.html---https://underjord.io/10-years-in-the-vertical-part-3.html</guid>
</item>
<item>
  <title>Post-mortem: 10 years in the vertical - Part 2</title>
  <description>From Underjord.io: This continues our dive from part 1.
Content warning: Contains poor technical decisions, inexperience and stories of a developer just starting out and up to roughly present day. Be kind to who I was, he gets enough shit from me.
The better system The team that had formed inside our little division of this larger organisation ended up breaking out to run an agency startup. Apps and web development would be our game.</description>
  <pubDate>Fri, 29 Jan 2021 07:00:00 +0000</pubDate>
  <link>https://underjord.io/10-years-in-the-vertical-part-2.html</link>
  <guid>https://underjord.io/10-years-in-the-vertical-part-2.html---https://underjord.io/10-years-in-the-vertical-part-2.html</guid>
</item>
<item>
  <title>Crafting Beautiful Emails in Elixir Using MJML</title>
  <description>From My.Thoughts v1: Intro In this blog post, we&amp;rsquo;ll be talking about what exactly MJML is, why it is an awesome tool for creating slick looking emails, how to build MJML templates during the Elixir compilation phase, and how we go about sending these beautiful emails using Swoosh. In order to make this a bit more real world, we&amp;rsquo;ll also be leveraging Phx.Gen.Auth and will craft a great looking welcome email to ensure our new users feel extra welcome to our new SaaS platform ;).</description>
  <pubDate>Wed, 20 Jan 2021 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/mjml-template-compliation/</link>
  <guid>https://akoutmos.com/post/mjml-template-compliation/---https://akoutmos.com/post/mjml-template-compliation/</guid>
</item>
<item>
  <title>Getting started with PETAL</title>
  <description>From Underjord.io: I recently wrote about the PETAL stack on Changelog.com and as I haven&amp;rsquo;t really had a chance to really get my hands dirty with it I decided that my next lab project should use it. So I set it up as a public repo and added the missing components manually by following some guides. You can see the commits or follow along.
So I used Phoenix 1.5.6 that I had already installed.</description>
  <pubDate>Mon, 18 Jan 2021 13:00:00 +0000</pubDate>
  <link>https://underjord.io/getting-started-with-petal.html</link>
  <guid>https://underjord.io/getting-started-with-petal.html---https://underjord.io/getting-started-with-petal.html</guid>
</item>
<item>
  <title>Post-mortem: 10 years in the vertical - Part 1</title>
  <description>From Underjord.io: Before I did the independent thing I was a developer at a few different companies. I worked on multiple things but one of the main things I worked on was a series of products in the preschool education segment here in Sweden, that&amp;rsquo;s the titular vertical. The products have since been shut down. This series of posts will cover my trajectory as a professional web developer as well as the evolution of these systems.</description>
  <pubDate>Fri, 15 Jan 2021 06:00:00 +0000</pubDate>
  <link>https://underjord.io/10-years-in-the-vertical-part-1.html</link>
  <guid>https://underjord.io/10-years-in-the-vertical-part-1.html---https://underjord.io/10-years-in-the-vertical-part-1.html</guid>
</item>
<item>
  <title>Advent Of Code makes me a better programmer</title>
  <description>From Bits of Code - Pieces of Writing: Advent Of Code is a yearly event offered by Eric Wastl: every day before Christmas, you can unlock a daily code challenge and crack it with the programming language of your choice. I n this post I will share with you my Advent Of code takeaways.</description>
  <pubDate>Mon, 11 Jan 2021 20:43:51 GMT</pubDate>
  <link>https://www.christianblavier.com/advent-of-code-makes-me-a-better-programmer/</link>
  <guid>https://www.christianblavier.com/advent-of-code-makes-me-a-better-programmer/---629603e163e1200001252ac4</guid>
</item>
<item>
  <title>Podcasts that supported my independence</title>
  <description>From Underjord.io: I want to shine a light on some podcasts that I really appreciated as I made my bid for independence and went out to contract, consult and strap boots. Because in some aspects running your own business can be lonely. It suits me well but I also immensely appreciate having some voices that share my concerns, contribute to my thinking and sometimes just keep me company.
Underjord is my business and most of the work is me doing things.</description>
  <pubDate>Mon, 11 Jan 2021 13:30:00 +0000</pubDate>
  <link>https://underjord.io/podcasts-that-supported-my-independence.html</link>
  <guid>https://underjord.io/podcasts-that-supported-my-independence.html---https://underjord.io/podcasts-that-supported-my-independence.html</guid>
</item>
<item>
  <title>Elixir businesses doing well</title>
  <description>From Underjord.io: I generally don&amp;rsquo;t track startup news and financing rounds closely but it filters in because I enjoy tech. And these two Elixir-based companies made a blip on my radar recently with their successful rounds. So why not. Let&amp;rsquo;s be a little bit business.
So I don&amp;rsquo;t particularly love the VC-backed model of building businesses. It sort of makes sense for some types of endeavors but generally I&amp;rsquo;m an organic growth and small teams kind of person.</description>
  <pubDate>Wed, 09 Dec 2020 13:00:00 +0000</pubDate>
  <link>https://underjord.io/elixir-businesses-doing-well.html</link>
  <guid>https://underjord.io/elixir-businesses-doing-well.html---https://underjord.io/elixir-businesses-doing-well.html</guid>
</item>
<item>
  <title>Eloquent Control Flow + Efficient Time Complexity in Elixir</title>
  <description>From The Great Code Adventure: In this post, I break down my Advent of Code Day 1 solution and dive into how you can use recursion, pattern matching and custom guard clauses to implement even complex logic and control flow in an easy-to-reason about way that also avoids common time complexity pitfalls.</description>
  <pubDate>Mon, 07 Dec 2020 22:09:34 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/eloquent-control-flow-and-efficient-time-complexity-in-elixir/</link>
  <guid>https://www.thegreatcodeadventure.com/eloquent-control-flow-and-efficient-time-complexity-in-elixir/---5fce96c70a88430039fcf683</guid>
</item>
<item>
  <title>Wisps, a touch of whimsy</title>
  <description>From Underjord.io: This has been the most fun I&amp;rsquo;ve had with JavaScript in some time. A spiritual successor to the stupid solution which allowed both me and my visitors to see the number of concurrent readers. It is also a piece of whimsy I&amp;rsquo;ve wanted to do for a while to add more magic and life to the site. The animations default to off to preserve CPU but do turn on the magic switch in the top right if you can and want to see the visuals.</description>
  <pubDate>Thu, 03 Dec 2020 07:00:00 +0000</pubDate>
  <link>https://underjord.io/wisps-a-touch-of-whimsy.html</link>
  <guid>https://underjord.io/wisps-a-touch-of-whimsy.html---https://underjord.io/wisps-a-touch-of-whimsy.html</guid>
</item>
<item>
  <title>The Race for Space</title>
  <description>From Posts on Claudio Ortolina: &lt;img src="https://claudio-ortolina.org/img/the-race-for-space/cover.jpg"/&gt;
&lt;p&gt;
&lt;a href="http://publicservicebroadcasting.net/"&gt;Public Service Broadcasting&lt;/a&gt; (PBS for short) are a trio based in London, whose music usually involves historical audio documents with a mix of jazz, rock and electronic.&lt;/p&gt;
&lt;p&gt;
Each one of their album revolves around a specific theme, usually inspired by modern/contemporary history, and includes a vast amount of archival audio footage, recording, and samples, with very little vocals on top.&lt;/p&gt;
&lt;p&gt;
In 2015 they released &lt;a href="https://en.wikipedia.org/wiki/The_Race_for_Space_(album)"&gt;The Race for Space&lt;/a&gt;, a concept album that explores the first 15 years of the &lt;a href="https://en.wikipedia.org/wiki/Space_Race"&gt;Space Race&lt;/a&gt; missions, from &lt;a href="https://en.wikipedia.org/wiki/Sputnik_1"&gt;Sputnik 1&lt;/a&gt;&amp;#39;s flight in 1957 to the &lt;a href="https://en.wikipedia.org/wiki/Apollo_17"&gt;last Apollo mission&lt;/a&gt; in 1972.&lt;/p&gt;
&lt;p&gt;
The general tone celebrates achievements of both US and Soviet Union, emphasising each step as a human endeavour that transcends borders and politics.&lt;/p&gt;
&lt;p&gt;
Between music, vocals and engineering, the album involved another 35 people, which is something you can only perceive after listening to it a few times: some of the instruments are particularly subtle and deserve special attention.&lt;/p&gt;
&lt;p&gt;
Each song has a different personality and it&amp;#39;s difficult to attribute a general genre to the entire album, but I can feel a general underlying cohesion which makes it that I rarely listen to one song and prefer to just play the entire record.&lt;/p&gt;
&lt;div id="outline-container-headline-1" class="outline-2"&gt;
&lt;h2 id="headline-1"&gt;
1. The Race for Space
&lt;/h2&gt;
&lt;div id="outline-text-headline-1" class="outline-text-2"&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/VlnwuV6RuMo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
The album opens with rearranged fragments of &lt;a href="https://en.wikipedia.org/wiki/We_choose_to_go_to_the_Moon"&gt;US President John F. Kennedy&amp;#39;s speech&lt;/a&gt; given at Rice University, Houston, Texas, on September 12, 1962. The speech sets the tone of the entire album, celebrating mankind&amp;#39;s efforts to reach beyond the stars:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Many years ago, Great British explorer George Mallory &lt;br&gt;
Who was to die on Mount Everest &lt;br&gt;
Was asked &amp;#39;why did he want to climb it?&amp;#39; &lt;br&gt;
He said &amp;#39;because it is there&amp;#39; &lt;br&gt;
Well space is there and we&amp;#39;re going to climb it &lt;br&gt;
And the moon and the planets are there &lt;br&gt;
And new hopes for knowledge and peace are there &lt;br&gt;
And therefore as we set sail we ask God&amp;#39;s blessing &lt;br&gt;
On the most hazardous, and dangerous, and greatest adventure &lt;br&gt;
On which man has ever embarked – John F. Kennedy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
It&amp;#39;s difficult to listen to this speech without anticipation, especially knowing &lt;a href="https://en.wikipedia.org/wiki/Apollo_11"&gt;what would happen 7 years later&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-2" class="outline-2"&gt;
&lt;h2 id="headline-2"&gt;
2. Sputnik
&lt;/h2&gt;
&lt;div id="outline-text-headline-2" class="outline-text-2"&gt;
&lt;p&gt;
The second track, &lt;em&gt;Sputnik&lt;/em&gt;, takes us to the Soviet Union in 1957, to tell the story of the first object ever sent into space, the low Earth orbit satellite &lt;a href="https://en.wikipedia.org/wiki/Sputnik_1"&gt;Sputnik 1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/It42TsD7_sI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
In &lt;em&gt;Sputnik&lt;/em&gt;, the original sounds of the unmanned spacecraft gradually become part of the main beat and gradually build excitement for the beginning of &amp;#34;man&amp;#39;s cosmic existence&amp;#34;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The man made celestial body, for the first time in history &lt;br&gt;
Overcame terrestrial gravity and flew into space &lt;br&gt;
All men of all nations recognise this as a great achievement &lt;br&gt;
In an age where the race to conquer space has become an all-absorbing factor&lt;/p&gt;
&lt;p&gt;
The era of man&amp;#39;s cosmic existence&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
Once again, the accent is on how people can be united by such an important event:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All over the world, people are tuning in to the &amp;#39;bleep bleep bleep&amp;#39; of the satellite&lt;/p&gt;
&lt;p&gt;
A dream cherished by men for many centuries comes true on October the 4th, 1957&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-3" class="outline-2"&gt;
&lt;h2 id="headline-3"&gt;
3. Gagarin
&lt;/h2&gt;
&lt;div id="outline-text-headline-3" class="outline-text-2"&gt;
&lt;p&gt;
If &lt;em&gt;Sputnik&lt;/em&gt; makes you tap your feet, then &lt;em&gt;Gagarin&lt;/em&gt; will make you dance: to tell the story of the first ever cosmonaut, &lt;a href="https://en.wikipedia.org/wiki/Yuri_Gagarin"&gt;Yuri Gagarin&lt;/a&gt;, PBS wrote an explosive song, a mix of funk, brass, marching band, you-name-it tune that captures the euphoria of such an important moment.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/wY-kAnvOY80?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
The jump into the unknown is not something to be scared of:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The World&amp;#39;s first cosmonaut &lt;br&gt;
The first to open the door into the unknown &lt;br&gt;
The first to step over the threshold of our homeland&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
Gagarin himself appreciated his new vantage point:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Astronaut to Earth: I can see forests, rivers, [?] all around &lt;br&gt;
Everything&amp;#39;s so beautiful, it&amp;#39;s wonderful, it wonderful…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-4" class="outline-2"&gt;
&lt;h2 id="headline-4"&gt;
4. Fire in the Cockpit
&lt;/h2&gt;
&lt;div id="outline-text-headline-4" class="outline-text-2"&gt;
&lt;p&gt;
After &lt;em&gt;Gagarin&lt;/em&gt;, &lt;em&gt;Fire in the Cockpit&lt;/em&gt; is a cold shower and a stark reminder of the risks and cost of exploration, telling the tragedy of &lt;a href="https://en.wikipedia.org/wiki/Apollo_1"&gt;Apollo 1&lt;/a&gt;, where three astronauts lost their lives due to a cabin fire during a test.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/QLA9-1U7Vrw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
The entire song revolves around the sound of static and deep keyboard tones, with a cello entering midway as we listen to the words of NASA&amp;#39;s description of the events. It&amp;#39;s a sad song, but exhibits the composure and respect owed to people who lost their lives while trying to advance the frontiers of human knowledge.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-5" class="outline-2"&gt;
&lt;h2 id="headline-5"&gt;
5. E.V.A.
&lt;/h2&gt;
&lt;div id="outline-text-headline-5" class="outline-text-2"&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/PFSq4Q8WDs0?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
In &lt;em&gt;E.V.A.&lt;/em&gt;&lt;sup class="footnote-reference"&gt;&lt;a id="footnote-reference-1" href="#footnote-1"&gt;1&lt;/a&gt;&lt;/sup&gt; we hear the story of Alexei Leonov, Alexei Leonov completed the first spacewalk in 1965, spending 10 minutes outside in open space.&lt;/p&gt;
&lt;p&gt;
It&amp;#39;s interesting how the music almost stops when the astronaut leaves the spacecraft, mimicking the silence of the vacuum of space. The few piano notes we can hear really complement the marvel of Leonov&amp;#39;s words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;#39;m on the edge of the opening &lt;br&gt;
Of the airlock chamber &lt;br&gt;
I feel excellent &lt;br&gt;
I see clouds and the sea &lt;br&gt;
I am beginning to move away&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-6" class="outline-2"&gt;
&lt;h2 id="headline-6"&gt;
6. The Other Side
&lt;/h2&gt;
&lt;div id="outline-text-headline-6" class="outline-text-2"&gt;
&lt;p&gt;
&lt;em&gt;The Other Side&lt;/em&gt; takes us one step closer to the moon landing, focusing on the &lt;a href="https://en.wikipedia.org/wiki/Apollo_8"&gt;Apollo 8&lt;/a&gt; mission, where for the first time a manned spacecraft completed an orbit of the moon, but from the unusual point of view of ground control.&lt;/p&gt;
&lt;p&gt;
We hear voices and recordings from the control room, where ground control monitors the spacecraft as it&amp;#39;s about to reach the blind side of the moon. The excitement and anxiety are palpable: to complete a lunar orbit, Apollo will temporarily lose signal with earth.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/P8LlUrT7MFo?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
Once again, the music tells the story without words, getting quiet during loss of signal and exploding into a liberating instrumental when Apollo finally replies back to Houston. The event is incredibly significant:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The unmanned lunar spacecraft traversed the moon perhaps over 10, 000 times &lt;br&gt;
But this is the first that a man aboard reported to his compatriots here on Earth&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-7" class="outline-2"&gt;
&lt;h2 id="headline-7"&gt;
7. Valentina
&lt;/h2&gt;
&lt;div id="outline-text-headline-7" class="outline-text-2"&gt;
&lt;p&gt;
&lt;em&gt;Valentina&lt;/em&gt; is a celebration of &lt;a href="https://en.wikipedia.org/wiki/Valentina_Tereshkova"&gt;Valentina Tereshkova&lt;/a&gt;, the first woman to ever go to space n 1963 (and to this date, the only one having ever been in a solo mission).&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/Bnmq4WR83Mw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
The song, which features choruses from &lt;a href="http://www.smokefairies.com/"&gt;The Smoke Fairies&lt;/a&gt;, is a graceful instrumental without any other vocals. J. Willgoose, Esq. writes on the matter:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One of the biggest problems with the material we use, from the period we address, is that it almost totally devoid of any female voice. &lt;br&gt;
It is often said that history is written by the winners, but it would be equally if not more apt to say that it has overwhelmingly been written by men. Of the footage I obtained of the first woman in space, all of it featured her voice being translated by male voices. &lt;br&gt;
Rather than yet more men - us, in this case - attempting to speak on her behalf, it seemed more appropriate to ask a guest singer to provide a female voice, so we tried a different approach with &amp;#39;Valentina&amp;#39; and I&amp;#39;m very glad we did.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-8" class="outline-2"&gt;
&lt;h2 id="headline-8"&gt;
8. Go!
&lt;/h2&gt;
&lt;div id="outline-text-headline-8" class="outline-text-2"&gt;
&lt;p&gt;
The story of the &lt;a href="https://en.wikipedia.org/wiki/Apollo_11"&gt;Apollo 11&lt;/a&gt; and the first crew to land on the moon in 1969 represents one of the most important moments of human history. Once again, PBS decides to focus on the point of view of the people on the ground, to celebrate the often unseen work of preparation, monitoring, incredible engineering that made the whole thing possible.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/BHIo6qwJarI?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Go!&lt;/em&gt; is giant checklist, where we hear the flight director Gene Kranz go through all the checks needed to make sure that the descent on the moon will be successful. It&amp;#39;s so interesting that the landing itself is just a couple of verses in the middle:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Houston, uh &lt;br&gt;
Tranquility base here, The Eagle has landed&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
The repetition in the dialogues provides the rhythm of the song, which matches the excitement of the mission with upbeat percussions, synth and keyboard.&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Go!&lt;/em&gt; is a reminder that we can achieve the impossible if we work together.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-9" class="outline-2"&gt;
&lt;h2 id="headline-9"&gt;
9. Tomorrow
&lt;/h2&gt;
&lt;div id="outline-text-headline-9" class="outline-text-2"&gt;
&lt;p&gt;
The &lt;a href="https://en.wikipedia.org/wiki/Apollo_17"&gt;Apollo 17&lt;/a&gt; mission, the last in the Apollo program, represents the end of an era and the last time we landed on the moon.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/5Id8P6yvcWs?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Tomorrow&lt;/em&gt; reflects on its significance: as a species, we managed to leave our own planet, albeit temporarily, and look beyond to a completely unexplored universe.&lt;/p&gt;
&lt;p&gt;
While it&amp;#39;s not possible to separate the space race from the politics that fueled it in the first place, it&amp;#39;s also a testament to the effort of thousands of people over decades, to literally take us where no one has ever been before.&lt;/p&gt;
&lt;p&gt;
As an outro, &lt;em&gt;Tomorrow&lt;/em&gt; tempers the excitement of the previous songs and focuses more on choruses and keyboard, painting a picture of anticipation of what&amp;#39;s gonna come next.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="footnotes"&gt;
&lt;hr class="footnotes-separatator"&gt;
&lt;div class="footnote-definitions"&gt;
&lt;div class="footnote-definition"&gt;
&lt;sup id="footnote-1"&gt;&lt;a href="#footnote-reference-1"&gt;1&lt;/a&gt;&lt;/sup&gt;
&lt;div class="footnote-body"&gt;
&lt;p&gt;the name is a shorthand for &lt;em&gt;extravehicular activity&lt;/em&gt;, which indicates a spacesuit designed for usage outside of a vehicle.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>Sat, 28 Nov 2020 17:55:37 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/the-race-for-space/</link>
  <guid>https://claudio-ortolina.org/posts/the-race-for-space/---https://claudio-ortolina.org/posts/the-race-for-space/</guid>
</item>
<item>
  <title>Asking a tech recruiter</title>
  <description>From Underjord.io: Since I left my comfy job as the tech lead for a SaaS product and went into running my own business I took a closer look at my relationship with recruiters. While working I mostly found the attention of recruiters slightly reassuring but often annoying. I think that annoyance is fairly common, usually built up from countless LinkedIn drive-by attempts from unreading keyword-hunting recruiters. I thought that now, out on my own, maybe this legion of recruiters can be my sales department.</description>
  <pubDate>Wed, 25 Nov 2020 07:00:00 +0000</pubDate>
  <link>https://underjord.io/asking-a-tech-recruiter.html</link>
  <guid>https://underjord.io/asking-a-tech-recruiter.html---https://underjord.io/asking-a-tech-recruiter.html</guid>
</item>
<item>
  <title>On Elixir Metaprogramming</title>
  <description>From Bits of Code - Pieces of Writing: Metaprogramming is a scary word: it sounds like voodoo for programmers, and in some manners it is. It is used by most popular Elixir libraries and as I leveled up as an Elixir programmer, I needed to understand how it worked under the hood.</description>
  <pubDate>Mon, 23 Nov 2020 17:00:00 GMT</pubDate>
  <link>https://www.christianblavier.com/on-elixir-metaprogramming/</link>
  <guid>https://www.christianblavier.com/on-elixir-metaprogramming/---629603e163e1200001252ac3</guid>
</item>
<item>
  <title>Building a Custom Page for Phoenix Live Dashboard</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;
One of the most interesting features provided by Phoenix Live Dashboard is the ability to &lt;a href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.PageBuilder.html#content"&gt;define your own pages&lt;/a&gt;, so that you can quickly and reliably extend a Live Dashboard instance with sections that are tailored to your application domain.&lt;/p&gt;
&lt;p&gt;
While working on &lt;a href="https://github.com/fully-forged/tune"&gt;Tune&lt;/a&gt;, I found a use case suitable for a custom live dashboard page: a debugging view where I can check open sessions and inspect the underlying processes.&lt;/p&gt;
&lt;div id="outline-container-headline-1" class="outline-2"&gt;
&lt;h2 id="headline-1"&gt;
On the use case
&lt;/h2&gt;
&lt;div id="outline-text-headline-1" class="outline-text-2"&gt;
&lt;p&gt;
I would encourage you to read &lt;a href="https://github.com/fully-forged/tune"&gt;Tune&amp;#39;s README&lt;/a&gt; to understand the use case in more detail, but I&amp;#39;ll quote the relevant architectural section:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tune assumes multiple browser sessions for the same user, which is why it defines a &lt;a href="https://tune-docs.fullyforged.com/Tune.Spotify.Session.html#content"&gt;&lt;code&gt;Tune.Spotify.Session&lt;/code&gt;&lt;/a&gt; behaviour with &lt;a href="https://tune-docs.fullyforged.com/Tune.Spotify.Session.HTTP.html#content"&gt;&lt;code&gt;Tune.Spotify.Session.HTTP&lt;/code&gt;&lt;/a&gt; as its main runtime implementation.&lt;/p&gt;
&lt;p&gt;
Each worker is responsible to proxy interaction with the Spotify API, periodically poll for data changes, and broadcast corresponding events.&lt;/p&gt;
&lt;p&gt;
When a user opens a browser session, &lt;a href="https://tune-docs.fullyforged.com/TuneWeb.ExplorerLive.html#content"&gt;&lt;code&gt;TuneWeb.ExplorerLive&lt;/code&gt;&lt;/a&gt; either starts or simply reuses a worker named with the same session ID.&lt;/p&gt;
&lt;p&gt;
Each worker monitors its subscribers, so that it can shutdown when a user closes their last browser window.&lt;/p&gt;
&lt;p&gt;
This architecture ensures that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The amount of automatic API calls against the Spotify API for a given user is constant and independent from the number of user sessions for the same user.&lt;/li&gt;
&lt;li&gt;Credential renewal happens in the background&lt;/li&gt;
&lt;li&gt;The explorer implementation remains entirely focused on UI interaction&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;
In other words:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For each Spotify account connected, there can only be one session (a &lt;code&gt;Tune.Spotify.Session.HTTP&lt;/code&gt; process named with the session ID).&lt;/li&gt;
&lt;li&gt;For each session, there can be many open clients (i.e. browser windows or &lt;code&gt;TuneWeb.ExplorerLive&lt;/code&gt; process).&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-2" class="outline-2"&gt;
&lt;h2 id="headline-2"&gt;
Requirements
&lt;/h2&gt;
&lt;div id="outline-text-headline-2" class="outline-text-2"&gt;
&lt;p&gt;
Our dashboard page will include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A table with session IDs, PIDs and count of open clients&lt;/li&gt;
&lt;li&gt;Ability to sort by session ID or clients count&lt;/li&gt;
&lt;li&gt;Search by session ID&lt;/li&gt;
&lt;li&gt;Support multiple nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-3" class="outline-2"&gt;
&lt;h2 id="headline-3"&gt;
Gathering the necessary data
&lt;/h2&gt;
&lt;div id="outline-text-headline-3" class="outline-text-2"&gt;
&lt;p&gt;
To populate the dashboard table, we first need to find a way to get a list of all active sessions, along with their clients count.&lt;/p&gt;
&lt;p&gt;
The simplest way is to leverage the fact that each &lt;code&gt;Tune.Spotify.Session.HTTP&lt;/code&gt; process is started with a name managed via a &lt;a href="https://hexdocs.pm/elixir/Registry.html"&gt;Registry&lt;/a&gt;, with the session ID as a key. Registration is in place to guarantee that there can only be one session process with the same ID on each node.&lt;/p&gt;
&lt;p&gt;
We can use &lt;a href="https://hexdocs.pm/elixir/Registry.html#select/2"&gt;&lt;code&gt;Registry.select/2&lt;/code&gt;&lt;/a&gt; to query the registry and receive back all session IDs and PIDs:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Registry&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Tune.Spotify.SessionRegistry&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [{{&lt;span style="color:#e6db74"&gt;:&amp;#34;$1&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:&amp;#34;$2&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:_&lt;/span&gt;}, [], [{{&lt;span style="color:#e6db74"&gt;:&amp;#34;$1&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:&amp;#34;$2&amp;#34;&lt;/span&gt;}}]}]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Which returns:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[{&lt;span style="color:#e6db74"&gt;&amp;#34;claudio.ortolina&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;#PID&amp;lt;0.565.0&amp;gt;}]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In the example above, we use a match specification to capture the registry key (the session ID) and the registered PID.&lt;/p&gt;
&lt;p&gt;
It&amp;#39;s important to understand straight away the constraints associated with this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;Registry&lt;/code&gt; is normally split into a variable number of partitions, so this query has to visit all partitions to return its results. While this is not a problem at this stage (the application has very little load), it can become a bottleneck once the number of registered processes grows.&lt;/li&gt;
&lt;li&gt;As data is partitioned, it&amp;#39;s not possible to apply sort order or limit the results without concatenating them all first, which means that both operations will need to be done by the caller.&lt;/li&gt;
&lt;li&gt;Results only apply to the current node, which works well with Phoenix Live Dashboard&amp;#39;s general structure, which always operates on one node at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given the registry query above, we can implement a function that provides the data necessary to populate an unfiltered, unsorted version of the table:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defmodule&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Tune.Spotify.Supervisor&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; sessions &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Tune.Spotify.SessionRegistry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Registry&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;select([{{&lt;span style="color:#e6db74"&gt;:&amp;#34;$1&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:&amp;#34;$2&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:_&lt;/span&gt;}, [], [{{&lt;span style="color:#e6db74"&gt;:&amp;#34;$1&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:&amp;#34;$2&amp;#34;&lt;/span&gt;}}]}])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Enum&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;map(&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; {id, pid} &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subscribers_count &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Tune.Spotify.Session.HTTP&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;subscribers_count(id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; %{&lt;span style="color:#e6db74"&gt;id&lt;/span&gt;: id, &lt;span style="color:#e6db74"&gt;pid&lt;/span&gt;: pid, &lt;span style="color:#e6db74"&gt;clients_count&lt;/span&gt;: subscribers_count}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The resulting data structure is a map with the necessary data:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[%{&lt;span style="color:#e6db74"&gt;clients_count&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;id&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;claudio.ortolina&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;pid&lt;/span&gt;: &lt;span style="color:#75715e"&gt;#PID&amp;lt;0.565.0&amp;gt;}]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-4" class="outline-2"&gt;
&lt;h2 id="headline-4"&gt;
Dashboard page structure
&lt;/h2&gt;
&lt;div id="outline-text-headline-4" class="outline-text-2"&gt;
&lt;p&gt;
To build a dashboard page, we need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a module that implements the &lt;code&gt;use Phoenix.LiveDashboard.PageBuilder&lt;/code&gt; behaviour.&lt;/li&gt;
&lt;li&gt;Mount that module into the Live Dashboard configuration defined into our application router.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What follows is a minimal implementation that shows the data we need, with the following limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no searching, sorting or limiting capabilities&lt;/li&gt;
&lt;li&gt;works only on a single node&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defmodule&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TuneWeb.LiveDashboard.SpotifySessionsPage&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@moduledoc&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;use&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Phoenix.LiveDashboard.PageBuilder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@impl&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; menu_link(_, _) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;:ok&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Spotify Sessions&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@impl&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; render_page(_assigns) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; table(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;columns&lt;/span&gt;: columns(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;id&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:spotify_sessions&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;row_attrs&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;row_attrs&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;row_fetcher&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;fetch_sessions&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;rows_name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;sessions&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;title&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Spotify Sessions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; fetch_sessions(_params, _node) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# returns [%{clients_count: 1, id: &amp;#34;claudio.ortolina&amp;#34;, pid: #PID&amp;lt;0.565.0&amp;gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sessions &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Tune.Spotify.Supervisor&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sessions()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {sessions, length(sessions)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; columns &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; %{&lt;span style="color:#e6db74"&gt;field&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:id&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;header&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Session ID&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;sortable&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:asc&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; %{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;field&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:pid&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;header&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Worker PID&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;format&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;(&amp;amp;1 &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; encode_pid() &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;replace_prefix(&lt;span style="color:#e6db74"&gt;&amp;#34;PID&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; %{&lt;span style="color:#e6db74"&gt;field&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:clients_count&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;header&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Clients count&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;sortable&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:asc&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; row_attrs(session) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;phx-click&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;show_info&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;phx-value-info&amp;#34;&lt;/span&gt;, encode_pid(session[&lt;span style="color:#e6db74"&gt;:pid&lt;/span&gt;])},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;phx-page-loading&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The main ingredients of this implementation are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;use Phoenix.LiveDashboard.PageBuilder&lt;/code&gt; directive, which adopts the behaviour with the same name and imports some convenience functions useful for building pages (e.g. &lt;code&gt;encode_pid/1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;menu_link/2&lt;/code&gt; callback, which is used to define the name of the page and its label in the top navigation bar.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;render_page/2&lt;/code&gt; callback, which has to return a valid &lt;a href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.PageBuilder.html#t:component/0"&gt;&lt;code&gt;component&lt;/code&gt;&lt;/a&gt; - in this case via the &lt;a href="https://hexdocs.pm/phoenix_live_dashboard/Phoenix.LiveDashboard.PageBuilder.html#table/1"&gt;&lt;code&gt;table/1&lt;/code&gt;&lt;/a&gt; function.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The table definition has a few moving parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;code&gt;id&lt;/code&gt; (unique among other Live Dashboard pages).&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;title&lt;/code&gt;, shown in the page.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;rows_name&lt;/code&gt;, interpolated in the short text blurb that details the total amount of results.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;columns&lt;/code&gt; attribute, which is a list of maps detailing the properties of each column.
For each column, the &lt;code&gt;id&lt;/code&gt; property has to map to a key in the data we will use to populate the table.
The &lt;code&gt;sortable&lt;/code&gt; property defines which column can be used for sorting (by clicking on the header chevron). Note that unless you specify a &lt;code&gt;default_sort_by&lt;/code&gt; attribute for the entire table, you have to have at least one column with the &lt;code&gt;sortable&lt;/code&gt; property defined, otherwise you will get a compile error.
The &lt;code&gt;format&lt;/code&gt; function takes the raw value for a cell in the column and transforms it to a string. It&amp;#39;s useful to provide a string representation of the value that is suitable for an HTML table. In the code above, we copy the format function defined in &lt;a href="https://github.com/phoenixframework/phoenix_live_dashboard/blob/8d7148d9c333a27766ee8bc971d4dba93c0f9695/lib/phoenix/live_dashboard/pages/processes_page.ex#L34"&gt;the Processes Live Dashboard page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;row_attrs&lt;/code&gt; function, which takes the data for each row and has to return a list of tuples representing the Phoenix LiveView attributes to apply to the table row itself. Defining attribute is necessary to enable functionality activated by clicking on the row itself. The implementation in this example lets you inspect the session PID in a modal overlay.
Similar to the &lt;code&gt;format&lt;/code&gt; function, we leverage &lt;code&gt;encode_pid/1&lt;/code&gt; to format the PID as string compatible with the &lt;code&gt;show_info&lt;/code&gt; LiveView event.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;row_fetcher&lt;/code&gt; function, which takes the current &lt;code&gt;params&lt;/code&gt; (search query, limit, sort key, sort direction) and the current node, and returns the data used to populate the table.
The return value has to conform to a tuple shape where the first value is a list of sessions (in the shape of maps with the same keys used for column ids) and the second value is the total number of results (irrespectively of the limit).
As we implemented &lt;code&gt;Tune.Spotify.Supervisor.sessions/0&lt;/code&gt; taking care of using the same key names, its return value perfectly fits the expectations of the &lt;code&gt;row_fetcher&lt;/code&gt; function.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-5" class="outline-2"&gt;
&lt;h2 id="headline-5"&gt;
Mounting the dashboard page
&lt;/h2&gt;
&lt;div id="outline-text-headline-5" class="outline-text-2"&gt;
&lt;p&gt;
To have the page up and running, we need to modify the &lt;code&gt;live_dashboard/2&lt;/code&gt; function inside the application router:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;live_dashboard &lt;span style="color:#e6db74"&gt;&amp;#34;/dashboard&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;metrics&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;TuneWeb.Telemetry&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;metrics_history&lt;/span&gt;: {&lt;span style="color:#a6e22e"&gt;TuneWeb.Telemetry.Storage&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:metrics_history&lt;/span&gt;, []},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;additional_pages&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;spotify_sessions&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;TuneWeb.LiveDashboard.SpotifySessionsPage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-6" class="outline-2"&gt;
&lt;h2 id="headline-6"&gt;
Filters and limits
&lt;/h2&gt;
&lt;div id="outline-text-headline-6" class="outline-text-2"&gt;
&lt;p&gt;
We can now focus on implementing search, sorting and limits. Conceptually, we need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If specified, apply the search filter.&lt;/li&gt;
&lt;li&gt;Always apply sort order.&lt;/li&gt;
&lt;li&gt;Count the sorted elements, to return the correct total.&lt;/li&gt;
&lt;li&gt;Always apply the limit clause to the sorted elements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these operations have to be handled by the implementation of the &lt;code&gt;row_fetcher&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;
The params map has the following keys:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:search&lt;/code&gt;: the string representing the contents of the search input (or &lt;code&gt;nil&lt;/code&gt; when empty).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:sort_by&lt;/code&gt;: the id of the column to sort by.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:sort_dir&lt;/code&gt;: the sort direction, expressed with the atoms &lt;code&gt;:asc&lt;/code&gt; and &lt;code&gt;:desc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:limit&lt;/code&gt;: the integer value representing the amount of max items requested by the user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The params map is very well thought out, as it has a fixed structure, applied defaults where available and values that play well with functions from the &lt;code&gt;Enum&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;
We can extend the &lt;code&gt;fetch_sessions/2&lt;/code&gt; function as follows:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defmodule&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TuneWeb.LiveDashboard.SpotifySessionsPage&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; fetch_sessions(params, _node) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sessions &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Tune.Spotify.Supervisor&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sessions()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; filter(params)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#a6e22e"&gt;Enum&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;take(sessions, params[&lt;span style="color:#e6db74"&gt;:limit&lt;/span&gt;]), length(sessions)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; filter(sessions, params) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sessions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Enum&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;filter(&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; session &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; session_match?(session, params[&lt;span style="color:#e6db74"&gt;:search&lt;/span&gt;]) &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Enum&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sort_by(&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; session &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; session[params[&lt;span style="color:#e6db74"&gt;:sort_by&lt;/span&gt;]] &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;, params[&lt;span style="color:#e6db74"&gt;:sort_dir&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; session_match?(_session, &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;), &lt;span style="color:#e6db74"&gt;do&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; session_match?(session, search_string), &lt;span style="color:#e6db74"&gt;do&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;contains?(session[&lt;span style="color:#e6db74"&gt;:id&lt;/span&gt;], search_string)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
As outlined above, we start by filtering by search, using a very simple logic that just checks if the session ID contains the searched string.&lt;/p&gt;
&lt;p&gt;
After search, we apply the sorting logic: the values of the &lt;code&gt;:sort_by&lt;/code&gt; and &lt;code&gt;:sort_dir&lt;/code&gt; perfectly fit using &lt;code&gt;Enum.sort_by/3&lt;/code&gt; (a really appreciated API design choice), making the implementation short and sweet.&lt;/p&gt;
&lt;p&gt;
When defining the returning tuple, we take care of applying the limit and returning the correct total count.&lt;/p&gt;
&lt;p&gt;
With these changes in place, the generated table behaves as expected:&lt;/p&gt;
&lt;p&gt;
&lt;img src="https://claudio-ortolina.org/img/building-a-custom-page-for-phoenix-live-dashboard/sessions-table.png" alt="A screenshot of the Spotify sessions table built in this blog post" class="left" /&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-7" class="outline-2"&gt;
&lt;h2 id="headline-7"&gt;
Supporting multiple nodes
&lt;/h2&gt;
&lt;div id="outline-text-headline-7" class="outline-text-2"&gt;
&lt;p&gt;
The last piece of the puzzle is making sure that we take into account the currently selected node.&lt;/p&gt;
&lt;p&gt;
Fortunately, we just need to make a very small change to &lt;code&gt;fetch_sessions/2&lt;/code&gt;:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; fetch_sessions(params, node) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sessions &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; node
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;:rpc&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;call(&lt;span style="color:#a6e22e"&gt;Tune.Spotify.Supervisor&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:sessions&lt;/span&gt;, [])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; filter(params)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#a6e22e"&gt;Enum&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;take(sessions, params[&lt;span style="color:#e6db74"&gt;:limit&lt;/span&gt;]), length(sessions)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The OTP &lt;a href="https://erlang.org/doc/man/rpc.html"&gt;rpc&lt;/a&gt; module conveniently provides a &lt;a href="https://erlang.org/doc/man/rpc.html#call-4"&gt;&lt;code&gt;call/4&lt;/code&gt;&lt;/a&gt; function that takes a node name, module, function, and arguments, returning the exact same value of the remotely executed function.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-8" class="outline-2"&gt;
&lt;h2 id="headline-8"&gt;
Conclusions
&lt;/h2&gt;
&lt;div id="outline-text-headline-8" class="outline-text-2"&gt;
&lt;p&gt;
To see the final version of &lt;code&gt;TuneWeb.LiveDashboard.SpotifySessionsPage&lt;/code&gt;, you can open &lt;a href="https://github.com/fully-forged/tune/blob/32038997bc89f94ca8ee18f80d2f1cae946f7acb/lib/tune_web/live_dashboard/spotify_sessions_page.ex"&gt;the file in the repo&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>Sat, 21 Nov 2020 09:29:01 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/building-a-custom-page-for-phoenix-live-dashboard/</link>
  <guid>https://claudio-ortolina.org/posts/building-a-custom-page-for-phoenix-live-dashboard/---https://claudio-ortolina.org/posts/building-a-custom-page-for-phoenix-live-dashboard/</guid>
</item>
<item>
  <title>The Mac is losing me</title>
  <description>From Underjord.io: I&amp;rsquo;ve been mostly happy using a Mac since I got myself my first computer earned with programmer money. I believe it was a mid 2009 15&amp;quot; MacBook Pro. That was a computer I used at least until 2016 which I consider very decent usable life. At that point I had replaced the hard-drive with an SSD, upgraded the RAM and switched a battery that was worn out. I stopped using it when it just straight died some time in 2016.</description>
  <pubDate>Wed, 18 Nov 2020 07:00:00 +0000</pubDate>
  <link>https://underjord.io/the-mac-is-losing-me.html</link>
  <guid>https://underjord.io/the-mac-is-losing-me.html---https://underjord.io/the-mac-is-losing-me.html</guid>
</item>
<item>
  <title>Tips for Finch, Telemetry, and Phoenix Live Dashboard</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;
While working on &lt;a href="https://github.com/fully-forged/tune"&gt;Tune&lt;/a&gt;, I needed to collect performance metrics related to the interaction with the Spotify API.&lt;/p&gt;
&lt;p&gt;
The Finch HTTP client &lt;a href="https://hexdocs.pm/finch/Finch.html#module-telemetry"&gt;exposes Telemetry metrics&lt;/a&gt;, which made it very easy to display them via &lt;a href="https://hex.pm/packages/phoenix_live_dashboard"&gt;Phoenix Live Dashboard&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;
Starting from the stock &lt;code&gt;TuneWeb.Telemetry&lt;/code&gt; file generated by Phoenix (see &lt;a href="https://hexdocs.pm/phoenix/telemetry.html#content"&gt;the official guides for an explanation&lt;/a&gt;), I just added two new summary metrics to the &lt;code&gt;metrics/0&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; summary(&lt;span style="color:#e6db74"&gt;&amp;#34;vm.total_run_queue_lengths.io&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# HTTP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; summary(&lt;span style="color:#e6db74"&gt;&amp;#34;finch.request.stop.duration&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;unit&lt;/span&gt;: {&lt;span style="color:#e6db74"&gt;:native&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:millisecond&lt;/span&gt;}, &lt;span style="color:#e6db74"&gt;tags&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;:path&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; summary(&lt;span style="color:#e6db74"&gt;&amp;#34;finch.response.stop.duration&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;unit&lt;/span&gt;: {&lt;span style="color:#e6db74"&gt;:native&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:millisecond&lt;/span&gt;}, &lt;span style="color:#e6db74"&gt;tags&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;:path&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
With this change in place (&lt;a href="https://github.com/fully-forged/tune/commit/7c573aa30313a8adf1954076b9cd957f0f910155"&gt;commit&lt;/a&gt;), I had all metrics being visualized in the dashboard, grouped by the Spotify API path. I wanted to make some improvements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;:path&lt;/code&gt; tag includes query string parameters, so calls like &lt;code&gt;search?q=marillion&lt;/code&gt; and &lt;code&gt;search?q=fish&lt;/code&gt; would be aggregated in different groups. Instead, I would want them to be part of the same group, ignoring query string parameters.&lt;/li&gt;
&lt;li&gt;Since I &lt;a href="https://claudio-ortolina.org/posts/using-finch-with-sentry/"&gt;setup Sentry to use Finch as a client&lt;/a&gt;, I wanted to exclude calls made to Sentry and only have charts reporting metrics about the interaction with Spotify&lt;/li&gt;
&lt;/ul&gt;
&lt;div id="outline-container-headline-1" class="outline-2"&gt;
&lt;h2 id="headline-1"&gt;
Aggregating by normalized path
&lt;/h2&gt;
&lt;div id="outline-text-headline-1" class="outline-text-2"&gt;
&lt;p&gt;
To aggregate metrics by normalized path, we can apply a transformation function to the metric tag values, generate a normalized path tag and use that to aggregate metrics. As shown &lt;a href="https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html#module-metrics"&gt;in the telemetry_metrics docs&lt;/a&gt;, the option we need is &lt;code&gt;tag_values&lt;/code&gt;:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; metrics &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; summary(&lt;span style="color:#e6db74"&gt;&amp;#34;finch.request.stop.duration&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;unit&lt;/span&gt;: {&lt;span style="color:#e6db74"&gt;:native&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:millisecond&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;tags&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;:normalized_path&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;tag_values&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;add_normalized_path&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; add_normalized_path(metadata) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Map&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;put(metadata, &lt;span style="color:#e6db74"&gt;:normalized_path&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;URI&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;parse(metadata&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
We can use the built-in &lt;code&gt;URI&lt;/code&gt; module to parse normalized path out of the Finch metric metadata and add it to the metadata itself. With that in place, we can update the &lt;code&gt;tags&lt;/code&gt; option to reference &lt;code&gt;:normalized_path&lt;/code&gt;. With this change, metrics are aggregated on the endpoint only, without any query string. For reference, here&amp;#39;s the relevant &lt;a href="https://github.com/fully-forged/tune/commit/8ab6fab59357e97579ac086a94e768193c2872a5?branch=8ab6fab59357e97579ac086a94e768193c2872a5&amp;amp;diff=unified"&gt;commit&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-2" class="outline-2"&gt;
&lt;h2 id="headline-2"&gt;
Filtering only Spotify calls
&lt;/h2&gt;
&lt;div id="outline-text-headline-2" class="outline-text-2"&gt;
&lt;p&gt;
To filter for Spotify calls only, we can use the &lt;code&gt;keep&lt;/code&gt; option, which specifies a predicate function that can be used to define which metrics should be kept and which ones should be discarded. Discarded metrics will not appear in the dashboard chart.&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; metrics &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# omitted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; summary(&lt;span style="color:#e6db74"&gt;&amp;#34;finch.response.stop.duration&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;unit&lt;/span&gt;: {&lt;span style="color:#e6db74"&gt;:native&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:millisecond&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;tags&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;:normalized_path&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;tag_values&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;add_normalized_path&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;keep&lt;/span&gt;: &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;keep_spotify&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;reporter_options&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;nav&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;HTTP - Spotify&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; keep_spotify(meta) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; meta&lt;span style="color:#f92672"&gt;.&lt;/span&gt;host &lt;span style="color:#f92672"&gt;=~&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;spotify&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
As the meta information already includes a host, we can compare it with the &lt;code&gt;spotify&lt;/code&gt; string. The &lt;code&gt;=~&lt;/code&gt; operator makes the comparison a bit more resilient, so that we don&amp;#39;t have to worry about the exact hostname, rather a hostname related to Spotify. This choice might need to be revised if we ever end up interacting via HTTP with another service with &amp;#34;spotify&amp;#34; in their host name (unlikely, but possible).&lt;/p&gt;
&lt;p&gt;
For some additional clarity, we can also use the &lt;code&gt;nav&lt;/code&gt; reporter option (see &lt;a href="https://hexdocs.pm/phoenix_live_dashboard/metrics.html#reporter-options"&gt;Phoenix LiveDashboard documentation&lt;/a&gt; for more details) to make sure that the navigation header displays a name that details the additional filter applied to the HTTP metrics. For reference, see the relevant &lt;a href="https://github.com/fully-forged/tune/commit/c9f483d93c0813c0e680a4aaf2a88fed0851334f#diff-f599bf85f0cafc16b50f0e1a561b6aa39e4ab256fb6d43e8726619570866c5b1"&gt;commit&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-3" class="outline-2"&gt;
&lt;h2 id="headline-3"&gt;
Conclusion
&lt;/h2&gt;
&lt;div id="outline-text-headline-3" class="outline-text-2"&gt;
&lt;p&gt;
Both improvements required very small updates. Here&amp;#39;s the final result, showing the custom Nav title (&amp;#34;HTTP - Spotify&amp;#34;) to hint at the filter used to only show Spotify calls, and aggregation by normalized path (without query string).&lt;/p&gt;
&lt;p&gt;
&lt;img src="https://claudio-ortolina.org/img/tips-for-finch-and-telemetry/charts.png" alt="A screenshot of the configured Finch Metrics inside Live Dashboard" class="left" /&gt;
&lt;/p&gt;
&lt;p&gt;
All in all, I was pleased to see that it was straightforward to customise the charts I needed. One thing I haven&amp;#39;t worked on yet is aggregating metrics by logical path, i.e. by route (&lt;code&gt;GET /artist/:id&lt;/code&gt;) instead of individual paths (&lt;code&gt;GET /artist/123&lt;/code&gt;), but I have some ideas and will come back on it in a future post.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>Tue, 17 Nov 2020 17:27:05 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/tips-for-finch-and-telemetry/</link>
  <guid>https://claudio-ortolina.org/posts/tips-for-finch-and-telemetry/---https://claudio-ortolina.org/posts/tips-for-finch-and-telemetry/</guid>
</item>
<item>
  <title>Using Finch With Sentry</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;
A few weeks ago I added enabled support for &lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt; inside &lt;a href="https://github.com/fully-forged/tune"&gt;Tune&lt;/a&gt;, my Spotify browser/client. Even if I&amp;#39;m pretty much the only user (I built it for myself after all), having exception tracking has already proved to be useful - band and song names can really create all sorts of issues.&lt;/p&gt;
&lt;p&gt;
The &lt;a href="https://hex.pm/packages/sentry"&gt;official Sentry package&lt;/a&gt; works as advertised and by default it communicates using &lt;a href="https://hex.pm/packages/hackney"&gt;Hackney&lt;/a&gt; as a http client. As I&amp;#39;ve been using &lt;a href="https://hex.pm/packages/finch"&gt;Finch&lt;/a&gt; in the project, I was pleased to see that Sentry exposed a &lt;code&gt;client&lt;/code&gt; configuration option that allowed using your own module, as long as it implemented the &lt;code&gt;Sentry.HTTPClient&lt;/code&gt; behaviour.&lt;/p&gt;
&lt;p&gt;
The advantage in swapping the http client library (on top of uniforming the building blocks of the application) is that Finch has support for &lt;a href="https://hex.pm/packages/telemetry"&gt;Telemetry&lt;/a&gt; metrics.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update #1: Thanks to Wojtek Mach for &lt;a href="https://github.com/fully-forged/tune/pull/131"&gt;a more streamlined implementation.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
The module I wrote is quite short:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defmodule&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Sentry.FinchClient&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@moduledoc&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; Defines a small shim to use `Finch` as a `Sentry.HTTPClient`.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@behaviour&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Sentry.HTTPClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@impl&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; child_spec &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Finch&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;child_spec(&lt;span style="color:#e6db74"&gt;name&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;Sentry.Finch&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@impl&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; post(url, headers, body) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Finch&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;build(&lt;span style="color:#e6db74"&gt;:post&lt;/span&gt;, url, headers, body)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Finch&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;request(request, &lt;span style="color:#a6e22e"&gt;Sentry.Finch&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;:ok&lt;/span&gt;, response} &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;:ok&lt;/span&gt;, response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;status, response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;headers, response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;body}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The trickiest bit was to get the &lt;code&gt;child_spec/0&lt;/code&gt; callback right while keeping &lt;a href="https://erlang.org/doc/man/dialyzer.html"&gt;dialyzer&lt;/a&gt; happy. The first implementation I wrote was simply &lt;code&gt;{Finch, name: Sentry.Finch}&lt;/code&gt;, but that would fail to satisfy &lt;a href="https://hexdocs.pm/sentry/Sentry.HTTPClient.html#c:child_spec/0"&gt;the typespec defined for &lt;code&gt;child_spec/0&lt;/code&gt;&lt;/a&gt;. I then switched to a more verbose version:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; child_spec &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; opts &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;name&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;Sentry.Finch&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Supervisor&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;child_spec(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; %{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;id&lt;/span&gt;: __MODULE__,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;start&lt;/span&gt;: {&lt;span style="color:#a6e22e"&gt;Finch&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;:start_link&lt;/span&gt;, [opts]},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;type&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:supervisor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
This version satisfied dialyzer, but turns out there&amp;#39;s a simpler way. After publishing this blog post, Wojtek Mach reached out and submitted a PR to streamline the specification to the version shown in the full example above.&lt;/p&gt;
&lt;p&gt;
I also updated my production configuration:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;config &lt;span style="color:#e6db74"&gt;:sentry&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;dsn&lt;/span&gt;: {&lt;span style="color:#e6db74"&gt;:system&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;SENTRY_DSN&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;environment_name&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:prod&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;enable_source_code_context&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;root_source_code_path&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;File&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;cwd!(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;client&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;Sentry.FinchClient&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;included_environments&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;:prod&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
You can of course expand on this implementation if you need to pass more options to the &lt;code&gt;Finch&lt;/code&gt; child specification - I found that for my use case, defaults are fine, so for now I don&amp;#39;t need to add any configuration hooks.&lt;/p&gt;
&lt;p&gt;
To see the change in context, &lt;a href="https://github.com/fully-forged/tune/pull/122"&gt;this is the original PR&lt;/a&gt;, with the &lt;a href="https://github.com/fully-forged/tune/pull/131"&gt;follow-up by Wojtek Mach&lt;/a&gt;.&lt;/p&gt;</description>
  <pubDate>Tue, 10 Nov 2020 08:41:30 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/using-finch-with-sentry/</link>
  <guid>https://claudio-ortolina.org/posts/using-finch-with-sentry/---https://claudio-ortolina.org/posts/using-finch-with-sentry/</guid>
</item>
<item>
  <title>Fish</title>
  <description>From Posts on Claudio Ortolina: &lt;img src="https://claudio-ortolina.org/img/fish/cover.jpg"/&gt;
&lt;div id="outline-container-headline-1" class="outline-3"&gt;
&lt;h3 id="headline-1"&gt;
Prelude
&lt;/h3&gt;
&lt;div id="outline-text-headline-1" class="outline-text-3"&gt;
&lt;p&gt;
I have very early memories of rock music in my life. Since I was a toddler, my parents (and particularly my dad) got me used to listen to 80s rock music. Bands like Queen, Guns N&amp;#39; Roses, AC/DC, Van Halen, and Bon Jovi are ingrained in my memories as sounds of my childhood.&lt;/p&gt;
&lt;p&gt;
It&amp;#39;s not surprising that my taste in music has developed from there, branching out over the years in metal and progressive rock. Music is a constant companion of my daily life and contributed to the formation of my identity.&lt;/p&gt;
&lt;p&gt;
My taste changed and adapted: around ten years ago I was dismissing &lt;a href="https://en.wikipedia.org/wiki/Operation:_Mindcrime"&gt;&lt;em&gt;Operation: Mindcrime&lt;/em&gt;&lt;/a&gt; by &lt;a href="https://en.wikipedia.org/wiki/Queensrÿche"&gt;Queensrÿche&lt;/a&gt; as a mediocre album, but two years ago I picked it up again and it was a revelation. Something similar happened while listening to &lt;a href="https://en.wikipedia.org/wiki/Rage_Against_the_Machine"&gt;Rage Against the Machine&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Tool"&gt;Tool&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/A_Perfect_Circle"&gt;A Perfect Circle&lt;/a&gt;, didn&amp;#39;t like them years ago, absolutely love them now.&lt;/p&gt;
&lt;p&gt;
This change got me thinking - why do these bands resonate with me now?&lt;/p&gt;
&lt;p&gt;
I think the answer lies primarily in the integrity of the message connected to these bands: their perspective, political stance, perspective on life really struck a cord with me in my 30s. They take an explicit position around issues I care about.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-2" class="outline-3"&gt;
&lt;h3 id="headline-2"&gt;
Enter Marillion and Fish
&lt;/h3&gt;
&lt;div id="outline-text-headline-2" class="outline-text-3"&gt;
&lt;p&gt;
&lt;img src="https://claudio-ortolina.org/img/fish/fish-photo.jpg" alt="Fish&amp;#39;s portrait" class="left" style="float: left; margin-right: 1rem;" /&gt;
&lt;/p&gt;
&lt;p&gt;
Until a year ago, I had a very cursory knowledge of &lt;a href="https://en.wikipedia.org/wiki/Marillion"&gt;Marillion&lt;/a&gt;&amp;#39;s body of work. I kney they existed, that they have a substantial discography and that they represent an important page in the history of 80s and 90s prog.&lt;/p&gt;
&lt;p&gt;
One morning, Spotify&amp;#39;s suggestions algorithm decided to play &lt;a href="https://www.youtube.com/watch?v=lalBmbrWEvQ"&gt;&lt;em&gt;Incommunicado&lt;/em&gt;&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=6COmtBk6lYo"&gt;&lt;em&gt;Sugar Mice&lt;/em&gt;&lt;/a&gt; in the space of an hour. I found myself stopping what I was doing in both instances - there was something about those songs that really dragged my attention. I loved the music - but the lyrics stopped me in my tracks.&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Incommunicado&lt;/em&gt; is about ambition, the drive to make it big without taking responsibilities. In the context of the entire album, it&amp;#39;s both a metaphor for the difficult life of Torch, the main character whose life is falling to pieces, but also a direct description of the music business.&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Sugar Mice&lt;/em&gt; is a song about the devastating effects of unemployment and economic recession. The title refers to a popular english sweet made of sugar. The main refrain &amp;#34;We&amp;#39;re just sugar mice in the rain&amp;#34; gives a very vivid representation of how fragile life can be.&lt;/p&gt;
&lt;p&gt;
The words, references, and metaphors included in the lyrics stood out as something completely out of the ordinary - it &lt;span style="text-decoration: underline;"&gt;sounded&lt;/span&gt; like poetry.&lt;/p&gt;
&lt;p&gt;
The more I explored other Marillion&amp;#39;s songs, the more I realized that what really resonated with me was &lt;a href="https://en.wikipedia.org/wiki/Fish_(singer)"&gt;Fish&lt;/a&gt;, their initial singer and lyricist. Following his body of work after he left Marillion, I kept finding incredible songs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-3" class="outline-3"&gt;
&lt;h3 id="headline-3"&gt;
Love and relationships
&lt;/h3&gt;
&lt;div id="outline-text-headline-3" class="outline-text-3"&gt;
&lt;blockquote&gt;
&lt;p&gt;Do you remember?&lt;br&gt;
Barefoot on the lawn with shooting stars&lt;br&gt;
Do you remember?&lt;br&gt;
The loving on the floor in Belsize Park&lt;br&gt;
Do you remember?&lt;br&gt;
Dancing in stilettoes in the snow&lt;br&gt;
Do you remember?&lt;br&gt;
You never understood I had to go&lt;br&gt;
By the way, didn&amp;#39;t I break your heart?&lt;br&gt;
Please excuse me, I never meant to break your heart&lt;br&gt;
So sorry, I never meant to break your heart&lt;br&gt;
But you broke mine — &lt;em&gt;Kayleigh, 1985&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
By far the most succesful Marillion song, &lt;em&gt;Kayleigh&lt;/em&gt; is often cited as Fish&amp;#39;s apology to different women for the failure of their romantic relationships, a collage of vivid images and melancholic moments.&lt;/p&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/OQ4oaLUilBc?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
To me, this song sows the seeds of a theme that goes through a few songs written by Fish: work/life balance. Relationships fail when you divert your attention away from them, when you don&amp;#39;t put in everyday work to keep them going.&lt;/p&gt;
&lt;p&gt;
In &lt;em&gt;Zoë 25&lt;/em&gt; it&amp;#39;s the aftermath of another relationship that didn&amp;#39;t go well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you&amp;#39;re looking for somebody, you might not even see them, &lt;br&gt;
When they&amp;#39;re standing there in front of you, right before your eyes, &lt;br&gt;
If you&amp;#39;re looking for somebody you&amp;#39;re gonna need some help, &lt;br&gt;
You know you&amp;#39;ll never find her when you&amp;#39;re looking for yourself. — &lt;em&gt;Zoë 25, 2007&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
In &lt;em&gt;Garden of Remembrance&lt;/em&gt;, it&amp;#39;s Alzheimer&amp;#39;s disease, something completely outside our control:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;He&amp;#39;s lost between the here and now&lt;br&gt;
Somewhere that he can&amp;#39;t be found&lt;br&gt;
She&amp;#39;s still here&lt;br&gt;
Her love a ghost of memory&lt;br&gt;
She&amp;#39;ll wait for an eternity&lt;br&gt;
He&amp;#39;s still here — &lt;em&gt;Garden of Remembrance, 2020&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/-RwwU8Nvs1g?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;
In &lt;em&gt;cliche&lt;/em&gt;, the song lyrics use estabilished cliches to acknowledge that no matter how much hard you try, sometimes the simplest thing you can say is what matters.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That&amp;#39;s why I&amp;#39;m trying to say with my deepest sincerity&lt;br&gt;
That&amp;#39;s why I&amp;#39;m finding it comes down to the basic simplicities&lt;br&gt;
The best way is with an old cliche&lt;br&gt;
It&amp;#39;s simply the best way is with an old cliche&lt;br&gt;
Always the best way is with an old cliche&lt;br&gt;
I&amp;#39;ll leave it to the best way, it&amp;#39;s an old cliche&lt;br&gt;
I love you. — &lt;em&gt;Cliche, 1990&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
In &lt;em&gt;Punch and Judy&lt;/em&gt;&lt;sup class="footnote-reference"&gt;&lt;a id="footnote-reference-1" href="#footnote-1"&gt;1&lt;/a&gt;&lt;/sup&gt; it&amp;#39;s the progressive deterioration of a relationship, escalating in murdering fantasies:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Whatever happened to morning smiles,&lt;br&gt;
Whatever happened to wicked wiles, permissive styles,&lt;br&gt;
Whatever happened to twinkling eyes,&lt;br&gt;
Whatever happened to hard fast drives,&lt;br&gt;
Complements on unnatural size&lt;/p&gt;
&lt;p&gt;
Punch, Punch, Punch And Judy, Punch, Punch, Punch And Judy&lt;br&gt;
Punch, Punch, Punch And Judy.&lt;/p&gt;
&lt;p&gt;
Propping up a bar, family car,&lt;br&gt;
Sweating out a mortgage as a balding clerk,&lt;br&gt;
Punch And Judy, [Judy]&lt;br&gt;
World war three, suburbanshee,&lt;br&gt;
Just slip her these pills and I&amp;#39;ll be free.&lt;/p&gt;
&lt;p&gt;
No more Judy, Judy. Judy no more! Goodbye Judy! — &lt;em&gt;Punch and Judy, 1984&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-4" class="outline-3"&gt;
&lt;h3 id="headline-4"&gt;
Living on your own terms
&lt;/h3&gt;
&lt;div id="outline-text-headline-4" class="outline-text-3"&gt;
&lt;p&gt;
Another recurring topic is the idea of living on your own terms. From &lt;em&gt;Tongues&lt;/em&gt;, where Fish lets out the frustration of dealing with lawyers during a very long lawsuit with music publisher EMI:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your entrenched opinions,&lt;br&gt;
On the border of arrogance,&lt;br&gt;
Dug in against the compromise.&lt;br&gt;
A position indefensible, your actions illogical&lt;br&gt;
You&amp;#39;re speaking in tongues&lt;/p&gt;
&lt;p&gt;
You swear contradictions&lt;br&gt;
Your tedious monologues, wielding authority,&lt;br&gt;
Demanding subservience, demanding&lt;br&gt;
I make your sense.&lt;br&gt;
Demanding speaking in tongues. — &lt;em&gt;Tongues, 1991&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
The inability to speak the same language becomes a massive blocker. The aforementioned lawsuit with EMI ended up being a fundamental event in Fish&amp;#39;s life, pushing him to pursue the ownership of all rights of his solo albums (except the first one, &lt;em&gt;Vigil in a Wilderness of Mirrors&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;
In &lt;em&gt;Circle Line&lt;/em&gt;&lt;sup class="footnote-reference"&gt;&lt;a id="footnote-reference-2" href="#footnote-2"&gt;2&lt;/a&gt;&lt;/sup&gt;, it&amp;#39;s the awareness of the 9-to-5 grind that is imposed on the majority of us:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;9 to 5&amp;#39;s the only time I try to kid myself that I&amp;#39;m still alive,&lt;br&gt;
That I&amp;#39;m living out the dream to earn my freedom from this rat race&lt;br&gt;
Where all I do&amp;#39;s survive, I live the lie, I serve my time.&lt;/p&gt;
&lt;p&gt;
The circle line.&lt;/p&gt;
&lt;p&gt;
Just another day, just another day, just another day,&lt;br&gt;
Just another day, just another day, just another day on the circle line.&lt;/p&gt;
&lt;p&gt;
The circle line, on the circle line. — &lt;em&gt;Circle Line, 2007&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;em&gt;Lost Plot&lt;/em&gt;, on losing track of what matters:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I was blinded by light but the vision had died, I&amp;#39;d forgotten&lt;br&gt;
In time just what I was fighting for&lt;br&gt;
I&amp;#39;d forgotten who&amp;#39;s side I was on, the difference between&lt;br&gt;
Right and wrong&lt;br&gt;
I was out of my depth, going out of my mind, going down in&lt;br&gt;
A field where no prisoners are taken, no quarter is given&lt;br&gt;
The writing was small, it burned on the wall, I&amp;#39;d sold out&lt;br&gt;
My soul for what it was worth&lt;br&gt;
I&amp;#39;d lost the plot, my number was up, the game was over&lt;br&gt;
Snakes and ladders, a world of snakes and ladders, snakes&lt;br&gt;
And ladders — &lt;em&gt;Lost Plot, 2004&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;em&gt;View from the Hill&lt;/em&gt;, where we the hill is an endless collection of things that don&amp;#39;t matter and just keep us imprisoned.&lt;/p&gt;
&lt;p&gt;
&lt;figure class="center" &gt;
&lt;img src="https://claudio-ortolina.org/img/fish/the-hill.jpg" alt="Illustration of the Hill, one of the main metaphors in the album Vigil in a Wilderness of Mirrors" /&gt;
&lt;figcaption class="center" &gt;Artwork for the album of Vigil in the Wilderness of Mirrors, by Mark Wilkinson&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You sit and think that everything is coming up roses&lt;br&gt;
But you can&amp;#39;t see the weeds that entangle your feet&lt;br&gt;
You can&amp;#39;t see the wood for the trees &amp;#39;cause the forest is burning&lt;br&gt;
And you say it&amp;#39;s the smoke in your eyes that&amp;#39;s making you cry&lt;/p&gt;
&lt;p&gt;
They sold you the view from a hill&lt;br&gt;
They told you that the view from the hill would be&lt;br&gt;
Further than you have ever seen before&lt;br&gt;
They sold you a view from a hill&lt;br&gt;
They sold you a view from a hill — &lt;em&gt;View from the Hill, 1990&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-5" class="outline-3"&gt;
&lt;h3 id="headline-5"&gt;
The system has failed (us)
&lt;/h3&gt;
&lt;div id="outline-text-headline-5" class="outline-text-3"&gt;
&lt;p&gt;
Fish has never shied away from commenting on politics: from &lt;em&gt;Market Square Heroes&lt;/em&gt; (Marillion&amp;#39;s first single) to &lt;em&gt;Weltschmerz&lt;/em&gt;, the ending track of his latest (and last) album.&lt;/p&gt;
&lt;p&gt;
&lt;em&gt;Market Square Heroes&lt;/em&gt;&lt;sup class="footnote-reference"&gt;&lt;a id="footnote-reference-3" href="#footnote-3"&gt;3&lt;/a&gt;&lt;/sup&gt; is once again an anthem of an angry generation that suffers the consequences of austerity and recession:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I give peace signs when I wage war in the disco&lt;br&gt;
I&amp;#39;m the warrior in the ultra violet haze&lt;br&gt;
Armed with antisocial insecurity&lt;br&gt;
I plan the path of destiny from this maze&lt;/p&gt;
&lt;p&gt;
Cause I&amp;#39;m a Market Square hero gathering the storms to troop&lt;br&gt;
Cause I&amp;#39;m a Market Square hero speeding the beat of the street pulse&lt;br&gt;
Are you following me, are you following me?&lt;br&gt;
Well suffer my fallen angels and follow me&lt;br&gt;
I&amp;#39;m the Market Square hero, I&amp;#39;m the Market Square hero&lt;br&gt;
We are Market Square Heroes, to be Market Square Heroes — &lt;em&gt;Market Square Heroes, 1982&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
&lt;em&gt;Weltschmerz&lt;/em&gt; is a summary of all fights worth fighting - from climate change, to poverty, to the general failure of a political system that emphasized polarisation and division:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am a grey bearded warrior, a poet of no mean acclaim&lt;br&gt;
My words are my weapons that I proffer with disdain&lt;br&gt;
My melancholy aspect is something you can’t disregard&lt;br&gt;
My motives you cannot question nor my strong sense of right and wrong\\&lt;/p&gt;
&lt;p&gt;
I’ve formed the opinion that things can’t stay as they are&lt;br&gt;
My anger and my fury trapped like a wasp in a jar&lt;br&gt;
It’s never too late to make a brave new start&lt;br&gt;
When the revolution is called I will play my part — &lt;em&gt;Weltschmerz, 2020&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-6" class="outline-3"&gt;
&lt;h3 id="headline-6"&gt;
Perfume River
&lt;/h3&gt;
&lt;div id="outline-text-headline-6" class="outline-text-3"&gt;
&lt;p&gt;
&lt;em&gt;Perfume River&lt;/em&gt;&lt;sup class="footnote-reference"&gt;&lt;a id="footnote-reference-4" href="#footnote-4"&gt;4&lt;/a&gt;&lt;/sup&gt; deserves a mention on its own: in this song, Fish looks at the consequences of the Vietnam War, whose images are burned in his childhood memory.&lt;/p&gt;
&lt;p&gt;
&lt;figure class="center" &gt;
&lt;img src="https://claudio-ortolina.org/img/fish/feast-of-consequences.jpg" alt="Artwork for the album Feast of Consequences" /&gt;
&lt;figcaption class="center" &gt;Artwork for the album Feast of Consequences by Mark Wilkinson&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fire breathing dragons swarm in sweltering skies, spewing flame on innocents below&lt;br&gt;
Charred and brittle corpses, blackened evidence, I am enraged, I am afraid, I am forlorn&lt;br&gt;
The ashes from wise pages fly from libraries, tumble in the clouds of smoke and flies&lt;br&gt;
To lie as dust in corners of dark palaces, the fetid smell of revolution haunts the air.&lt;/p&gt;
&lt;p&gt;
Take me away to the Perfume River; carry me down to the perfume river&lt;br&gt;
Set me adrift on a well-stocked open boat&lt;br&gt;
Show me the way to the Perfume River, send me away down the perfume river&lt;br&gt;
Pour that sweet, sweet liquor down my throat; pour it down my throat — &lt;em&gt;Perfume River, 2013&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
Once again, the images evoked are incredibly strong, full of colour - red flames and rage, smoke, dust and death.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-headline-7" class="outline-3"&gt;
&lt;h3 id="headline-7"&gt;
Visuals
&lt;/h3&gt;
&lt;div id="outline-text-headline-7" class="outline-text-3"&gt;
&lt;p&gt;
A powerful ingredient in Fish&amp;#39;s artistic work has always been his collaboration with &lt;a href="https://www.the-masque.com/mainpage.html"&gt;Mark Wilkinson&lt;/a&gt;, who illustrated all Marillion&amp;#39;s albums (until Fish left the band) and all of Fish&amp;#39;s work. His surrealistic style is unmistakable and perfectly complements the &amp;#34;visual&amp;#34; nature of a lot of Fish&amp;#39;s songs.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="footnotes"&gt;
&lt;hr class="footnotes-separatator"&gt;
&lt;div class="footnote-definitions"&gt;
&lt;div class="footnote-definition"&gt;
&lt;sup id="footnote-1"&gt;&lt;a href="#footnote-reference-1"&gt;1&lt;/a&gt;&lt;/sup&gt;
&lt;div class="footnote-body"&gt;
&lt;p&gt;The title refers to Punch and Judy, the main characters of a puppet show popular in British culture. See &lt;a href="https://en.wikipedia.org/wiki/Punch_and_Judy"&gt;the Wikipedia page for more information.&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="footnote-definition"&gt;
&lt;sup id="footnote-2"&gt;&lt;a href="#footnote-reference-2"&gt;2&lt;/a&gt;&lt;/sup&gt;
&lt;div class="footnote-body"&gt;
&lt;p&gt;The Circle Line is one of London&amp;#39;s Underground lines.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="footnote-definition"&gt;
&lt;sup id="footnote-3"&gt;&lt;a href="#footnote-reference-3"&gt;3&lt;/a&gt;&lt;/sup&gt;
&lt;div class="footnote-body"&gt;
&lt;p&gt;The title is both a reference to a market square in Ailesbury, an english town, and Nietzsche&amp;#39;s &lt;a href="https://sourcebooks.fordham.edu/mod/nietzsche-madman.asp"&gt;Parable of the Madman&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="footnote-definition"&gt;
&lt;sup id="footnote-4"&gt;&lt;a href="#footnote-reference-4"&gt;4&lt;/a&gt;&lt;/sup&gt;
&lt;div class="footnote-body"&gt;
&lt;p&gt;The title refers to a river running through the city of Huế in Vietnam, one of the cities deeply affected by the Vietnam War.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>Fri, 06 Nov 2020 12:28:16 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/fish/</link>
  <guid>https://claudio-ortolina.org/posts/fish/---https://claudio-ortolina.org/posts/fish/</guid>
</item>
<item>
  <title>How to Deploy a Phoenix App to Gigalixir in 20 Minutes</title>
  <description>From Mitchell Hanberg's Blog: &lt;div class="flex justify-center py-8"&gt;
  &lt;iframe width="560" height="315" src="https://www.youtube.com/embed/z2nko60GcGo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;This is a quick screencast to demonstrate how easy it is to deploy an Elixir application to &lt;a href="https://gigalixir.com"&gt;Gigalixir&lt;/a&gt;, a hosting platform built specifically for Elixir.&lt;/p&gt;
&lt;p&gt;I included a condensed text version of the video for those who prefer it, but you can find the full guide &lt;a href="https://hexdocs.pm/phoenix/overview.html#content"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#create-the-phoenix-app" aria-hidden="true" class="anchor" id="create-the-phoenix-app"&gt;&lt;/a&gt;Create the Phoenix App&lt;/h2&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;# install the generator
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;$ mix archive.install hex phx_new 1.5.6
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;# generate the project
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;$ mix phx.new foobar
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;# cd into the directory
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;$ cd foobar
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;# create our database
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;$ mix ecto.create
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;# start the server
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;$ mix phx.server
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;# commit your code to git
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;$ git init &amp;amp;&amp;amp; git commit -am &amp;#39;Initial Commit&amp;#39;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#prod-configuration" aria-hidden="true" class="anchor" id="prod-configuration"&gt;&lt;/a&gt;Prod Configuration&lt;/h2&gt;
&lt;p&gt;You will have to adjust some prod configuration for our app to work on Gigalixir. Let's make the following adjustments to the &lt;code&gt;config/prod.exs&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-diff" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;  config :foobar, FoobarWeb.Endpoint,
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;&lt;span style="color: #ffc0b9;"&gt;&lt;span style="color: #8cf8f7;"&gt;-&lt;/span&gt;   url: [host: &amp;quot;example.com&amp;quot;, port: 80],&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #b3f6c0;"&gt;&lt;span style="color: #8cf8f7;"&gt;+&lt;/span&gt;   url: [host: &amp;quot;your-app-name.gigalixirapp.com&amp;quot;, port: 443],&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;&lt;span style="color: #ffc0b9;"&gt;&lt;span style="color: #8cf8f7;"&gt;-&lt;/span&gt;   cache_static_manifest: &amp;quot;priv/static/cache_manifest.json&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #b3f6c0;"&gt;&lt;span style="color: #8cf8f7;"&gt;+&lt;/span&gt;   cache_static_manifest: &amp;quot;priv/static/cache_manifest.json&amp;quot;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;&lt;span style="color: #b3f6c0;"&gt;&lt;span style="color: #8cf8f7;"&gt;+&lt;/span&gt;   force_ssl: [rewrite_on: [:x_forwarded_proto]]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt; 
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;&lt;span style="color: #b3f6c0;"&gt;&lt;span style="color: #8cf8f7;"&gt;+&lt;/span&gt; config :foobar, Foobar.Repo,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;&lt;span style="color: #b3f6c0;"&gt;&lt;span style="color: #8cf8f7;"&gt;+&lt;/span&gt;   ssl: true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#deploy-to-gigalixir" aria-hidden="true" class="anchor" id="deploy-to-gigalixir"&gt;&lt;/a&gt;Deploy to Gigalixir&lt;/h2&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;# install gigalixir (using Homebrew)
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;$ brew tap gigalixir/brew &amp;amp;&amp;amp; brew install gigalixir
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;# sign up for gigalixir
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;$ gigalixir signup
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;# log in to gigalixir
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;$ gigalixir login
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;# create the app
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;$ gigalixir create
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;# set up your buildpacks
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;$ echo &amp;quot;elixir_version=1.10.3&amp;quot; &amp;gt; elixir_buildpack.config
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;$ echo &amp;quot;erlang_version=22.3&amp;quot; &amp;gt;&amp;gt; elixir_buildpack.config
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;$ echo &amp;quot;node_version=12.16.3&amp;quot; &amp;gt; phoenix_static_buildpack.config
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;# commit those changes
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;$ git commit -am &amp;#39;Set up gigalixir buildpacks&amp;#39;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;# create your prod database
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;$ gigalixir pg:create --free
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;# deploy your app
&lt;/div&gt;&lt;div class="line" data-line="25"&gt;$ git push gigalixir main
&lt;/div&gt;&lt;div class="line" data-line="26"&gt;
&lt;/div&gt;&lt;div class="line" data-line="27"&gt;# open your prod app
&lt;/div&gt;&lt;div class="line" data-line="28"&gt;$ gigalixir open
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;</description>
  <pubDate>Fri, 06 Nov 2020 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/how-to-deploy-a-phoenix-app-to-gigalixir-in-20-minutes/</link>
  <guid>https://www.mitchellhanberg.com/how-to-deploy-a-phoenix-app-to-gigalixir-in-20-minutes/---https://www.mitchellhanberg.com/how-to-deploy-a-phoenix-app-to-gigalixir-in-20-minutes/</guid>
</item>
<item>
  <title>Remote 1-on-1 meetings</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;
In the last two years I&amp;#39;ve done hundreds of remote 1-on-1 meetings, both
as a contributor talking to my manager(s) and as a manager talking to
people in my team.&lt;/p&gt;
&lt;p&gt;
As a manager, I consider 1-on-1 meetings the most important
responsibility I have: empowering other people to do their best work can
greatly outmeasure any contribution I can give on my own.&lt;/p&gt;
&lt;p&gt;
What follows is some notes on what seems to work for me. It does not
represent by any means a proper research on the matter, so please take
it with a grain of salt.&lt;/p&gt;
&lt;div id="outline-container-long-term-objectives" class="outline-3"&gt;
&lt;h3 id="long-term-objectives"&gt;
Long term objectives
&lt;/h3&gt;
&lt;div id="outline-text-long-term-objectives" class="outline-text-3"&gt;
&lt;p&gt;
Regular 1-on-1 meetings can certainly be used to solve immediate,
short-term blockers or clarify some questions, but the long term
objective is building a working professional relationship where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Both parties can safely be vulnerable and address deep issues that
make day to day work more difficult. This can be anything from anxiety
about the world at large, to having to homeschool kids, to
difficulties in multitasking different responsibilities. Note that as
a manager, you&amp;#39;re allowed to show that you&amp;#39;re struggling to the other
person. We&amp;#39;re human after all and that helps building transparent
relationships. Needless to say, confidentiality and respect are
paramount.&lt;/li&gt;
&lt;li&gt;Trust takes time and effort. It may take months before people open up
and start addressing important topics: what motivates them, problems
they see in the team and in the company, their own aspirations and
ultimately work on their professional growth.&lt;/li&gt;
&lt;li&gt;Lead by example: if you ask a complex question (e.g. what do you wanna
focus on this year?), answer it yourself first, so that they
understand better.&lt;/li&gt;
&lt;li&gt;Always prepare something to talk about: even if it seems artificial,
it will gradually tune your perception to be on the lookout between
meetings for topics to talk about and it will get easier. At the same
time, if the other party doesn&amp;#39;t provide topics over multiple
meetings, make sure you address that. There&amp;#39;s always something to talk
about.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-preparation" class="outline-3"&gt;
&lt;h3 id="preparation"&gt;
Preparation
&lt;/h3&gt;
&lt;div id="outline-text-preparation" class="outline-text-3"&gt;
&lt;p&gt;
Regular 1-on-1 meetings require preparation: the easiest thing to do to
ease preparation is to keep an agenda document shared between the two
participants.&lt;/p&gt;
&lt;p&gt;
I can recommend a shared Google Doc, with sections titled by date and
sorted in reverse chronological order (most recent first).&lt;/p&gt;
&lt;p&gt;
This way you can:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Predictably add topics to discuss between meetings (it&amp;#39;s always at
the top).&lt;/li&gt;
&lt;li&gt;Have implicit time tracking, i.e. you know when something was
discussed.&lt;/li&gt;
&lt;li&gt;Easily collaborate in real time during the meeting to capture ideas
in a form that correctly reflects the other person&amp;#39;s thoughts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This system is by no means perfect (especially if you end up having
dozens of these documents), but it&amp;#39;s flexible enough that you can adapt
it to individual preferences. For example, I find that with some people
we can use the agenda to discuss topic asynchronously in writing
beforehand (and draw some conclusions in the meeting), while with other
we defer discussion to the meeting itself. With other ones, we end up
adding topics during the meeting as the conversation naturally goes in
different directions.&lt;/p&gt;
&lt;p&gt;
In addition, it&amp;#39;s important to prepare for the conversation, even for
just 5-10 minutes before the meeting. Having such time buffer not only
helps clarifying thoughts beforehand, but also gives you an opportunity
to stop thinking about what you were doing before and what you&amp;#39;re gonna
be doing after the meeting.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-during-the-meeting" class="outline-3"&gt;
&lt;h3 id="during-the-meeting"&gt;
During the meeting
&lt;/h3&gt;
&lt;div id="outline-text-during-the-meeting" class="outline-text-3"&gt;
&lt;ol&gt;
&lt;li&gt;Respect the agenda as much as you can, making sure that you either
address all points on it or explicitly say &amp;#34;We&amp;#39;ll need to address
this in a separate call. Can it wait our next planned session or do
you wanna schedule an earlier follow-up?&amp;#34;.&lt;/li&gt;
&lt;li&gt;If the conversation drifts to a completely different direction and
time is running tight, make sure both parties are happy to discuss
that direction at the expense of other topics. You always have the
option to capture the new topic and address it at a later stage.&lt;/li&gt;
&lt;li&gt;Be at ease in saying &amp;#34;I don&amp;#39;t know, but here&amp;#39;s how I plan to find
out and here&amp;#39;s when I&amp;#39;m gonna report back about it&amp;#34;. At the end of
the day, trust is built on transparency and predictability.&lt;/li&gt;
&lt;li&gt;When the other person explains something to you and you wanna make
sure you got it right, repeat it back and ask for confirmation. By
doing that, you both check your understanding and implicitly ask the
other person to check their own explanation. I&amp;#39;ve used this
technique in all sorts of other conversations in my professional and
personal life and it does absolute wonders in clarifying
expectations on both sides.&lt;/li&gt;
&lt;li&gt;If you take notes, learn how to do that quickly by using keywords,
then fill in the blanks later (and ask for confirmation to the other
person once done). Writing can break the flow of the conversation,
so it needs to be done carefully in order to minimize its impact.&lt;/li&gt;
&lt;li&gt;If a conversation topic implies someone taking an action, explicitly
state that in the form of &amp;#34;I will&amp;#34; or &amp;#34;You will&amp;#34; or &amp;#34;Someone else
will&amp;#34;, with an indication of when that would happen.&lt;/li&gt;
&lt;li&gt;If the meeting resulted in some actions, recap them at the end of
the call.&lt;/li&gt;
&lt;li&gt;Video conversations have a different pace - let people speak, listen
carefully, slow down, repeat a few times if needed.&lt;/li&gt;
&lt;li&gt;Explicitly ask the other person if they think a topic has been
exhausted before moving on.&lt;/li&gt;
&lt;li&gt;Respectfully ask the other person how they&amp;#39;re doing, leaving them
space to decide what they can comfortably share with you. Do no pry
and always qualify your questions with the reason why you&amp;#39;re asking
them. For example, I&amp;#39;ve been recently asking people how they&amp;#39;re
dealing with the COVID-19 pandemic, because I noticed erratic
working patterns that suggest they may be working too much (for many
people, work is much simpler to deal with, so they end up using it
as a safe haven - I&amp;#39;m not a psychologist though so this is another
thing to take with a grain of salt).&lt;/li&gt;
&lt;li&gt;Provide context: while this is important in any company, I believe it&amp;#39;s fundamental
in a remote company because people have more limited opportunities to gather
context by casually taking part to unscheduled conversations. So if I&amp;#39;m
discussing a specific project that I think it&amp;#39;s connected to other projects,
I&amp;#39;ll share that. More often than not, the person on the other end will
appreciate the additional information and will make good use of it.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-after-the-meeting" class="outline-3"&gt;
&lt;h3 id="after-the-meeting"&gt;
After the meeting
&lt;/h3&gt;
&lt;div id="outline-text-after-the-meeting" class="outline-text-3"&gt;
&lt;p&gt;
If you have any action, just do it as early as possible. Your ability to
follow up is by far the most important factor in building trust. If the
other person asks you to do something, you agree to it and you don&amp;#39;t,
they will not ask you again.&lt;/p&gt;
&lt;p&gt;
If at any point you realize you didn&amp;#39;t do something you promised to do,
acknowledge your shortcoming, apologize and make up for it. It happens,
and if you&amp;#39;re transparent about it usually the other person will
understand.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-topics" class="outline-3"&gt;
&lt;h3 id="topics"&gt;
Topics
&lt;/h3&gt;
&lt;div id="outline-text-topics" class="outline-text-3"&gt;
&lt;p&gt;
1-on-1 meetings are structured around the people involved: while you can
definitely start from some guideline questions, they should over time
develop into a unique conversation.&lt;/p&gt;
&lt;p&gt;
That said, over the course of multiple meetings you should aim at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unblocking specific issue related to current streams of work,
e.g. &amp;#34;I&amp;#39;m undecided on how to build X, if using this or that.&amp;#34;&lt;/li&gt;
&lt;li&gt;Clarify responsibilities, e.g. &amp;#34;Yes, you need to take care of X, while
Alice can take care of Y.&amp;#34;&lt;/li&gt;
&lt;li&gt;Provide feedback on work done, e.g. &amp;#34;I really liked how you did X
because…&amp;#34; or &amp;#34;I&amp;#39;d like to speak about Y, as there&amp;#39;s an opportunity
to improve Z.&amp;#34;&lt;/li&gt;
&lt;li&gt;Useful things to learn about, e.g. &amp;#34;As you&amp;#39;re working on X, you might
enjoy learning about Y.&amp;#34;&lt;/li&gt;
&lt;li&gt;Connect the dots with other projects, e.g. &amp;#34;As you&amp;#39;re working on X,
you might be interested to speak to Alice, as she&amp;#39;s working on Y,
which is related to X as…&amp;#34;&lt;/li&gt;
&lt;li&gt;Happiness, satisfaction, and future work e.g. &amp;#34;If we look at the
roadmap, there&amp;#39;s X, Y, Z. Do they interest you? Which one would be
your initial preference to work on?&amp;#34;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One important aspect is balance: too often 1-on-1 meetings are focused on the
day to day work and don&amp;#39;t cover the larger picture. This is why there should be
scheduled Feedback Sessions where you go through some meta-questions that allow
expanding scope.&lt;/p&gt;
&lt;p&gt;
These are some examples of questions useful for those sessions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Are you happy about the work you&amp;#39;re doing? Is it satisfactory?&lt;/li&gt;
&lt;li&gt;Looking at X time period, can you point out a piece of your work you&amp;#39;re proud of?&lt;/li&gt;
&lt;li&gt;Looking at the same time period, can you point out 3 team
achievements you&amp;#39;re proud of?&lt;/li&gt;
&lt;li&gt;What should the team focus on in this quarter?&lt;/li&gt;
&lt;li&gt;If you had a magic wand and could instantly change anything in the
team, what would that be?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In general, Feedback Sessions are an opportunity to look at the larger picture
and think about the future. For more inspiration, you can consult the &lt;a href="https://help.small-improvements.com/article/264-24-questions-to-ask-in-your-next-11-meeting"&gt;Small
Improvements guide to 1-on-1 meetings&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-conclusions" class="outline-3"&gt;
&lt;h3 id="conclusions"&gt;
Conclusions
&lt;/h3&gt;
&lt;div id="outline-text-conclusions" class="outline-text-3"&gt;
&lt;p&gt;
As mentioned before, this is by no means an exhaustive guide, but a collection
on thoughts based on my experience. At the end of the day, if you always focus
on listening to the other person and acting swiftly on their feedback, you will
get good results.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>Wed, 04 Nov 2020 09:10:38 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/one-on-one-notes/</link>
  <guid>https://claudio-ortolina.org/posts/one-on-one-notes/---https://claudio-ortolina.org/posts/one-on-one-notes/</guid>
</item>
<item>
  <title>A Short Profiling Story</title>
  <description>From Posts on Claudio Ortolina: &lt;p&gt;
While transcribing
&lt;a href="https://www.elixirconf.eu/talks/The-Perils-of-Large-Files/"&gt;the talk I
gave at the last ElixirConf.eu&lt;/a&gt; conference, one of my colleagues
pointed out that I glossed over the details of one of the examples. This
prompted me to do some digging and I want to share what I found.&lt;/p&gt;
&lt;div id="outline-container-the-problem" class="outline-3"&gt;
&lt;h3 id="the-problem"&gt;
The problem
&lt;/h3&gt;
&lt;div id="outline-text-the-problem" class="outline-text-3"&gt;
&lt;p&gt;
The example in question is a module responsible to fetch a file from a
remote source and write it at the specified path.&lt;/p&gt;
&lt;p&gt;
The implementation is very simplistic and lacks both error handling and
retry logic.&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defmodule&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Perils.Examples.Store&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; write(file_name, url) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; with {&lt;span style="color:#e6db74"&gt;:ok&lt;/span&gt;, data} &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt; get(url) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;File&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write!(file_name, data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defp&lt;/span&gt; get(url) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;:httpc&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;request(&lt;span style="color:#e6db74"&gt;:get&lt;/span&gt;, {&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_charlist(url), []}, [], [])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;:ok&lt;/span&gt;, result} &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {{_, &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;, _}, _headers, body} &lt;span style="color:#f92672"&gt;=&lt;/span&gt; result
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#e6db74"&gt;:ok&lt;/span&gt;, body}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; error
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Looking at the code, we can see that it relies on &lt;code class="verbatim"&gt;:httpc&lt;/code&gt;, the http
client that ships with Erlang/OTP.&lt;/p&gt;
&lt;p&gt;
Both in my talk and in the initial transcription draft, I pointed out
that running this code with a 12MB file would result in a memory usage
peak at around 350/375MB, but didn&amp;#39;t really look into why.&lt;/p&gt;
&lt;p&gt;
&lt;img src="https://claudio-ortolina.org/img/a-short-profiling-story/before.png" alt="A chart visualizing a 350MB memory spike" class="left" /&gt;
&lt;/p&gt;
&lt;p&gt;
Such delta between the file size and peak memory usage is suspicious and
worth investigating.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-the-investigation" class="outline-3"&gt;
&lt;h3 id="the-investigation"&gt;
The investigation
&lt;/h3&gt;
&lt;div id="outline-text-the-investigation" class="outline-text-3"&gt;
&lt;p&gt;
I started by setting up an &lt;a href="https://github.com/parroty/exprof"&gt;exprof&lt;/a&gt;
test, so that I could profile resource usage associated with the
problematic function.&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defmodule&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;A&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ExProf.Macro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; run(url) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; profile &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Perils.Examples.Store&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write(&lt;span style="color:#e6db74"&gt;&amp;#34;magazine.pdf&amp;#34;&lt;/span&gt;, url)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://web-examples.pspdfkit.com/magazine/example.pdf&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;#12MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {records, _block_result} &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;A&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(url)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; total_percent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Enum&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;reduce(records, &lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;(&amp;amp;1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;percent &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &amp;amp;2))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;IO&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;inspect(&lt;span style="color:#e6db74"&gt;&amp;#34;total = &lt;/span&gt;&lt;span style="color:#e6db74"&gt;#{&lt;/span&gt;total_percent&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The result (with some lines omitted) shows that most of the time
(51.74%) is spent converting the binary response body to a list inside
the &lt;code class="verbatim"&gt;maybe_format_body/2&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FUNCTION CALLS % TIME [uS / CALLS]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -------- ----- ------- ---- [----------]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;omitted&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; erlang:iolist_to_binary/1 1 20.46 49705 [ 49705.00]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; erlang:binary_to_list/1 1 27.54 66887 [ 66887.00]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; httpc:maybe_format_body/2 1 51.74 125664 [ 125664.00]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
While this is not an indication of higher memory usage per se, it&amp;#39;s a
good lead: binary to list conversion can be memory intensive.&lt;/p&gt;
&lt;p&gt;
I then looked at the
&lt;a href="https://github.com/erlang/otp/blob/3f21ce1e6a5d6c548867fa4bc9a8c666c626ade1/lib/inets/src/http_client/httpc.erl#L655-L661"&gt;source
for &lt;code class="verbatim"&gt;maybe_format_body/2&lt;/code&gt;&lt;/a&gt;, making sure to match on the OTP version I
tested against (23.1.1).&lt;/p&gt;
&lt;div class="src src-erlang"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-erlang" data-lang="erlang"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; maybe_format_body(BinBody, Options) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; proplists:&lt;span style="color:#a6e22e"&gt;get_value&lt;/span&gt;(body_format, Options, string) &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; string &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; binary_to_list(BinBody);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _ &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BinBody
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
As expected, the function uses &lt;code class="verbatim"&gt;binary_to_list/1&lt;/code&gt; to transform the
response binary body into a list. Luckily, this behaviour can be tweaked
via the &lt;code class="verbatim"&gt;body_format&lt;/code&gt; option, which defaults to &lt;code class="verbatim"&gt;string&lt;/code&gt; (as in Erlang
string, which maps to a character list in Elixir).&lt;/p&gt;
&lt;p&gt;
Searching for &lt;code class="verbatim"&gt;body_format&lt;/code&gt; in
&lt;a href="http://erlang.org/doc/man/httpc.html#request-5"&gt;the Erlang docs for
&lt;code class="verbatim"&gt;request/5&lt;/code&gt;&lt;/a&gt; shows that indeed it&amp;#39;s possible to tweak our problematic
implementation to:&lt;/p&gt;
&lt;div class="src src-elixir"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;:httpc&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;request(&lt;span style="color:#e6db74"&gt;:get&lt;/span&gt;, {&lt;span style="color:#a6e22e"&gt;String&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_charlist(url), []}, [], &lt;span style="color:#e6db74"&gt;body_format&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:binary&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
With this change, memory usage decreases dramatically, showing a delta
only slightly larger than the file size.&lt;/p&gt;
&lt;p&gt;
&lt;img src="https://claudio-ortolina.org/img/a-short-profiling-story/after.png" alt="A chart visualizing a 15MB memory spike" class="left" /&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="outline-container-conclusion" class="outline-3"&gt;
&lt;h3 id="conclusion"&gt;
Conclusion
&lt;/h3&gt;
&lt;div id="outline-text-conclusion" class="outline-text-3"&gt;
&lt;p&gt;
This whole investigation got me thinking, as the &lt;code class="verbatim"&gt;body_format&lt;/code&gt; option
had been in the docs all along, yet I hadn&amp;#39;t seen it. I can find three
reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The overall logic in the example doesn&amp;#39;t really care about the
response body contents, as it just writes them to a file. Without
seeing that response, there was no way for me to even notice its
type.&lt;/li&gt;
&lt;li&gt;&lt;code class="verbatim"&gt;File.write/2&lt;/code&gt; accepts binaries, strings and character lists - again
I didn&amp;#39;t have a reason to even wonder about the type used to
represent that returned response body.&lt;/li&gt;
&lt;li&gt;Working primarily in Elixir, everything tends to be either a string
or a binary. I just &amp;#34;forget&amp;#34; that character lists exist, which lead
to the implicit assumption that this would be the default for
&lt;code class="verbatim"&gt;:httpc&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In other words, I didn&amp;#39;t know what to search in the docs. Profiling
tools helped me understand the problem space and pointed me in the right
direction.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>Tue, 03 Nov 2020 11:42:42 +0000</pubDate>
  <link>https://claudio-ortolina.org/posts/a-short-profiling-story/</link>
  <guid>https://claudio-ortolina.org/posts/a-short-profiling-story/---https://claudio-ortolina.org/posts/a-short-profiling-story/</guid>
</item>
<item>
  <title>The BEAM marches forward</title>
  <description>From Underjord.io: The BEAM is the virtual machine that Erlang and Elixir runs on. It is widely cited as a battle-tested piece of software though I don&amp;rsquo;t know in which wars it has seen action. It has definitely paid its dues in the telecom space as well as globally scaled projects such as Whatsapp and Discord. It is well suited to tackle soft-realtime distributed systems with heavy concurrency. It has been a good platform chugging along.</description>
  <pubDate>Mon, 26 Oct 2020 09:00:00 +0000</pubDate>
  <link>https://underjord.io/the-beam-marches-forward.html</link>
  <guid>https://underjord.io/the-beam-marches-forward.html---https://underjord.io/the-beam-marches-forward.html</guid>
</item>
<item>
  <title>Supervision trees, an example in Elixir</title>
  <description>From Underjord.io: So any time recently that I&amp;rsquo;ve gone looking for a good overview of supervision trees in Elixir I haven&amp;rsquo;t found what I want. I&amp;rsquo;m pretty sure I used to find some that covered making simple supervisors and workers without assuming you want a module for each Supervisor. I now believe those were following ye olde Supervisor.Spec which had helpers for that. How to make a module based Supervisor is in the docs so I won&amp;rsquo;t be spending time on that.</description>
  <pubDate>Wed, 21 Oct 2020 07:00:00 +0000</pubDate>
  <link>https://underjord.io/elixir-supervision-trees-an-example.html</link>
  <guid>https://underjord.io/elixir-supervision-trees-an-example.html---https://underjord.io/elixir-supervision-trees-an-example.html</guid>
</item>
<item>
  <title>Stupid solutions: Live server push without JS</title>
  <description>From Underjord.io: So in my post Is this evil? I covered a way of tracking users with CSS. While thinking about those weird ways of using the web I also started thinking about pushing live data to clients without JS. Or at least maintaining a connection. So WebSockets requires JS. WebRTC requires JS. Even HLS (video streaming), which would otherwise be super cool, with captions for accessibility. But no. Or rather, maybe on Apple platforms.</description>
  <pubDate>Fri, 25 Sep 2020 17:00:00 +0000</pubDate>
  <link>https://underjord.io/live-server-push-without-js.html</link>
  <guid>https://underjord.io/live-server-push-without-js.html---https://underjord.io/live-server-push-without-js.html</guid>
</item>
<item>
  <title>Vlog - 2020-09-22 - Helping Junior Developers</title>
  <description>From Underjord.io: Pondering one of the things I want to make an impact in: helping inexperienced developers get the traction they need to find their way into the industry.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn't support embedded videos.</description>
  <pubDate>Tue, 22 Sep 2020 15:45:00 +0000</pubDate>
  <link>https://underjord.io/vlog-2020-09-22.html</link>
  <guid>https://underjord.io/vlog-2020-09-22.html---https://underjord.io/vlog-2020-09-22.html</guid>
</item>
<item>
  <title>Absinthe subscriptions with ReasonML and Urql</title>
  <description>From Maarten van Vliet: Lately I&amp;rsquo;ve been looking into ReasonML as a language to pick up. In the past I&amp;rsquo;ve dabbled with Elm and really enjoyed it but it&amp;rsquo;s lack of interoperability with javascript hampers its development. ReasonML offers a far better story in this regards, there are excellent React and React-Native libraries/bindings available as well as bindings for popular Graphql clients. That, combinated with great type-safety makes it a compelling alternative to Elm and Typescript.</description>
  <pubDate>Mon, 21 Sep 2020 09:08:54 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2020/09/21/reason_urql_and_absinthe/</link>
  <guid>https://maartenvanvliet.nl/2020/09/21/reason_urql_and_absinthe/---https://maartenvanvliet.nl/2020/09/21/reason_urql_and_absinthe/</guid>
</item>
<item>
  <title>Vlog - 2020-09-17</title>
  <description>From Underjord.io: A brief weekly run-down of what I've been doing.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn't support embedded videos.</description>
  <pubDate>Thu, 17 Sep 2020 15:45:00 +0000</pubDate>
  <link>https://underjord.io/vlog-2020-09-17.html</link>
  <guid>https://underjord.io/vlog-2020-09-17.html---https://underjord.io/vlog-2020-09-17.html</guid>
</item>
<item>
  <title>Is this evil?</title>
  <description>From Underjord.io: I try to be a friendly citizen of the web. I try to keep my site in good shape. It should load quickly, track nothing beyond your basic server logs, not hassle you about cookies, GDPR or my newsletter. I do have a newsletter but it won't pop up in your face here. I try to stay firmly on the side of friendly and a good experience in how I run this site.</description>
  <pubDate>Tue, 15 Sep 2020 14:00:01 +0000</pubDate>
  <link>https://underjord.io/is-this-evil.html</link>
  <guid>https://underjord.io/is-this-evil.html---https://underjord.io/is-this-evil.html</guid>
</item>
<item>
  <title>Vlog - 2020-09-07</title>
  <description>From Underjord.io: A brief weekly run-down of what I've been doing and what I'm planning on doing.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn't support embedded videos.</description>
  <pubDate>Tue, 08 Sep 2020 08:00:00 +0000</pubDate>
  <link>https://underjord.io/vlog-2020-09-07.html</link>
  <guid>https://underjord.io/vlog-2020-09-07.html---https://underjord.io/vlog-2020-09-07.html</guid>
</item>
<item>
  <title>Vlog - 2020-09-07</title>
  <description>From Underjord.io: A brief weekly run-down of what I've been doing and what I'm planning on doing.
 function switch_video(element) { var src = element.getAttribute("href"); var video = element.parentElement.querySelector("video"); var sources = video.querySelector("source"); video.pause(); sources.setAttribute("src", src); video.load(); video.play(); return false; }  4K HD 720 Sorry, your browser doesn't support embedded videos.</description>
  <pubDate>Tue, 08 Sep 2020 08:00:00 +0000</pubDate>
  <link>https://underjord.io/vlog-2020-07-07.html</link>
  <guid>https://underjord.io/vlog-2020-07-07.html---https://underjord.io/vlog-2020-07-07.html</guid>
</item>
<item>
  <title>Nerves-keyboard - Running a mechanical keyboard with Elixir</title>
  <description>From Underjord.io: Chris Dosé was interviewed by us on Elixir Mix. When he spoke about his work on a Nerves-powered keyboard I knew this was a project I wanted to try out. So I dropped into their dev channel, acquired the hardware (thanks for the help) and have done some playing around with it.  So what is the draw of a Nerves keyboard? Well, you can program it in Elixir. That's a big one for me personally.</description>
  <pubDate>Tue, 01 Sep 2020 09:15:00 +0000</pubDate>
  <link>https://underjord.io/nerves-keyboard-running-a-mechanical-keyboard-with-elixir.html</link>
  <guid>https://underjord.io/nerves-keyboard-running-a-mechanical-keyboard-with-elixir.html---https://underjord.io/nerves-keyboard-running-a-mechanical-keyboard-with-elixir.html</guid>
</item>
<item>
  <title>Urql graphql subscriptions with Absinthe</title>
  <description>From Maarten van Vliet: Many Graphql stacks use Apollo as the javascript client library. It&amp;rsquo;s well maintained, popular and has lot&amp;rsquo;s of examples. It also works together with Absinthe, the Elixir Graphql library. If you need to use graphql subscriptions in combination with Apollo and Absinthe there is the socket-apollo-link to connect the two.
There&amp;rsquo;s another interesting javascript client library for Graphql on the horizon, Urql. It&amp;rsquo;s a bit more lightweight and looks really promising.</description>
  <pubDate>Sat, 29 Aug 2020 11:08:54 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2020/08/29/absinthe_and_urql/</link>
  <guid>https://maartenvanvliet.nl/2020/08/29/absinthe_and_urql/---https://maartenvanvliet.nl/2020/08/29/absinthe_and_urql/</guid>
</item>
<item>
  <title>Simple Solutions: UI choices without JS</title>
  <description>From Underjord.io: I've been looking at creating some progressively enhanced UI which shouldn't require JS for any basic operations. The idea being that I can accelerate and simplify any operation with interactivity provided by Javascript but I won't implement things in a way that requires JS.
The demonstration below is one such example which can be used to give a very common type of interactivity without using Javascript. You can select an option or even expand a selection (similar method, but with a checkbox) without a line of Javascript.</description>
  <pubDate>Wed, 26 Aug 2020 15:00:00 +0000</pubDate>
  <link>https://underjord.io/simple-solutions-ui-choices-without-js.html</link>
  <guid>https://underjord.io/simple-solutions-ui-choices-without-js.html---https://underjord.io/simple-solutions-ui-choices-without-js.html</guid>
</item>
<item>
  <title>Beam Bloggers Webring</title>
  <description>From Underjord.io: This is a brief announcement. If you are interested in following bloggers in the BEAM, Elixir, Erlang, etc. ecosystem or you blog yourself. Please check out beambloggers.com to get in on the old-school webring action. Bloggers can join it. It might lead to some traffic. Mostly it is a small nod to a stranger old time on the web when sites organized in rings for discoverability. I think the need for discoverability remains and some webrings have been showing up again.</description>
  <pubDate>Wed, 19 Aug 2020 08:00:00 +0000</pubDate>
  <link>https://underjord.io/beambloggers-webring.html</link>
  <guid>https://underjord.io/beambloggers-webring.html---https://underjord.io/beambloggers-webring.html</guid>
</item>
<item>
  <title>Dynamically Configure Your Plugs at Run-time</title>
  <description>From My.Thoughts v1: Intro In this blog post, we&amp;rsquo;ll be talking about the various ways that Plugs [1] can be dynamically configured, why you would need to configure your Plugs dynamically, and I also introduce two libraries that I wrote that attempt to solve a particular set of these problems. If you have been a reader of the blog for some time now, this post is a slight deviation from the usual in-depth project based tutorial that I usually present.</description>
  <pubDate>Tue, 18 Aug 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/plug-runtime-config/</link>
  <guid>https://akoutmos.com/post/plug-runtime-config/---https://akoutmos.com/post/plug-runtime-config/</guid>
</item>
<item>
  <title>The Strong Technologies</title>
  <description>From Underjord.io: I feel like a curmudgeonly sort recently. I'm honestly a pretty optimistic and positive person. But I'm becoming increasingly technically curmudgeonly. I don't think it is age turning me conservative. But I feel like I'm moving back to what I find tried and true in many ways. I feel resentful in a lot of cases where I can't really reasonably go back. So what am I talking about here?</description>
  <pubDate>Fri, 14 Aug 2020 09:30:00 +0000</pubDate>
  <link>https://underjord.io/the-strong-technologies.html</link>
  <guid>https://underjord.io/the-strong-technologies.html---https://underjord.io/the-strong-technologies.html</guid>
</item>
<item>
  <title>Better Terminal Git Diffs</title>
  <description>From Mitchell Hanberg's Blog: Normally, our terminal git diffs look like this.

![ normal git diff ](https://res.cloudinary.com/mhanberg/image/upload/v1597177346/normal-git-diff.png){: .bg-transparent }

Let's improve our experience by adding syntax highlighting!

To accomplish this, we're going to replace the pager that git uses with a tool called [delta](https://github.com/dandavison/delta). Add the following to `~/.gitconfig`.

```
[core]
  pager = "delta"
```

Let's see what our git diffs look like now.

![git diff using delta as the pager](https://res.cloudinary.com/mhanberg/image/upload/v1597174958/git-diff-delta.png){: .bg-transparent}

This looks great, but I think we can do even better. Let's configure `delta` to use the same theme as our terminal. `delta` uses [bat](https://github.com/sharkdp/bat/) for syntax highlighting, so we can use any theme that `bat` can use.

Grab your theme (you'll have to find a TextMate/Sublime Text version of it), copy it into `~/.config/bat/themes`, and add the following option to your pager configuration.

```
[core]
  pager = "delta --theme='Forest Night'"
```

Now, the highlighting blends seamlessly with the rest of my development experience.

![git diff using delta as the pager](https://res.cloudinary.com/mhanberg/image/upload/v1597175033/delta-git-diff-with-theme.png){: .bg-transparent}

One last improvement you can make is to use the same diff colors that your theme uses. I copied them from the theme file and added the following options to my pager configuration.

```
[core]
  pager = "delta --plus-color=\"#333B2F\" --minus-color=\"#382F32\" --theme='Forest Night'"
```

![git diff using delta as the pager](https://res.cloudinary.com/mhanberg/image/upload/v1597175171/delta-git-diff-with-theme-with-diff-color.png){: .bg-transparent}

Okay, now that our diffs look good, we can finally get back to work 😄.</description>
  <pubDate>Wed, 12 Aug 2020 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/better-terminal-git-diffs/</link>
  <guid>https://www.mitchellhanberg.com/better-terminal-git-diffs/---https://www.mitchellhanberg.com/better-terminal-git-diffs/</guid>
</item>
<item>
  <title>Testing GenServers with Erlang Trace</title>
  <description>From The Great Code Adventure: Thanks to lots of Googling and some help from a friend, I learned you can test that a GenServer received a message with the help of Erlang tracing.</description>
  <pubDate>Tue, 11 Aug 2020 01:07:11 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/testing-genservers-with-erlang-trace/</link>
  <guid>https://www.thegreatcodeadventure.com/testing-genservers-with-erlang-trace/---5f31eda9da9f050039f4ed12</guid>
</item>
<item>
  <title>The State of Elixir HTTP Clients</title>
  <description>From My.Thoughts v1: This post was sponsored by the kind folks over at Check them out at https://appsignal.com In today&amp;rsquo;s post, we&amp;rsquo;ll learn about the Elixir HTTP client libraries Mint and Finch. We&amp;rsquo;ll discuss how Finch is built on top of Mint and what the benefits are of this abstraction layer. We&amp;rsquo;ll also talk about some of the existing HTTP client libraries in the ecosystem and discuss some of the things that make Mint and Finch different.</description>
  <pubDate>Mon, 10 Aug 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/elixir-http-clients/</link>
  <guid>https://akoutmos.com/post/elixir-http-clients/---https://akoutmos.com/post/elixir-http-clients/</guid>
</item>
<item>
  <title>The best parts of Visual Studio Code are proprietary</title>
  <description>From Underjord.io: I've been very surprised and delighted over a number of years now by Microsoft's strong efforts in open source. I understand the skeptics, I was on Slashdot when they tried to sue Linux out of existence and I think only time will tell. I figure MS contributing is better than them hunting Linux distributions for sport. So I was mostly onboard for Microsofts efforts and I've especially found Visual Studio Code useful.</description>
  <pubDate>Mon, 03 Aug 2020 07:25:00 +0000</pubDate>
  <link>https://underjord.io/the-best-parts-of-visual-studio-code-are-proprietary.html</link>
  <guid>https://underjord.io/the-best-parts-of-visual-studio-code-are-proprietary.html---https://underjord.io/the-best-parts-of-visual-studio-code-are-proprietary.html</guid>
</item>
<item>
  <title>OTP as the Core of Your Application Part 2</title>
  <description>From My.Thoughts v1: Intro In OTP as the Core of Your Application Part 1 we covered what exactly the Actor Model is, how it is implemented in the Erlang Virtual machine (or BEAM for short), and some of the benefits of this kind of pattern. In the final part of this series, we&amp;rsquo;ll be implementing the Actor Model specific parts of our application, putting together a simple stress test tool, and comparing the performance characteristics of a traditional database centric application where every request is dependant on a round trip to the database.</description>
  <pubDate>Wed, 29 Jul 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/actor-model-genserver-app-two/</link>
  <guid>https://akoutmos.com/post/actor-model-genserver-app-two/---https://akoutmos.com/post/actor-model-genserver-app-two/</guid>
</item>
<item>
  <title>Five Buck Fatigue</title>
  <description>From Underjord.io: This is about a reaction I noticed with myself in response to membership programs, patreons and individual sponsorship as I've run into significantly more of these following Corona. The advertising-supported model has confirmed many of the concerns about its sustainability and creators are looking for more reliable options. I think this feeling I have might be something other people experience and something for creators to be prepared for.
Creating anything in a sustainable fashion is hard work.</description>
  <pubDate>Thu, 23 Jul 2020 23:00:00 +0000</pubDate>
  <link>https://underjord.io/five-buck-fatigue.html</link>
  <guid>https://underjord.io/five-buck-fatigue.html---https://underjord.io/five-buck-fatigue.html</guid>
</item>
<item>
  <title>Inspect Yourself Before You Wreck Yourself</title>
  <description>From Sayan Chakraborty | Blog | Research | AI | Engineering | Tech: A post where we investigate why our game engine's comments system went down</description>
  <pubDate>Sun, 19 Jul 2020 04:40:32 GMT</pubDate>
  <link>https://sayanc93.github.ioelixir-erlang-debugging-otp</link>
  <guid>https://sayanc93.github.ioelixir-erlang-debugging-otp---https://sayanc93.github.ioelixir-erlang-debugging-otp</guid>
</item>
<item>
  <title>OTP as the Core of Your Application Part 1</title>
  <description>From My.Thoughts v1: Intro In this two part series, we&amp;rsquo;ll be taking a deep dive into what exactly the Actor Model is, how exactly the Actor Model is implemented in Elixir/Erlang and how we can leverage this pattern in a pragmatic way from within our applications.
To really understand these concepts, we will be writing a Phoenix application that relies on GenServers as the primary datasource that powers our business logic. In regards to our database, we will only be leveraging it as a means of persistent storage only when needed.</description>
  <pubDate>Tue, 30 Jun 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/actor-model-genserver-app/</link>
  <guid>https://akoutmos.com/post/actor-model-genserver-app/---https://akoutmos.com/post/actor-model-genserver-app/</guid>
</item>
<item>
  <title>&amp;quot;More than one thing at a time&amp;quot;</title>
  <description>From Underjord.io: On a recent Elixir Outlaws episode Chris Keathley told us all a nice story of the advantages of Elixir as opposed to Ruby. His frustration with Ruby and appreciation of how Elixir works resonate at the frequency of my own frustrations and joys. I believe my titular quote is accurate, that's one of the primary things he noted. How nice it is to use a runtime that can do more than one thing at a time.</description>
  <pubDate>Wed, 17 Jun 2020 17:45:00 +0000</pubDate>
  <link>https://underjord.io/more-than-one-thing-at-a-time.html</link>
  <guid>https://underjord.io/more-than-one-thing-at-a-time.html---https://underjord.io/more-than-one-thing-at-a-time.html</guid>
</item>
<item>
  <title>The Repository Pattern, Ecto, and Database-less Testing</title>
  <description>From My.Thoughts v1: Intro In this blog post, we&amp;rsquo;ll be talking about what exactly the repository pattern is, how it applies to Ecto, and how you can go about testing your Ecto backed applications without using a database. We&amp;rsquo;ll play around with this concept by putting together a simple Elixir application that leverages Postgres during development. But, then we will write some tests that make use of a database-less mock Repo. Without further ado, let&amp;rsquo;s dive right into things!</description>
  <pubDate>Tue, 02 Jun 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/ecto-repo-testing/</link>
  <guid>https://akoutmos.com/post/ecto-repo-testing/---https://akoutmos.com/post/ecto-repo-testing/</guid>
</item>
<item>
  <title>Elixir and ETS Alchemy</title>
  <description>From Sayan Chakraborty | Blog | Research | AI | Engineering | Tech: A post where we dive deep into ETS, table management, best practices and handling/debugging ETS tables in production</description>
  <pubDate>Sat, 30 May 2020 04:40:32 GMT</pubDate>
  <link>https://sayanc93.github.ioelixir-erlang-and-ets-alchemy</link>
  <guid>https://sayanc93.github.ioelixir-erlang-and-ets-alchemy---https://sayanc93.github.ioelixir-erlang-and-ets-alchemy</guid>
</item>
<item>
  <title>Using Mnesia in your Elixir application</title>
  <description>From My.Thoughts v1: This post was sponsored by the kind folks over at Check them out at https://appsignal.com Using Mnesia in an Elixir Application In today&amp;rsquo;s post we&amp;rsquo;ll be learning about what exactly Mnesia is, when you should reach for a tool like Mnesia, and some of the pros and cons of using using it. After covering the fundementals of Mnesia, we&amp;rsquo;ll dive right into a sample application where we will build an Elixir application that makes use of Mnesia as its database.</description>
  <pubDate>Mon, 25 May 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/using-mnesia/</link>
  <guid>https://akoutmos.com/post/using-mnesia/---https://akoutmos.com/post/using-mnesia/</guid>
</item>
<item>
  <title>WordPress &amp;amp; the gross inefficiencies</title>
  <description>From Underjord.io: My recent work with WordPress revived a frustration I built up while working with software from that era of the 2000's. Drupal, WordPress, Joomla and friends. Whenever you visit a page, the system will generate it from scratch.
Note: WordPress gets to take the brunt of the focus here. But it just happens to be what I recently used that re-ignited these frustrations. I believe this approach remains the standard for Drupal, Joomla, Mediawiki and many, many others.</description>
  <pubDate>Fri, 22 May 2020 12:00:00 +0000</pubDate>
  <link>https://underjord.io/wordpress-and-the-gross-inefficiences.html</link>
  <guid>https://underjord.io/wordpress-and-the-gross-inefficiences.html---https://underjord.io/wordpress-and-the-gross-inefficiences.html</guid>
</item>
<item>
  <title>Maqbool on System and Application Architecture @ Elixir Wizards Podcast</title>
  <description>From ⚡ Maqbool Khan: </description>
  <pubDate>Fri, 22 May 2020 05:02:07 GMT</pubDate>
  <link>https://www.maqbool.net/maqbool-on-system-and-application-architecture-elixir-wizards-podcast-8ed80cbc37e7</link>
  <guid>https://www.maqbool.net/maqbool-on-system-and-application-architecture-elixir-wizards-podcast-8ed80cbc37e7---https://www.maqbool.net/maqbool-on-system-and-application-architecture-elixir-wizards-podcast-8ed80cbc37e7</guid>
</item>
<item>
  <title>The WordPress merging problem</title>
  <description>From Underjord.io: WordPress is approximately the most popular CMS out there. I've worked with it plenty over the years, off and on, as clients, employers and others have needed websites.
After a while every WordPress site has ended up feeling slightly sticky, icky and annoying to deal with in the longer run. So I don't really recommend it these days. It remains a popular choice. Because people don't ask me first which is probably reasonable.</description>
  <pubDate>Mon, 18 May 2020 14:00:00 +0000</pubDate>
  <link>https://underjord.io/the-wordpress-merging-problem.html</link>
  <guid>https://underjord.io/the-wordpress-merging-problem.html---https://underjord.io/the-wordpress-merging-problem.html</guid>
</item>
<item>
  <title>A wall too tall - Nerves &amp; k3s</title>
  <description>From Underjord.io: Short update on the general state of things. Pandemic quarantine in full swing. Me and mine are doing fine. Thankfully. We are staying at home awaiting a baby. I'm likely to be fairly sporadic for a few months. But I do intend to keep writing. Most of my blog posts are written to be useful in the longer term. If you want more in-the-moment writing, the newsletter is more temporally anchored publication (signup further down, no pop-up).</description>
  <pubDate>Wed, 06 May 2020 23:00:00 +0000</pubDate>
  <link>https://underjord.io/a-wall-too-tall-nerves-k3s.html</link>
  <guid>https://underjord.io/a-wall-too-tall-nerves-k3s.html---https://underjord.io/a-wall-too-tall-nerves-k3s.html</guid>
</item>
<item>
  <title>Getting started with Phoenix LiveDashboard</title>
  <description>From My.Thoughts v1: Intro In this blog post, we&amp;rsquo;ll be talking about what exactly Phoenix LiveDashboard [1] is, what you gain by using it, and my opinions on when/how you should use Phoenix LiveDashboard. Finally, we&amp;rsquo;ll set up a Phoenix application that makes use of Phoenix LiveDashboard and we&amp;rsquo;ll also put together a simple load testing script to exercise the application a bit. Without further ado, let&amp;rsquo;s dive right into things!
What is Phoenix LiveDashboard?</description>
  <pubDate>Mon, 04 May 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/phoenix_live_dashboard/</link>
  <guid>https://akoutmos.com/post/phoenix_live_dashboard/---https://akoutmos.com/post/phoenix_live_dashboard/</guid>
</item>
<item>
  <title>We're All Project Managers Now</title>
  <description>From The Great Code Adventure: Engineers thinking like project managers makes for higher performing teams, whether you're remote and async or not.</description>
  <pubDate>Tue, 21 Apr 2020 11:29:05 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/were-all-project-managers-now/</link>
  <guid>https://www.thegreatcodeadventure.com/were-all-project-managers-now/---5e9ed047b66181003892c3c1</guid>
</item>
<item>
  <title>Self-evaluation improvements</title>
  <description>From Underjord.io: A little while back I released a tool for self-evaluating as a web developer. I have just now updated it to include some explanation and guidance for things topics where the learner indicates a need for it.
You can try it for yourself at Web Development - Self-evaluation. It is centered on inexperienced developers. It might be of some benefit to old dogs that need to remind themselves about tricks forgotten as well.</description>
  <pubDate>Tue, 21 Apr 2020 06:15:00 +0000</pubDate>
  <link>https://underjord.io/self-evaluation-improvements.html</link>
  <guid>https://underjord.io/self-evaluation-improvements.html---https://underjord.io/self-evaluation-improvements.html</guid>
</item>
<item>
  <title>Check yourself - Web developer self-evaluation</title>
  <description>From Underjord.io: I've been thinking a lot about inexperienced (junior, if you must) web developers and just how much there is to learn about programming in general but the web in particular. You often hear people say that you don't need to know everything but you should have a solid foundation. Well, how do you establish a solid foundation and how do you know if you have one? How do you get introduced to all the relevant terminology and how do you find out what you haven't learned yet?</description>
  <pubDate>Wed, 08 Apr 2020 06:30:00 +0000</pubDate>
  <link>https://underjord.io/check-yourself.html</link>
  <guid>https://underjord.io/check-yourself.html---https://underjord.io/check-yourself.html</guid>
</item>
<item>
  <title>How to Use gRPC in Elixir</title>
  <description>From My.Thoughts v1: This post was sponsored by the kind folks over at Check them out at https://appsignal.com How to use gRPC in Elixir In today’s post, we’ll learn what gRPC is, when you should reach for such a tool, and some of the pros and cons of using it. After going over an introduction of gRPC, we’ll dive right into a sample application where we’ll build an Elixir backend API powered by gRPC.</description>
  <pubDate>Tue, 31 Mar 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/elixir-grpc/</link>
  <guid>https://akoutmos.com/post/elixir-grpc/---https://akoutmos.com/post/elixir-grpc/</guid>
</item>
<item>
  <title>Synchronizing Go Routines with Channels and WaitGroups</title>
  <description>From The Great Code Adventure: In debugging a mysteriously hanging Go function recently, I learned something new about how to use WaitGroups and Channels to synchronize Go routines. Keep reading to learn more!</description>
  <pubDate>Fri, 27 Mar 2020 22:13:49 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/synchronizing-go-routines-with-channels-and-waitgroups/</link>
  <guid>https://www.thegreatcodeadventure.com/synchronizing-go-routines-with-channels-and-waitgroups/---5e7e727d3060df004484d80b</guid>
</item>
<item>
  <title>Structured logging in Elixir using Loki</title>
  <description>From My.Thoughts v1: Intro In this blog post, we&amp;rsquo;ll be talking about what exactly logging is, and how it differs from monitoring. We&amp;rsquo;ll also cover some best practices that should be considered when logging from within your application. In addition, we&amp;rsquo;ll cover the differences between structured and unstructured logs. And finally we&amp;rsquo;ll set up a simple Phoenix application backed by Postgres and run Loki along side it to collect all of our application logs.</description>
  <pubDate>Tue, 10 Mar 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/elixir-logging-loki/</link>
  <guid>https://akoutmos.com/post/elixir-logging-loki/---https://akoutmos.com/post/elixir-logging-loki/</guid>
</item>
<item>
  <title>I always want to do it all</title>
  <description>From Underjord.io: My brain has very little chill on a day-to-day basis. There are moments where I can find a very peaceful state of mind. Doing something menial in the garden for an extended time, cooling off outside after a while in a sauna, winding down after heavy exercise. At most other times my mind is usually working on something or I'm itching with the need to do things.
I've had to do a lot of pacing myself recently.</description>
  <pubDate>Mon, 09 Mar 2020 06:00:00 +0000</pubDate>
  <link>https://underjord.io/i-always-want-to-do-it-all.html</link>
  <guid>https://underjord.io/i-always-want-to-do-it-all.html---https://underjord.io/i-always-want-to-do-it-all.html</guid>
</item>
<item>
  <title>CI/CD with Phoenix, GitHub Actions, and Gigalixir</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;I like to start my side projects by immediately configuring them with a CI pipeline and automatic deployments. I normally go with Heroku, but Heroku has some drawbacks when deploying Elixir applications.&lt;/p&gt;
&lt;p&gt;Luckily, another PaaS solution exists and is designed specifically for Elixir, &lt;a href="https://www.gigalixir.com"&gt;Gigalixir&lt;/a&gt;! Let's build a project called "Pipsqueak" and learn how to automatically test and deploy a new Phoenix application to Gigalixir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This article will assume that you already know how to install Elixir/Erlang and will reference tools like &lt;a href="https://github.com/github/hub"&gt;hub&lt;/a&gt; that I actually used while implementing the application that I describe below.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#create-a-new-phoenix-project" aria-hidden="true" class="anchor" id="create-a-new-phoenix-project"&gt;&lt;/a&gt;Create a new Phoenix project&lt;/h2&gt;
&lt;p&gt;Our first step is to create a new Phoenix application. We'll then immediately initialize our &lt;code&gt;git&lt;/code&gt; repository and push it up to GitHub.&lt;/p&gt;
&lt;p&gt;I like to make the first commit right after generating the project so you don't get lost when making your initial changes. This way, when you look at the second commit, you'll see the first changes that the developer made.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ mix phx.new pipsqueak &amp;amp;&amp;amp; cd pipsqueak
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ git add . &amp;amp;&amp;amp; git commit -m &amp;quot;Initial Commit&amp;quot;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;$ hub create
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;$ git push -u origin master
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#switch-to-yarn" aria-hidden="true" class="anchor" id="switch-to-yarn"&gt;&lt;/a&gt;Switch to Yarn&lt;/h2&gt;
&lt;p&gt;I like to use Yarn instead of NPM to manage my JavaScript packages, so we'll re-install the packages using Yarn to create our &lt;code&gt;yarn.lock&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;If you don't use Yarn, you can skip this step.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ rm assets/package-lock.json
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ (cd assets &amp;amp;&amp;amp; yarn install)
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;$ git add . &amp;amp;&amp;amp; git commit -m &amp;quot;Switch to Yarn&amp;quot;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#continuous-integration-with-github-actions" aria-hidden="true" class="anchor" id="continuous-integration-with-github-actions"&gt;&lt;/a&gt;Continuous Integration with GitHub Actions&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.github.com/actions"&gt;GitHub Actions&lt;/a&gt; is a new product by GitHub that is used to run arbitrary workflows and CI pipelines in response to events emitted by GitHub. Let's run our new test suite by implementing a Workflow.&lt;/p&gt;
&lt;p&gt;Our workflow will run on every push, check that our code is formatted, and run our test suite.&lt;/p&gt;
&lt;p&gt;Notable pieces of this workflow are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We use the matrix strategy even though we are only using a single combination. This way we can use our version in our cache keys, so that we update cache will invalidate when we inevitably update our language versions.&lt;/li&gt;
&lt;li&gt;We boot up our Postgres server as a service container. Make sure the user/password/db match your application's test config.&lt;/li&gt;
&lt;li&gt;We cache our Elixir and JavaScript dependencies so that we only install them if we really have to.&lt;/li&gt;
&lt;li&gt;We cache our Elixir build so that the compiler only has to compile the files that have changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;# .github/workflows/verify.yml
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;on: push
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;jobs:
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  verify:
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;    runs-on: ubuntu-latest
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;    strategy:
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;      matrix:
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;        otp: [22.2.7]
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;        elixir: [1.10.1]
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;    services:
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;      db:
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;        image: postgres:12
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;        env:
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;          POSTGRES_USER: postgres
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;          POSTGRES_PASSWORD: postgres
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;          POSTGRES_DB: pipsqueak_test
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;        ports: [&amp;#39;5432:5432&amp;#39;]
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;    steps:
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;      - uses: actions/checkout@v2
&lt;/div&gt;&lt;div class="line" data-line="25"&gt;      - uses: actions/setup-elixir@v1
&lt;/div&gt;&lt;div class="line" data-line="26"&gt;        with:
&lt;/div&gt;&lt;div class="line" data-line="27"&gt;          otp-version: $&amp;lbrace;&amp;lbrace; matrix.otp &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="28"&gt;          elixir-version: $&amp;lbrace;&amp;lbrace; matrix.elixir &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="29"&gt;
&lt;/div&gt;&lt;div class="line" data-line="30"&gt;      - name: Setup Node
&lt;/div&gt;&lt;div class="line" data-line="31"&gt;        uses: actions/setup-node@v1
&lt;/div&gt;&lt;div class="line" data-line="32"&gt;        with:
&lt;/div&gt;&lt;div class="line" data-line="33"&gt;          node-version: 13.8.0
&lt;/div&gt;&lt;div class="line" data-line="34"&gt;
&lt;/div&gt;&lt;div class="line" data-line="35"&gt;      - uses: actions/cache@v1
&lt;/div&gt;&lt;div class="line" data-line="36"&gt;        id: deps-cache
&lt;/div&gt;&lt;div class="line" data-line="37"&gt;        with:
&lt;/div&gt;&lt;div class="line" data-line="38"&gt;          path: deps
&lt;/div&gt;&lt;div class="line" data-line="39"&gt;          key: $&amp;lbrace;&amp;lbrace; runner.os &amp;rbrace;&amp;rbrace;-mix-$&amp;lbrace;&amp;lbrace; hashFiles(format(&amp;#39;&amp;lbrace;0&amp;rbrace;&amp;lbrace;1&amp;rbrace;&amp;#39;, github.workspace, &amp;#39;/mix.lock&amp;#39;)) &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="40"&gt;
&lt;/div&gt;&lt;div class="line" data-line="41"&gt;      - uses: actions/cache@v1
&lt;/div&gt;&lt;div class="line" data-line="42"&gt;        id: build-cache
&lt;/div&gt;&lt;div class="line" data-line="43"&gt;        with:
&lt;/div&gt;&lt;div class="line" data-line="44"&gt;          path: _build
&lt;/div&gt;&lt;div class="line" data-line="45"&gt;          key: $&amp;lbrace;&amp;lbrace; runner.os &amp;rbrace;&amp;rbrace;-build-$&amp;lbrace;&amp;lbrace; matrix.otp &amp;rbrace;&amp;rbrace;-$&amp;lbrace;&amp;lbrace; matrix.elixir &amp;rbrace;&amp;rbrace;-$&amp;lbrace;&amp;lbrace; hashFiles(format(&amp;#39;&amp;lbrace;0&amp;rbrace;&amp;lbrace;1&amp;rbrace;&amp;#39;, github.workspace, &amp;#39;/mix.lock&amp;#39;)) &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="46"&gt;
&lt;/div&gt;&lt;div class="line" data-line="47"&gt;      - name: Find yarn cache location
&lt;/div&gt;&lt;div class="line" data-line="48"&gt;        id: yarn-cache
&lt;/div&gt;&lt;div class="line" data-line="49"&gt;        run: echo &amp;quot;::set-output name=dir::$(yarn cache dir)&amp;quot;
&lt;/div&gt;&lt;div class="line" data-line="50"&gt;
&lt;/div&gt;&lt;div class="line" data-line="51"&gt;      - uses: actions/cache@v1
&lt;/div&gt;&lt;div class="line" data-line="52"&gt;        with:
&lt;/div&gt;&lt;div class="line" data-line="53"&gt;          path: $&amp;lbrace;&amp;lbrace; steps.yarn-cache.outputs.dir &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="54"&gt;          key: $&amp;lbrace;&amp;lbrace; runner.os &amp;rbrace;&amp;rbrace;-yarn-$&amp;lbrace;&amp;lbrace; hashFiles(&amp;#39;**/yarn.lock&amp;#39;) &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="55"&gt;          restore-keys: |
&lt;/div&gt;&lt;div class="line" data-line="56"&gt;            $&amp;lbrace;&amp;lbrace; runner.os &amp;rbrace;&amp;rbrace;-yarn-
&lt;/div&gt;&lt;div class="line" data-line="57"&gt;
&lt;/div&gt;&lt;div class="line" data-line="58"&gt;      - name: Install deps
&lt;/div&gt;&lt;div class="line" data-line="59"&gt;        run: |
&lt;/div&gt;&lt;div class="line" data-line="60"&gt;          mix deps.get
&lt;/div&gt;&lt;div class="line" data-line="61"&gt;          (cd assets &amp;amp;&amp;amp; yarn)
&lt;/div&gt;&lt;div class="line" data-line="62"&gt;
&lt;/div&gt;&lt;div class="line" data-line="63"&gt;      - run: mix format --check-formatted
&lt;/div&gt;&lt;div class="line" data-line="64"&gt;      - run: mix test&amp;lbrace;% endraw %&amp;rbrace;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To test our workflow, let's commit and push to GitHub.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ git add . &amp;amp;&amp;amp; git commit -m &amp;quot;Verify workflow&amp;quot;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ git push origin master
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#create-a-new-gigalixir-app" aria-hidden="true" class="anchor" id="create-a-new-gigalixir-app"&gt;&lt;/a&gt;Create a new Gigalixir app&lt;/h2&gt;
&lt;p&gt;Before proceeding with this section, please refer to the &lt;a href="https://gigalixir.readthedocs.io/en/latest/main.html#getting-started-guide"&gt;Gigalixir Getting Started Guide&lt;/a&gt; to create an account and install the &lt;code&gt;gigalixir&lt;/code&gt; command line tool.&lt;/p&gt;
&lt;p&gt;Our application will deploy using mix releases, so we need to add a &lt;code&gt;config/releases.exs&lt;/code&gt; file. Gigalixir will detect this file and assume we are using a mix release.&lt;/p&gt;
&lt;p&gt;The following will ensure that our server is started and URLs will be properly constructed.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;# config/releases.exs&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Config&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #8cf8f7;"&gt;config&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:pipsqueak&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;PipsqueakWeb.Endpoint&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #8cf8f7;"&gt;server: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;  &lt;span style="color: #8cf8f7;"&gt;http: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;port: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:system&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;PORT&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #8cf8f7;"&gt;url: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;host: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;System&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;get_env&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;APP_NAME&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;.gigalixirapp.com&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;port: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;443&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, let's configure the Elixir, Erlang, Node, and Yarn versions to be used with our deployment. We'll use the latest version that are available at the time of writing.&lt;/p&gt;
&lt;p&gt;Create the following files in the root of repository.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;# elixir_buildpack.config
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;elixir_version=1.10.2
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;erlang_version=22.2.8
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;# phoenix_static_buildpack.config
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;node_version=13.8.0
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;yarn_version=1.22.0
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Time to commit the changes we've made.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ git add . &amp;amp;&amp;amp; git commit -m &amp;quot;Configure for Gigalixir&amp;quot;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we can deploy our application, we'll have to create an app and a database using the &lt;code&gt;gigalixir&lt;/code&gt; command line tool.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ gigalixir create
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ gigalixir pg:create --free
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have somewhere to deploy our code. Like Heroku, Gigalixir uses git for deployments. Running &lt;code&gt;gigalixir create&lt;/code&gt; added a git remote named &lt;code&gt;gigalixir&lt;/code&gt; and now we can push to it.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ git push gigalixir master
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now be able to visit &lt;code&gt;&amp;lt;your app name&amp;gt;.gigalixirapp.com&lt;/code&gt; and see our stock Phoenix application!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://res.cloudinary.com/mhanberg/image/upload/v1583205573/pipsqueak-gigalixirapp.png" alt="https://pipsqueak.gigalixirapp.com" /&gt;&lt;/p&gt;
&lt;p&gt;While this is quick and easy when first starting out, it can be tedious and error prone in the long run.&lt;/p&gt;
&lt;p&gt;This is where Gigalixir falls short compared to Heroku. Heroku connects to GitHub and automatically deploys when all of your &lt;a href="https://developer.github.com/v3/checks/"&gt;checks&lt;/a&gt; have passed. Checks are GitHub integrations that run some sort of test on your code and then either pass or fail.&lt;/p&gt;
&lt;p&gt;Since we're using GitHub Actions, we have an entire marketplace of actions at our disposal. I saw that there wasn't an existing action for Gigalixir, so I decided to write my own!&lt;/p&gt;
&lt;h2&gt;&lt;a href="#gigalixir-action" aria-hidden="true" class="anchor" id="gigalixir-action"&gt;&lt;/a&gt;Gigalixir Action&lt;/h2&gt;
&lt;p&gt;Check out the source code for this action &lt;a href="https://github.com/mhanberg/gigalixir-action"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This action has a few features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy our application to Gigalixir.&lt;/li&gt;
&lt;li&gt;Run our migrations upon a successful deployment.&lt;/li&gt;
&lt;li&gt;If our migrations fail, it will rollback our deployment to the last version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Running migrations requires ssh access to the deployment, so before we can get started we need to generate a new public/private key pair, upload the public key to Gigalixir, and add the private key to GitHub as a secret.&lt;/p&gt;
&lt;p&gt;Let's generate an SSH key pair (without a passphrase) called &lt;code&gt;gigalixir_rsa&lt;/code&gt; in our current directory and add it to Gigalixir.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ ssh-keygen -t rsa -b 4096 -C &amp;quot;Gigalixir SSH Key&amp;quot;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ gigalixir account:ssh_keys:add &amp;quot;$(cat gigalixir_rsa.pub)&amp;quot;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;$ cat gigalixir_rsa | pbcopy
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also copied our private key to the clipboard so that we can add it as a GitHub secret.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://res.cloudinary.com/mhanberg/image/upload/v1583210165/pipsqueak-ssh-private-key.png" alt="Adding our SSH_PRIVATE_KEY as a secret" /&gt;&lt;/p&gt;
&lt;p&gt;We also need to need to add our &lt;code&gt;GIGALIXIR_USERNAME&lt;/code&gt; and &lt;code&gt;GIGALIXIR_PASSWORD&lt;/code&gt; as secrets in the same way we added the &lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For now you can use the same account that you used to create the app, but in the future you should create a separate user for added security.&lt;/p&gt;
&lt;p&gt;We can now configure the &lt;code&gt;gigalixir-action&lt;/code&gt; to deploy our code.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-yaml" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;% raw %&amp;rbrace;# .github/workflows/verify.yml
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;jobs: 
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;  verify:
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;    ...
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;  deploy:
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;    # only run this job if the verify job succeeds
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;    needs: verify
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;    # only run this job if the workflow is running on the master branch
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;    if: github.ref == &amp;#39;refs/heads/master&amp;#39;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;    runs-on: ubuntu-latest
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;    steps:
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;      - uses: actions/checkout@v2
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;        
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;        # actions/checkout@v2 only checks out the latest commit,
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;        # so we need to tell it to check out the entire master branch
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;        with:
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;          ref: master
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;          fetch-depth: 0
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;
&lt;/div&gt;&lt;div class="line" data-line="25"&gt;      # configure the gigalixir-actions with our credentials and app name
&lt;/div&gt;&lt;div class="line" data-line="26"&gt;      - uses: mhanberg/gigalixir-action@v0.1.0
&lt;/div&gt;&lt;div class="line" data-line="27"&gt;        with:
&lt;/div&gt;&lt;div class="line" data-line="28"&gt;          GIGALIXIR_USERNAME: $&amp;lbrace;&amp;lbrace; secrets.GIGALIXIR_USERNAME &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="29"&gt;          GIGALIXIR_PASSWORD: $&amp;lbrace;&amp;lbrace; secrets.GIGALIXIR_PASSWORD &amp;rbrace;&amp;rbrace;
&lt;/div&gt;&lt;div class="line" data-line="30"&gt;          GIGALIXIR_APP: &amp;lt;your app name&amp;gt;
&lt;/div&gt;&lt;div class="line" data-line="31"&gt;          SSH_PRIVATE_KEY: $&amp;lbrace;&amp;lbrace; secrets.SSH_PRIVATE_KEY &amp;rbrace;&amp;rbrace;&amp;lbrace;% endraw %&amp;rbrace;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now all we have to do is commit and push our code!&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ git add . &amp;amp;&amp;amp; git commit -m &amp;quot;Automatically deploy to Gigalixir&amp;quot;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#whats-next" aria-hidden="true" class="anchor" id="whats-next"&gt;&lt;/a&gt;What's next?&lt;/h2&gt;
&lt;p&gt;Now there's nothing holding you back from moving forward with your next project 😄.&lt;/p&gt;
&lt;p&gt;If this article has helped you deploy your latest project, please &lt;a href="https://twitter.com/mitchhanberg"&gt;let me know&lt;/a&gt; on Twitter!&lt;/p&gt;</description>
  <pubDate>Tue, 03 Mar 2020 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/ci-cd-with-phoenix-github-actions-and-gigalixir/</link>
  <guid>https://www.mitchellhanberg.com/ci-cd-with-phoenix-github-actions-and-gigalixir/---https://www.mitchellhanberg.com/ci-cd-with-phoenix-github-actions-and-gigalixir/</guid>
</item>
<item>
  <title>My Long Distance Relationship With GitHub</title>
  <description>From The Great Code Adventure: Coming up on my one month 💖 anniversary 💖 of joining the GitHub engineering organization, I'm reflecting on the challenges and benefits of remote, asynchronous teams.</description>
  <pubDate>Mon, 02 Mar 2020 12:56:07 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/my-long-distance-relationship-with-github/</link>
  <guid>https://www.thegreatcodeadventure.com/my-long-distance-relationship-with-github/---5e5d0089a2c3ae00384e565f</guid>
</item>
<item>
  <title>Setting up my new computer</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;I started a new job last week, so I naturally received a new computer that required some set up.&lt;/p&gt;
&lt;p&gt;Usually this could take hours or even days, as there's always an app I forget to download or some random setting that I can't remember how to configure.&lt;/p&gt;
&lt;p&gt;Luckily, I have automated most of the process! In my &lt;a href="https://github.com/mhanberg/.dotfiles"&gt;dotfiles&lt;/a&gt; repository, I wrote (some parts &lt;a href="https://github.com/thoughtbot/laptop"&gt;borrowed&lt;/a&gt;) a shell script to install all of my CLI and GUI tools for me, as well as configure my shell environment.&lt;/p&gt;
&lt;p&gt;Installing everything still took at least an hour (the bulk of this is due to &lt;code&gt;brew&lt;/code&gt; compiling packages from source), but at least I am able to do it by running only a few shell commands!&lt;/p&gt;
&lt;h2&gt;&lt;a href="#my-process" aria-hidden="true" class="anchor" id="my-process"&gt;&lt;/a&gt;My Process&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://help.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent"&gt;Create a new ssh key&lt;/a&gt; and upload the public key to my Github profile.&lt;/li&gt;
&lt;li&gt;Install the Xcode Command Line Tools (&lt;code&gt;xcode-select --install&lt;/code&gt;). This is necessary to be able to use &lt;code&gt;git&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Clone my dotfiles into my home directory &lt;code&gt;git clone git@github.com:mhanberg/.dotfiles.git &amp;amp;&amp;amp; cd .dotfiles&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run the install script &lt;code&gt;./install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Watch as my computer magically bootstraps itself 😄.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is all made possible by &lt;a href="https://brew.sh"&gt;Homebrew&lt;/a&gt; and &lt;a href="https://github.com/thoughtbot/rcm"&gt;rcm&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#install" aria-hidden="true" class="anchor" id="install"&gt;&lt;/a&gt;./install&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;install&lt;/code&gt; script installs Homebrew and then runs the command &lt;code&gt;brew bundle&lt;/code&gt;. The &lt;code&gt;bundle&lt;/code&gt; subcommand will install every package that is declared in the &lt;code&gt;Brewfile&lt;/code&gt; that is located in the root of my dotfiles.&lt;/p&gt;
&lt;p&gt;An awesome thing about Homebrew is that it can install GUI tools (Firefox, Postico, Slack) in addition to CLI developer tools.&lt;/p&gt;
&lt;p&gt;You can hand-craft a &lt;code&gt;Brewfile&lt;/code&gt; or generate one based on the current packages and casks you have installed using &lt;code&gt;brew bundle dump&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So far I haven't found a good way to keep my &lt;code&gt;Brewfile&lt;/code&gt; up to date. I think that there might be a way to hook into your shell to print a message every time you install something using &lt;code&gt;brew install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The script then switches the default shell to the &lt;code&gt;zsh&lt;/code&gt; that is installed by &lt;code&gt;brew&lt;/code&gt; (if it isn't already). &lt;code&gt;zsh&lt;/code&gt; is the default shell on macOS now, but if you want to use the newest releases you'll want to install via &lt;code&gt;brew&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-bash" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;#! /usr/bin/env bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #8cf8f7;"&gt;set&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-e&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;!&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;command&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-v&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;brew&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;/dev/null&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;==&amp;gt; Installing Homebrew ...&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;[[&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;OSTYPE&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;darwin&lt;span style="color: #e0e2ea;"&gt;*&lt;/span&gt;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;]]&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;    &lt;span style="color: #8cf8f7;"&gt;curl&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-fsS&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;https://raw.githubusercontent.com/Homebrew/install/master/install&amp;#39;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;|&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;ruby&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;    &lt;span style="color: #8cf8f7;"&gt;curl&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-fsSL&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh&amp;#39;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;|&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;sh&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-c&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;&lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;==&amp;gt; Install Homebrew dependencies&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;&lt;span style="color: #8cf8f7;"&gt;brew&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;bundle&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;&lt;span style="color: #8cf8f7;"&gt;update_shell&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;local&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;shell_path&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;  &lt;span style="color: #e0e2ea;"&gt;shell_path&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;command&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-v&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;zsh&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;)&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;  &lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;==&amp;gt; Changing your shell to zsh ...&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;!&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;grep&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;shell_path&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;/etc/shells&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;/dev/null&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;2&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;1&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;    &lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;==&amp;gt; Adding &amp;#39;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;shell_path&lt;/span&gt;&amp;#39; to /etc/shells&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;    &lt;span style="color: #8cf8f7;"&gt;sudo&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;sh&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-c&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;echo &lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;shell_path&lt;/span&gt; &amp;gt;&amp;gt; /etc/shells&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="25"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="26"&gt;  &lt;span style="color: #8cf8f7;"&gt;sudo&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;chsh&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-s&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;shell_path&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;USER&lt;/span&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="27"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="28"&gt;
&lt;/div&gt;&lt;div class="line" data-line="29"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;case&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;SHELL&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;in&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="30"&gt;  &lt;span style="color: #8cf8f7;"&gt;*/zsh&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="31"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;&lt;span style="color: #8cf8f7;"&gt;$(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;command&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-v&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;zsh&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;)&lt;/span&gt;&amp;quot;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;!=&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;/usr/local/bin/zsh&amp;#39;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="32"&gt;      &lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;==&amp;gt; Updating shell to ZSH&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="33"&gt;      &lt;span style="color: #8cf8f7;"&gt;update_shell&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="34"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="35"&gt;    &lt;span style="color: #e0e2ea;"&gt;;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="36"&gt;  &lt;span style="color: #8cf8f7;"&gt;*&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="37"&gt;    &lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;==&amp;gt; Updating shell to ZSH&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="38"&gt;    &lt;span style="color: #8cf8f7;"&gt;update_shell&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="39"&gt;    &lt;span style="color: #e0e2ea;"&gt;;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="40"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;esac&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="41"&gt;
&lt;/div&gt;&lt;div class="line" data-line="42"&gt;&lt;span style="color: #8cf8f7;"&gt;./rcup&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#rcup" aria-hidden="true" class="anchor" id="rcup"&gt;&lt;/a&gt;./rcup&lt;/h2&gt;
&lt;p&gt;And finally, we run my &lt;code&gt;rcup&lt;/code&gt; wrapper script. This calls the &lt;code&gt;rcup&lt;/code&gt; utility included with &lt;code&gt;rcm&lt;/code&gt;, with a few options that I want to always use, such as ignoring certain files.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rcm&lt;/code&gt; is a set of utilities that help manage your dotfiles using symlinks. When I call &lt;code&gt;rcup&lt;/code&gt;, it will make symlinks in my home directory for any files (other than those I tell it to ignore) in &lt;code&gt;~/.dotfiles&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When adding a new dotfile, I can either create it in my home directory and call &lt;code&gt;mkrc .my-new-dotfile&lt;/code&gt; or I can create it in &lt;code&gt;~/dotfiles/&lt;/code&gt; and run &lt;code&gt;~/.dotfiles/rcup&lt;/code&gt; once more.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-bash" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;#! /usr/bin/env bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #8cf8f7;"&gt;set&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-e&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #8cf8f7;"&gt;echo&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;==&amp;gt; Installing dotfiles&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;&lt;span style="color: #8cf8f7;"&gt;rcup&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-U&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Brewfile&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-x&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;README.md&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-x&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;mitch-preonic.json&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-x&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;install&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-x&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;rcup&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-v&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#room-for-improvement" aria-hidden="true" class="anchor" id="room-for-improvement"&gt;&lt;/a&gt;Room for improvement&lt;/h2&gt;
&lt;p&gt;My install script automates &lt;em&gt;most&lt;/em&gt; things, but there are still some places optimize.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automate the manual steps with a "curl to bash" script that installs XCode and clones my dotfiles. e.g. &lt;code&gt;curl https://raw.githubusercontent.com/mhanberg/.dotfiles/master/bootstrap.sh | bash&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a new ssh key.&lt;/li&gt;
&lt;li&gt;Configure macOS specific settings like key repeat speed, dock size/behavior, etc.&lt;/li&gt;
&lt;li&gt;Install my &lt;code&gt;zsh&lt;/code&gt; plugins with &lt;a href="https://github.com/zplug/zplug"&gt;zplug&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description>
  <pubDate>Mon, 02 Mar 2020 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/setting-up-my-new-computer/</link>
  <guid>https://www.mitchellhanberg.com/setting-up-my-new-computer/---https://www.mitchellhanberg.com/setting-up-my-new-computer/</guid>
</item>
<item>
  <title>Boring is the new Black</title>
  <description>From Bits of Code - Pieces of Writing: In this article I will explain why I believe that boring - yet pragmatic - choices are often the most powerful.</description>
  <pubDate>Wed, 26 Feb 2020 10:39:45 GMT</pubDate>
  <link>https://www.christianblavier.com/boring-is-the-new-black/</link>
  <guid>https://www.christianblavier.com/boring-is-the-new-black/---629603e163e1200001252ac2</guid>
</item>
<item>
  <title>Getting your Elixir application ready for CI/CD</title>
  <description>From My.Thoughts v1: This post was sponsored by the kind folks over at Check them out at https://appsignal.com In today&amp;rsquo;s post we&amp;rsquo;ll be going over what exactly continuous integration and continuous delivery are, the benefits that come along with employing CI/CD, and some best practices that you should follow. In addition, we&amp;rsquo;ll also explore a wide array of Elixir ecosystem tools that can help you to create top notch CI pipelines. In order to experiment with a handful of the tools that we will be discussing, we will use a Git hooks Elixir library to execute our CI/CD validation steps, but on our local machine.</description>
  <pubDate>Mon, 10 Feb 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/elixir-cicd/</link>
  <guid>https://akoutmos.com/post/elixir-cicd/---https://akoutmos.com/post/elixir-cicd/</guid>
</item>
<item>
  <title>Building Concurrent Workflows in Go with Goroutines and Channels</title>
  <description>From The Great Code Adventure: Using goroutines and channels, we can ensure that our program works on on more than one task in a given time period. In this post, we'll use goroutines, channels and WaitGroups to process a "bulk user registration" request.</description>
  <pubDate>Mon, 27 Jan 2020 01:19:55 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/building-concurrent-workflows-in-go-with-goroutines-and-channels/</link>
  <guid>https://www.thegreatcodeadventure.com/building-concurrent-workflows-in-go-with-goroutines-and-channels/---5e260de584476f0044ef0ca6</guid>
</item>
<item>
  <title>Lumen - Elixir &amp;amp; Erlang in the browser</title>
  <description>From Underjord.io: The Lumen Project is an alternative implementation of the Erlang VM, more known as the BEAM. It is designed to work in WebAssembly with the specific goal of bringing Elixir and Erlang to the browser.
Note: This guide is outdated. These instructions will not work. Lumen is at the time of this update (2020-12-10) a moving target with an early release out. I plan on making an updated post at some point but will leave this post as it is.</description>
  <pubDate>Thu, 23 Jan 2020 23:59:00 +0000</pubDate>
  <link>https://underjord.io/lumen-elixir-in-the-browser.html</link>
  <guid>https://underjord.io/lumen-elixir-in-the-browser.html---https://underjord.io/lumen-elixir-in-the-browser.html</guid>
</item>
<item>
  <title>Why am I still excited about Elixir?</title>
  <description>From Underjord.io: A good ol' while back I wrote about why I'm interested in Elixir. I think that deserves some follow-up.
So what's different? Well, I've worked a lot more with Elixir in a professional capacity since then. I've built more things in it both for work and play. I've also gone digging and written about it in weird and extensive ways that I hadn't expected.
Professional work in Elixir I started working with a client that needed work done on an existing Elixir backend.</description>
  <pubDate>Mon, 20 Jan 2020 21:30:00 +0000</pubDate>
  <link>https://underjord.io/why-am-i-still-excited-about-elixir.html</link>
  <guid>https://underjord.io/why-am-i-still-excited-about-elixir.html---https://underjord.io/why-am-i-still-excited-about-elixir.html</guid>
</item>
<item>
  <title>Debugging a Go Web App with VSCode and Delve</title>
  <description>From The Great Code Adventure: I found that using Delve to debug a Golang web app was fairly non-intuitive. Keep reading to find out how its done!</description>
  <pubDate>Fri, 17 Jan 2020 12:06:00 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/debugging-a-go-web-app-with-vscode-and-delve/</link>
  <guid>https://www.thegreatcodeadventure.com/debugging-a-go-web-app-with-vscode-and-delve/---5e1ef9ad84476f0044ef0c48</guid>
</item>
<item>
  <title>Mocking HTTP Requests in Golang</title>
  <description>From The Great Code Adventure: Let's take a look at how we can use interfaces to build a shared mock HTTP client that we can use across the test suite of our Golang app.</description>
  <pubDate>Tue, 14 Jan 2020 15:52:37 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/</link>
  <guid>https://www.thegreatcodeadventure.com/mocking-http-requests-in-golang/---5e1ddf64192fb30038b39d5b</guid>
</item>
<item>
  <title>Ecto &amp;amp; Multi-tenancy - Prefixes - Part 3</title>
  <description>From Underjord.io: This should be the final piece of this saga. Previous parts can be found here:
 Part 1 - Getting started Part 2  Now I'll cover the approach I finally ended up using. I had dynamic repos fully working but was somewhat haunted by the heavy-handed approach of running all those pools long before I knew anything about the loads I could expect for each tenant.
I think it would work fine.</description>
  <pubDate>Fri, 10 Jan 2020 13:00:00 +0000</pubDate>
  <link>https://underjord.io/ecto-multi-tenancy-prefixes-part-3.html</link>
  <guid>https://underjord.io/ecto-multi-tenancy-prefixes-part-3.html---https://underjord.io/ecto-multi-tenancy-prefixes-part-3.html</guid>
</item>
<item>
  <title>How to Use Broadway in Your Elixir Application</title>
  <description>From My.Thoughts v1: This post was sponsored by the kind folks over at Check them out at https://appsignal.com How to Use Broadway in Your Elixir Application In today&amp;rsquo;s post, we will be covering the Elixir library Broadway. Broadway is a library which is maintained by the kind folks over at Plataformatec and allows us to create highly concurrent data processing pipelines with relative ease. After an overview of how Broadway works and when to use it, we&amp;rsquo;ll dive into a sample project where we leverage Broadway to fetch temperature data from https://openweathermap.</description>
  <pubDate>Tue, 07 Jan 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/using-broadway/</link>
  <guid>https://akoutmos.com/post/using-broadway/---https://akoutmos.com/post/using-broadway/</guid>
</item>
<item>
  <title>Easy and Robust Rate Limiting in Elixir</title>
  <description>From My.Thoughts v1: Intro In this blog post, we&amp;rsquo;ll be talking about what rate limiters are, when they are applicable, and how we can write 2 styles of rate limiters leveraging GenServer, Erlang&amp;rsquo;s queue data structure, and Task.Supervisor. Finally, we&amp;rsquo;ll write a sample application that leverages a rate limiter and we&amp;rsquo;ll make our application modular in the sense that we can swap our rate limiters via configuration to achieve the operational characteristics that we desire.</description>
  <pubDate>Tue, 07 Jan 2020 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/rate-limiting-with-genservers/</link>
  <guid>https://akoutmos.com/post/rate-limiting-with-genservers/---https://akoutmos.com/post/rate-limiting-with-genservers/</guid>
</item>
<item>
  <title>A Slight Delight - Compile-time checking things</title>
  <description>From Underjord.io: This was a short-but-sweet thing that struck me while working with a client code-base. It was trivial but both useful and delightful and it is a type of thing I haven't been able to do in Python, PHP and Javascript in quite the same way.
Initially, the code was something like this:
elixir  defmodule MyApp.Schemas do alias ExJson.Schema def my_schema do %{ &amp;#34;properties&amp;#34; =&amp;gt; %{ &amp;#34;email&amp;#34; =&amp;gt; %{ &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;, }, &amp;#34;password&amp;#34; =&amp;gt; %{ &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34;, } } } |&amp;gt; Schema.</description>
  <pubDate>Mon, 06 Jan 2020 11:30:00 +0000</pubDate>
  <link>https://underjord.io/a-slight-delight-compile-time-checking.html</link>
  <guid>https://underjord.io/a-slight-delight-compile-time-checking.html---https://underjord.io/a-slight-delight-compile-time-checking.html</guid>
</item>
<item>
  <title>Building a MySQL Database Driver in Elixir @ Code BEAM Lite</title>
  <description>From ⚡ Maqbool Khan: Have you ever wondered what happens beneath the covers when you talk to your Database? Well, you are in for a treat! In this talk, we are going to uncover the dark magic behind Database Drivers. We will look at everything that is needed to talk to a ...</description>
  <pubDate>Wed, 25 Dec 2019 05:28:33 GMT</pubDate>
  <link>https://www.maqbool.net/building-a-mysql-database-driver-in-elixir-422710302b72</link>
  <guid>https://www.maqbool.net/building-a-mysql-database-driver-in-elixir-422710302b72---https://www.maqbool.net/building-a-mysql-database-driver-in-elixir-422710302b72</guid>
</item>
<item>
  <title>Elixir - Signing for Cloudfront resources</title>
  <description>From Underjord.io: This covers how to create Signed URL Custom Policies with Cloudfront in Elixir.
If you are working with Elixir and AWS you are probably familiar with ExAws. If you are unfamiliar, let's cover some quick ground:
 AWS - Amazon's cloud offering, has a whole bunch of oddly named services. S3 - File storage on AWS. Cloudfront - CDN for AWS, useful for better delivery performance for files for example.</description>
  <pubDate>Fri, 20 Dec 2019 10:00:00 +0000</pubDate>
  <link>https://underjord.io/elixir-signing-for-cloudfront-resources.html</link>
  <guid>https://underjord.io/elixir-signing-for-cloudfront-resources.html---https://underjord.io/elixir-signing-for-cloudfront-resources.html</guid>
</item>
<item>
  <title>Happy little screens (with Elixir)</title>
  <description>From Underjord.io: So me and Emilio Nyaray made Inky. We built on top of what was there from Nerves and Scenic and in the end we had the Inky series of eInk displays for Raspberry Pi devices working with Nerves through Elixir. Cool. That was a fun trip I've covered previously:
 Inky library release Getting started, guide The story of how it came together Me &amp;amp; Emilio spoke about it on Elixir Mix (podcast)  Recently Frank Hunleth, Justin Schneck and Luis Gabriel Roldan (@groldan) who've all been involved in some display-related shenanigans of different sorts move their repos into our organization that we've used for Inky.</description>
  <pubDate>Mon, 16 Dec 2019 16:23:00 +0000</pubDate>
  <link>https://underjord.io/happy-little-screens-with-elixir.html</link>
  <guid>https://underjord.io/happy-little-screens-with-elixir.html---https://underjord.io/happy-little-screens-with-elixir.html</guid>
</item>
<item>
  <title>Broadway, RabbitMQ, and the Rise of Elixir Part 2</title>
  <description>From My.Thoughts v1: Intro In Broadway, RabbitMQ, and the Rise of Elixir Part 1 we covered what exactly a message queue is and when it is appropriate to use one. We also put together the foundation for our application which will mine data from the HackerNews Firebase API. In the final part of this series, we will be setting up our Broadway pipelines, a GenServer that will generate IDs for us to process, and some database related modules that will allow us to persist our results for later analysis.</description>
  <pubDate>Thu, 05 Dec 2019 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/broadway-rabbitmq-and-the-rise-of-elixir-two/</link>
  <guid>https://akoutmos.com/post/broadway-rabbitmq-and-the-rise-of-elixir-two/---https://akoutmos.com/post/broadway-rabbitmq-and-the-rise-of-elixir-two/</guid>
</item>
<item>
  <title>Broadway, RabbitMQ, and the Rise of Elixir Part 1</title>
  <description>From My.Thoughts v1: Intro In this two part series we&amp;rsquo;ll be talking about what a message queue is and when you would use one in your application. From there we&amp;rsquo;ll dive into the Broadway library maintained by Plataformatec and see how it can be leveraged from within an application for data processing. Finally, we&amp;rsquo;ll create a sample project that makes use of RabbitMQ+Broadway to scrape comments from HackerNews so we can measure the popularity of Elixir over time.</description>
  <pubDate>Sun, 10 Nov 2019 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/broadway-rabbitmq-and-the-rise-of-elixir/</link>
  <guid>https://akoutmos.com/post/broadway-rabbitmq-and-the-rise-of-elixir/---https://akoutmos.com/post/broadway-rabbitmq-and-the-rise-of-elixir/</guid>
</item>
<item>
  <title>Consider signing up for the Elixir Radar</title>
  <description>From Underjord.io: Old page, kept to avoid 404.</description>
  <pubDate>Fri, 08 Nov 2019 06:00:00 +0000</pubDate>
  <link>https://underjord.io/elixir-radar-referral.html</link>
  <guid>https://underjord.io/elixir-radar-referral.html---https://underjord.io/elixir-radar-referral.html</guid>
</item>
<item>
  <title>Ecto &amp;amp; Multi-tenancy - Dynamic Repos - Part 2</title>
  <description>From Underjord.io: In the first part I covered the basics of getting started with Dynamic repositories with Ecto. Using that post we can create one or more repos at runtime, create the necessary database, run migrations to get it ready and then direct queries to it. That's a good start. Building blocks for something better. I'll try to get into the better bits here.
That said, I still ended up using prefixes because it made my code simpler.</description>
  <pubDate>Fri, 01 Nov 2019 07:00:00 +0000</pubDate>
  <link>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-2.html</link>
  <guid>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-2.html---https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-2.html</guid>
</item>
<item>
  <title>I was on a podcast</title>
  <description>From Underjord.io: You can listen to it here.
Mark Ericksen of Elixir Mix got in touch and asked if I'd come on to talk about the things I've done with Inky, Elixir and Nerves. So I did. And I brought my co-conspirator Emilio with me. I thought the episode turned out well. I certainly enjoyed it and I haven't been on a podcast before. So thanks to my readers, the community and the Elixir Mix gang for the opportunity.</description>
  <pubDate>Wed, 23 Oct 2019 06:00:00 +0000</pubDate>
  <link>https://underjord.io/i-was-on-a-podcast.html</link>
  <guid>https://underjord.io/i-was-on-a-podcast.html---https://underjord.io/i-was-on-a-podcast.html</guid>
</item>
<item>
  <title>Ecto &amp;amp; Multi-tenancy - Dynamic Repos - Part 1 - Getting started</title>
  <description>From Underjord.io: Ecto is the database library we know and love from the Elixir ecosystem. It is used by default in Phoenix, the high-profile web framework. Ecto has a bunch of cool features and ideas. But this post is about a corner full of nuts, bolts and very little of the shiny or hot stuff. It just covers some rather specific needs. Ecto docs for these features are this guide and this API.</description>
  <pubDate>Mon, 14 Oct 2019 21:37:00 +0000</pubDate>
  <link>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html</link>
  <guid>https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html---https://underjord.io/ecto-multi-tenancy-dynamic-repos-part-1-getting-started.html</guid>
</item>
<item>
  <title>How we improved our Elixir build speed by 5x</title>
  <description>From Bits of Code - Pieces of Writing: &lt;p&gt;&lt;em&gt;&lt;em&gt;I&amp;#x2019;m really happy and proud to say that since this story was first posted, I received great feedback and relay throughout the Elixir community! The last advice given in this article has indeed been taken as an inspiration by &lt;/em&gt;&lt;/em&gt;&lt;a href="https://medium.com/u/f31378845318?source=post_page-----d45393c6700f----------------------" rel="noopener"&gt;&lt;em&gt;&lt;em&gt;Jos&amp;#xE9; Valim&lt;/em&gt;&lt;/em&gt;&lt;/a&gt;&lt;em&gt;&lt;em&gt; to add a &lt;/em&gt;&lt;/em&gt;&lt;a href="https://github.com/elixir-lang/elixir/pull/9422?ref=bits-of-code-pieces-of-writing" rel="noopener nofollow"&gt;&lt;em&gt;&lt;em&gt;test partitioning feature&lt;/em&gt;&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;</description>
  <pubDate>Tue, 08 Oct 2019 20:53:00 GMT</pubDate>
  <link>https://www.christianblavier.com/how-we-improved-our-elixir-build-speed-by-5x/</link>
  <guid>https://www.christianblavier.com/how-we-improved-our-elixir-build-speed-by-5x/---629603e163e1200001252ac1</guid>
</item>
<item>
  <title>What I'm up to - Mostly Elixir things</title>
  <description>From Underjord.io: While I'm writing something a bit more involved and substantial I figured I could give an update on what I've been doing. Mostly around Elixir. But I'll cover a few different things.
Ecto - A deep dive Working on a project I'll cover more at a later date has led me to work more with Ecto. I rather like it. What I didn't like is that my use-case seems fairly unexplored and a little bit light on documentation.</description>
  <pubDate>Thu, 03 Oct 2019 11:00:00 +0000</pubDate>
  <link>https://underjord.io/what-im-up-to.html</link>
  <guid>https://underjord.io/what-im-up-to.html---https://underjord.io/what-im-up-to.html</guid>
</item>
<item>
  <title>Building Dynamic Outputs with Terraform Expressions and Functions</title>
  <description>From The Great Code Adventure: &lt;p&gt;We know we can define a Terraform module that produces output for &lt;em&gt;another&lt;/em&gt; module to use as input. But how can we build dynamic output from a module that creates a &lt;em&gt;set&lt;/em&gt; resources, and format that output &lt;em&gt;just right&lt;/em&gt; to act as input elsewhere? It&amp;apos;s possible with the&lt;/p&gt;</description>
  <pubDate>Wed, 18 Sep 2019 13:04:00 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/building-dynamic-outputs-with-terraform-for_each-for-and-zipmap/</link>
  <guid>https://www.thegreatcodeadventure.com/building-dynamic-outputs-with-terraform-for_each-for-and-zipmap/---5d784a110b3ac40038a57069</guid>
</item>
<item>
  <title>Prometheus, PostGIS and Phoenix Part 2</title>
  <description>From My.Thoughts v1: Intro In Prometheus, PostGIS and Phoenix Part 1 we covered what exactly monitoring is and what are its benefits. We also put together the foundation for our application that we will be monitoring with Prometheus and Grafana. In the final part of this series, we&amp;rsquo;ll be setting up our monitoring stack, instrumenting our application, and writing a simple stress tester to make our graphs dance. Part 2 assumes that you have already gone through Part 1 and have the code and database at a point where we can jump right in.</description>
  <pubDate>Mon, 16 Sep 2019 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/prometheus-postgis-and-phoenix-two/</link>
  <guid>https://akoutmos.com/post/prometheus-postgis-and-phoenix-two/---https://akoutmos.com/post/prometheus-postgis-and-phoenix-two/</guid>
</item>
<item>
  <title>Publishex - publish static pages</title>
  <description>From Maarten van Vliet: Publishex hex package Library easily publish static pages to S3 or Netlify</description>
  <pubDate>Sun, 15 Sep 2019 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/publishex/</link>
  <guid>https://maartenvanvliet.nl/project/publishex/---https://maartenvanvliet.nl/project/publishex/</guid>
</item>
<item>
  <title>Why a newsletter?</title>
  <description>From Underjord.io: What is it good for? Doing it wrong Simple tech Readership contact Resilience and sustainability  So I'm launching a newsletter. The sign-up is at the bottom of the page, it won't pop up here, so read on in peace.
What is it good for? Every tech blogger has a newsletter these days. How come? Because it works. Because it is bed-rock old-web stuff. It is relationship building, it is building a network, an audience and connections.</description>
  <pubDate>Thu, 12 Sep 2019 08:56:00 +0000</pubDate>
  <link>https://underjord.io/why-a-newsletter.html</link>
  <guid>https://underjord.io/why-a-newsletter.html---https://underjord.io/why-a-newsletter.html</guid>
</item>
<item>
  <title>Knock Down Your House: Refactoring and The Creative Process</title>
  <description>From The Great Code Adventure: &lt;p&gt;Recently, my team and I were tasked with the holy grail of assignments--a greenfield project. We were writing brand new code in a brand new app to meet brand new requirements. Like many developers, we jumped at this chance to design our own codebase and solve a new set of&lt;/p&gt;</description>
  <pubDate>Wed, 11 Sep 2019 01:01:53 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/knock-down-your-hourse-refactoring-and-the-creative-process/</link>
  <guid>https://www.thegreatcodeadventure.com/knock-down-your-hourse-refactoring-and-the-creative-process/---5d7845a10b3ac40038a5705c</guid>
</item>
<item>
  <title>Prometheus, PostGIS and Phoenix Part 1</title>
  <description>From My.Thoughts v1: Intro In this two part series, we&amp;rsquo;ll talk about what exactly monitoring is and why you need it in your production application. We&amp;rsquo;ll also cover how monitoring differs from logging and some considerations that need to be made when selecting a monitoring solution. Finally we&amp;rsquo;ll go through setting up a monitoring stack alongside a sample Phoenix application. Without further ado, let&amp;rsquo;s dive right into things!
What is monitoring? Monitoring is the process of collecting metrics from your applications, infrastructure, supporting services, and databases and then storing this data over time.</description>
  <pubDate>Tue, 20 Aug 2019 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/prometheus-postgis-and-phoenix/</link>
  <guid>https://akoutmos.com/post/prometheus-postgis-and-phoenix/---https://akoutmos.com/post/prometheus-postgis-and-phoenix/</guid>
</item>
<item>
  <title>Case Study: Inky - An elixir library</title>
  <description>From Underjord.io: This is a post covering the creation and refinement of an open source project within the Elixir ecosystem. More words than code. Be warned.  I bought* a display from Pimoroni called the Inky PHAT, the red version. It is an eInk display. That means it can show an image even without power being supplied to the screen. In this case it can show black, white and red. It refreshes very slowly (12 seconds or more) but is a cool and potentially useful piece of tech.</description>
  <pubDate>Fri, 09 Aug 2019 14:00:00 +0000</pubDate>
  <link>https://underjord.io/case-study-inky-an-elixir-library.html</link>
  <guid>https://underjord.io/case-study-inky-an-elixir-library.html---https://underjord.io/case-study-inky-an-elixir-library.html</guid>
</item>
<item>
  <title>Monitor Your Phoenix Application with Sentry</title>
  <description>From My.Thoughts v1: Intro In this post we&amp;rsquo;ll talk about what what exactly error monitoring is, why you need it in your production application, and what tools are available in the space. Finally, we&amp;rsquo;ll go over setting up a self hosted error monitoring solution for a sample Phoenix application. Without further ado, let&amp;rsquo;s dive right into things!
What is error monitoring? Error monitoring is the practice of monitoring your application for errors or unhandled exceptions.</description>
  <pubDate>Fri, 19 Jul 2019 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/error-monitoring-phoenix-with-sentry/</link>
  <guid>https://akoutmos.com/post/error-monitoring-phoenix-with-sentry/---https://akoutmos.com/post/error-monitoring-phoenix-with-sentry/</guid>
</item>
<item>
  <title>Artisanal software - Beyond pragmatism</title>
  <description>From Underjord.io: Whenever we design and create software we need to pay attention to the trade-offs we are making.
When you make software for a business purpose within a company, the trade-offs are made in relation to business goals, available resources, the deadline and often office and team politics. Quite a few things come into play to shape what you are doing. And developers in these environments generally can't control all the trade-offs that are being made.</description>
  <pubDate>Mon, 15 Jul 2019 15:00:00 +0000</pubDate>
  <link>https://underjord.io/artisanal-software-beyond-pragmatism.html</link>
  <guid>https://underjord.io/artisanal-software-beyond-pragmatism.html---https://underjord.io/artisanal-software-beyond-pragmatism.html</guid>
</item>
<item>
  <title>Serve Letsencrypt certificates dynamically for Plug/Phoenix</title>
  <description>From Maarten van Vliet: Certbot hex package Serve Letsencrypt certificates dynamically for Plug/Phoenix</description>
  <pubDate>Fri, 12 Jul 2019 10:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/certbot/</link>
  <guid>https://maartenvanvliet.nl/project/certbot/---https://maartenvanvliet.nl/project/certbot/</guid>
</item>
<item>
  <title>Introducing Temple: An elegant HTML library for Elixir and Phoenix</title>
  <description>From Mitchell Hanberg's Blog: &lt;img src="/images/temple.png" class="bg-transparent mx-auto my-16" style="filter: invert(100%);"&gt;

Temple is a macro DSL for writing HTML markup in [Elixir](https://elixir-lang.org) and a template engine for [Phoenix](https://phoenixframework.org/).

Conventional template languages like [EEx](https://hexdocs.pm/eex/EEx.html) use a form of interpolation to embed a programming language into a markup language, which can result in some ugly code that can be difficult to write and debug.

Temple is written using _pure_ elixir.

Let's take a look.

```elixir
use Temple

@items ["eggs", "milk", "bacon"]

temple do
  h2 "todos"

  ul class: "list" do
    for item &lt;- @items do
      li class: "item" do
        div class: "checkbox" do
          div class: "bullet hidden"
        end

        div item
      end
    end
  end

  script """
  function toggleCheck({currentTarget}) {
    currentTarget.children[0].children[0].classList.toggle("hidden");
  }

  let items = document.querySelectorAll("li");

  Array.from(items).forEach(checkbox =&gt;
    checkbox.addEventListener("click", toggleCheck)
  );
  """
end
```

### Phoenix Template Engine

By implementing the `Phoenix.Template.Engine` behaviour, Temple becomes a full fledged template engine.

```elixir
# config/config.exs

config :phoenix, :template_engines, exs: Temple.Engine
```

```elixir
# lib/blog_web.ex

def view do
  quote do
    # ...

    use Temple
  end
end
```

```elixir
# lib/blog_web/templates/post/show.html.exs

h2 @post.title

div do
  text "By #{Enum.join(@post.authors, ", ")}"
end

article do
  text @post.body
end

aside do
  for c &lt;- @post.comments do
    div c.name
    div c.body
  end
end
```

### Phoenix.HTML

All of the Phoenix form and link helpers have been wrapped to be compatible with Temple.

The semantics and naming of some helpers have been change to work as macros and avoid name conflicts with standard HTML5 tags.

```elixir
# takes a block and has access to the variable `form`
form_for @post, Routes.post_path(@conn, :create) do

  # prefixed with `phx_` to avoid a conflict with the &lt;label&gt; tag
  phx_label form, :title
  text_input form, :title

  phx_label form, :authors
  multiple_select form, :authors, @users

  phx_label form, :body
  text_area form, :body
end
```

### Components

Temple offers [React-ish](https://reactjs.org) style API for extracting reusable components. 

`defcomponent` will define macros for you to use in your markup without them looking any different from normal tags.

```elixir
# layout_view.ex

defmodule MyAppWeb.LayoutView do
  use MyAppWeb, :view

  defcomponent :nav_item do
    div id: @id, class: "flex flex-col" do
      div @name, class: "margin-bottom-2"
      div @description
    end
  end
end

# app.html.exs

html do
  head do
    # stuff
  end

  body do
    header do
      nav do
        for item &lt;- @nav_items do
          nav_item id: item.key,
                   name: item.name,
                   description: item.description
        end
      end
    end

    main role: "main", class: "container" do
      p get_flash(@conn, :info), class: "alert alert-info", role: "alert"
      p get_flash(@conn, :error), class: "alert alert-danger", role: "alert"

      partial render(@view_module, @view_template, assigns)
    end
  end
end
```

Components can also take children if passed a block and are accessed via the `@children` variable.

```elixir
defcomponent :takes_children do
  div class: "some-wrapping-class" do
    @children
  end
end

takes_children do
  span do
    text "child one"
  end
  span do
    text "child two"
  end
  span do
    text "child three"
  end
end

# &lt;div class="some-wrapping-class"&gt;
#   &lt;span&gt;child one&lt;/span&gt;
#   &lt;span&gt;child two&lt;/span&gt;
#   &lt;span&gt;child three&lt;/span&gt;
# &lt;/div&gt;
```

## Wrapping up

If you're interested in using Temple, you can install it from [Hex](https://hex.pm/packages/temple) and check it out on [GitHub](https://github.com/mhanberg/temple).

Let me know what you think!</description>
  <pubDate>Fri, 12 Jul 2019 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/introducing-temple-an-elegant-html-library-for-elixir-and-phoenix/</link>
  <guid>https://www.mitchellhanberg.com/introducing-temple-an-elegant-html-library-for-elixir-and-phoenix/---https://www.mitchellhanberg.com/introducing-temple-an-elegant-html-library-for-elixir-and-phoenix/</guid>
</item>
<item>
  <title>An eInk display with Nerves &amp;amp; Elixir - Getting started with Inky</title>
  <description>From Underjord.io: So I've been curious about what kinds of displays you can connect to the Pi-series single board computers for a while. I happened to accidentally order a few. Among others an eInk display. I ordered the PaPiRus ePaper. It ended up being dead on arrival and then out of stock so I received an Inky to replace it. Fair enough.
eInk, as you know, is cool because it doesn't need power to keep displaying whatever you made it display last.</description>
  <pubDate>Sun, 07 Jul 2019 18:10:00 +0000</pubDate>
  <link>https://underjord.io/an-eink-display-with-nerves-elixir.html</link>
  <guid>https://underjord.io/an-eink-display-with-nerves-elixir.html---https://underjord.io/an-eink-display-with-nerves-elixir.html</guid>
</item>
<item>
  <title>Inky library released!</title>
  <description>From Underjord.io: Me and nyaray finally finished up our work on the Inky eInk display library to a level where we are happy to release it. So Inky 1.0.0 is now out on Hex! Docs are on there too.
This is a small library and what started as a simple almost line-by-line port of the Python library on my part is now testable and tested. It has a simulator for on-host development (separate library, inky_host_dev).</description>
  <pubDate>Thu, 04 Jul 2019 10:38:00 +0000</pubDate>
  <link>https://underjord.io/inky-library-release.html</link>
  <guid>https://underjord.io/inky-library-release.html---https://underjord.io/inky-library-release.html</guid>
</item>
<item>
  <title>Revitalizing valuable legacy systems</title>
  <description>From Underjord.io: Do you have a system that is vital to your business that your development team seems to have given up on? Do they consider it old, slow, complicated or impossible to work with? Are they pushing heavily for a rewrite?
They are probably right. It has probably become old, slow and difficult to work with. And it might be fair that they are trying to sell you on a rewrite.</description>
  <pubDate>Mon, 24 Jun 2019 11:00:00 +0000</pubDate>
  <link>https://underjord.io/revitalizing-valuable-legacy-systems.html</link>
  <guid>https://underjord.io/revitalizing-valuable-legacy-systems.html---https://underjord.io/revitalizing-valuable-legacy-systems.html</guid>
</item>
<item>
  <title>Multi-stage Docker Builds and Elixir 1.9 Releases</title>
  <description>From My.Thoughts v1: Intro In this post we&amp;rsquo;ll talk about what a release is in the context of Elixir and why/when you should use it. We&amp;rsquo;ll also cover how this was performed historically and how this changes in Elixir 1.9. Finally we&amp;rsquo;ll go through creating an Elixir release inside of a Docker container using multi-stage builds. Without further ado, let&amp;rsquo;s dive right into things!
What is a release? An Elixir release (and Erlang of course) is the process of taking your application and bundling it so that it is ready for distribution (generally called an OTP release).</description>
  <pubDate>Thu, 20 Jun 2019 00:00:00 +0000</pubDate>
  <link>https://akoutmos.com/post/multipart-docker-and-elixir-1.9-releases/</link>
  <guid>https://akoutmos.com/post/multipart-docker-and-elixir-1.9-releases/---https://akoutmos.com/post/multipart-docker-and-elixir-1.9-releases/</guid>
</item>
<item>
  <title>Why am I interested in Elixir?</title>
  <description>From Underjord.io: The juicy bits  OTP &amp;amp; the BEAM Phoenix Presence Phoenix LiveView Nerves Scenic Rustler  I’ve had Elixir on the brain recently. And by recently I probably mean 2 years. In my defense I think it is fair to say it is blooming right now. I haven’t had much need of it, or opportunity for it, in my day-to-day of maintaining a Python legacy system, renewing another legacy or optimizing Elasticsearch.</description>
  <pubDate>Tue, 11 Jun 2019 17:30:00 +0000</pubDate>
  <link>https://underjord.io/why-am-i-interested-in-elixir.html</link>
  <guid>https://underjord.io/why-am-i-interested-in-elixir.html---https://underjord.io/why-am-i-interested-in-elixir.html</guid>
</item>
<item>
  <title>Open Source</title>
  <description>From My.Thoughts v1: In addition to blogging, I also enjoy writing Elixir libraries in order to solve some of my day to day development problems. I publish those libraries to https://hex.pm/users/akoutmos and have the code freely available on my GitHub at https://github.com/akoutmos.
Below are some of the projects that I currently maintain along with some details as to what they do. Feel free to leave feedback on GitHub if you encounter any issues or if there are any features that you would like to see!</description>
  <pubDate>Thu, 06 Jun 2019 16:56:55 -0400</pubDate>
  <link>https://akoutmos.com/top/open_source/</link>
  <guid>https://akoutmos.com/top/open_source/---https://akoutmos.com/top/open_source/</guid>
</item>
<item>
  <title>Elixir Tips</title>
  <description>From My.Thoughts v1: Since the summer of 2020 I have been publishing Elixir tips three times a week on Twitter. These are small and focused code snippets that show you some cool (and possibly lesser known) things about Elixir and tools from around the ecosystem. I found that I often needed to lookup some of my old tips for my own purposes and had a hard time tracking them down in my timeline.</description>
  <pubDate>Thu, 06 Jun 2019 16:56:55 -0400</pubDate>
  <link>https://akoutmos.com/top/elixir_tips/</link>
  <guid>https://akoutmos.com/top/elixir_tips/---https://akoutmos.com/top/elixir_tips/</guid>
</item>
<item>
  <title>About</title>
  <description>From My.Thoughts v1: Hello and thanks for stopping by! My name is Alex Koutmos and I am the father of 2 beautiful girls and 1 shy dog. My wife and I enjoy taking our girls on hiking adventures and road trips.
When I am not hanging out with my beautiful family, I enjoy fighting working with computers and cars. By day I am a software engineer, and by night I am a grease monkey.</description>
  <pubDate>Thu, 06 Jun 2019 16:56:55 -0400</pubDate>
  <link>https://akoutmos.com/top/about/</link>
  <guid>https://akoutmos.com/top/about/---https://akoutmos.com/top/about/</guid>
</item>
<item>
  <title>Elixir and The Beam: How Concurrency Really Works</title>
  <description>From The Great Code Adventure: &lt;hr&gt;&lt;p&gt;&lt;em&gt;This post was originally published on the Flatiron Labs blog. Check it out &lt;a href="https://medium.com/flatiron-labs?ref=thegreatcodeadventure.com"&gt;here&lt;/a&gt; for more awesome content by The Flatiron School&amp;apos;s technology team. &lt;/em&gt;&lt;/p&gt;&lt;h3 id="elixir-and-the-beam-how-concurrency-really-works"&gt;Elixir and The Beam: How Concurrency Really Works&lt;/h3&gt;&lt;p&gt;Elixir has become famous as the shiny new &amp;#x201C;concurrent&amp;#x201D; programming language, with more and&lt;/p&gt;</description>
  <pubDate>Thu, 23 May 2019 10:01:16 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/elixir-and-the-beam-how-concurrency-really-works/</link>
  <guid>https://www.thegreatcodeadventure.com/elixir-and-the-beam-how-concurrency-really-works/---5ce66ed064761900c0b87ba2</guid>
</item>
<item>
  <title>Scenic - Getting started from scratch</title>
  <description>From Underjord.io: This post covers setting up a Scenic project in the Elixir programming language. It briefly covers the default method but largely dives into adding Scenic to an existing project, which covers the different parts that Scenic requires to run.
The official approach Requirements  Elixir 18, OTP 21 (I recommend using asdf to install and use the right version) Scenic 0.10 (this is covered in the guide)  A brand new project Scenic, the OpenGL-based and very tasty UI framework for Elixir (&amp;amp; friends) has a very reasonable project generator.</description>
  <pubDate>Mon, 20 May 2019 18:00:00 +0000</pubDate>
  <link>https://underjord.io/scenic-getting-started.html</link>
  <guid>https://underjord.io/scenic-getting-started.html---https://underjord.io/scenic-getting-started.html</guid>
</item>
<item>
  <title>Using Recursive Common Table Expressions with Ecto</title>
  <description>From Maarten van Vliet: A presentation I gave at the Amsterdam Elixir Meetup.
We faced a problem where we needed to recursively load records from a database. The application was doing the recursion itself, recursively querying the mariadb instance. I explained how we moved to RCTE&amp;rsquo;s and how this works in Ecto.
Slides</description>
  <pubDate>Thu, 02 May 2019 12:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/presentation/sketch_rcte/</link>
  <guid>https://maartenvanvliet.nl/presentation/sketch_rcte/---https://maartenvanvliet.nl/presentation/sketch_rcte/</guid>
</item>
<item>
  <title>Elixir Test Mocking with Mox</title>
  <description>From The Great Code Adventure: &lt;h3 id="building-an-api-client-mock-and-learning-to-love-mocks-as-nouns"&gt;Building an api client mock and learning to love mocks-as-nouns&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;This post was originally published on the Flatiron Labs blog, The Flatiron School Technology Team&amp;apos;s blog. Check out more awesome Flatiron Labs posts &lt;a href="https://medium.com/flatiron-labs?ref=thegreatcodeadventure.com"&gt;here&lt;/a&gt;. &lt;/em&gt;&lt;/p&gt;&lt;h3 id="why-we-need-mox"&gt;Why We Need Mox&lt;/h3&gt;&lt;p&gt;In a &lt;a href="https://medium.com/flatiron-labs/rolling-your-own-mock-server-for-testing-in-elixir-2cdb5ccdd1a0?ref=thegreatcodeadventure.com"&gt;recent post&lt;/a&gt;, we talked about the age-old question:&lt;/p&gt;</description>
  <pubDate>Thu, 11 Apr 2019 09:47:19 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/elixir-test-mocking-with-mox/</link>
  <guid>https://www.thegreatcodeadventure.com/elixir-test-mocking-with-mox/---5caf0844cd0bb600c0cfdaf1</guid>
</item>
<item>
  <title>Implementing link following with OAuth</title>
  <description>From Mitchell Hanberg's Blog: **Link following** is the process for dynamically redirecting a user after successful authentication.

Disclaimer: I'm not sure what this is actually called, so I made up "Link following." If you know the actual name, [let me know!](https://twitter.com/mitchhanberg)

If you are new to using OAuth to handle authentication, implementing link following won't be as intuitive as it would be if you were hand rolling your own.

The trouble comes with remembering the link that the user clicked. If you were to write your own auth, this would all be handled within your own application and it would be easy to maintain that state. With OAuth, authentication is actually split between your application and the OAuth service that you don't control.

So how are you supposed to remember where the user wanted to go after the OAuth service authenticates the user?

The OAuth protocol specifies a [state](https://auth0.com/docs/protocols/oauth2/oauth-state) parameter that can be sent along with the authentication request and is returned with the response. 

## Implementation

I recently implemented link following with the [Elixir](https://elixir-lang.org/) libraries [Phoenix](https://phoenixframework.org/) and [Ueberauth](https://github.com/ueberauth/ueberauth), but these principles apply to any language or library.

When the request comes in, we need to determine if the user is logged in. If they aren't we will need to redirect them to the log in page and take note of the original request path. Let's define a plug to do this for us.

(The code snippets here are stripped down for brevity)

```elixir
defmodule MyAppWeb.LoggedIn do
  def call(conn, _opts) do
    case current_user(conn) do
      nil -&gt;
        conn
        |&gt; Phoenix.Controller.redirect(to:
          Routes.some_controller_path(
            conn,
            :login,
            path: conn.request_path
          )
        )
        |&gt; halt()

      _ -&gt;
        conn
    end
  end
end
```

Now that we are sending the original request path along with the redirect to the login page, we'll want to use that information to form our OAuth link.

```elixir
link(
  to: Routes.auth_path(conn,
    :request,
    "oauth service",
    state: path
  )
)
```

If the user is able to authenticate with the OAuth service, we'll be able to take advantage of the state parameter that is sent back with the response in our controller action.

The `User.find_or_create/1` function attempts to find an existing user and will create one if it can't, returning the user.

`handle_find_for_create/2` will handle the response from the previous function, such as putting the user in the session and setting the flash message, returning the conn.

`redirect/2` will send the user to their original destination based on the state parameter returned by the OAuth service. 

```elixir
def callback(
    %{assigns: %{ueberauth_auth: auth}} = conn,
    %{"state" =&gt; path}
  ) do
  conn =
    auth
    |&gt; User.find_or_create()
    |&gt; handle_find_or_create(conn)

  redirect conn, to: path
end
```

## Wrapping up

The OAuth protocol takes a state parameter that allows you to maintain a piece of data during the authentication handshake, we can use this to remember what link the user was attempting to access, and redirect them after a successful handshake.</description>
  <pubDate>Sun, 07 Apr 2019 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/implementing-link-following-with-oauth/</link>
  <guid>https://www.mitchellhanberg.com/implementing-link-following-with-oauth/---https://www.mitchellhanberg.com/implementing-link-following-with-oauth/</guid>
</item>
<item>
  <title>Tools I use for mobile web development</title>
  <description>From Mitchell Hanberg's Blog: It's never been easier to write web applications for mobile and I want to share some of the tools I use everyday that help me do so.

## ngrok

[ngrok](https://ngrok.com/) is a tool used to create encrypted web tunnels to your computer in order to expose a local development server to the internet.

While modern web browsers have "mobile mode" dev tools, they don't perform as well as actually using the app on your phone.

My practice is to open the app on my phone and computer side by side, allowing me to make sure that the design looks great at all screen sizes.

## Live reload

Live reloading typically involves a file watcher that triggers a browser refresh when files change on disk.  Manually reloading the page is a pain, especially when you have your app open in multiple browsers and devices. 

Luckily, many web frameworks come with built-in live reloading functionality.

The tools that I use often that provide this are [Jekyll](https://jekyllrb.com/) `jekyll serve --livereload` and the Elixir web framework [Phoenix](https://phoenixframework.org/) `iex -S mix phx.server` (it works without any extra options).

## Safari mobile web inspector

I use the Mobile Safari dev tools to debug JavaScript and design in the browser when I'm running to problems with my applications on mobile.

To use the Web Inspector on your iPhone, toggle the following option under Settings &gt; Safari &gt; Advanced and following the instructions.

![Picture of the web inspector option for the Safari web browser on iOS](/images/safari-web-inspector.jpg)

## Tailwind CSS

My favorite tool for making mobile friendly applications is the [Tailwind CSS](https://tailwindcss.com/docs/what-is-tailwind/) utility-first CSS framework.

Tailwind generates responsive versions of all of it's CSS classes and provides custom [at-rules](https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule) to help with writing your own.

Here are a couple examples of responsive design using Tailwind.

```html
&lt;!-- progressively scale headings as the screen width increases --&gt;
&lt;h2 class="text-2xl md:text-3xl"&gt;
  Developing for Mobile Efficiently
&lt;/h2&gt;

&lt;!-- start as as a column and move to a row at medium size screens --&gt;
&lt;div class="flex flex-col md:flex-row"&gt;
  &lt;div&gt;1&lt;/div&gt;
  &lt;div&gt;2&lt;/div&gt;
  &lt;div&gt;3&lt;/div&gt;
&lt;/div&gt;
```

You can create your own responsive classes using the `@responsive` at-rule.

```css
@responsive {
  .my-style {
    /* styles... */
  }
}
```

## What else is out there?

I'm always interested in finding new tools, so if you use something not mentioned here, please share it with me on Twitter! [@mitchhanberg](https://twitter.com/mitchhanberg)</description>
  <pubDate>Thu, 04 Apr 2019 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/tools-i-use-for-mobile-web-development/</link>
  <guid>https://www.mitchellhanberg.com/tools-i-use-for-mobile-web-development/---https://www.mitchellhanberg.com/tools-i-use-for-mobile-web-development/</guid>
</item>
<item>
  <title>Walk-Though of Phoenix Live View</title>
  <description>From The Great Code Adventure: &lt;p&gt;It&amp;apos;s here! Phoenix Live View leverages server-rendered HTML and Phoenix&amp;apos;s native WebSocket tooling so you can build fancy real-time features without all that complicated JavaScript. If you&amp;apos;re sick to death of writing JS (I had a bad day with Redux, don&amp;apos;t ask)&lt;/p&gt;</description>
  <pubDate>Sun, 17 Mar 2019 20:32:00 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/walk-though-of-phoenix-live-view/</link>
  <guid>https://www.thegreatcodeadventure.com/walk-though-of-phoenix-live-view/---5c8e9aa2d25b2c00cc834067</guid>
</item>
<item>
  <title>Experiment in the REPL</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;A technique I've picked up for learning new tools is &lt;strong&gt;experimenting with them in the REPL&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A read–eval–print loop (REPL), is a simple, interactive computer programming environment that takes single user inputs, evaluates them, and returns the result to the user. &lt;a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop"&gt;*&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Elixir (&lt;code&gt;iex&lt;/code&gt;), Ruby (&lt;code&gt;irb&lt;/code&gt;), and Node.js (&lt;code&gt;node&lt;/code&gt;) all have interactive shells that allow you to evaluate expressions easily and quickly.&lt;/p&gt;
&lt;p&gt;This works great for small things like remembering how division works or grokking a tricky enumerable method.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;iex(1)&amp;gt; 1 / 2
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;0.5
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;iex(2)&amp;gt; div 1, 2
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;0
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;iex(3)&amp;gt; Enum.reject([1, 2, 3], fn n -&amp;gt; rem(n, 2) == 0 end)
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;[1, 3]
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's also a convenient way to try out new libraries.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;iex(4)&amp;gt; Slack.Web.Chat.post_message(&amp;quot;C68FV6MDH&amp;quot;, nil, %&amp;lbrace;attachments: [%&amp;lbrace;color: &amp;quot;good&amp;quot;, text: &amp;quot;Hello world!&amp;quot;&amp;rbrace;]&amp;rbrace;)
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;** (ArgumentError) argument error
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;    :erlang.list_to_binary([%&amp;lbrace;color: &amp;quot;good&amp;quot;, text: &amp;quot;Hello world!&amp;quot;&amp;rbrace;])
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;    (hackney) /Users/mitchell/Development/my_app/deps/hackney/src/hackney_bstr.erl:36: :hackney_bstr.to_binary/1
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;    (hackney) /Users/mitchell/Development/my_app/deps/hackney/src/hackney_url.erl:300: :hackney_url.urlencode/2
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;    (hackney) /Users/mitchell/Development/my_app/deps/hackney/src/hackney_url.erl:360: :hackney_url.qs/3
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;    (hackney) /Users/mitchell/Development/my_app/deps/hackney/src/hackney_request.erl:310: :hackney_request.encode_form/1
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;    (hackney) /Users/mitchell/Development/my_app/deps/hackney/src/hackney_request.erl:318: :hackney_request.handle_body/4
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;    (hackney) /Users/mitchell/Development/my_app/deps/hackney/src/hackney_request.erl:81: :hackney_request.perform/2
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;    (hackney) /Users/mitchell/Development/my_app/deps/hackney/src/hackney.erl:373: :hackney.send_request/2
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;    (httpoison) lib/httpoison/base.ex:787: HTTPoison.Base.request/6
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;    (httpoison) lib/httpoison.ex:128: HTTPoison.request!/5
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;    (slack) lib/slack/web/web.ex:51: Slack.Web.Chat.post_message/3
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;# Turns out we need to JSON encode the attachments list
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;iex(5)&amp;gt;Slack.Web.Chat.post_message(&amp;quot;C68FV6MDH&amp;quot;, nil, %&amp;lbrace;attachments: Jason.encode!([%&amp;lbrace;color: &amp;quot;good&amp;quot;, text: &amp;quot;Hello world!&amp;quot;&amp;rbrace;])&amp;rbrace;)
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;%&amp;lbrace;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;  &amp;quot;channel&amp;quot; =&amp;gt; &amp;quot;C68FV6MDH&amp;quot;,
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;  &amp;quot;ok&amp;quot; =&amp;gt; true,
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;&amp;rbrace; 
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I find experimenting in the REPL to be useful when I need a &lt;strong&gt;short feedback loop&lt;/strong&gt; that isn't tightly coupled to the full stack of my application.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#avoid-analysis-paralysis" aria-hidden="true" class="anchor" id="avoid-analysis-paralysis"&gt;&lt;/a&gt;Avoid Analysis Paralysis&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Analysis paralysis is when the fear of potential error outweighs the realistic expectation or potential value of success, and this imbalance results in suppressed decision-making in an unconscious effort to preserve existing options. &lt;a href="https://en.wikipedia.org/wiki/Analysis_paralysis#Software_development"&gt;*&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Idle hands build nothing. I fell into this trap recently, wasting hours before I remembered the power of the REPL.&lt;/p&gt;
&lt;p&gt;I knew I needed to use either a &lt;a href="https://hexdocs.pm/elixir/Supervisor.html"&gt;Supervisor&lt;/a&gt; or a &lt;a href="https://hexdocs.pm/elixir/DynamicSupervisor.html"&gt;Dynamic Supervisor&lt;/a&gt;, but I wasn't sure which one was right for the job.&lt;/p&gt;
&lt;p&gt;Unclear on how to proceed, I spent a &lt;strong&gt;few hours&lt;/strong&gt; reading documentation, searching for blog posts, and asking people for advice. Did I learn enough to make a decision? &lt;em&gt;No&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Then I spent &lt;strong&gt;20 minutes&lt;/strong&gt; experimenting with them in &lt;code&gt;iex&lt;/code&gt; and I knew which one to use.&lt;/p&gt;</description>
  <pubDate>Tue, 19 Feb 2019 11:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/experiment-in-the-repl/</link>
  <guid>https://www.mitchellhanberg.com/experiment-in-the-repl/---https://www.mitchellhanberg.com/experiment-in-the-repl/</guid>
</item>
<item>
  <title>From Elixir School: TIL GenServer's `handle_continue/2`</title>
  <description>From The Great Code Adventure: &lt;!--kg-card-begin: markdown--&gt;&lt;p&gt;&lt;em&gt;This post was originally published on the &lt;a href="https://elixirschool.com/blog?ref=thegreatcodeadventure.com"&gt;Elixir School blog&lt;/a&gt;. Elixir School is an open source Elixir curriculum and we&amp;apos;re looking for contributors! You can write a short TIL blog post, a longer blog post, add a lesson, help with translation and more. Check out our open issues&lt;/em&gt;&lt;/p&gt;</description>
  <pubDate>Sat, 16 Feb 2019 12:39:23 GMT</pubDate>
  <link>https://www.thegreatcodeadventure.com/from-elixir-school-til-genservers-handle_continue-2/</link>
  <guid>https://www.thegreatcodeadventure.com/from-elixir-school-til-genservers-handle_continue-2/---5c710c19fb02ab00177cc0b5</guid>
</item>
<item>
  <title>Tips for Reading More</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;In 2017, I challenged myself to read 20 books and I was able to read 22 books.&lt;/p&gt;
&lt;p&gt;In 2018, I raised the bar to &lt;em&gt;30&lt;/em&gt; books, but I was only able to read 16.&lt;/p&gt;
&lt;p&gt;In 2019, I'm still aiming for 30 books; how can I make this happen?&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So far in 2019 I've already read 7 books and have found good strategies for frequent and consistent reading.&lt;/p&gt;
&lt;p&gt;I've started &lt;strong&gt;reading during boring activities that don't require my eyes&lt;/strong&gt;. I read in bed when I'm struggling to fall asleep. I read in the morning when I'm riding my stationary bike. This is similar to the idea of listening to a podcast while driving to work.&lt;/p&gt;
&lt;p&gt;I've started &lt;strong&gt;buying books in groups of three&lt;/strong&gt;. Always having something in the queue decreases the number of times that I have to figure out what I'm going to read next.&lt;/p&gt;
&lt;p&gt;I've started &lt;strong&gt;asking my friends for book recommendations&lt;/strong&gt;. This decreases the risk of starting a bad book and relieves &lt;a href="https://xkcd.com/1801/"&gt;decision paralysis&lt;/a&gt;. I'll show the list of &lt;a href="https://www.goodreads.com/review/list/69703261-mitchell?shelf=to-read"&gt;books I want to read&lt;/a&gt; to a friend and ask if they recommend anything on the list, essentially cross referencing &lt;em&gt;my&lt;/em&gt; interests with &lt;em&gt;their&lt;/em&gt; recommendations.&lt;/p&gt;
&lt;p&gt;I've started &lt;strong&gt;sharing what I'm reading&lt;/strong&gt;. When I make my three-book purchase, I share a &lt;a href="https://twitter.com/mitchhanberg/status/1083199091646046208?s=20"&gt;screenshot on Twitter&lt;/a&gt;. This creates pseudo social pressure to finish and sharing it in the open helps solidify my &lt;a href="https://jamesclear.com/identity-based-habits"&gt;identity&lt;/a&gt; as a "reader."&lt;/p&gt;</description>
  <pubDate>Thu, 14 Feb 2019 13:30:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/tips-for-reading-more/</link>
  <guid>https://www.mitchellhanberg.com/tips-for-reading-more/---https://www.mitchellhanberg.com/tips-for-reading-more/</guid>
</item>
<item>
  <title>Creating Responsive Popovers with Popper.js</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;Making &lt;em&gt;simple&lt;/em&gt; popovers is pretty easy.&lt;/p&gt;
&lt;p&gt;Making popovers that position themselves based on the available screen real estate so they're &lt;em&gt;always visible&lt;/em&gt; is not.&lt;/p&gt;
&lt;p&gt;Luckily &lt;a href="https://popper.js.org/"&gt;Popper.js&lt;/a&gt; will do the math for us and is straight forward to implement given the proper instructions.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#javascript" aria-hidden="true" class="anchor" id="javascript"&gt;&lt;/a&gt;JavaScript&lt;/h2&gt;
&lt;p&gt;The JavaScript portion is simple. The &lt;code&gt;Popper&lt;/code&gt; constructor takes a DOM node to attach the popover to and a DOM node that will be the body of the popover.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-javascript" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;// index.js&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;const&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;attachmentNode&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;document&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;querySelector&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;#attachment-point&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;const&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;popoverNode&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;document&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;querySelector&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;#my-popover&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;const&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;myPopper&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;new&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;Popper&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;attachmentNode&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;popoverNode&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#html-and-css" aria-hidden="true" class="anchor" id="html-and-css"&gt;&lt;/a&gt;HTML and CSS&lt;/h2&gt;
&lt;p&gt;The HTML and CSS is simple unless you want an arrow tab on your popover.&lt;/p&gt;
&lt;p&gt;The docs make it seem like the arrow comes for free, but you'll have to implement it yourself.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-html" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;&amp;lt;!-- index.html --&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;button&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;id&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;attachment-point&amp;quot;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt; Open Sesame! &lt;span style="color: #8cf8f7;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;button&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;div&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;class&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;popover&amp;quot;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;id&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;my-popover&amp;quot;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #8cf8f7;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;div&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;x-arrow&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;div&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #8cf8f7;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;div&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;    &lt;span style="color: #9b9ea4;"&gt;&amp;lt;!-- your popover content here --&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;  &lt;span style="color: #8cf8f7;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;div&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;div&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Popper.js will look for an element with the &lt;code&gt;x-arrow&lt;/code&gt; attribute to use for the arrow tab.&lt;/p&gt;
&lt;p&gt;In order for the arrow to be on the correct side of the popover, we need to style it as described below.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-css" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;/* styles.css */&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;x-arrow&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;  &lt;span style="color: #a6dbff;"&gt;position&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; absolute&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;popover&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #a6dbff;"&gt;margin-top&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;  &lt;span style="color: #a6dbff;"&gt;margin-bottom&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;popover&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;x-placement&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;bottom&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;x-arrow&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;  &lt;span style="color: #a6dbff;"&gt;top&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;  &lt;span style="color: #a6dbff;"&gt;border-bottom&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt; solid white&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;  &lt;span style="color: #a6dbff;"&gt;border-right&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt; solid transparent&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;  &lt;span style="color: #a6dbff;"&gt;border-left&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt; solid transparent&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;popover&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;x-placement&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;top&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;x-arrow&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;  &lt;span style="color: #a6dbff;"&gt;bottom&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;  &lt;span style="color: #a6dbff;"&gt;border-top&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt; solid white&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;  &lt;span style="color: #a6dbff;"&gt;border-right&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt; solid transparent&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;  &lt;span style="color: #a6dbff;"&gt;border-left&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;10&lt;span style="color: #b3f6c0;"&gt;px&lt;/span&gt;&lt;/span&gt; solid transparent&lt;span style="color: #e0e2ea;"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;x-placement&lt;/code&gt; attribute added by Popper.js allows you to style the arrow based on the orientation of the popover.&lt;/p&gt;
&lt;p&gt;The goofiness you see with the borders is a hack you can use to create a triangle using a &lt;code&gt;div&lt;/code&gt;. I haven't tried it, but I imagine using an &lt;code&gt;svg&lt;/code&gt; yields better results.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#wrapping-up" aria-hidden="true" class="anchor" id="wrapping-up"&gt;&lt;/a&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Popper.js takes care of the hard part so you can focus on building your application.&lt;/p&gt;
&lt;p&gt;If you want the popover to show on click or hover, you'll have to add a little more JavaScript and CSS.&lt;/p&gt;
&lt;p&gt;Hop into this Code Sandbox and give it a shot.&lt;/p&gt;
&lt;iframe src="https://codesandbox.io/embed/501wn1yvk?hidenavigation=1" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"&gt;&lt;/iframe&gt;</description>
  <pubDate>Mon, 11 Feb 2019 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/creating-responsive-popovers-with-popper.js/</link>
  <guid>https://www.mitchellhanberg.com/creating-responsive-popovers-with-popper.js/---https://www.mitchellhanberg.com/creating-responsive-popovers-with-popper.js/</guid>
</item>
<item>
  <title>Using serverless Ruby on AWS Lambda to resize images</title>
  <description>From Maarten van Vliet: At the last AWS ReInvent, it was announced that AWS Lambda would support Ruby as a runtime language. I was eager to try this out, Ruby&amp;rsquo;s powerful syntax and features are a joy to work with and coupling this with AWS Lambda I figured it could be leveraged for some easy image resizing Lambda.
I started off with the serverless framework as this is an easy way provision Lambda functions. The goal is that when an image is uploaded to an S3 bucket, a Lambda is started, it resizes the image, and then uploads it to another bucket.</description>
  <pubDate>Fri, 04 Jan 2019 10:08:54 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2019/01/04/ruby_aws_lambda/</link>
  <guid>https://maartenvanvliet.nl/2019/01/04/ruby_aws_lambda/---https://maartenvanvliet.nl/2019/01/04/ruby_aws_lambda/</guid>
</item>
<item>
  <title>Absinthe SDL</title>
  <description>From Maarten van Vliet: Absinthe SDL Create Graphql SDL schemas from Absinthe schemas</description>
  <pubDate>Wed, 02 Jan 2019 12:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/absinthe_sdl/</link>
  <guid>https://maartenvanvliet.nl/project/absinthe_sdl/---https://maartenvanvliet.nl/project/absinthe_sdl/</guid>
</item>
<item>
  <title>Writing your own Absinthe DSL with macros and middleware</title>
  <description>From Maarten van Vliet: Absinthe is a great library to do graphql in Elixir. However, when writing your resolvers you may find that you are writing some boilerplate multiple times. So, in the spirit of keeping your code DRY, in this post I&amp;rsquo;ll show how we can leverage middleware and a macro to write your own DSL for your Graphql api.
I assume some basic knowledge on how Absinthe works, most of the information can be found on in the guides</description>
  <pubDate>Sat, 29 Dec 2018 11:08:54 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2018/12/29/absinthe_macro_dsl/</link>
  <guid>https://maartenvanvliet.nl/2018/12/29/absinthe_macro_dsl/---https://maartenvanvliet.nl/2018/12/29/absinthe_macro_dsl/</guid>
</item>
<item>
  <title>Receivex - incoming email webhook handler</title>
  <description>From Maarten van Vliet: Receivex hex package Library to handle incoming email webhook from popular mail providers</description>
  <pubDate>Sat, 15 Dec 2018 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/receivex/</link>
  <guid>https://maartenvanvliet.nl/project/receivex/---https://maartenvanvliet.nl/project/receivex/</guid>
</item>
<item>
  <title>Automatic Persisted Queries hex package</title>
  <description>From Maarten van Vliet: APQ hex package Cache Apollo graphql queries automatically on Absinthe</description>
  <pubDate>Sat, 01 Dec 2018 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/apq/</link>
  <guid>https://maartenvanvliet.nl/project/apq/---https://maartenvanvliet.nl/project/apq/</guid>
</item>
<item>
  <title>Conducting Good Retrospectives</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;Every team experiences moments of success or failure, moments of working like a well oiled machine, and moments of not being able to stand the sound of each other's voices. But many teams let these moments pass by without taking time to ponder "why did these things happen"?&lt;/p&gt;
&lt;p&gt;A structured way to make sure your team doesn't miss asking that question is to hold a &lt;strong&gt;retrospective&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#what-is-a-retrospective" aria-hidden="true" class="anchor" id="what-is-a-retrospective"&gt;&lt;/a&gt;What is a retrospective?&lt;/h2&gt;
&lt;p&gt;A &lt;a href="https://www.scrum.org/resources/what-is-a-sprint-retrospective"&gt;retrospective&lt;/a&gt; (or "retro") is a recurring meeting used by teams to look back at the last sprint, week, or any major event in order to figure out what went well and what went poorly.&lt;/p&gt;
&lt;p&gt;The realm of topics is wide ranging: from mishandled social interactions to achieving a major milestone. These meetings are not intended to make you feel good or bad about yourselves, they're designed to create actionable items for your team.&lt;/p&gt;
&lt;p&gt;Setting aside dedicated time for these matters assures that they will get some attention and helps keep these conversations from spilling into normal work time.&lt;/p&gt;
&lt;p&gt;Along with &lt;strong&gt;learning from your successes and failures&lt;/strong&gt;, one of the goals of a retrospective to &lt;strong&gt;build trust among your team&lt;/strong&gt;. Teams perform better when they operate in an environment of &lt;a href="https://hbr.org/2017/08/high-performing-teams-need-psychological-safety-heres-how-to-create-it"&gt;psychological safety&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#how-to-conduct-a-retrospective" aria-hidden="true" class="anchor" id="how-to-conduct-a-retrospective"&gt;&lt;/a&gt;How to conduct a retrospective&lt;/h2&gt;
&lt;p&gt;Anyone can host a retrospective, but hosting a &lt;em&gt;good&lt;/em&gt; retrospective takes finesse. There are five key aspects that are paramount to a good retrospective: Facilitation, Conversational Turn Taking, Format/Activity, Action Items, and Trust.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#facilitation" aria-hidden="true" class="anchor" id="facilitation"&gt;&lt;/a&gt;Facilitation&lt;/h3&gt;
&lt;p&gt;Someone should volunteer to facilitate the meeting and lead the chosen activity. Depending on the activity, this can include taking notes, keeping a conversation timer, or tracking whose turn it is to speak.&lt;/p&gt;
&lt;p&gt;A facilitator &lt;strong&gt;isolates the responsibility of running the meeting to one person&lt;/strong&gt; and allows everyone else to concentrate on participating.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#conversational-turn-taking" aria-hidden="true" class="anchor" id="conversational-turn-taking"&gt;&lt;/a&gt;Conversational Turn Taking&lt;/h3&gt;
&lt;p&gt;It's important that everyone is heard. Your team should decide on a signal that makes it clear when you have something to say. My team has opted to use a finger raise to signal when you want to say something. Once someone is done speaking, motion to the next person in line to speak.&lt;/p&gt;
&lt;p&gt;When someone has a tangential point to contribute, they can delay their turn to speak to allow the current thread of discussion to finish.&lt;/p&gt;
&lt;p&gt;The primary motivation for the technique to is &lt;strong&gt;avoid interruptions&lt;/strong&gt; and &lt;strong&gt;talking over each other&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#formatactivity" aria-hidden="true" class="anchor" id="formatactivity"&gt;&lt;/a&gt;Format/Activity&lt;/h3&gt;
&lt;p&gt;Free form conversation is not conducive to reaching conclusions, so pick a meeting format that fits well with your team. At the beginning of the meeting, the facilitator will explain the format and confirm that everyone is okay with that decision. Different formats optimize for different aspects of your team: project performance, interpersonal communication, or general frustration.&lt;/p&gt;
&lt;p&gt;Picking a format gives the meeting &lt;strong&gt;structure&lt;/strong&gt; and an &lt;strong&gt;objective&lt;/strong&gt;, allowing the team to work together towards a known and shared goal.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#action-items" aria-hidden="true" class="anchor" id="action-items"&gt;&lt;/a&gt;Action Items&lt;/h3&gt;
&lt;p&gt;A necessary output of the meeting is a list of action items. An action item is a task that requires further action in order to accomplish it and is assigned to a specific person. You've probably experienced the situation where everyone agrees something should get done, but no one ever does it.&lt;/p&gt;
&lt;p&gt;Sometimes you run into an action item that isn't a specific task, but possibly a general change in behavior that is meant to be addressed by the whole team. In this case, the action item would be shepherding the adoption of this new behavior for a couple weeks.&lt;/p&gt;
&lt;p&gt;Leaving the meeting with a list of action items ensures that the tasks you spent an hour discussing &lt;strong&gt;actually get done&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#trust" aria-hidden="true" class="anchor" id="trust"&gt;&lt;/a&gt;Trust&lt;/h3&gt;
&lt;p&gt;Building trust will take time, but it is the keystone to having productive retrospectives and becoming a great team.&lt;/p&gt;
&lt;p&gt;People thrive when working in an environment where everyone has mutual respect and the freedom to speak their mind and take risks. Google conducted a study, &lt;a href="https://www.nytimes.com/2016/02/28/magazine/what-google-learned-from-its-quest-to-build-the-perfect-team.html"&gt;Project Aristotle&lt;/a&gt;, on hundreds of teams and determined that &lt;strong&gt;Psychological Safety&lt;/strong&gt; is the most &lt;a href="https://rework.withgoogle.com/blog/five-keys-to-a-successful-google-team/"&gt;important dynamic of a successful team&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The leader of the study, Julia Rozovksy, said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Individuals on teams with higher psychological safety are less likely to leave Google, they’re more likely to harness the power of diverse ideas from their teammates, they bring in more revenue, and they’re rated as effective twice as often by executives.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a href="#case-study" aria-hidden="true" class="anchor" id="case-study"&gt;&lt;/a&gt;Case Study&lt;/h2&gt;
&lt;p&gt;For the last year, my team has had a weekly retro, alternating the format each time. We operate in two week sprints, so in the middle of the sprint we concentrate on interpersonal issues or other general frustrations and at the end of the sprint we focus on project-specific issues.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#mid-sprint-retrospective" aria-hidden="true" class="anchor" id="mid-sprint-retrospective"&gt;&lt;/a&gt;Mid-Sprint Retrospective&lt;/h3&gt;
&lt;p&gt;During the Mid-Sprint Retrospective, we engage in the &lt;a href="http://leancoffee.org"&gt;Lean Coffee&lt;/a&gt; activity. The chosen facilitator will explain the activity and pass out the required supplies.&lt;/p&gt;
&lt;p&gt;The basics of the Lean Coffee activity are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate discussion topics
&lt;ul&gt;
&lt;li&gt;Limit to 5 minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Vote on topics&lt;/li&gt;
&lt;li&gt;Discuss topics
&lt;ul&gt;
&lt;li&gt;Limit to 5 minutes, then vote on continuing with a 2 minute extension, or else move on to next topic&lt;/li&gt;
&lt;li&gt;Facilitator will take notes and track action items&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Any topics that aren't discussed are carried over to the next meeting if anyone still has interest.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#sprint-retrospective" aria-hidden="true" class="anchor" id="sprint-retrospective"&gt;&lt;/a&gt;Sprint Retrospective&lt;/h3&gt;
&lt;p&gt;During the Sprint Retrospective we use a variant of the &lt;a href="http://www.funretrospectives.com/lessons-learned-quadrants-planning-vs-success/"&gt;Four Quadrant&lt;/a&gt; activity. As with the Mid-Sprint retro, the chosen facilitator will explain the activity and pass out the required supplies.&lt;/p&gt;
&lt;p&gt;The basics of our version of the Four Quadrant activity are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enumerate events that happened during the last two weeks on sticky notes.
&lt;ul&gt;
&lt;li&gt;Limit to 5 minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The facilitator will draw a horizontal axis on the whiteboard, with the left side indicating &lt;em&gt;bad&lt;/em&gt; and the right side &lt;em&gt;good&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Each person will give a brief description of their stickies and place them on the axis where they think they belong.&lt;/li&gt;
&lt;li&gt;The facilitator then draws a vertical axis, with the top indicating the event was &lt;em&gt;in the team's control&lt;/em&gt; and the bottom that it was &lt;em&gt;not in the team's control&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;The facilitator quickly pulls each sticky to the appropriate place on the vertical axis. This is usually obvious, but the rest of the team will signal where they think it should go in case of disagreement.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point, the board should resemble something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://res.cloudinary.com/mhanberg/image/upload/v1541686229/four-quadrants.png" alt="Diagram of a completed Four Quadrants diagram" /&gt;&lt;/p&gt;
&lt;p&gt;Starting in the top right corner and moving counter-clockwise, the team discusses the events in an attempt to learn. If your team is prone to drawing out discussions, consider using a timer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep doing&lt;/strong&gt; - good and in your control: give yourselves a quick pat on the back to celebrate good work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Improve&lt;/strong&gt; - bad and in your control: these are events that your team has the power to avoid in the future. The facilitator will lead discussion on the topics, driving toward specific action items.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Escalate&lt;/strong&gt; - bad and out of your control: these events can only be addressed by someone external to your team. Now is the time to figure out with who to escalate the issue, or assign someone the action of figuring that out later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kudos&lt;/strong&gt; - good and out of your control: these are positive events that were caused by someone external to your team. Here the team will assign an action to someone to reach out and express the team's gratitude.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Done right, retrospectives can be a boon for successful and struggling teams alike.&lt;/p&gt;
&lt;p&gt;The goal is to &lt;strong&gt;build a culture of trust&lt;/strong&gt; among your team, where everyone feels safe to express themselves without the fear of being punished for making a mistake or speaking their mind. If there are barriers to bringing up problems on your team, you can never expect to be able to work through them and improve.&lt;/p&gt;
&lt;p&gt;If your team has never participated in a retrospective, &lt;strong&gt;my challenge to you is to bring the idea to your team and get it on the calendar!&lt;/strong&gt;&lt;/p&gt;</description>
  <pubDate>Fri, 09 Nov 2018 21:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/11/09/conducting-good-retrospectives/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/11/09/conducting-good-retrospectives/---https://www.mitchellhanberg.com/post/2018/11/09/conducting-good-retrospectives/</guid>
</item>
<item>
  <title>Reducers: Exploring State Management in React (Part 2)</title>
  <description>From Mitchell Hanberg's Blog: In a previous [post](https://www.mitchellhanberg.com/post/2018/07/25/exploring-state-management-in-react-container-components/), we demonstrated how the Container/Presenter pattern is a solid approach to managing your React state. This time we are going to look into using Reducer functions as the method to managing change in state of your components.

## Reduce

Reduce (also known as a [fold](https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29)) is a functional programming concept that deals with the transformation of data structures using recursion and higher order functions. If you have used either the `Array.prototype.reduce` or `Array.prototype.map` [functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), you already have experience with this technique.

## The Approach

The general approach is to have a `reduce` (can be named whatever you'd like) function that understands how to respond to certain messages and will output the transformed state. We'll normally call this `reduce` function from a `dispatch` function.

This is essentially the same pattern you use with [Redux](https://redux.js.org), but we don't need to install any packages to use it.

Let's examine the code snippet below.

```jsx
function reduce(prevState, message) {
  switch (message.type) {
    case "ADD":
      return {
        items: [...prevState.items, prevState.newItem],
        newItem: ""
      };
    case "REMOVE":
      return {
        items: prevState.items.filter(
          (i, idx, prevItems) =&gt; idx !== prevItems.indexOf(message.item)
        )
      };
    case "CHANGE":
      return { newItem: message.item };
    case "DISCOUNTS":
      return { discounts: message.discounts };
    case "INITIAL":
      return {
        newItem: "",
        items: [],
        discounts: []
      };
    default:
      throw new Error("Unknown message type received");
  }
}

class Cart extends React.Component {
  state = reduce(undefined, { type: "INITIAL" });

  componentDidUpdate(prevProps, prevState) {
    if (prevState.items.length !== this.state.items.length) {
      const itemsQuery = this.state.items.join(",");

      ajax(`https://discountdb.com/?items=${itemsQuery}`)
        .then(discounts =&gt; this.dispatch({
          type: "DISCOUNTS", discounts 
        }));
    }
  }

  dispatch = action =&gt; 
    this.setState(prevState =&gt; reduce(prevState, action));

  onNewItemChange = event =&gt;
    this.dispatch({ type: "CHANGE", item: event.target.value });

  addItem = () =&gt; this.dispatch({ type: "ADD" });

  removeItem = item =&gt; () =&gt; this.dispatch({ type: "REMOVE", item });

  render() {
    return (
      &lt;ul&gt;
        &lt;h2&gt;Cart&lt;/h2&gt;
        &lt;input
          type="text"
          onChange={this.onNewItemChange}
          value={this.state.newItem}
        /&gt;
        &lt;button onClick={this.addItem}&gt;Add item&lt;/button&gt;

        {this.state.items.map((item, idx) =&gt; (
          &lt;li key={idx}&gt;
            &lt;button onClick={this.removeItem(item)}&gt;Remove&lt;/button&gt;
            {item}, &amp;ensp;
            {this.state.discounts[idx]}% off!
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    );
  }
}
```

[![Edit zrww3wp57m](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/zrww3wp57m)

Here our `reduce` function resolves to a switch statement which delegates according to certain messages. We call this function in two places: directly in our `state` initializer to bootstrap our component and in our `dispatch` function to allow our event handlers to dynamically pass messages.

Unlike the function you pass to `Array.prototype.reduce`, our reduce only returns the changes to state and not the entire new state. This is because we only pass changes to `this.setState`.

_Notice that the only place we call `this.setState` directly is in the `dispatch` function._

## Benefits

What we have done is extract and enumerate the various transformations that can happen to our component state.

Isolating our state transformations this way can be beneficial when it comes to unit testing; our reducer is just plain JavaScript (no React). We fully extracted all state transformations into the reducer, but you are free to only pull out the ones that can benefit from the indirection.

I have added this technique to a React codebase that was written with no state management patterns in mind, and I found that it really simplified and focused each component.

By creating the `dispatch` function, we are able to pass only one function as a prop to child components if they need to manipulate their parent's state. I found that this drastically reduces [prop drilling](https://blog.kentcdodds.com/prop-drilling-bb62e02cb691).

## Drawbacks

While this allows you to pass fewer callbacks to your child components, you will still need to thread it through your component tree if you want to modify top-level state from a leaf-node. 

If you're implementing this pattern and think to yourself (like how I felt writing the above contrived example), "Why am I even doing this?" your component(s) might not be complex enough to warrant this.

## Wrapping Up

This isn't an original concept; [Dan Abramov](https://twitter.com/dan_abramov) has discussed this before and his [article](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367) is also worth reading. This post is mostly an exercise in exploring different ways to organize and transform React component state. 

Next time you are working with a React component, try to think of new ways you can work with state and let me know what you come up with on Twitter: [@mitchhanberg](https://twitter.com/mitchhanberg).</description>
  <pubDate>Tue, 23 Oct 2018 23:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/10/24/reducers-exploring-state-management-in-react/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/10/24/reducers-exploring-state-management-in-react/---https://www.mitchellhanberg.com/post/2018/10/24/reducers-exploring-state-management-in-react/</guid>
</item>
<item>
  <title>How to use Elixir LS with Vim</title>
  <description>From Mitchell Hanberg's Blog: ### What is Elixir LS?

[Elixir LS](https://github.com/elixir-lsp/elixir-ls) by Jake Becker (now maintained by the [elixir-lsp](https://github.com/elixir-lsp) organization) is the language server implementation for Elixir.

### What is a language server?

If you've been following the story of [Visual Studio Code](https://code.visualstudio.com), there is a chance you've heard of another recent creation from Microsoft: the [Language Server Protocol](https://langserver.org). 

&gt;The Language Server Protocol (LSP) defines the protocol used between an editor or IDE and a language server that provides language features like auto complete, go to definition, find all references etc.

This allows creators to build universal "language servers" that can be used by any text editor. 

### How to integrate with Vim

Using a language server requires a client implementation for your editor and we are going to use [ALE](https://github.com/dense-analysis/ale) by dense-analysis.

If you're using [vim-plug](https://github.com/junegunn/vim-plug) you can install ALE by adding the following to your `.vimrc` and running `:PlugInstall`. Otherwise, consult your plugin manager's documentation.

```vim
" .vimrc

call plug#begin('~/.vim/bundle')
...
Plug 'dense-analysis/ale'
call plug#end()
```
Now let's install Elixir LS!

```shell
$ git clone git@github.com:elixir-lsp/elixir-ls.git
$ cd elixir-ls &amp;&amp; mkdir rel

# checkout the latest release
$ git checkout tags/v0.4.0

$ mix deps.get &amp;&amp; mix compile

$ mix elixir_ls.release -o rel
```

Perfect, our final step is to configure ALE to use Elixir LS.

```vim
" .vimrc

" Required, explicitly enable Elixir LS
let g:ale_linters.elixir = ['elixir-ls']

" Required, tell ALE where to find Elixir LS
let g:ale_elixir_elixir_ls_release = expand("&lt;path to your release&gt;")

" Optional, you can disable Dialyzer with this setting
let g:ale_elixir_elixir_ls_config = {'elixirLS': {'dialyzerEnabled': v:false}}

" Optional, configure as-you-type completions
set completeopt=menu,menuone,preview,noselect,noinsert
let g:ale_completion_enabled = 1
```

### Now what?

I would familiarize yourself with the [features of ALE](https://github.com/dense-analysis/ale#usage) and decide how you want to incorporate them into your workflow. ALE doesn't prescribe any keymappings, so feel free to experiment to see what works best for you!

Check out my [.vimrc](https://github.com/mhanberg/.dotfiles/blob/fb9831367e5543aa84df15b0d1b08e8993c6a905/vimrc#L203..L232) to see how I use ALE.</description>
  <pubDate>Thu, 18 Oct 2018 08:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/10/18/how-to-use-elixir-ls-with-vim/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/10/18/how-to-use-elixir-ls-with-vim/---https://www.mitchellhanberg.com/post/2018/10/18/how-to-use-elixir-ls-with-vim/</guid>
</item>
<item>
  <title>Announcing PlanetEx: an open source blog aggregator written in Elixir</title>
  <description>From Mitchell Hanberg's Blog: </description>
  <pubDate>Mon, 01 Oct 2018 13:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/10/01/announcing-planetex-an-open-source-blog-aggregator-written-in-elixir/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/10/01/announcing-planetex-an-open-source-blog-aggregator-written-in-elixir/---https://www.mitchellhanberg.com/post/2018/10/01/announcing-planetex-an-open-source-blog-aggregator-written-in-elixir/</guid>
</item>
<item>
  <title>Elixir in Action: Book Review</title>
  <description>From Mitchell Hanberg's Blog: ### Who is the target audience?

The book is intended for beginner and intermediate Elixir developers. As the book progresses, the author dives into topics that get more complex, which might not be suitable unless you already understand the basics of Elixir.

### Review

_Elixir in Action_ covers the full breadth of all that is Elixir, from the basic syntax to building distributed, fault tolerant, and scalable systems. This is definitely a lot, especially if _Elixir in Action_ is the first book on Elixir you've read. 

The book is broken into three parts: The Language, The Platform, and Production. 

If this is your first foray into Elixir, I would suggest reading The Language and then putting the book down and write some code. You're going to be able to understand and absorb so much more from part 2 (The Platform) once you become comfortable with the syntax and the functional paradigm. The author provides numerous exercises to extend the examples he provides throughout the book.

The Platform dives into what really makes Elixir shine, which is the BEAM and the concurrency model that enables you to develop scalable and fault tolerant software. I would highly suggest working through the examples (or start a side project!) while reading this part. Reading concurrent code can be tough, so being able to see it _in action_ will accelerate your understanding of how to develop software in Elixir.

Production covers the basics of how software in the Erlang ecosystem is generally packaged and deployed. This is possibly more involved than other languages you've worked in because Elixir allows you to do _hot code upgrades_ without restarting the system. 

I read _Elixir in Action_ about a year into my Elixir journey and I definitely wished I would have read it in the beginning, but I still learned a lot and would recommend it to anyone interested in Elixir.</description>
  <pubDate>Wed, 19 Sep 2018 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/09/19/elixir-in-action-book-review/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/09/19/elixir-in-action-book-review/---https://www.mitchellhanberg.com/post/2018/09/19/elixir-in-action-book-review/</guid>
</item>
<item>
  <title>Writing an Absinthe Phase</title>
  <description>From Maarten van Vliet: Absinthe does a lot when you fire a GraphQL query at it. The incoming query is parsed into an internal representation, validated and finally executed. This process is done by phases, these are individual modules chained together in a pipeline that each do a single step in processing the queries.
Phases in Absinthe do a lot of work. They are the building blocks in validating and executing the GraphQL query. E.</description>
  <pubDate>Fri, 07 Sep 2018 22:35:36 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2018/09/07/writing-an-absinthe-phase/</link>
  <guid>https://maartenvanvliet.nl/2018/09/07/writing-an-absinthe-phase/---https://maartenvanvliet.nl/2018/09/07/writing-an-absinthe-phase/</guid>
</item>
<item>
  <title>Absinthe Tips and Tricks</title>
  <description>From Maarten van Vliet: Absinthe is a great package for building GraphQL api&amp;rsquo;s in Elixir. It is fast, good DSL and overall great implementation of the GraphQL spec for servers.
Working with Absinthe I&amp;rsquo;ve come across some patterns that have helped clean up code. I&amp;rsquo;d like to share some of them here.
The Self function Imagine you have an image object you want to expose, and for each image you want a separate object with just the dimensions of the image.</description>
  <pubDate>Wed, 05 Sep 2018 22:18:38 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2018/09/05/absinthe-tips-and-tricks/</link>
  <guid>https://maartenvanvliet.nl/2018/09/05/absinthe-tips-and-tricks/---https://maartenvanvliet.nl/2018/09/05/absinthe-tips-and-tricks/</guid>
</item>
<item>
  <title>How to Subscribe to SharePoint RSS Feeds Without NTLM Authentication</title>
  <description>From Mitchell Hanberg's Blog: </description>
  <pubDate>Sat, 18 Aug 2018 21:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/08/19/how-to-subscribe-to-sharepoint-rss-feeds-without-ntlm-authentication/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/08/19/how-to-subscribe-to-sharepoint-rss-feeds-without-ntlm-authentication/---https://www.mitchellhanberg.com/post/2018/08/19/how-to-subscribe-to-sharepoint-rss-feeds-without-ntlm-authentication/</guid>
</item>
<item>
  <title>Metaprogramming Elixir: Book Review</title>
  <description>From Mitchell Hanberg's Blog: ### Who is the target audience?

Advanced beginner to intermediate Elixir developers who have mastered the syntax and basic structure of Elixir applications who want to add advanced language features to their skill set.

### Review

Chris McCord (creator of the [Phoenix Framework](https://phoenixframework.org/)) introduces you to the power of macros with the Elixir programming language.

This book (a member of the [Pragmatic exPress](https://pragprog.com/pragmatic-express) series) is short and to the point. If you're reading this book, you probably are interested in the practical application of macros and metaprogramming, and this book delivers.

_Metaprogramming Elixir_ guides you through the basics of defining macros, but quickly dives into best practices and how you can use these techniques to improve your real life code. 

In addition to showing you when it's most appropriate to utilize a macro, Chris teaches you how to test macros and what aspects of metaprogramming are worth testing. As a practitioner of [Test Driven Development](https://en.wikipedia.org/wiki/Test-driven_development), I find this incredibly valuable.

I thoroughly enjoyed this book and will definitely be referencing it in the future.</description>
  <pubDate>Sat, 18 Aug 2018 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/08/18/metaprogramming-elixir-book-review/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/08/18/metaprogramming-elixir-book-review/---https://www.mitchellhanberg.com/post/2018/08/18/metaprogramming-elixir-book-review/</guid>
</item>
<item>
  <title>Swotex hex package</title>
  <description>From Maarten van Vliet: Swotex hex package Identify email addresses or domains names that belong to colleges or universities.</description>
  <pubDate>Sun, 12 Aug 2018 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/swotex/</link>
  <guid>https://maartenvanvliet.nl/project/swotex/---https://maartenvanvliet.nl/project/swotex/</guid>
</item>
<item>
  <title>Container Components: Exploring State Management in React (Part I)</title>
  <description>From Mitchell Hanberg's Blog: &gt;At what level of complexity will my React application require Redux?

React developers have been asking this question for a long time, and answers still vary wildly. The truth is there is quite a bit we can do before needing to pull in Redux, and even then, _**Redux**_ isn't our only option! 

Even the creator of Redux, [Dan Abramov](https://twitter.com/dan_abramov?lang=en) thinks that we might not need Redux (although, I think the spirit this statement applies to all 3rd-party libraries meant to help reduce complexity of state).

&lt;blockquote class="twitter-tweet" data-lang="en"&gt;&lt;p lang="en" dir="ltr"&gt;You Might Not Need Redux &lt;a href="https://t.co/3zBPrbhFeL"&gt;https://t.co/3zBPrbhFeL&lt;/a&gt;&lt;/p&gt;&amp;mdash; Dan Abramov (@dan_abramov) &lt;a href="https://twitter.com/dan_abramov/status/777983404914671616?ref_src=twsrc%5Etfw"&gt;September 19, 2016&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

In this series, we'll explore a few different patterns you can introduce to your code base before reaching for a 3rd party solution!

## Container/Presenter Components

This pattern separates what might be a single component into two: a Container component to maintain state and a Presenter component to render visual markup. 

```jsx
const Cart = props =&gt; (
  &lt;ul&gt;
    &lt;h2&gt;Cart&lt;/h2&gt;
    &lt;input
      type="text"
      onChange={props.onNewItemChange}
      value={props.newItem}
    /&gt;
    &lt;button onClick={props.addItem}&gt;Add item&lt;/button&gt;

    {props.items.map((item, idx) =&gt; (
      &lt;li key={idx}&gt;
        &lt;button onClick={props.removeItem(item)}&gt;Remove&lt;/button&gt;
        {item}, &amp;ensp;
        {props.discounts[idx]}% off!
      &lt;/li&gt;
    ))}
  &lt;/ul&gt;
);

class CartContainer extends React.Component {
  state = {
    newItem: "",
    items: [],
    discounts: []
  };

  componentDidUpdate(prevProps, prevState) {
    if (prevState.items.length !== this.state.items.length) {
      const itemsQuery = this.state.items.join(",");

      ajax(`https://discountdb.com/?items=${itemsQuery}`)
        .then(discounts =&gt; this.setState({ discounts }));
    }
  }

  onNewItemChange = event =&gt; this.setState({ newItem: event.target.value });

  addItem = () =&gt; {
    this.setState(prevState =&gt; ({
      items: [...prevState.items, prevState.newItem],
      newItem: ""
    }));
  };

  removeItem = item =&gt; () =&gt; {
    this.setState(prevState =&gt; ({
      items: prevState.items.filter(
        (i, idx, prevItems) =&gt; idx !== prevItems.indexOf(item)
      )
    }));
  };

  render() {
    return (
      &lt;div&gt;
        &lt;Cart
          newItem={this.state.newItem}
          onNewItemChange={this.onNewItemChange}
          items={this.state.items}
          discounts={this.state.discounts}
          addItem={this.addItem}
          removeItem={this.removeItem}
        /&gt;
      &lt;/div&gt;
    );
  }
}
```
[![Edit wo7y9voowk](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/wo7y9voowk)
&amp;nbsp;

Here we can see that we have a Container component, `CartContainer`, that handles controlling component state with the `addItem`, `removeItem`, and `onNewItemChange` callbacks, and fetching a list of discounts from an external REST api. This enables us to write `Cart`, our Presenter component, as a Pure Functional component. 

After extracting a Container and a Presenter from one of our bigger components, we might find the Container to still be fairly large, or handling several concerns, potentially signaling that we can break down our Container even further. 

In our case, we might extract a `DiscountContainer` from `CartContainer` to segregate the logic of maintaining the contents of the cart from the logic of fetching the discounts for those items.

The hierarchy of this would look like `CartContainer` -&gt; `DiscountContainer` -&gt; `Cart`, having `CartContainer` pass the discount-less items to the `DiscountContainer`, which will fetch the discounts and then pass the now discounted items to the `Cart`.

## Benefits

Partitioning our components on their state boundary will help reduce complexity by simply having less code to work with at a time, while still staying "inside React".

I think this pattern really starts to pay dividends when we have a lifecycle method, like `componentDidUpdate`, doing a lot of asynchronous work (like making HTTP requests). Given the asynchronous nature, this sort of code tends to be very difficult to test (with both automated unit testing and manual testing), so breaking this stateful logic into separate components helps keeps us sane and our code focused. 

It's helpful to remind ourselves that when unit testing a React component, we are essentially testing the `render` function. Given the inputs (`props`), what is the output? You've probably noticed tests are painful to write if there is a lot of setup, especially if the setup is required for a feature of the component that you aren't even testing.

Keeping our components small and focused will go a long way for keeping ourselves happy and productive!

## Drawbacks

While the Container/Presenter pattern is not always one-for-one (`Cart` &lt;-&gt; `CartContainer`), you will encounter a lot of similarly named components. This can sometimes cause a communication breakdown amongst the team, as you will trip over your own words attempting to say things like "The CartContainer passes the products to the Cart which then passes them to the Checkout component, or is it the CheckoutContainer component?".

If you can think of better names, I would suggest using them! Your code will still be following the pattern even if they don't have the word Container in the name. 😏

## Wrapping Up

My team has utilized this pattern heavily, and I believe it is a solid option to consider before reaching for a tool like Redux.

If you've never heard of this pattern and would like to learn more, Dan Abramov [has also written about this topic](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0).

Now try this in your codebase or introduce the idea to your team during a lunch-and-learn and let me know how it goes!</description>
  <pubDate>Wed, 25 Jul 2018 00:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/07/25/exploring-state-management-in-react-container-components/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/07/25/exploring-state-management-in-react-container-components/---https://www.mitchellhanberg.com/post/2018/07/25/exploring-state-management-in-react-container-components/</guid>
</item>
<item>
  <title>Building with Elm at SEP:Makes</title>
  <description>From Mitchell Hanberg's Blog: </description>
  <pubDate>Thu, 08 Mar 2018 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/03/08/building-elm-at-sep-makes/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/03/08/building-elm-at-sep-makes/---https://www.mitchellhanberg.com/post/2018/03/08/building-elm-at-sep-makes/</guid>
</item>
<item>
  <title>Integrate and Deploy React with Phoenix</title>
  <description>From Mitchell Hanberg's Blog: &lt;blockquote&gt;
&lt;p&gt;You've just finished your lightning fast Phoenix JSON API, what's next?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a href="#motivation" aria-hidden="true" class="anchor" id="motivation"&gt;&lt;/a&gt;Motivation&lt;/h2&gt;
&lt;p&gt;&lt;img src="/images/contact.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;My most recent side project, &lt;a href="https://www.github.com/mhanberg/contact"&gt;Contact&lt;/a&gt;, is a JSON REST API written with &lt;a href="https://elixir-lang.org/"&gt;Elixir&lt;/a&gt; and &lt;a href="https://github.com/phoenixframework/phoenix"&gt;Phoenix&lt;/a&gt;, designed to be the backend to an instant messaging application (e.g., Slack). There was a hackathon coming up at work, and I thought it'd be fun to make a frontend for Contact during it, and although Contact's development was thoroughly test-driven, I wanted to make sure that my API was ready to be used.&lt;/p&gt;
&lt;p&gt;The hackathon was two weeks away, so I needed to quickly prototype a UI to flesh out any oversights I made.&lt;/p&gt;
&lt;p&gt;I decided to go with React over basic server-rendered html templates because my next project at work will be using React and figured I could use this to level up my skills.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#lets-add-react" aria-hidden="true" class="anchor" id="lets-add-react"&gt;&lt;/a&gt;Let's add React!&lt;/h2&gt;
&lt;p&gt;If you generated your Phoenix project using the &lt;code&gt;mix phx.new --no-html --no-brunch&lt;/code&gt; command, you're good to go!&lt;/p&gt;
&lt;p&gt;If not, let's rip out the stock html and Javascript scaffolding that Phoenix generates for you. You can safely remove the &lt;code&gt;priv/assets&lt;/code&gt; directory (which contains all of the Brunch configuration) and &lt;code&gt;lib/&amp;lt;path to your web directory&amp;gt;/templates&lt;/code&gt;, along with any Phoenix views, controllers, and routes that you aren't using.&lt;/p&gt;
&lt;p&gt;A good place to install our React app is the &lt;code&gt;priv&lt;/code&gt; directory, so let's move into there and run the installer.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;# priv/
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;$ npx create-react-app contact-react 
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ cd contact-react 
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;$ yarn start # npm start if you don&amp;#39;t use yarn
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://github.com/facebook/create-react-app"&gt;create-react-app&lt;/a&gt; gets us set up with React, Babel, and Webpack out of the box, allowing us to get started developing our application and not mess around with a ton of esoteric configuration.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#success" aria-hidden="true" class="anchor" id="success"&gt;&lt;/a&gt;Success!&lt;/h2&gt;
&lt;p&gt;We now have a development server running, serving the generated React application.&lt;/p&gt;
&lt;p&gt;&lt;img src="/images/create-react-default.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href="#connecting-the-frontend-to-the-backend" aria-hidden="true" class="anchor" id="connecting-the-frontend-to-the-backend"&gt;&lt;/a&gt;Connecting the frontend to the backend&lt;/h2&gt;
&lt;p&gt;You may have noticed that your Phoenix server and the React development server are running on two different ports, how can we allow our two applications to communicate with each other?&lt;/p&gt;
&lt;h3&gt;&lt;a href="#development" aria-hidden="true" class="anchor" id="development"&gt;&lt;/a&gt;Development&lt;/h3&gt;
&lt;p&gt;We'll set up a proxy for development, so we won't have to specify the absolute URI of the API endpoints we want to hit. Let's add this line to our &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-json" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&amp;quot;proxy&amp;quot;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &amp;quot;http://localhost:4000&amp;quot;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This line will set all network request URIs to be made relative to our Phoenix server.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#production" aria-hidden="true" class="anchor" id="production"&gt;&lt;/a&gt;Production&lt;/h3&gt;
&lt;p&gt;In production, we'll have the Phoenix server send the frontend to the client.&lt;/p&gt;
&lt;p&gt;To accomplish this, we'll set up the root endpoint to serve the contents of the &lt;code&gt;build&lt;/code&gt; directory of our React app.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;# endpoint.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #8cf8f7;"&gt;plug&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;Plug.Static.IndexHtml&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;at: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;&lt;span style="color: #8cf8f7;"&gt;plug&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;  &lt;span style="color: #e0e2ea;"&gt;Plug.Static&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #8cf8f7;"&gt;at: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;  &lt;span style="color: #8cf8f7;"&gt;from: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;priv/contact-react/build/&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #8cf8f7;"&gt;only: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;~&lt;/span&gt;w&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;index.html favicon.ico static service-worker.js&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://hex.pm/packages/plug_static_index_html"&gt;&lt;code&gt;Plug.Static.IndexHtml&lt;/code&gt;&lt;/a&gt; will allow us to serve the &lt;code&gt;index.html&lt;/code&gt; that Webpack generates from the root endpoint.&lt;/p&gt;
&lt;p&gt;Now if we run &lt;code&gt;yarn build&lt;/code&gt;, start our Phoenix server, and navigate to &lt;code&gt;localhost:4000&lt;/code&gt; in the browser, we should see our React application!&lt;/p&gt;
&lt;h2&gt;&lt;a href="#deployment" aria-hidden="true" class="anchor" id="deployment"&gt;&lt;/a&gt;Deployment&lt;/h2&gt;
&lt;p&gt;Since we have added another build step to our workflow, we'll need to include that in our deployment process. I will describe the steps needed to deploy using &lt;a href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I added a &lt;code&gt;before_deploy&lt;/code&gt; step and set the &lt;code&gt;skip_cleanup&lt;/code&gt; flag to the &lt;code&gt;deploy&lt;/code&gt; step to my &lt;code&gt;.travis.yml&lt;/code&gt; file, resembling the following.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-yaml" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #a6dbff;"&gt;before_deploy&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  &lt;span style="color: #e0e2ea;"&gt;-&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;rm -rf ~/.nvm &amp;amp;&amp;amp; curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash &amp;amp;&amp;amp; nvm install node &amp;amp;&amp;amp; nvm use node&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;  &lt;span style="color: #e0e2ea;"&gt;-&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;cd priv/contact-react &amp;amp;&amp;amp; yarn install &amp;amp;&amp;amp; yarn build &amp;amp;&amp;amp; cd -&lt;/span&gt; 
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;&lt;span style="color: #a6dbff;"&gt;deploy&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;  &lt;span style="color: #a6dbff;"&gt;skip_cleanup&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;:&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A breakdown of what is happening here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reinstall the latest version of &lt;a href="https://github.com/creationix/nvm"&gt;Node Version Manager&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Install the latest version of Node.js.&lt;/li&gt;
&lt;li&gt;Set the current version of Node.js to the one we just installed.&lt;/li&gt;
&lt;li&gt;Build our React application (Yarn is already installed).&lt;/li&gt;
&lt;li&gt;Tell Travis to deploy the compiled React application (otherwise, Travis would see the &lt;code&gt;build&lt;/code&gt; directory as a build artifact and clean it up before deployment).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="#its-time-to-get-to-work" aria-hidden="true" class="anchor" id="its-time-to-get-to-work"&gt;&lt;/a&gt;It's time to get to work!&lt;/h2&gt;
&lt;p&gt;In 10 minutes we've gone from nothing to a deployed application!&lt;/p&gt;
&lt;p&gt;Following these steps allowed me to get right to business; I was successful in prototyping 90% of my application before the hackathon.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;&lt;a href="#references" aria-hidden="true" class="anchor" id="references"&gt;&lt;/a&gt;References&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.petecorey.com/blog/2017/04/03/using-create-react-app-with-phoenix/"&gt;http://www.petecorey.com/blog/2017/04/03/using-create-react-app-with-phoenix/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.travis-ci.com/user/deployment/heroku/#Deploying-build-artifacts"&gt;https://docs.travis-ci.com/user/deployment/heroku/#Deploying-build-artifacts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.travis-ci.com/user/deployment/heroku/#Running-commands-before-and-after-deploy"&gt;https://docs.travis-ci.com/user/deployment/heroku/#Running-commands-before-and-after-deploy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
  <pubDate>Thu, 22 Feb 2018 08:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/02/22/integrate-and-deploy-react-with-phoenix/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/02/22/integrate-and-deploy-react-with-phoenix/---https://www.mitchellhanberg.com/post/2018/02/22/integrate-and-deploy-react-with-phoenix/</guid>
</item>
<item>
  <title>Version Your Dotfiles for Great Good</title>
  <description>From Mitchell Hanberg's Blog: &lt;h2&gt;&lt;a href="#what-are-dotfiles" aria-hidden="true" class="anchor" id="what-are-dotfiles"&gt;&lt;/a&gt;What are dotfiles?&lt;/h2&gt;
&lt;p&gt;Your dotfiles are the hidden files or folders that live in your home directory, for example your &lt;code&gt;.vimrc&lt;/code&gt; and your &lt;code&gt;.bashrc&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#what-do-i-mean-by-version" aria-hidden="true" class="anchor" id="what-do-i-mean-by-version"&gt;&lt;/a&gt;What do I mean by version?&lt;/h2&gt;
&lt;p&gt;By "version", I mean to track your dotfiles using a version control system, like git, and a hosting service, like Github.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#why-would-you-want-to-version-them" aria-hidden="true" class="anchor" id="why-would-you-want-to-version-them"&gt;&lt;/a&gt;Why would you want to version them?&lt;/h2&gt;
&lt;p&gt;Versioning your dotfiles allows you to track them and to be able to share them between computers, making it easy to provision a new computer.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#lets-get-started" aria-hidden="true" class="anchor" id="lets-get-started"&gt;&lt;/a&gt;Let's get started&lt;/h2&gt;
&lt;p&gt;There's a good chance that you arleady have some dotfiles, don't worry, it's easy to start tracking them. Our first order of business is to install a handy suite of tools called &lt;a href="https://github.com/thoughtbot/rcm"&gt;rcm&lt;/a&gt;, which abstracts the process of symlinking our dotfiles.&lt;/p&gt;
&lt;p&gt;On macOS we would install via &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ brew install rcm
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rcm expects a &lt;code&gt;~/.dotfiles&lt;/code&gt; directory, so let's create that and initialize our git repository.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ mkdir ~/.dotfiles &amp;amp;&amp;amp; cd ~/.dotfiles &amp;amp;&amp;amp; git init
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, if you have any dotfiles (you probably do), you'll want to add them to your &lt;code&gt;~/.dotfiles&lt;/code&gt; directory, and for this, we use the &lt;a href="http://thoughtbot.github.io/rcm/mkrc.1.html"&gt;mkrc&lt;/a&gt; tool provided by rcm.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ mkrc .vimrc
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;$ mkrc .zshrc
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ mkrc .rubocop.yml
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, rcm has copied these files to your &lt;code&gt;~/.dotfiles&lt;/code&gt; directory with the dot stripped from the name (&lt;code&gt;.vimrc -&amp;gt; vimrc&lt;/code&gt;) and replaced the copy in your home directory with a &lt;a href="https://en.wikipedia.org/wiki/Symbolic_link"&gt;symlink&lt;/a&gt; to the new file in your &lt;code&gt;~/.dotfiles&lt;/code&gt; directory.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#what-about-my-vim-plugins" aria-hidden="true" class="anchor" id="what-about-my-vim-plugins"&gt;&lt;/a&gt;What about my vim plugins?&lt;/h3&gt;
&lt;p&gt;For most vim plugin managers, you are probably going to be cloning at least one git repository somewhere in your &lt;code&gt;~/.vim&lt;/code&gt; directory. If you don't plan on ever updating these repos, you can go ahead and run &lt;code&gt;mkrc .vim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you want to be able to update these plugins (or any other tool you use which relies on a git repo), you'll need to use &lt;a href="http://www.vogella.com/tutorials/GitSubmodules/article.html"&gt;git submodules&lt;/a&gt;. I attempted to turn my existing vim plugins into submodules within my &lt;code&gt;.dotfiles&lt;/code&gt; repository, but I wasn't successful. So instead of re-cloning each plugin as a submodule, I decided to switch to a vim plugin manager that doesn't require manually cloning repos.&lt;/p&gt;
&lt;p&gt;I opted to start using &lt;a href="https://github.com/junegunn/vim-plug"&gt;vim-plug&lt;/a&gt; over &lt;a href="https://github.com/tpope/vim-pathogen"&gt;pathogen&lt;/a&gt;, which requires less boilerplate and all of the configuration goes in your &lt;code&gt;.vimrc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once I was done, the section of my &lt;code&gt;.vimrc&lt;/code&gt; handling my vim plugins looked like so:&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-vim" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;&amp;quot; Install vim-plug and plugins if vim-plug is not already installed&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;empty&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;glob&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;#39;~/.vim/autoload/plug.vim&amp;#39;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;silent&lt;/span&gt; !curl -fLo ~/.vim/autoload/plug.vim --create-dirs
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;    \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;autocmd&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;VimEnter&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;*&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;PlugInstall&lt;/span&gt; --sync | &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;source&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;$MYVIMRC&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;endif&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;&lt;span style="color: #9b9ea4;"&gt;&amp;quot; Plugins&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;call&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;plug#begin&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;#39;~/.vim/bundle&amp;#39;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;slashmili/alchemist.vim&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;kien/ctrlp.vim&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;vim-airline/vim-airline&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;tpope/vim-bundler&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;tpope/vim-dispatch&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;elixir-editors/vim-elixir&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;tpope/vim-endwise&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;tpope/vim-fugitive&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;airblade/vim-gitgutter&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;JamshedVesuna/vim-markdown-preview&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;tpope/vim-rails&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;tpope/vim-sensible&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;janko-m/vim-test&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;&lt;span style="color: #8cf8f7;"&gt;Plug&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;#39;christoomey/vim-tmux-navigator&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;call&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;plug#end&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href="#yeah-but-what-about-the-submodules-i-cant-avoid-making" aria-hidden="true" class="anchor" id="yeah-but-what-about-the-submodules-i-cant-avoid-making"&gt;&lt;/a&gt;Yeah, but what about the submodules I can't avoid making?&lt;/h3&gt;
&lt;p&gt;Thought you might ask that, I encountered the same challenge. I use &lt;a href="https://github.com/robbyrussell/oh-my-zsh"&gt;oh-my-zsh&lt;/a&gt; for &lt;a href="https://en.wikipedia.org/wiki/Z_shell"&gt;zsh&lt;/a&gt; customizations (mostly the themes¯\_(ツ)_/¯), which relies on a git repository in your home directory &lt;code&gt;.oh-my-zsh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While I wasn't able to transfer the existing directory to my new &lt;code&gt;.dotfiles&lt;/code&gt; directory, I was able to reclone it as a git submodule.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ cd ~/.dotfiles
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;$ git submodule add -b master &amp;lt;repo url&amp;gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;$ git submodule init # you should only have to run this the first time
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;$ rcup -v
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The submodule is set up to track the master branch, has been cloned into your repository, and a symlink to your home directory has been made using the &lt;a href="http://thoughtbot.github.io/rcm/rcup.1.html"&gt;rcup&lt;/a&gt; tool provided by rcm.&lt;/p&gt;
&lt;p&gt;If you wish to update it at any time, you only need to move into the directory of the submodule and do a &lt;code&gt;git pull&lt;/code&gt;, followed by  returning to the parent repository and committing the change.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#final-steps" aria-hidden="true" class="anchor" id="final-steps"&gt;&lt;/a&gt;Final Steps&lt;/h2&gt;
&lt;p&gt;Now that you have all of your dotfiles in a git repository, it's time to push that repository to Github and figure out how you are going to install these dotfiles the next time you are setting up a computer.&lt;/p&gt;
&lt;h3&gt;&lt;a href="#provisioning-a-new-machine-with-your-dotfiles" aria-hidden="true" class="anchor" id="provisioning-a-new-machine-with-your-dotfiles"&gt;&lt;/a&gt;Provisioning a new machine with your dotfiles&lt;/h3&gt;
&lt;p&gt;This is the part we've been working towards, and thanks to our efforts, this part is a breeze.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;# Install git if not installed
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;# Install rcm
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;$ git clone --recursive &amp;lt;url&amp;gt; ~/.dotfiles
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;$ rcup -v
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voila!&lt;/p&gt;
&lt;p&gt;If you want to make it even easier to install, rcm provides a utlity to generate a standalone install script.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The rcup(1) tool can be used to generate a portable shell script. Instead of running a command such as ln(1) or rm(1), it will print the command to stdout. This is controlled with the -g flag. Note that this will generate a script to create an exact replica of the synchronization, including tags, host-specific files, and dotfiles directories.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;env RCRC=/dev/null rcup -B 0 -g &amp;gt; install.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Using the above command, you can now run install.sh to install (or re-install) your rc files. The install.sh script can be stored in your dotfiles directory, copied between computers, and so on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a href="#wrapping-up" aria-hidden="true" class="anchor" id="wrapping-up"&gt;&lt;/a&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;If you've followed along and have made it this far, congratulations! You now have a backup of all of your configuration files!&lt;/p&gt;
&lt;p&gt;If you started reading this, but thought, "Dang, I don't have any dotfiles to version, why am I reading this?", you're in luck! Since versioning one's dotfiles is a common practice, there are plenty of repositories out there for which you can choose to fork and start your own collection.&lt;/p&gt;
&lt;p&gt;For example, if you were to checkout &lt;a href="https://github.com/mhanberg/.dotfiles"&gt;my dotfiles&lt;/a&gt; you would find a pretty basic setup for &lt;a href="https://www.ruby-lang.org/en/"&gt;ruby&lt;/a&gt; and &lt;a href="https://elixir-lang.org/"&gt;elixir&lt;/a&gt; development, along with helpful configurations for &lt;a href="https://github.com/tmux/tmux/wiki"&gt;tmux&lt;/a&gt; and &lt;a href="http://www.vim.org/"&gt;vim&lt;/a&gt;, which would be a good starting point if you didn't want to curate your dotfiles from scratch.&lt;/p&gt;</description>
  <pubDate>Mon, 29 Jan 2018 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/post/2018/01/29/version-your-dotfiles-for-great-good/</link>
  <guid>https://www.mitchellhanberg.com/post/2018/01/29/version-your-dotfiles-for-great-good/---https://www.mitchellhanberg.com/post/2018/01/29/version-your-dotfiles-for-great-good/</guid>
</item>
<item>
  <title>Looking back at year in tech</title>
  <description>From Maarten van Vliet: Retrospective Last year was an interesting year for me. I was able to try out a whole lot of new technologies, assess their capabilities and get a good look at their pros and cons. I want to go over a couple of them, and shortly summarize my findings.
Typescript In short, Typescript made Javascript fun for me again. Although a lot of this can be attributed to the support of ES6/ES7 in Typescript.</description>
  <pubDate>Sat, 30 Dec 2017 22:34:06 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2017/12/30/tech_2017_looking_back/</link>
  <guid>https://maartenvanvliet.nl/2017/12/30/tech_2017_looking_back/---https://maartenvanvliet.nl/2017/12/30/tech_2017_looking_back/</guid>
</item>
<item>
  <title>Upgrading Phoenix to Http2</title>
  <description>From Maarten van Vliet: Upgrading phoenix framework to serve http2 Http2 is the next version of the http protocol and offers several advantages. It serves over a single connection, reducing the number of roundtrips necessary. With multiplexing it can handle multiple requests at the same time and as a whole is just better.
As the internet slowly moves to support http2, Phoenix does not lag behind. The webserver Phoenix uses, Cowboy, has released version 2 with support for http2 and the updates to Phoenix and Plug have followed in its path.</description>
  <pubDate>Fri, 15 Dec 2017 21:30:43 +0100</pubDate>
  <link>https://maartenvanvliet.nl/2017/12/15/upgrading_phoenix_to_http2/</link>
  <guid>https://maartenvanvliet.nl/2017/12/15/upgrading_phoenix_to_http2/---https://maartenvanvliet.nl/2017/12/15/upgrading_phoenix_to_http2/</guid>
</item>
<item>
  <title>Implementing API Authentication with Guardian in Phoenix</title>
  <description>From Mitchell Hanberg's Blog: &lt;p&gt;Most applications need some sort of authentication and authorization, and REST API's are no different. If you are familiar with web development but have never worked on one that does not have a front end (like me), then the authentication functionality might stump you at first.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#what-is-guardian" aria-hidden="true" class="anchor" id="what-is-guardian"&gt;&lt;/a&gt;What is Guardian?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Guardian is a token based authentication library for use with Elixir applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;More can be learned by reading its &lt;a href="https://github.com/ueberauth/guardian"&gt;documentation&lt;/a&gt;, which I highly recommend.&lt;/li&gt;
&lt;li&gt;Keep in mind that the "tokens" that Guardians refers to are &lt;a href="https://jwt.io/introduction/"&gt;JSON Web Tokens&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="#installation-and-configuration" aria-hidden="true" class="anchor" id="installation-and-configuration"&gt;&lt;/a&gt;Installation and Configuration&lt;/h2&gt;
&lt;p&gt;Add Guardian 1.0 as a dependency in &lt;code&gt;mix.exs&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:guardian&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;~&amp;gt; 1.0&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And install&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;$ mix deps.get
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Guardian requires that you add an implementation module, I added mine to an &lt;code&gt;auth&lt;/code&gt; folder in my web directory.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;# lib/my_app_web/auth/guardian.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defmodule&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;use&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Guardian&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;otp_app: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:my_app&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;subject_for_token&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;resource&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #9b9ea4;"&gt;_claims&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;    &lt;span style="color: #9b9ea4;"&gt;# You can use any value for the subject of your token but&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;    &lt;span style="color: #9b9ea4;"&gt;# it should be useful in retrieving the resource later, see&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;    &lt;span style="color: #9b9ea4;"&gt;# how it being used on `resource_from_claims/1` function.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;    &lt;span style="color: #9b9ea4;"&gt;# A unique `id` is a good subject, a non-unique email address&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;    &lt;span style="color: #9b9ea4;"&gt;# is a poor subject.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;    &lt;span style="color: #e0e2ea;"&gt;sub&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;to_string&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;resource&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;id&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;    &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:ok&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;sub&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;resource_from_claims&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;claims&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;    &lt;span style="color: #9b9ea4;"&gt;# Here we&amp;#39;ll look up our resource from the claims, the subject can be&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;    &lt;span style="color: #9b9ea4;"&gt;# found in the `&amp;quot;sub&amp;quot;` key. In `above subject_for_token/2` we returned&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;    &lt;span style="color: #9b9ea4;"&gt;# the resource id so here we&amp;#39;ll rely on that to look it up.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;    &lt;span style="color: #e0e2ea;"&gt;id&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;claims&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;sub&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="23"&gt;    &lt;span style="color: #e0e2ea;"&gt;resource&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyApp&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;get_resource_by_id&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;id&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="24"&gt;    &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:ok&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;  &lt;span style="color: #e0e2ea;"&gt;resource&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="25"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="26"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two functions that you are required to implement, &lt;code&gt;subject_for_token/2&lt;/code&gt; and &lt;code&gt;resource_for_claims/2&lt;/code&gt;. The compiler will complain if they are not implemented.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;resource&lt;/code&gt; refers to a user, and the &lt;code&gt;sub&lt;/code&gt; (subject) is the user's primary key (or another unique identifier). The function &lt;code&gt;MyApp.get_resource_by_id(id)&lt;/code&gt;, that is called in &lt;code&gt;resource_from_claims/2&lt;/code&gt;, is just a placeholder. You will need to implement that function (the name is not important, it can be whatever you want) and it should retrieve the user based on the &lt;code&gt;sub&lt;/code&gt; (subject).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is taken directly from Guardian's example code.&lt;/li&gt;
&lt;li&gt;More can be learned about claims by reading the &lt;a href="https://jwt.io/introduction/"&gt;JSON Web Token documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, we add a config to &lt;code&gt;config.exs&lt;/code&gt;&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #8cf8f7;"&gt;config&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:my_app&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;       &lt;span style="color: #8cf8f7;"&gt;issuer: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;my_app&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;       &lt;span style="color: #8cf8f7;"&gt;secret_key: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;Secret key. You can use `mix guardian.gen.secret` to get one&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need to create a secret key using the method above. If you're writing a production application, you should use an environment variable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The atom &lt;code&gt;:my_app&lt;/code&gt; corresponds to the atom in the implementation module and the namespacing of the module also corresponds to the implementation module.&lt;/li&gt;
&lt;li&gt;The value for the key &lt;code&gt;issuer&lt;/code&gt; can be whatever you want.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href="#authentication" aria-hidden="true" class="anchor" id="authentication"&gt;&lt;/a&gt;Authentication&lt;/h2&gt;
&lt;p&gt;Our next step will be to perform the authentication of the credentials that the client has sent to our API.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;authenticate&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;user: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;password: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;password&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  &lt;span style="color: #9b9ea4;"&gt;# Does password match the one stored in the database?&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;case&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Comeonin.Bcrypt&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;checkpw&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;password&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;password_digest&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;    &lt;span style="color: #e0e2ea;"&gt;true&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;      &lt;span style="color: #9b9ea4;"&gt;# Yes, create and return the token&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;      &lt;span style="color: #e0e2ea;"&gt;MyAppWebWeb.Guardian&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;encode_and_sign&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;    &lt;span style="color: #9b9ea4;"&gt;_&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;-&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;      &lt;span style="color: #9b9ea4;"&gt;# No, return an error&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;      &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:error&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:unauthorized&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is my authentication function, it takes the User struct that I located in the database based on the unique identifier that was passed by the request (in my case can be either an email address or a username) and the password attempt.&lt;/p&gt;
&lt;p&gt;I am using the password hashing library &lt;a href="https://github.com/riverrun/comeonin"&gt;Comeonin&lt;/a&gt; to hash my passwords, so here I use the &lt;code&gt;checkpw/2&lt;/code&gt; function to check the password attempt against the digest (AKA a hash) stored in the database. This function returns either true or false, so if the password is correct, we fall into the true case and we create our token (JSON Web Token, or JWT) and return it.&lt;/p&gt;
&lt;p&gt;Otherwise we return the tuple &lt;code&gt;&amp;lbrace;:error, :unauthorized&amp;rbrace;&lt;/code&gt; to signify that the authentication attempt failed.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#the-controller" aria-hidden="true" class="anchor" id="the-controller"&gt;&lt;/a&gt;The Controller&lt;/h2&gt;
&lt;p&gt;Let's expose this functionality to our public API by making a controller endpoint to sign in a user.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;sign_in&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;params&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  &lt;span style="color: #9b9ea4;"&gt;# Find the user in the database based on the credentials sent with the request&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;with&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;User&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Accounts&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;find&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;params&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;email&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;    &lt;span style="color: #9b9ea4;"&gt;# Attempt to authenticate the user&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;with&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:ok&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;token&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #9b9ea4;"&gt;_claims&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Accounts&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;authenticate&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;user: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;password: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;login_cred&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;password&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;      &lt;span style="color: #9b9ea4;"&gt;# Render the token&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;      &lt;span style="color: #8cf8f7;"&gt;render&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;token.json&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;token: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;token&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;    &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we find the user in the database and authenticate the user. If the authentication is successful, we render the token back to the consumer.&lt;/p&gt;
&lt;p&gt;Note: Here I am using the &lt;code&gt;with&lt;/code&gt; syntax along with Phoenix's &lt;a href="http://phoenixframework.org/blog/phoenix-1-3-0-released"&gt;&lt;code&gt;action_fallback&lt;/code&gt;&lt;/a&gt; functionality.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#authorization" aria-hidden="true" class="anchor" id="authorization"&gt;&lt;/a&gt;Authorization&lt;/h2&gt;
&lt;p&gt;In the context of a web application, this is the process of fencing off most of the routes from unauthenticated visitors. However, there are two routes that should &lt;em&gt;&lt;strong&gt;not&lt;/strong&gt;&lt;/em&gt; be fenced off, the route to sign in a user and the route to create a user.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;# router.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #8cf8f7;"&gt;pipeline&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:api&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;  &lt;span style="color: #8cf8f7;"&gt;plug&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:accepts&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;&lt;span style="color: #8cf8f7;"&gt;pipeline&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:api_auth&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #8cf8f7;"&gt;plug&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian.AuthPipeline&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;&lt;span style="color: #8cf8f7;"&gt;scope&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;/api&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Api&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;  &lt;span style="color: #8cf8f7;"&gt;pipe_through&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:api&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;
&lt;/div&gt;&lt;div class="line" data-line="14"&gt;  &lt;span style="color: #8cf8f7;"&gt;resources&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;/users&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;UserController&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;only: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:create&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="15"&gt;  &lt;span style="color: #8cf8f7;"&gt;post&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;/users/sign_in&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;UserController&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:sign_in&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="16"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="17"&gt;
&lt;/div&gt;&lt;div class="line" data-line="18"&gt;&lt;span style="color: #8cf8f7;"&gt;scope&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;/api&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Api&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="19"&gt;  &lt;span style="color: #8cf8f7;"&gt;pipe_through&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:api&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:api_auth&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="20"&gt;
&lt;/div&gt;&lt;div class="line" data-line="21"&gt;  &lt;span style="color: #8cf8f7;"&gt;resources&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;/users&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;UserController&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;only: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;[&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:update&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:show&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;:delete&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="22"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For those unfamiliar with &lt;code&gt;pipelines&lt;/code&gt;, please reference the &lt;a href="https://hexdocs.pm/phoenix/routing.html#pipelines"&gt;Phoenix guides&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We define a new pipeline called &lt;code&gt;api_auth&lt;/code&gt; for routes that require authorization, which in my case will be all routes except for &lt;code&gt;UserController#sign_in&lt;/code&gt; and &lt;code&gt;UserController#create&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Phoenix lets us define the same scopes multiple times without overwriting them. Here I define the &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;v1&lt;/code&gt; scopes twice, piping the first only through the &lt;code&gt;api&lt;/code&gt; pipeline and piping the second through the &lt;code&gt;api&lt;/code&gt; &lt;em&gt;&lt;strong&gt;and&lt;/strong&gt;&lt;/em&gt; &lt;code&gt;api_auth&lt;/code&gt; pipelines.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;api_auth&lt;/code&gt; pipeline consists of a custom plug I defined in &lt;code&gt;/lib/my_app_web/auth/auth_pipeline.ex&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;# /lib/my_app_web/auth/auth_pipeline.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defmodule&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian.AuthPipeline&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;use&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Guardian.Plug.Pipeline&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;otp_app: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:my_app&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;                              &lt;span style="color: #8cf8f7;"&gt;module: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;                              &lt;span style="color: #8cf8f7;"&gt;error_handler: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian.AuthErrorHandler&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #8cf8f7;"&gt;plug&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Guardian.Plug.VerifyHeader&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;  &lt;span style="color: #8cf8f7;"&gt;plug&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Guardian.Plug.EnsureAuthenticated&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line of this plug defines some boiler plate that Guardian will use. The last two lines are what we want to check the authorization of the user. &lt;code&gt;VerifyHeader&lt;/code&gt; will look for the the Authorization header in the request, which should contain &lt;code&gt;Bearer &amp;lt;your token&amp;gt;&lt;/code&gt;, and &lt;code&gt;EnsureAuthenticated&lt;/code&gt; will make sure that the token is valid.&lt;/p&gt;
&lt;p&gt;In the first line of boiler plate, we defined an error handler, &lt;code&gt;MyAppWeb.Guardian.AuthErrorHandler&lt;/code&gt;, mine consists of the example code from the Guardian documentation.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #9b9ea4;"&gt;# /lib/my_app_web/auth/auth_error_handler.ex&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;defmodule&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian.AuthErrorHandler&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Plug.Conn&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;auth_error&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;type&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #9b9ea4;"&gt;_reason&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #9b9ea4;"&gt;_opts&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;    &lt;span style="color: #e0e2ea;"&gt;body&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;Poison&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;encode!&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;message: &lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;to_string&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;type&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;    &lt;span style="color: #8cf8f7;"&gt;send_resp&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;401&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;body&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;  &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href="#testing" aria-hidden="true" class="anchor" id="testing"&gt;&lt;/a&gt;Testing&lt;/h2&gt;
&lt;p&gt;At first I was unsure of how to test this functionality, but the solution turned out to be rather simple.&lt;/p&gt;
&lt;p&gt;I've organized my test file to have two &lt;code&gt;describe&lt;/code&gt; blocks, one for tests that require authentication and one for tests that don't require it. I have a top level &lt;code&gt;setup&lt;/code&gt; block that adds the headers to the request that are common to all tests.&lt;/p&gt;
&lt;p&gt;In the describe block for the tests requiring authentication, I wrote another &lt;code&gt;setup&lt;/code&gt; block.&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-elixir" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;&lt;span style="color: #8cf8f7;"&gt;setup&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;%&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;conn: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="2"&gt;  &lt;span style="color: #9b9ea4;"&gt;# create a user&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;  &lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;insert&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:user&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;email: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;user@email.com&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;username: &lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;user&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="4"&gt;
&lt;/div&gt;&lt;div class="line" data-line="5"&gt;  &lt;span style="color: #9b9ea4;"&gt;# create the token&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="6"&gt;  &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:ok&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;token&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #9b9ea4;"&gt;_claims&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;MyAppWeb.Guardian&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;.&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;encode_and_sign&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="7"&gt;
&lt;/div&gt;&lt;div class="line" data-line="8"&gt;  &lt;span style="color: #9b9ea4;"&gt;# add authorization header to request&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="9"&gt;  &lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;=&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt; &lt;span style="color: #e0e2ea;"&gt;|&amp;gt;&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;put_req_header&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;(&lt;/span&gt;&lt;span style="color: #b3f6c0;"&gt;&amp;quot;authorization&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #b3f6c0;"&gt;&amp;quot;Bearer &lt;span style="color: #8cf8f7;"&gt;#&amp;lbrace;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;token&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;&amp;rbrace;&lt;/span&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="10"&gt;
&lt;/div&gt;&lt;div class="line" data-line="11"&gt;  &lt;span style="color: #9b9ea4;"&gt;# pass the connection and the user to the test&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="12"&gt;  &lt;span style="color: #e0e2ea;"&gt;&amp;lbrace;&lt;/span&gt;&lt;span style="color: #8cf8f7;"&gt;:ok&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;conn: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;conn&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;,&lt;/span&gt; &lt;span style="color: #8cf8f7;"&gt;user: &lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;user&lt;/span&gt;&lt;span style="color: #e0e2ea;"&gt;&amp;rbrace;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line" data-line="13"&gt;&lt;span style="color: #e0e2ea; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any requests you make in your tests should now have the appropriate headers!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I am using the &lt;a href="https://hexdocs.pm/ex_unit/ExUnit.html"&gt;ExUnit&lt;/a&gt; testing framework (comes with Elixir).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;If you found this helpful, please let me know! You can find me on twitter as &lt;a href="https://twitter.com/mitchhanberg"&gt;@mitchhanberg&lt;/a&gt; or you can shoot me an email.&lt;/p&gt;</description>
  <pubDate>Tue, 28 Nov 2017 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/11/28/implementing-api-authentication-with-guardian/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/11/28/implementing-api-authentication-with-guardian/---https://www.mitchellhanberg.com/post/2017/11/28/implementing-api-authentication-with-guardian/</guid>
</item>
<item>
  <title>How to install Pharo on Gnu/Linux</title>
  <description>From ⚡ Maqbool Khan: Pharo the Modern Smalltalk
Download the Pharo and decompress the file
$ unzip Pharo.zip 
$ sudo mv Pharo /opt/
Create the pharo.desktop file and paste the following contents
[Desktop Entry]
Encoding=UTF-8
Name=Pharo
GenericName=Pharo
Exec=pharo
Icon=...</description>
  <pubDate>Fri, 24 Nov 2017 00:14:59 GMT</pubDate>
  <link>https://www.maqbool.net/how-to-install-pharo-on-linux-a053de254bfa</link>
  <guid>https://www.maqbool.net/how-to-install-pharo-on-linux-a053de254bfa---https://www.maqbool.net/how-to-install-pharo-on-linux-a053de254bfa</guid>
</item>
<item>
  <title>Scheduling Cron Jobs on Heroku with Ruby on Rails</title>
  <description>From Mitchell Hanberg's Blog: A common practice is to create a __cron job__ whenever you have a task you need done periodically.

&gt;The software utility Cron is a time-based job scheduler in Unix-like computer operating systems. People who set up and maintain software environments use cron to schedule jobs (commands or shell scripts) to run periodically at fixed times, dates, or intervals. [[1]](#1)

On something like AWS, you would have the ability to create normal cron jobs and, when dealing with a Ruby on Rails application, you could use the Whenever gem [[2]](#2)  to interface between Cron and your application code.

&lt;p style="font-size: x-large"&gt;On Heroku, this isn't possible.&lt;/p&gt;

This is due to Heroku's Dyno [[3]](#3) infrastructure, which is composed of virtualized Linux containers that are restarted daily, which resets it's file system. Heroku also states that Cron does not perform well on horizontally scaling platforms [[4]](#4).

Luckily, Heroku has an add-on that just came out of beta which makes for a pretty easy solution. The Scheduler [[5]](#5) add-on is free, utlizes one-off dynos (whose uptime will count towards your monthly usage), and will make use of Rake tasks (for non-rails applications, scripts can be add to the `bin/` directory).

Let's install the add-on. This can be done via the CLI or the web UI, but I'll show you the CLI method. (This assumes you already have the Heroku CLI installed and logged in).

```zsh
$ heroku addons:create scheduler:standard
```

Now let's define a task. Let's say you're app needs to send a reminder for customers with appointments that day.

```ruby
desc "Send appointment reminders"
task :send_reminders =&gt; :environment do # :environment will load our Rails app, so we can query the database with ActiveRecord
  appts = Appointments.where(appointment_date: Date.today)

  appts.each do |appt|
    AppointmentMailer.reminder(appt)
  end
end
```

Now it's time to deploy this code.

Great, now how do we schedule this task to run? Let's hop over to Heroku's web UI. You can find this in the list of installed add-ons on the dashboard for your app, or you can run the following command in your terminal. (If you have more than one app, append `--app &lt;your app's name&gt;` to the command.)

```shell
$ heroku addons:open scheduler
```

You should now see a screen similar to this.

![](/images/scheduler-add-on.jpg)

Since we want to remind customers of their appointments for the day, we'll set the __Frequency__ to __Daily__ and the __Next Due__ to __10:00__. Note that the __Next Due__ time is in UTC, so __10:00__ is 6:00AM where I live. After clicking __Save__, your scheduled job should be live.

And there we have it! This solution is ideal for basic tasks like sending emails, but for heavier, longer running jobs, you should look into configuring a Custom Clock process [[6]](#6).

&lt;br&gt;[1]&lt;a name='1'&gt;&lt;/a&gt; [https://en.wikipedia.org/wiki/Cron](https://en.wikipedia.org/wiki/Cron)
&lt;br&gt;[2]&lt;a name='2'&gt;&lt;/a&gt; [https://github.com/javan/whenever](https://github.com/javan/whenever)
&lt;br&gt;[3]&lt;a name='3'&gt;&lt;/a&gt; [[https://www.heroku.com/dynos](https://www.heroku.com/dynos)
&lt;br&gt;[4]&lt;a name='4'&gt;&lt;/a&gt; [https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/](https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/)
&lt;br&gt;[5]&lt;a name='5'&gt;&lt;/a&gt; [https://elements.heroku.com/addons/scheduler](https://elements.heroku.com/addons/scheduler)
&lt;br&gt;[6]&lt;a name='6'&gt;&lt;/a&gt; [https://devcenter.heroku.com/articles/scheduled-jobs-custom-clock-processes](https://devcenter.heroku.com/articles/scheduled-jobs-custom-clock-processes)</description>
  <pubDate>Wed, 25 Oct 2017 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/10/25/cron-jobs-on-heroku/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/10/25/cron-jobs-on-heroku/---https://www.mitchellhanberg.com/post/2017/10/25/cron-jobs-on-heroku/</guid>
</item>
<item>
  <title>Encoding Ecto Validation Errors in Phoenix 1.3</title>
  <description>From Mitchell Hanberg's Blog: # Problem

I recently ran into this error while implementing the first endpoint of my Phoenix JSON API.


```shell
** (Poison.EncodeError) unable to encode value: {:username, {"has already been taken", []}}
```

After a bit of googling and detective work, I found the offending piece of code, located in my `error_view.ex` file.

```elixir
def render("409.json", %{changeset: changeset}) do
  %{
    status: "failure",
    errors: changeset.errors # this line causes the error
  }
end
```
This function handles rendering the JSON payload that the controller sends back to the client when there is an error.

The `errors` property of the `changeset` struct is a [keyword list](https://elixir-lang.org/getting-started/keywords-and-maps.html#keyword-lists)* of `error`'s, with `error` being  a type defined in the [Changeset module](https://github.com/elixir-ecto/ecto/blob/v2.2.6/lib/ecto/changeset.ex#L250).

```elixir
@type error :: {String.t, Keyword.t}
```

[Poison](https://github.com/devinus/poison) is not able to encode this, so a `Poison.EncodeError` error is raised.

\* It's important to remember that a keyword list is a list of 2-item tuples with the first item of the tuple being an atom. So the error we originally saw was the key-value pair that couldn't be encoded, shown in tuple form.

---

# Solution

If you created your Phoenix app when Phoenix was at v1.3, then you should have this function in the `/lib/your_app_web/views/error_helpers.ex` file. If not, go ahead and paste it in that file.

```elixir
@doc """
  Translates an error message using gettext.
  """
  def translate_error({msg, opts}) do
    # Because error messages were defined within Ecto, we must
    # call the Gettext module passing our Gettext backend. We
    # also use the "errors" domain as translations are placed
    # in the errors.po file.
    # Ecto will pass the :count keyword if the error message is
    # meant to be pluralized.
    # On your own code and templates, depending on whether you
    # need the message to be pluralized or not, this could be
    # written simply as:
    #
    #     dngettext "errors", "1 file", "%{count} files", count
    #     dgettext "errors", "is invalid"
    #
    if count = opts[:count] do
      Gettext.dngettext(ContactWeb.Gettext, "errors", msg, msg, count, opts)
    else
      Gettext.dgettext(ContactWeb.Gettext, "errors", msg, opts)
    end
  end
```

And then we make the following change.

```diff
def render("409.json", %{changeset: changeset}) do
  %{
    status: "failure",
-   errors: changeset.errors # this line causes the error
+   errors: Ecto.Changeset.traverse_errors(changeset, &amp;translate_error/1)
  }
end
```

Here we use the [Ecto.Changeset.traverse_errors/2](https://hexdocs.pm/ecto/Ecto.Changeset.html#traverse_errors/2) function to apply the `translate_errors/1` function to each error, which will return a map that can then be encoded by Poison.

Here is the JSON that we can now render and send to the client!

```json
{
  "status": "failure",
  "errors": {
    "email": [
        "has already been taken"
    ]
  }
}
```

---

If you found this helpful, please let me know! You can find me on twitter as [@mitchhanberg](https://twitter.com/mitchhanberg) or you can shoot me an email.</description>
  <pubDate>Mon, 23 Oct 2017 09:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/10/23/encoding-ecto-validation-errors-in-phoenix/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/10/23/encoding-ecto-validation-errors-in-phoenix/---https://www.mitchellhanberg.com/post/2017/10/23/encoding-ecto-validation-errors-in-phoenix/</guid>
</item>
<item>
  <title>Installing Erlang and Elixir with asdf</title>
  <description>From Mitchell Hanberg's Blog: **Let's set the scene**

_You've been fully entrenched in the Ruby and Rails world for 10 months_

_You've just tied a bow on your latest side project_

_You're already bored and excited to start something new_

_For the sake of this post, you work on a mac_

So you've decided to mess around with Elixir! Nice choice, let's get started.

---

## What is Erlang?

&gt; Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability. Some of its uses are in telecoms, banking, e-commerce, computer telephony and instant messaging. Erlang's runtime system has built-in support for concurrency, distribution and fault tolerance.

## What is Elixir?

&gt; Elixir is a dynamic, functional language designed for building scalable and maintainable applications.
&gt;
&gt; Elixir leverages the Erlang VM, known for running low-latency, distributed and fault-tolerant systems, while also being successfully used in web development and the embedded software domain.

Since you are coming from a Ruby background, you know how valuable using a version manager like [rbenv](https://github.com/rbenv/rbenv) or [rvm](https://github.com/rvm/rvm) can be. Since we are going to be installing two different programming language platforms, let's find a solution that can handle all of our needs.

## What is asdf?

&gt; _extendable version manager_
&gt;
&gt; Supported languages include Ruby, Node.js, Elixir and more. Supporting a new language is as simple as this plugin API.

[asdf](https://github.com/asdf-vm/asdf) allows us to use one tool to manage both Elixir and Erlang, and the next time I'm working on a Ruby project, I will probably switch over to using it as well.

We can easily install asdf using [Homebrew](https://brew.sh/).

```shell
$ brew install asdf coreutils curl git
```

The next important bit is at the bottom of the output stating that you will need to initialize asdf whenever you start your shell session. I ran the following command to add that line to my .zshrc file, but you should add it to the approriate rc file for your shell.

```shell
$ echo 'source $(brew --prefix asdf)/asdf.sh' &gt;&gt; .zshrc
```

## Installing Erlang and Elixir

We're almost there! Since asdf relies on language plugins, we have to add those now and any dependencies they might require.

```shell
$ brew install autoconf
$ asdf plugin add erlang

$ brew install unzip
$ asdf plugin add elixir
```

Now we want to install the actual languages. The syntax for this is `asdf install &lt;language&gt; &lt;version&gt;`, so we can do the following.

```shell
$ asdf install erlang 23.2.5

$ asdf install elixir 1.11.3
```

If you want to see a list of available versions for each langauge, you can run the command `asdf list all &lt;language&gt;`

Let's make sure that they were successfully installed, running the following commands should produce this output.

```shell
$ source ~/.zshrc

$ which erl
/Users/mitchell/.asdf/shims/erl

$ which elixir
/Users/mitchell/.asdf/shims/elixir
```

asdf has the concepts of global versions and a local versions, but we are going to focus on the local version concept.

Once we navigate into the directory of our project, we can set the version of Erlang and Elixir that we want it to use.

```shell
$ cd ~/my-elixir-project
$ asdf local erlang 23.2.5
$ asdf local elixir 1.11.3

# Set them globally
$ asdf global erlang 23.2.5
$ asdf global elixir 1.11.3
```

This will add to (or create if there isn't one already) the `.tools-version` file in the root of your project folder. It should now resemble this.

```
erlang 23.2.5
elixir 1.11.3
```

Boom! You are now ready to get started with Elixir. Checking the version of Elixir should now confirm that your project can find and use it.

```shell
$ elixir --version
Erlang/OTP 23 [erts-11.1.7] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Elixir 1.11.3 (compiled with Erlang/OTP 21)
```</description>
  <pubDate>Thu, 05 Oct 2017 11:30:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/10/05/installing-erlang-and-elixir-using-asdf/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/10/05/installing-erlang-and-elixir-using-asdf/---https://www.mitchellhanberg.com/post/2017/10/05/installing-erlang-and-elixir-using-asdf/</guid>
</item>
<item>
  <title>Habits of Successful Projects</title>
  <description>From Mitchell Hanberg's Blog: Over the course of a project, it is easy to become jaded and to lose track of why your team has certain processes. It's important to stay vigilant to keep those processes from degrading, because you will have trouble finding the cause of a reduction in velocity or quality due to the misunderstanding of how the team is operating.

This post details some habits that I've noticed to be invaluable.

## Trying something new

Pick something and _execute_.

This is especially important when you are giving a new project strategy a trial run, because if you don't follow through then you have no idea if the strategy didn't work. You can't be iterative if you can't learn from your mistakes and you can't learn from your mistakes if you can't identify them.

Have a meeting and make some documents that detail the new protocol, so everyone should know what is expected and be able to comply.

## The little things do matter

Focusing on rules can seem pedantic but setting expectations will create a consistent, trustworthy environment that optimizes for visibility. If your team is following the project's rules, you can be confident that you know the current state of the project. This passive communication can relieve a heavy mental load that comes with constantly wondering what progress the team is making.

If you find your team going through your project board during a stand-up/status meeting and teammates are saying, "Oh, I finished that a week ago" or "Oh, that's blocked on _thing_," you have a problem and need to address it.

## Meetings

With the exception of a daily stand-up/status meeting, meetings should take the form of a group activity or should spin off activities. Things should either be _getting done_ or be explicitly delegated to _get done_, the phrasing of the latter is important. This is not the same as saying, "We'll worry about this later."

Don't go down rabbit holes, so be comfortable steering a discussion back on track if it has veered off topic. The meeting is not the time to engineer a solution to the problem at hand, but to decide on the direction and determine who is best equipped to tackle it.

## Retrospectives

Retros are the time to decide which practices to toss, which to keep, and on which to double down. Care is needed to prevent these meetings from turning into an hour-long venting session that doesn't offer any solutions.

During your sprint, write down topics that you'd like to discuss and put them in a centralized place so that others can see them. This gives your teammates time to ponder your topic, making their response more valuable than if you gave them 10 seconds to think it through.

---

We love designing, building and deploying, so it's easy to get carried away and forgo process for the sake of getting things done. That's okay, but if you and your team have an "official" protocol you should follow it or exchange it for something that better suits your project's needs.</description>
  <pubDate>Tue, 03 Oct 2017 08:00:00 -04</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/10/03/habits-of-successful-projects/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/10/03/habits-of-successful-projects/---https://www.mitchellhanberg.com/post/2017/10/03/habits-of-successful-projects/</guid>
</item>
<item>
  <title>Using Elm within VueJS</title>
  <description>From Maarten van Vliet: Using Elm within VueJS Lately I have been using VueJS a lot. It&amp;rsquo;s an easy framework to learn and gets results quickly. There is an optional Typescript package to get some much needed typesafety. Overall, I&amp;rsquo;m pretty happy with it. However, the templates in Vue are where the typesafety breaks down. Typescript just has no way of knowing what&amp;rsquo;s happening in there, whereas it does have support for JSX/TXS and will supply you with type information in e.</description>
  <pubDate>Thu, 17 Aug 2017 22:02:15 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2017/08/17/using-elm-within-vuejs/</link>
  <guid>https://maartenvanvliet.nl/2017/08/17/using-elm-within-vuejs/---https://maartenvanvliet.nl/2017/08/17/using-elm-within-vuejs/</guid>
</item>
<item>
  <title>Early look at Rails ActiveStorage</title>
  <description>From Maarten van Vliet: I took an early peek at the new Active Storage project in Rails 5.2. It is a built-in solution to handling uploads, something that is all to common in web applications. Also something that is covered by quite a few gems, CarrierWave, PaperClip and Refile for example. These are just the ones I know off the top of my head.
At times I have also handled uploads without gems, it&amp;rsquo;s not that difficult and didn&amp;rsquo;t warrant the inclusion of another gem for those projects.</description>
  <pubDate>Sun, 13 Aug 2017 22:34:06 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2017/08/13/early_look_at_active_storage/</link>
  <guid>https://maartenvanvliet.nl/2017/08/13/early_look_at_active_storage/---https://maartenvanvliet.nl/2017/08/13/early_look_at_active_storage/</guid>
</item>
<item>
  <title>Uploading to Google Cloud Storage using Arc and Phoenix</title>
  <description>From Maarten van Vliet: In this post I will demonstrate how to upload to Google Cloud Storage using Arc and Arc.Ecto in Phoenix. There are several examples on how to use Arc in combination with S3 or local file uploads but the GCS integration is a bit more unknown. I assume you have an up to date elixir installation and know your way around Phoenix and GCS.
mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez mix phx.new web --no-brunch Follow instructions to configure your postgres credentials in config/dev.</description>
  <pubDate>Sat, 05 Aug 2017 22:34:06 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2017/08/05/arc_uploads_to_google_cloud_storage/</link>
  <guid>https://maartenvanvliet.nl/2017/08/05/arc_uploads_to_google_cloud_storage/---https://maartenvanvliet.nl/2017/08/05/arc_uploads_to_google_cloud_storage/</guid>
</item>
<item>
  <title>Dynamic image resizing with Google Cloud Functions</title>
  <description>From Maarten van Vliet: For a small project I needed dynamic image resizing, meaning I would call an image in an url, append some parameters for resizing and return a smaller image, so something like example.com/image.jpg?w=50&amp;amp;h=50. Now there are several open source projects capable of doing that, most notably Thumbor.
However, I wanted to try something new. I recently dabbled a little with Google Cloud Functions and it seemed like a good candidate. However, I feared the initial spin-up time of the functions might be problematic.</description>
  <pubDate>Sat, 29 Jul 2017 15:28:27 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2017/07/29/dynamic-image-resizing-with-google-cloud-function/</link>
  <guid>https://maartenvanvliet.nl/2017/07/29/dynamic-image-resizing-with-google-cloud-function/---https://maartenvanvliet.nl/2017/07/29/dynamic-image-resizing-with-google-cloud-function/</guid>
</item>
<item>
  <title>Upgrade js dependencies with yarn</title>
  <description>From Maarten van Vliet: A quick trick to upgrade the javascript dependencies in your project.
Use the command $ yarn upgrade-interactive
It will show you an interface to select the packages you want to upgrade. The packages listed in red would be upgraded when you would execute the $ yarn upgrade command. Yellow packages are semver incompatible, meaning that they may be incompatible with your code. Extra caution is needed when upgrading those.
You can use space to select the packages, press enter to install them.</description>
  <pubDate>Wed, 28 Jun 2017 22:12:44 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2017/06/28/yarn/</link>
  <guid>https://maartenvanvliet.nl/2017/06/28/yarn/---https://maartenvanvliet.nl/2017/06/28/yarn/</guid>
</item>
<item>
  <title>Todoist Gem</title>
  <description>From Maarten van Vliet: Todoist gem Todoist</description>
  <pubDate>Fri, 12 May 2017 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/todoist-gem/</link>
  <guid>https://maartenvanvliet.nl/project/todoist-gem/---https://maartenvanvliet.nl/project/todoist-gem/</guid>
</item>
<item>
  <title>Moneybird Gem</title>
  <description>From Maarten van Vliet: Moneybird gem moneybird</description>
  <pubDate>Fri, 12 May 2017 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/moneybird-gem/</link>
  <guid>https://maartenvanvliet.nl/project/moneybird-gem/---https://maartenvanvliet.nl/project/moneybird-gem/</guid>
</item>
<item>
  <title>Factuursturen Gem</title>
  <description>From Maarten van Vliet: Factuursturen gem Factuursturen</description>
  <pubDate>Fri, 12 May 2017 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/fs-gem/</link>
  <guid>https://maartenvanvliet.nl/project/fs-gem/---https://maartenvanvliet.nl/project/fs-gem/</guid>
</item>
<item>
  <title>about</title>
  <description>From Maarten van Vliet: Software consultant Hi, I am Maarten, a full stack web consultant based in Rotterdam.
I am good at building scalable products using simple and proven technologies.
Technologies I have worked with include: Javascript, Ruby, Elixir, Php, R, Python, Mysql, Postgresql, Elasticsearch, Redis, AWS, GCP.
Send me an email :)</description>
  <pubDate>Wed, 10 May 2017 14:37:32 +0200</pubDate>
  <link>https://maartenvanvliet.nl/page/about/</link>
  <guid>https://maartenvanvliet.nl/page/about/---https://maartenvanvliet.nl/page/about/</guid>
</item>
<item>
  <title>Adding Elm to a Rails 5.1 application</title>
  <description>From Maarten van Vliet: Note: Webpacker update Although this tutorial will still work it has been superseded by the addition of Elm to the webpacker gem. See this commit and the entry in the README. The end result is nearly the same.
Adding Elm to a Rails 5.1 application The release of Rails 5.1 (http://weblog.rubyonrails.org/2017/4/27/Rails-5-1-final/)has brought some interesting changes for frontend development. Most notably is that it comes with yarn, a tool to manage your javascript dependencies from NPM.</description>
  <pubDate>Tue, 09 May 2017 22:07:33 +0200</pubDate>
  <link>https://maartenvanvliet.nl/2017/05/09/rails5.1-elm/</link>
  <guid>https://maartenvanvliet.nl/2017/05/09/rails5.1-elm/---https://maartenvanvliet.nl/2017/05/09/rails5.1-elm/</guid>
</item>
<item>
  <title>First Dive into Development for VR</title>
  <description>From Mitchell Hanberg's Blog: ## Intro

Having some free time, I was asked to investigate the current state of Virtual Reality (hardware, development kits, etc) and to attempt build a prototype in order to start building our chops.

Going into this, I didn't have any prior experience with VR, 3D modeling, or physics engines and I wasn't sure if I would be able to even get anything to compile, yet I was able to build a small game and deploy it to a VR headset in only a few days. 

## The Platforms

There are a couple of categories: Desktop VR and Mobile VR. 

Examples of desktop VR are the Oculus Rift and the HTC Vive. These devices are headsets that are connected to a PC primarily used to play video games that have been optimized for VR.

Examples of mobile VR are the Samsung Gear VR (which is powered by Oculus) and Google Daydream. Both of which can only run on certain devices.

For our situation, we already had a Gear VR compatible phone (Samsung Galaxy S7), so we went down that path.

## Development

What I learned during this experiment is that building a "VR app" is really just creating something in a game engine, enabling VR support, and then building it for whatever platform. The VR layer of the whole process is rather thin, it's more about keeping VR in mind during development in order to really take advantage of the platform. 

Most of the VR platforms have support for Unity and Unreal Engine, so I chose Unity and followed their basic [tutorial](https://unity3d.com/learn/tutorials/projects/roll-ball-tutorial) (a roll-a-ball game, in which the player moves a ball to collect objects), just to get something up and running. 

Unity has an amazing amount of documentation available, so getting it installed and commencing on the tutorial game was a fairly painless process. I was able to get the tutorial finished in a couple of hours.

![picture](/images/roll-a-ball-screenshot.png)

At this point it was time to get it on the phone and be able to view it in the headset. We hadn't bought the actual Gear VR headset yet, but we were able to find a 3rd party headset somewhere in the office. 

The steps to deploy were pretty simple

1. Install the Android SDK
2. Point Unity to the path of the SDK
3. Create an Oculus .osig and copy it to the appropriate folder in your project
4. Configure the appropriate build settings
5. Hit "Build and Run"

Normally the phone will have a "Insert into headset" message and won't display your app until it's inserted, but after enabling developer mode from within the "Gear VR Service", you are able to start the app and view it with a 3rd party headset. 

The Gear VR headset has a touchpad on it, which allows you to interact with the app while viewing it with the headset. The headset I was using did not have such capabilities and I had yet to create a work around, so at this point you couldn't actually play the game while wearing the headset.

## Retrospective

The key aspects of creating content for the Virtual Reality platform were not what I expected and it feels cool to play around with a game engine. The business opportunities for VR are still being uncovered and it'll be interesting to see how the platform evolves.</description>
  <pubDate>Tue, 11 Apr 2017 00:00:00 UTC</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/04/11/vr-first-steps/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/04/11/vr-first-steps/---https://www.mitchellhanberg.com/post/2017/04/11/vr-first-steps/</guid>
</item>
<item>
  <title>Leaving Your Legacy</title>
  <description>From Mitchell Hanberg's Blog: &lt;h2&gt;&lt;a href="#introduction" aria-hidden="true" class="anchor" id="introduction"&gt;&lt;/a&gt;Introduction&lt;/h2&gt;
&lt;p&gt;At one point or another, we all will work on a legacy project. You're between projects and you get put on an internal effort, you're starting a new project with a client and inherit the old codebase, or something or another.&lt;/p&gt;
&lt;p&gt;I was fortunate enough to get placed directly on a project when I joined my company, rich with modern coding practices. Code review, scrum-style backlog and story mapping, and a team full of seasoned engineers who were all familiar with the codebase.&lt;/p&gt;
&lt;p&gt;Until it was my turn to say goodbye to the team and hello to new opportunities, and in the meantime, I would concentrate my efforts on one of our internal projects.&lt;/p&gt;
&lt;p&gt;This post serves as an outlet for my experience.&lt;/p&gt;
&lt;div class="markdown-alert markdown-alert-important"&gt;
&lt;p class="markdown-alert-title"&gt;Important&lt;/p&gt;
&lt;p&gt;Team size = me.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;&lt;a href="#the-stack" aria-hidden="true" class="anchor" id="the-stack"&gt;&lt;/a&gt;The Stack&lt;/h2&gt;
&lt;p&gt;Before starting on this project, I had an interest in exploring a new stack of technology, particularly Ruby web development with the Ruby on Rails framework. I had previously worked heavily in the .NET web stack and wanted to try my hand at something different.&lt;/p&gt;
&lt;p&gt;And then just like that, I'm told I have the opportunity to work on a Rails project in a professional setting, woo! I'm excited.&lt;/p&gt;
&lt;p&gt;Working through my first task, naturally, I do some googling. I notice that everything I'm seeing online looks totally different than what I'm seeing in the codebase. Eventually, I come to the realization that I'm working on Rails 3.2 and Ruby 1.9, a 5-year-old framework and a 10-year-old language. All I know about Ruby at this point has been deprecated for years (to a degree, the general syntax is similar).&lt;/p&gt;
&lt;p&gt;Learning the ins and outs of an old version of a tool just seems backward, especially if you haven't used that tool before. Is upgrading to the latest versions worth it for this project? Not really, but that seems to be part of the problem.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#testing" aria-hidden="true" class="anchor" id="testing"&gt;&lt;/a&gt;Testing&lt;/h2&gt;
&lt;p&gt;When I first started exploring the codebase, I was excited to see how one went about testing in Ruby/Rails, only to find that half of the tests were failing.&lt;/p&gt;
&lt;p&gt;I spent a week ironing out the tests. With test failures ranging from a lack of test fixtures to commented out blocks of deprecated code from a framework upgrade that never got finished.&lt;/p&gt;
&lt;p&gt;Testing stops being useful when you can't trust your tests.&lt;/p&gt;
&lt;p&gt;This is where the difficulties of working on an internal project start to crop up, you see all these broken windows, but don't have the drive to fix them. You become desensitized to the lower quality of code and the codebase suffers for it.&lt;/p&gt;
&lt;p&gt;I fixed all the tests, but at this point, I ask myself, are these tests even valuable? What are they testing?&lt;/p&gt;
&lt;h2&gt;&lt;a href="#deployment" aria-hidden="true" class="anchor" id="deployment"&gt;&lt;/a&gt;Deployment&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Previously there was Jenkins integration with building and deployment using CloudFoundry, but this was not maintained and no longer works. Therefore, all staging and production deployments must be done manually&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was an actual quote from the project's wiki page (until I deleted it). I wasn't surprised by a defunct continuous integration setup, but that the expected deployment process was to &lt;em&gt;manually copy the files&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I did not think that this project needed some fancy system for deployment, but that just seemed wrong.&lt;/p&gt;
&lt;p&gt;I then experimented with just replacing the code on the server with a clone of the repository, and it deploy all it required was a git pull. Not much effort was put into this, but it was simple to implement and simple to use.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#decay" aria-hidden="true" class="anchor" id="decay"&gt;&lt;/a&gt;Decay&lt;/h2&gt;
&lt;p&gt;This is the general theme, each time a new developer joins the project temporarily they will leave behind something corrosive, slowly decaying the project. Even experienced engineers can write some bad code if the circumstances are begging for it.&lt;/p&gt;
&lt;p&gt;Out-of-date comments are left to rot, wikis are stale and inaccurate, and tests break and remain broken. Now this happens over time, and I'm not sure if you'd be able to recognize it happening in real time.&lt;/p&gt;
&lt;p&gt;In order to stop (or at least slow down) the decay is to maintain verbose documentation. Commit messages need to be wordy and the wiki (if you have one) needs to be awfully explicit. Now I don't think it needs to be written for someone who isn't a developer, but it at least should be written for someone who has no experience in any of the project stack.&lt;/p&gt;
&lt;p&gt;Example of a commit message that is not helpful&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;fixed brokn thing .
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example of a commit message that is helpful&lt;/p&gt;
&lt;pre class="athl" style="color: #e0e2ea; background-color: #14161b;"&gt;&lt;code class="language-plaintext" translate="no" tabindex="0"&gt;&lt;div class="line" data-line="1"&gt;Fixed broken pagination after filtering list
&lt;/div&gt;&lt;div class="line" data-line="2"&gt; * Code was attempting to filter during pagination, now 
&lt;/div&gt;&lt;div class="line" data-line="3"&gt;   the list filters completely and then paginates the filtered list
&lt;/div&gt;&lt;div class="line" data-line="4"&gt; * Updated the gem used for pagination from v0.5.1 to v1.0.1
&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I even think a project journal would be beneficial. After each task is finished, the developer will log everything that went into the task, including reasoning, failed attempts and possibly even short explanation of how a certain library was utilized. Anything that isn't written down is essentially lost for eternity, so any epiphanies you had are useless if you don't write them down for the next developer down the line.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#conclusion" aria-hidden="true" class="anchor" id="conclusion"&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Working with legacy code is inevitable, but with a little TLC, you can make it less painful for those who will work with it after you.&lt;/p&gt;
&lt;p&gt;A couple takeaways from this experience are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Always leave the project (or code, documentation, etc.) in a better condition than when you joined. Take the bit of extra time to jot down broken windows you see or valuable (yet not expensive) improvements you can make, to either fix after your task or during.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find yourself a healthy level of criticality, don't assume all the code is crap, but don't assume that it's all great code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ask questions. If you have to track down someone who worked on the project 4 years ago, do it. You're not going to find their rationale for a design decision on Stack Overflow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
  <pubDate>Thu, 09 Mar 2017 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/03/09/leaving-your-legacy/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/03/09/leaving-your-legacy/---https://www.mitchellhanberg.com/post/2017/03/09/leaving-your-legacy/</guid>
</item>
<item>
  <title>If You Don't Have Anything Valuable To Say... Say It Anyways</title>
  <description>From Mitchell Hanberg's Blog: &lt;h2&gt;&lt;a href="#what-does-this-mean" aria-hidden="true" class="anchor" id="what-does-this-mean"&gt;&lt;/a&gt;What does this mean?&lt;/h2&gt;
&lt;p&gt;In the professional world, feedback is incredibly important and can vastly beneficial, but sometimes our fear of criticism can stop you from sharing, which then inhibits you from learning.&lt;/p&gt;
&lt;p&gt;There's nothing I hate more than being wrong, more so than others knowing I was wrong. Being able to leverage others knowledge to accelerate your own growth is a key to success (in my opinion, this is the part where you can tell me I'm wrong).&lt;/p&gt;
&lt;p&gt;Obviously the title of the post is insinuating that you should make a blog and write articles about the stuff you are up to or any remarkable experiences, but I think it also applies to other areas of your life. It's not just about saying or writing, but it's also about &lt;strong&gt;doing&lt;/strong&gt;, and this is something with which I struggle.&lt;/p&gt;
&lt;h2&gt;&lt;a href="#youll-never-finish-if-you-dont-start" aria-hidden="true" class="anchor" id="youll-never-finish-if-you-dont-start"&gt;&lt;/a&gt;You'll never finish if you don't start&lt;/h2&gt;
&lt;p&gt;Many people never start a project because they don't want to get it wrong; they're afraid to build something that doesn't work because it'll mean that they're dumb.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Wrong&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Having produced half of a project is still better than having not started one at all. The experience you gained is more important than the finished product (not saying that finishing something isn't worth it, it definitely is).&lt;/p&gt;
&lt;p&gt;The likelihood that anything revolutionary will come from this blog is low, but that's not going to stop me and it shouldn't stop you. Get out there and start that project, read that book, or just &lt;strong&gt;do&lt;/strong&gt; that thing that you've been putting off.&lt;/p&gt;</description>
  <pubDate>Tue, 28 Feb 2017 09:00:00 EST</pubDate>
  <link>https://www.mitchellhanberg.com/post/2017/02/28/first-post/</link>
  <guid>https://www.mitchellhanberg.com/post/2017/02/28/first-post/---https://www.mitchellhanberg.com/post/2017/02/28/first-post/</guid>
</item>
<item>
  <title>Toon thermostat Gem</title>
  <description>From Maarten van Vliet: Toon thermostat gem Toon thermostat</description>
  <pubDate>Thu, 12 May 2016 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/toon_api/</link>
  <guid>https://maartenvanvliet.nl/project/toon_api/---https://maartenvanvliet.nl/project/toon_api/</guid>
</item>
<item>
  <title>NL Api</title>
  <description>From Maarten van Vliet: Nl Api</description>
  <pubDate>Thu, 12 May 2016 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/nl_api/</link>
  <guid>https://maartenvanvliet.nl/project/nl_api/---https://maartenvanvliet.nl/project/nl_api/</guid>
</item>
<item>
  <title>Rails with no JS framework</title>
  <description>From Bits of Code - Pieces of Writing: &lt;h3 id="because-all-apps-don-t-need-ember-or-angular"&gt;Because all apps don&amp;#x2019;t need Ember or Angular&lt;/h3&gt;&lt;p&gt;I&amp;#x2019;m writing code for over a decade now, consider myself as a geek, and as a geek I&amp;#x2019;m always very curious and excited about any new fancy technology released every now and then.&lt;/p&gt;&lt;p&gt;These days client-side&lt;/p&gt;</description>
  <pubDate>Fri, 28 Feb 2014 21:57:00 GMT</pubDate>
  <link>https://www.christianblavier.com/rails-with-no-js-framework/</link>
  <guid>https://www.christianblavier.com/rails-with-no-js-framework/---629603e163e1200001252ac0</guid>
</item>
<item>
  <title>Kohana-MPTT</title>
  <description>From Maarten van Vliet: Kohana MPTT</description>
  <pubDate>Mon, 12 May 2008 23:04:18 +0200</pubDate>
  <link>https://maartenvanvliet.nl/project/kohana_mptt/</link>
  <guid>https://maartenvanvliet.nl/project/kohana_mptt/---https://maartenvanvliet.nl/project/kohana_mptt/</guid>
</item>
<item>
  <title>Iteraptor → Unforeseen utilization</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-12-31T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/12/31/iteraptor-unforeseen-utilization</link>
  <guid>https://rocket-science.ru/hacking/2018/12/31/iteraptor-unforeseen-utilization---https://rocket-science.ru/hacking/2018/12/31/iteraptor-unforeseen-utilization</guid>
</item>
<item>
  <title>Ruby Challenges as QotD</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-05-31T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/05/31/qotd-ruby-challenges</link>
  <guid>https://rocket-science.ru/hacking/2018/05/31/qotd-ruby-challenges---https://rocket-science.ru/hacking/2018/05/31/qotd-ruby-challenges</guid>
</item>
<item>
  <title>Setting up Cloudflare R2 buckets for Active Storage</title>
  <description>From Notes to self: Rails comes with a built-in support for saving and uploading files to S3 and S3-compatible storage services in Active Storage. Here’s how to set up Cloudflare R2.</description>
  <pubDate>2025-01-31T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/rails-cloudflare-r2-activestorage/</link>
  <guid>https://nts.strzibny.name/rails-cloudflare-r2-activestorage/---https://nts.strzibny.name/setting-up-cloudflare-r2-buckets-for-active-storage</guid>
</item>
<item>
  <title>Business Class 2.0 with Rails 8, Pay 8, Solid, Kamal 2, and fancy generator</title>
  <description>From Notes to self: The Ruby on Rails template Business Class gets a whole new edition. Rails 8, new licencing, and improved CRUD generator.</description>
  <pubDate>2024-12-30T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/businessclass-2.0/</link>
  <guid>https://nts.strzibny.name/businessclass-2.0/---https://nts.strzibny.name/business-class-20-rails-8-pay-8-solid-kamal-2-and-fancy-generator</guid>
</item>
<item>
  <title>Test Driving Rails, 1st edition is released!</title>
  <description>From Notes to self: I am releasing my third book today. This time about Rails-native testing!</description>
  <pubDate>2024-11-30T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/test-driving-rails-released/</link>
  <guid>https://nts.strzibny.name/test-driving-rails-released/---https://nts.strzibny.name/test-driving-rails-1st-edition-is-released</guid>
</item>
<item>
  <title>Setting up My Blog as Procrastination for Writing</title>
  <description>From Timmo's Website: I've been meaning to setup my blog for about three years. This time around I decided to push through and instead of delaying my writing even more, use it as a force. Once it's up and published I no longer have to configure it, make it look a specific way, figure out some new tooling, it's already there. Ready to publish.</description>
  <pubDate>2020-10-30T00:00:00+01:00</pubDate>
  <link>https://timmo.immo/blog/setup-procrastination</link>
  <guid>https://timmo.immo/blog/setup-procrastination---https://timmo.immo/blog/setup-procrastination</guid>
</item>
<item>
  <title>Generated Module As A Guard</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/30/generated-module-as-a-guard</link>
  <guid>https://rocket-science.ru/hacking/2018/10/30/generated-module-as-a-guard---https://rocket-science.ru/hacking/2018/10/30/generated-module-as-a-guard</guid>
</item>
<item>
  <title>ActiveRecord Smell With Elixir/Ecto</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/30/active-record-smell-in-elixir</link>
  <guid>https://rocket-science.ru/hacking/2018/10/30/active-record-smell-in-elixir---https://rocket-science.ru/hacking/2018/10/30/active-record-smell-in-elixir</guid>
</item>
<item>
  <title>Envío as a reincarnation of GenEvent²</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-07-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/07/30/envio-as-genevent-2-0</link>
  <guid>https://rocket-science.ru/hacking/2018/07/30/envio-as-genevent-2-0---https://rocket-science.ru/hacking/2018/07/30/envio-as-genevent-2-0</guid>
</item>
<item>
  <title>Thou shalt not make unto thee any graven image</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-07-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/culture/2016/07/30/thou-shalt-not-make-unto-thee-any-graven-image</link>
  <guid>https://rocket-science.ru/culture/2016/07/30/thou-shalt-not-make-unto-thee-any-graven-image---https://rocket-science.ru/culture/2016/07/30/thou-shalt-not-make-unto-thee-any-graven-image</guid>
</item>
<item>
  <title>Telemetría</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-05-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/05/30/welcome-telemetria</link>
  <guid>https://rocket-science.ru/hacking/2020/05/30/welcome-telemetria---https://rocket-science.ru/hacking/2020/05/30/welcome-telemetria</guid>
</item>
<item>
  <title>Finitomata :: First Class Documentation</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-04-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/04/30/finitomata-ex-docs</link>
  <guid>https://rocket-science.ru/hacking/2022/04/30/finitomata-ex-docs---https://rocket-science.ru/hacking/2022/04/30/finitomata-ex-docs</guid>
</item>
<item>
  <title>Finite Automata with Tarearbol</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-04-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/04/30/fsm-with-tarearbol</link>
  <guid>https://rocket-science.ru/hacking/2021/04/30/fsm-with-tarearbol---https://rocket-science.ru/hacking/2021/04/30/fsm-with-tarearbol</guid>
</item>
<item>
  <title>Make your system more predictable by using idempotent interfaces</title>
  <description>From Patryk Bak - a programming blog: </description>
  <pubDate>2021-04-30T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2021/04/30/idempotent-interfaces-make-system-predictable.html</link>
  <guid>https://patrykbak.com/2021/04/30/idempotent-interfaces-make-system-predictable.html---https://patrykbak.com/2021/04/30/idempotent-interfaces-make-system-predictable</guid>
</item>
<item>
  <title>Help To Test Your Library</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-01-30T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/01/30/help-to-test-your-library</link>
  <guid>https://rocket-science.ru/hacking/2022/01/30/help-to-test-your-library---https://rocket-science.ru/hacking/2022/01/30/help-to-test-your-library</guid>
</item>
<item>
  <title>Idempotent Supervision Tree</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-12-29T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/12/29/idempotent-supervision-tree</link>
  <guid>https://rocket-science.ru/hacking/2018/12/29/idempotent-supervision-tree---https://rocket-science.ru/hacking/2018/12/29/idempotent-supervision-tree</guid>
</item>
<item>
  <title>.iex.exs to the rescue</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-12-29T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/12/29/iex.exs-to-the-rescue</link>
  <guid>https://rocket-science.ru/hacking/2017/12/29/iex.exs-to-the-rescue---https://rocket-science.ru/hacking/2017/12/29/iex.exs-to-the-rescue</guid>
</item>
<item>
  <title>Recursion Without Explicit Method</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-10-29T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/10/29/recursion-without-explicit-method</link>
  <guid>https://rocket-science.ru/hacking/2015/10/29/recursion-without-explicit-method---https://rocket-science.ru/hacking/2015/10/29/recursion-without-explicit-method</guid>
</item>
<item>
  <title>CASE-WHEN :: another trick for N+1 problem</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-08-29T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/08/29/active-record-case-when</link>
  <guid>https://rocket-science.ru/hacking/2015/08/29/active-record-case-when---https://rocket-science.ru/hacking/2015/08/29/active-record-case-when</guid>
</item>
<item>
  <title>We don’t need to look for an Elixir developer to have one</title>
  <description>From Patryk Bak - a programming blog: Is command of a specific programming language the most crucial skill we’re looking for among candidates? I would say no, especially when we’re considering a less experienced applicant. Regardless of someone’s skill, it takes time to gain domain knowledge and start delivering value to the product.</description>
  <pubDate>2021-06-29T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2021/06/29/do-not-seek-elixir-developer.html</link>
  <guid>https://patrykbak.com/2021/06/29/do-not-seek-elixir-developer.html---https://patrykbak.com/2021/06/29/do-not-seek-elixir-developer</guid>
</item>
<item>
  <title>These Weird Accents</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-05-29T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/05/29/these-weird-accents</link>
  <guid>https://rocket-science.ru/hacking/2018/05/29/these-weird-accents---https://rocket-science.ru/hacking/2018/05/29/these-weird-accents</guid>
</item>
<item>
  <title>Iteraptor :: Iterating Nested Terms Like I’m Five</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-03-29T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/03/29/iteraptor-for-the-rescue</link>
  <guid>https://rocket-science.ru/hacking/2018/03/29/iteraptor-for-the-rescue---https://rocket-science.ru/hacking/2018/03/29/iteraptor-for-the-rescue</guid>
</item>
<item>
  <title>Erlang Records vs. Elixir Structs</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I use Erlang and Elixir regularly, and I often get hung up on the differences between Erlang records and Elixir structs. Both serve the same purpose most of the time but are implemented differently.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this blog post I will document the differences between these two constructs.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2021-06-28T21:41:03-04:00</pubDate>
  <link>https://stratus3d.com/blog/2021/06/28/erlang-records-vs-elixir-structs/</link>
  <guid>https://stratus3d.com/blog/2021/06/28/erlang-records-vs-elixir-structs/---https://stratus3d.com/blog/2021/06/28/erlang-records-vs-elixir-structs</guid>
</item>
<item>
  <title>Extending Rails authentication generator with registration flow</title>
  <description>From Notes to self: Rails 8 comes with a built-in authentication generator. However, it doesn’t yet come with registrations. Here’s how to add them.</description>
  <pubDate>2024-12-28T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/rails-authentication-registrations/</link>
  <guid>https://nts.strzibny.name/rails-authentication-registrations/---https://nts.strzibny.name/extending-rails-authentication-generator-with-registration-flow</guid>
</item>
<item>
  <title>Plug in JSON API Readonly Webserver</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-12-28T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/12/28/json-api-read-only-web-server</link>
  <guid>https://rocket-science.ru/hacking/2018/12/28/json-api-read-only-web-server---https://rocket-science.ru/hacking/2018/12/28/json-api-read-only-web-server</guid>
</item>
<item>
  <title>Beware of Tests et Fudicia Ferentes</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-08-28T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/08/28/beware-of-tests-et-fiducia-ferentes</link>
  <guid>https://rocket-science.ru/hacking/2019/08/28/beware-of-tests-et-fiducia-ferentes---https://rocket-science.ru/hacking/2019/08/28/beware-of-tests-et-fiducia-ferentes</guid>
</item>
<item>
  <title>Standing on the Shoulders of Giants</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-08-28T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/culture/2019/08/28/standing-on-the-shoulders-of-giants</link>
  <guid>https://rocket-science.ru/culture/2019/08/28/standing-on-the-shoulders-of-giants---https://rocket-science.ru/culture/2019/08/28/standing-on-the-shoulders-of-giants</guid>
</item>
<item>
  <title>Jeopardy! world</title>
  <description>From Hauleth's blog: should be before you can write a useful prompt.</description>
  <pubDate>2025-07-28T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/jeopardy-world/</link>
  <guid>https://hauleth.dev/post/jeopardy-world/---https://hauleth.dev/post/jeopardy-world/</guid>
</item>
<item>
  <title>How to migrate live production data</title>
  <description>From Patryk Bak - a programming blog: Migrating live data is risky. Let’s analyze the example below to see why.</description>
  <pubDate>2020-05-28T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2020/05/28/how-to-migrate-live-production-data.html</link>
  <guid>https://patrykbak.com/2020/05/28/how-to-migrate-live-production-data.html---https://patrykbak.com/2020/05/28/how-to-migrate-live-production-data</guid>
</item>
<item>
  <title>`is_empty` Guard for Binaries in Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-03-28T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/03/28/is-empty-guard-for-binaries</link>
  <guid>https://rocket-science.ru/hacking/2017/03/28/is-empty-guard-for-binaries---https://rocket-science.ru/hacking/2017/03/28/is-empty-guard-for-binaries</guid>
</item>
<item>
  <title>Handling Async Responses with Tarearbol</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-02-28T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/02/28/async-response-handling-with-tarearbol</link>
  <guid>https://rocket-science.ru/hacking/2021/02/28/async-response-handling-with-tarearbol---https://rocket-science.ru/hacking/2021/02/28/async-response-handling-with-tarearbol</guid>
</item>
<item>
  <title>Why I Chose Go</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I&amp;#8217;ve written a couple of blog posts this year on the rewrite of asdf in Go. My previous &lt;a href="https://stratus3d.com/blog/2025/02/03/asdf-has-been-rewritten-in-go/#_why-a-rewrite"&gt;posts have covered some of the technical details&lt;/a&gt; of the rewrite. I thought I spent quite a bit of time weighing the merits of several different languages before I settled on Go. I&amp;#8217;d take some time to share my reasons for choosing Go.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2025-09-27T15:33:56-04:00</pubDate>
  <link>https://stratus3d.com/blog/2025/09/27/why-i-chose-go/</link>
  <guid>https://stratus3d.com/blog/2025/09/27/why-i-chose-go/---https://stratus3d.com/blog/2025/09/27/why-i-chose-go</guid>
</item>
<item>
  <title>Iterating Tuples in Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-12-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/12/27/iterating-tuples-in-elixir</link>
  <guid>https://rocket-science.ru/hacking/2016/12/27/iterating-tuples-in-elixir---https://rocket-science.ru/hacking/2016/12/27/iterating-tuples-in-elixir</guid>
</item>
<item>
  <title>Better pry for Ruby REPL</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/27/pry-with-whistles</link>
  <guid>https://rocket-science.ru/hacking/2018/10/27/pry-with-whistles---https://rocket-science.ru/hacking/2018/10/27/pry-with-whistles</guid>
</item>
<item>
  <title>Suggested New SPR Wording, or “We’re VMS and You’re Not.”</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2018/10/27/we-are-vms-and-you-are-not</link>
  <guid>https://rocket-science.ru/fun/2018/10/27/we-are-vms-and-you-are-not---https://rocket-science.ru/fun/2018/10/27/we-are-vms-and-you-are-not</guid>
</item>
<item>
  <title>Parent Of My Child Is Not Exactly Me</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-08-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/08/27/parent-of-my-child-is-not-exactly-me</link>
  <guid>https://rocket-science.ru/hacking/2018/08/27/parent-of-my-child-is-not-exactly-me---https://rocket-science.ru/hacking/2018/08/27/parent-of-my-child-is-not-exactly-me</guid>
</item>
<item>
  <title>Workflow as FSM: lost transitions</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-07-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/07/27/workflow-as-fsm</link>
  <guid>https://rocket-science.ru/hacking/2017/07/27/workflow-as-fsm---https://rocket-science.ru/hacking/2017/07/27/workflow-as-fsm</guid>
</item>
<item>
  <title>Monkeypatch It!</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-06-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/06/27/monkeypatch-it</link>
  <guid>https://rocket-science.ru/hacking/2016/06/27/monkeypatch-it---https://rocket-science.ru/hacking/2016/06/27/monkeypatch-it</guid>
</item>
<item>
  <title>URL Shortener</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-05-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/05/27/url-shortener</link>
  <guid>https://rocket-science.ru/hacking/2022/05/27/url-shortener---https://rocket-science.ru/hacking/2022/05/27/url-shortener</guid>
</item>
<item>
  <title>Treachery of Representation</title>
  <description>From Hauleth's blog: Representation is not data - something that developers (and not only them)
often forgot when working with different data.</description>
  <pubDate>2020-04-27T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/treachery-of-representation/</link>
  <guid>https://hauleth.dev/post/treachery-of-representation/---https://hauleth.dev/post/treachery-of-representation/</guid>
</item>
<item>
  <title>Internet Explorer vs Murder Rate</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-01-27T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2013/01/27/internet-explorer-vs-murder-rate</link>
  <guid>https://rocket-science.ru/fun/2013/01/27/internet-explorer-vs-murder-rate---https://rocket-science.ru/fun/2013/01/27/internet-explorer-vs-murder-rate</guid>
</item>
<item>
  <title>Using Regulator</title>
  <description>From keathley.github.io: I originally wrote this for the backend engineers at Bleacher Report. I thought that it might be useful to others to repost it here. I’ve obfuscated the names of the specific services but otherwise left it as is.</description>
  <pubDate>2021-02-26T13:52:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/regulator.html</link>
  <guid>http://keathley.github.io/blog/regulator.html---http://keathley.github.io/blog/regulator</guid>
</item>
<item>
  <title>Horizontal Scaling</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-12-26T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/12/26/horizontal-scaling</link>
  <guid>https://rocket-science.ru/hacking/2023/12/26/horizontal-scaling---https://rocket-science.ru/hacking/2023/12/26/horizontal-scaling</guid>
</item>
<item>
  <title>Elixir Compilation Hooks</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-12-26T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/12/26/elixir-compilation-hooks</link>
  <guid>https://rocket-science.ru/hacking/2018/12/26/elixir-compilation-hooks---https://rocket-science.ru/hacking/2018/12/26/elixir-compilation-hooks</guid>
</item>
<item>
  <title>Scaffolds Backed Up By Behaviours</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-08-26T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/08/26/behaviour-backed-scaffolds</link>
  <guid>https://rocket-science.ru/hacking/2019/08/26/behaviour-backed-scaffolds---https://rocket-science.ru/hacking/2019/08/26/behaviour-backed-scaffolds</guid>
</item>
<item>
  <title>Let's talk about `application/0`</title>
  <description>From Hauleth's blog: Have you ever thought about that one simple function in your `mix.exs`? It
comes out as quite powerful and useful place for storing configuration and
post-launch scripts.</description>
  <pubDate>2019-07-26T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/elixir-application/</link>
  <guid>https://hauleth.dev/post/elixir-application/---https://hauleth.dev/post/elixir-application/</guid>
</item>
<item>
  <title>Developer Omniboxes for Chrome</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-04-26T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/04/26/developer-omniboxes-for-chrome</link>
  <guid>https://rocket-science.ru/hacking/2013/04/26/developer-omniboxes-for-chrome---https://rocket-science.ru/hacking/2013/04/26/developer-omniboxes-for-chrome</guid>
</item>
<item>
  <title>Ruby Predefined Globals</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-26T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/tips/2013/02/26/ruby-predefined-globals</link>
  <guid>https://rocket-science.ru/tips/2013/02/26/ruby-predefined-globals---https://rocket-science.ru/tips/2013/02/26/ruby-predefined-globals</guid>
</item>
<item>
  <title>YAGNIN, but YAGNIL</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-12-25T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/12/25/yagnin-but-yagnil</link>
  <guid>https://rocket-science.ru/hacking/2023/12/25/yagnin-but-yagnil---https://rocket-science.ru/hacking/2023/12/25/yagnin-but-yagnil</guid>
</item>
<item>
  <title>Go Outta Here</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-12-25T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/12/25/go-outta-here</link>
  <guid>https://rocket-science.ru/hacking/2018/12/25/go-outta-here---https://rocket-science.ru/hacking/2018/12/25/go-outta-here</guid>
</item>
<item>
  <title>Queso al Romero</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-12-25T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hobby/2016/12/25/queso-al-romero</link>
  <guid>https://rocket-science.ru/hobby/2016/12/25/queso-al-romero---https://rocket-science.ru/hobby/2016/12/25/queso-al-romero</guid>
</item>
<item>
  <title>Using non-root users in Kamal</title>
  <description>From Notes to self: Kamal gives us an option to connect with a non-root user, but how can we create it?</description>
  <pubDate>2024-11-25T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/using-non-root-users-kamal/</link>
  <guid>https://nts.strzibny.name/using-non-root-users-kamal/---https://nts.strzibny.name/using-non-root-users-in-kamal</guid>
</item>
<item>
  <title>Concurrency and parallelism with Elixir and BEAM</title>
  <description>From Patryk Bak - a programming blog: Imagine coming to an overcrowded fast food restaurant to order the best taco in the country and there’s only one cook working there. He prepares each taco one by one. Although the cook has an extra time while waiting until a tortilla heats up, he doesn’t start preparing another order until the first one is finished.</description>
  <pubDate>2020-08-25T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2020/08/25/concurrency-and-parallelism-with-elixir-and-beam.html</link>
  <guid>https://patrykbak.com/2020/08/25/concurrency-and-parallelism-with-elixir-and-beam.html---https://patrykbak.com/2020/08/25/concurrency-and-parallelism-with-elixir-and-beam</guid>
</item>
<item>
  <title>GenServer At The Therapist's Appointment</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-03-25T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/jokes/2019/03/25/genserver-at-the-therapists-appointment</link>
  <guid>https://rocket-science.ru/jokes/2019/03/25/genserver-at-the-therapists-appointment---https://rocket-science.ru/jokes/2019/03/25/genserver-at-the-therapists-appointment</guid>
</item>
<item>
  <title>Guide to Tracing in Erlang</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I&amp;#8217;ve spent a lot of time debugging Elixir and Erlang applications over the last 5 years and decided to share what I&amp;#8217;ve learned here. I started writing this blog post in April of 2017 and wasn&amp;#8217;t inspired to finish it until watching &lt;a href="https://www.youtube.com/watch?v=sR9h3DZAA74"&gt;Jeffery Utter&amp;#8217;s Debugging Live Systems on the BEAM&lt;/a&gt; talk about a month ago. If you&amp;#8217;d prefer a more concise presentation of Erlang tracing I suggest you watch that talk instead of reading this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing to understand when debugging Erlang applications is that Erlang&amp;#8217;s concurrency greatly influences how debugging is done. While Erlang does have a traditional debugger its breakpoints pause process execution and may cause other processes in the system to crash if they are waiting on a message from the process that has been paused. Tracing on the other hand can be done under any circumstances and provides just as much information as a typical debugger. Tracing is also significantly more flexible than breakpoint debugging as it doesn&amp;#8217;t interfere with process execution and can gather data from multiple processes at once.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2021-08-24T21:48:30-04:00</pubDate>
  <link>https://stratus3d.com/blog/2021/08/24/guide-to-tracing-in-erlang/</link>
  <guid>https://stratus3d.com/blog/2021/08/24/guide-to-tracing-in-erlang/---https://stratus3d.com/blog/2021/08/24/guide-to-tracing-in-erlang</guid>
</item>
<item>
  <title>Visualizing Backoff Functions With Gnuplot</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;Today I was building some retry logic for some code that forwarded data to a third-party service and wanted to visualize the backoff function I had written. I wanted to see how many retries would be made in the 10-15 minute window after the initial retry. Gnuplot was the obvious choice for visualizing this as I had recently used it for some data exploration work and knew it had built-in support for plotting mathematical functions. The final result looks like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://stratus3d.com/images/posts/backoff-functions-gnuplot/backoff-comparison-plot.svg" alt="backoff comparison plot"&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>2022-11-24T09:43:33-05:00</pubDate>
  <link>https://stratus3d.com/blog/2022/11/24/visualizing-backoff-functions-with-gnuplot/</link>
  <guid>https://stratus3d.com/blog/2022/11/24/visualizing-backoff-functions-with-gnuplot/---https://stratus3d.com/blog/2022/11/24/visualizing-backoff-functions-with-gnuplot</guid>
</item>
<item>
  <title>Progress Bar in Console for Rake Tasks</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-11-24T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/11/24/progress-bar-for-dummies</link>
  <guid>https://rocket-science.ru/hacking/2016/11/24/progress-bar-for-dummies---https://rocket-science.ru/hacking/2016/11/24/progress-bar-for-dummies</guid>
</item>
<item>
  <title>Understanding Kamal proxy roles</title>
  <description>From Notes to self: Kamal’s configuration comes with one primary proxy role to accept HTTP traffic. Here’s how to think about proxy roles and how to configure others.</description>
  <pubDate>2024-10-24T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/kamal-proxy-roles/</link>
  <guid>https://nts.strzibny.name/kamal-proxy-roles/---https://nts.strzibny.name/kamal-proxy-roles</guid>
</item>
<item>
  <title>Developer Is The Next Ideological Beggar Job</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-24T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/24/developer-is-the-next-ideological-beggar-job</link>
  <guid>https://rocket-science.ru/hacking/2018/10/24/developer-is-the-next-ideological-beggar-job---https://rocket-science.ru/hacking/2018/10/24/developer-is-the-next-ideological-beggar-job</guid>
</item>
<item>
  <title>EMACS SHIT</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-10-24T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2013/10/24/emacs-shit</link>
  <guid>https://rocket-science.ru/fun/2013/10/24/emacs-shit---https://rocket-science.ru/fun/2013/10/24/emacs-shit</guid>
</item>
<item>
  <title>Generated Types II — Down the Rabbit Hole</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-07-24T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/07/24/generated-types-2</link>
  <guid>https://rocket-science.ru/hacking/2020/07/24/generated-types-2---https://rocket-science.ru/hacking/2020/07/24/generated-types-2</guid>
</item>
<item>
  <title>Make your library test-friendly</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2024-05-24T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2024/05/24/finitomata-exunit</link>
  <guid>https://rocket-science.ru/hacking/2024/05/24/finitomata-exunit---https://rocket-science.ru/hacking/2024/05/24/finitomata-exunit</guid>
</item>
<item>
  <title>Use `credo` Linter in Git `pre-commit` Hook</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-04-24T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/04/24/git-commit-credo-linter-hook</link>
  <guid>https://rocket-science.ru/hacking/2017/04/24/git-commit-credo-linter-hook---https://rocket-science.ru/hacking/2017/04/24/git-commit-credo-linter-hook</guid>
</item>
<item>
  <title>Running JavaScript after a Turbo Stream renders</title>
  <description>From Notes to self: Turbo comes with turbo:before-stream-render but unfortunately doesn’t ship with the equivalent turbo:after-stream-render. Here’s how to run JavaScript after the stream renders.</description>
  <pubDate>2025-03-24T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/rails-turbo-after-stream-render/</link>
  <guid>https://nts.strzibny.name/rails-turbo-after-stream-render/---https://nts.strzibny.name/runing-javascript-after-a-turbo-stream-renders</guid>
</item>
<item>
  <title>Using local sources in Gemfile</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2014-01-24T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2014/01/24/using-local-sources-in-gemfile</link>
  <guid>https://rocket-science.ru/hacking/2014/01/24/using-local-sources-in-gemfile---https://rocket-science.ru/hacking/2014/01/24/using-local-sources-in-gemfile</guid>
</item>
<item>
  <title>Idiomatic function memoization in Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-11-23T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/11/23/idiomatic-function-memoization-in-elixir</link>
  <guid>https://rocket-science.ru/hacking/2017/11/23/idiomatic-function-memoization-in-elixir---https://rocket-science.ru/hacking/2017/11/23/idiomatic-function-memoization-in-elixir</guid>
</item>
<item>
  <title>Software Development in 2026</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2026-03-23T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2026/03/23/software-development-in-2026</link>
  <guid>https://rocket-science.ru/hacking/2026/03/23/software-development-in-2026---https://rocket-science.ru/hacking/2026/03/23/software-development-in-2026</guid>
</item>
<item>
  <title>Pattern matching on dynamic struct types</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-02-23T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/02/23/pattern-matching-on-dynamic-struct-types</link>
  <guid>https://rocket-science.ru/hacking/2018/02/23/pattern-matching-on-dynamic-struct-types---https://rocket-science.ru/hacking/2018/02/23/pattern-matching-on-dynamic-struct-types</guid>
</item>
<item>
  <title>Open Graph Protocol …and Her Friends</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-12-22T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/12/22/open-graph-protocol-and-friends</link>
  <guid>https://rocket-science.ru/hacking/2016/12/22/open-graph-protocol-and-friends---https://rocket-science.ru/hacking/2016/12/22/open-graph-protocol-and-friends</guid>
</item>
<item>
  <title>A brief look at the new Kamal Proxy replacing Traefik</title>
  <description>From Notes to self: Kamal 2 is coming with a brand new custom proxy that’s replacing Traefik. Let’s have a look at why is that and what it means.</description>
  <pubDate>2024-09-22T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/kamal-proxy/</link>
  <guid>https://nts.strzibny.name/kamal-proxy/---https://nts.strzibny.name/a-brief-look-at-the-new-kamal-proxy-replacing-traefik</guid>
</item>
<item>
  <title>StackOverflow Achievements</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-04-22T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/04/22/stack-overflow-achievements</link>
  <guid>https://rocket-science.ru/hacking/2016/04/22/stack-overflow-achievements---https://rocket-science.ru/hacking/2016/04/22/stack-overflow-achievements</guid>
</item>
<item>
  <title>WordCount in Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-02-22T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/02/22/wc-in-elixir</link>
  <guid>https://rocket-science.ru/hacking/2020/02/22/wc-in-elixir---https://rocket-science.ru/hacking/2020/02/22/wc-in-elixir</guid>
</item>
<item>
  <title>Real applications of flip-flop in ruby</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-02-22T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/02/22/flip-flop-real-application</link>
  <guid>https://rocket-science.ru/hacking/2018/02/22/flip-flop-real-application---https://rocket-science.ru/hacking/2018/02/22/flip-flop-real-application</guid>
</item>
<item>
  <title>Creating LiveView Modals with Tailwind CSS and AlpineJS</title>
  <description>From Patrick Thompson: </description>
  <pubDate>2020-07-21T10:00:39-07:00</pubDate>
  <link>http://blog.pthompson.org/liveview-tailwind-css-alpine-js-modal</link>
  <guid>http://blog.pthompson.org/liveview-tailwind-css-alpine-js-modal---tag:blog.pthompson.org,2014:Post/liveview-tailwind-css-alpine-js-modal</guid>
</item>
<item>
  <title>Renaming column and table in database migrations with Elixir and PostgreSQL</title>
  <description>From Patryk Bak - a programming blog: To guarantee zero downtime deployment, while deploying a new version of our application on more than one node, we can use rolling updates. What if this new version has a migration which renames either column or table? Will rolling updates protect our application against downtime?</description>
  <pubDate>2020-12-21T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2020/12/21/renaming-column-and-table-in-migrations.html</link>
  <guid>https://patrykbak.com/2020/12/21/renaming-column-and-table-in-migrations.html---https://patrykbak.com/2020/12/21/renaming-column-and-table-in-migrations</guid>
</item>
<item>
  <title>Fixing Broken Lightbulbs in a Nutshell</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-12-21T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2016/12/21/fixing-broken-lightbulbs-in-a-nutshell</link>
  <guid>https://rocket-science.ru/fun/2016/12/21/fixing-broken-lightbulbs-in-a-nutshell---https://rocket-science.ru/fun/2016/12/21/fixing-broken-lightbulbs-in-a-nutshell</guid>
</item>
<item>
  <title>devise-otp 2.0 released</title>
  <description>From Notes to self: The OTP plugin for Devise I help to maintain goes 2.0 this week. Here is what’s new and how to upgrade.</description>
  <pubDate>2025-10-21T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/devise-otp-2/</link>
  <guid>https://nts.strzibny.name/devise-otp-2/---https://nts.strzibny.name/devise-otp-200-released</guid>
</item>
<item>
  <title>A closer look at Rails force_ssl and assume_ssl</title>
  <description>From Notes to self: Rails comes with a built-in support for SSL in form of config.force_ssl. But what does it exactly do?</description>
  <pubDate>2024-10-21T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/rails-force-ssl/</link>
  <guid>https://nts.strzibny.name/rails-force-ssl/---https://nts.strzibny.name/a-closer-look-at-rails-force-ssl-and-assume-ssl</guid>
</item>
<item>
  <title>Raise For The Rescue</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-21T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/21/raise-for-the-rescue</link>
  <guid>https://rocket-science.ru/hacking/2018/10/21/raise-for-the-rescue---https://rocket-science.ru/hacking/2018/10/21/raise-for-the-rescue</guid>
</item>
<item>
  <title>Erlang/OTP 28.0</title>
  <description>From Erlang/OTP | News: OTP 28.0</description>
  <pubDate>2025-05-21T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/180</link>
  <guid>https://www.erlang.org/news/180---https://www.erlang.org/news/180</guid>
</item>
<item>
  <title>Shining Access</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-05-21T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/05/21/shining-access</link>
  <guid>https://rocket-science.ru/hacking/2022/05/21/shining-access---https://rocket-science.ru/hacking/2022/05/21/shining-access</guid>
</item>
<item>
  <title>To IF Or Not To IF—That’s The Conditional Statement</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-05-21T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/05/21/to-if-or-not-to-if</link>
  <guid>https://rocket-science.ru/hacking/2018/05/21/to-if-or-not-to-if---https://rocket-science.ru/hacking/2018/05/21/to-if-or-not-to-if</guid>
</item>
<item>
  <title>Running interactive sessions with Kamal</title>
  <description>From Notes to self: How to connect to a container on a server managed by Kamal and run an interactive session?</description>
  <pubDate>2025-03-21T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/interactive-session-kamal/</link>
  <guid>https://nts.strzibny.name/interactive-session-kamal/---https://nts.strzibny.name/running-interactive-sessions-with-kamal</guid>
</item>
<item>
  <title>Stop Abusing Nihil</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-03-21T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/03/21/stop-abusing-nihil</link>
  <guid>https://rocket-science.ru/hacking/2019/03/21/stop-abusing-nihil---https://rocket-science.ru/hacking/2019/03/21/stop-abusing-nihil</guid>
</item>
<item>
  <title>Ruby Blocks: Do-end vs. Braces</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-21T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/tips/2013/02/21/ruby-blocks-do-end-vs-braces</link>
  <guid>https://rocket-science.ru/tips/2013/02/21/ruby-blocks-do-end-vs-braces---https://rocket-science.ru/tips/2013/02/21/ruby-blocks-do-end-vs-braces</guid>
</item>
<item>
  <title>Introducing eFlambé</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I recently published an article on the Code for RentPath blog about a new tool I created during SpawnFest this year.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="quoteblock"&gt;
&lt;blockquote&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;SpawnFest is an annual 48 hour online contest where teams try to build the best BEAM-based application, as determined by the judges based on &lt;a href="https://spawnfest.org/#rules"&gt;certain criteria&lt;/a&gt;. In this blog post I am going to introduce the new tool I created during SpawnFest.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;eFlambé is a tool for rapidly profiling Erlang and Elixir code. It is intended to be one of the first tools you reach for when debugging a performance issue in your Elixir or Erlang application. With a single command you can visualize your code’s performance as an interactive flame graph in your flame graph viewer of choice. It’s written in Erlang and published to &lt;a href="https://hex.pm/packages/eflambe"&gt;hex.pm&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Read the rest of the article on &lt;a href="https://blog.rentpathcode.com/introducing-eflamb%C3%A9-3065e70f9eb"&gt;blog.rentpathcode.com&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://stratus3d.com/images/posts/eflambe/screenshot-speedscope.png" alt="screenshot of speedscope gui"&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>2021-10-20T12:57:49-04:00</pubDate>
  <link>https://stratus3d.com/blog/2021/10/20/introducing-eflambe/</link>
  <guid>https://stratus3d.com/blog/2021/10/20/introducing-eflambe/---https://stratus3d.com/blog/2021/10/20/introducing-eflambe</guid>
</item>
<item>
  <title>Telemetry Conventions</title>
  <description>From keathley.github.io: I’m a big fan of telemetry. It’s arguably the most important elixir project released in the past few years. Most of the mainstream libraries have started to adopt it, and that’s a good thing. But, there’s still a lot of inconsistency in how telemetry is used across projects. I thought it would be good to write up some of the conventions that I’ve been using.</description>
  <pubDate>2020-07-20T10:00:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/telemetry-conventions.html</link>
  <guid>http://keathley.github.io/blog/telemetry-conventions.html---http://keathley.github.io/blog/telemetry-conventions</guid>
</item>
<item>
  <title>InvoicePrinter 2.5 with QR images and Ruby 3.4 support</title>
  <description>From Notes to self: Today I released a new version of InvoicePrinter, my Ruby library for generating PDF invoices. Here’s what’s new.</description>
  <pubDate>2025-10-20T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/invoiceprinter-2.5/</link>
  <guid>https://nts.strzibny.name/invoiceprinter-2.5/---https://nts.strzibny.name/invoiceprinter-25-with-qr-images-and-ruby-34-support</guid>
</item>
<item>
  <title>SeeAsVee Library For Handy CSV Processing</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-20T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/20/see_as_vee-to-the-rescue</link>
  <guid>https://rocket-science.ru/hacking/2018/10/20/see_as_vee-to-the-rescue---https://rocket-science.ru/hacking/2018/10/20/see_as_vee-to-the-rescue</guid>
</item>
<item>
  <title>StringNaming to call UTF8 by name</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-06-20T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/06/20/string-naming-module</link>
  <guid>https://rocket-science.ru/hacking/2017/06/20/string-naming-module---https://rocket-science.ru/hacking/2017/06/20/string-naming-module</guid>
</item>
<item>
  <title>Продолжая писать в то самое время, когда технологии поломали все социальные договоренности в сети</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-05-20T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/translation/2023/05/20/writing-when-tech-has-broken-social-contract</link>
  <guid>https://rocket-science.ru/translation/2023/05/20/writing-when-tech-has-broken-social-contract---https://rocket-science.ru/translation/2023/05/20/writing-when-tech-has-broken-social-contract</guid>
</item>
<item>
  <title>How atom keys with JSON in Ecto can break your system</title>
  <description>From Patryk Bak - a programming blog: In Elixir, you can use, among others, a string and an atom as a map key. Let’s start with an atom as a map key:</description>
  <pubDate>2021-02-20T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2021/02/20/atom-keys-with-json-in-ecto.html</link>
  <guid>https://patrykbak.com/2021/02/20/atom-keys-with-json-in-ecto.html---https://patrykbak.com/2021/02/20/atom-keys-with-json-in-ecto</guid>
</item>
<item>
  <title>Why immutability rules</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-02-20T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/02/20/why-immutability-rules</link>
  <guid>https://rocket-science.ru/hacking/2018/02/20/why-immutability-rules---https://rocket-science.ru/hacking/2018/02/20/why-immutability-rules</guid>
</item>
<item>
  <title>Zsh :: Weird Right Prompt</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-01-20T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/shell/2013/01/20/zsh-weird-right-prompt</link>
  <guid>https://rocket-science.ru/shell/2013/01/20/zsh-weird-right-prompt---https://rocket-science.ru/shell/2013/01/20/zsh-weird-right-prompt</guid>
</item>
<item>
  <title>256 Color Term (Nightmare Level)</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-01-20T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/shell/2013/01/20/256-color-terminal</link>
  <guid>https://rocket-science.ru/shell/2013/01/20/256-color-terminal---https://rocket-science.ru/shell/2013/01/20/256-color-terminal</guid>
</item>
<item>
  <title>Cannot connect to the Docker daemon after switching to OrbStack</title>
  <description>From Notes to self: Switching from Docker Desktop to OrbStack and stuck at Docker daemon error? Here’s how to fix it</description>
  <pubDate>2024-12-19T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/kamal-cannot-connect-to-docker-daemon/</link>
  <guid>https://nts.strzibny.name/kamal-cannot-connect-to-docker-daemon/---https://nts.strzibny.name/cannot-connect-to-the-docker-daemon-after-switching-to-orbstack</guid>
</item>
<item>
  <title>Dry Behaviour aka Protocol Pattern in Ruby</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-12-19T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/12/19/dry-behaviour</link>
  <guid>https://rocket-science.ru/hacking/2016/12/19/dry-behaviour---https://rocket-science.ru/hacking/2016/12/19/dry-behaviour</guid>
</item>
<item>
  <title>Testing migrations</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-11-19T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/11/19/testing-migrations</link>
  <guid>https://rocket-science.ru/hacking/2015/11/19/testing-migrations---https://rocket-science.ru/hacking/2015/11/19/testing-migrations</guid>
</item>
<item>
  <title>Use Github CI for Elixir Projects</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-08-19T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/08/19/use-github-ci-for-elixir-projects</link>
  <guid>https://rocket-science.ru/hacking/2019/08/19/use-github-ci-for-elixir-projects---https://rocket-science.ru/hacking/2019/08/19/use-github-ci-for-elixir-projects</guid>
</item>
<item>
  <title>Erlang/OTP 28.0 Release Candidate 2</title>
  <description>From Erlang/OTP | News: OTP 28.0-rc2</description>
  <pubDate>2025-03-19T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/177</link>
  <guid>https://www.erlang.org/news/177---https://www.erlang.org/news/177</guid>
</item>
<item>
  <title>Gospel of Barabbas or Concurrent Execution</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-01-19T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/01/19/gospel-of-barabbas-or-concurrent-execution</link>
  <guid>https://rocket-science.ru/hacking/2019/01/19/gospel-of-barabbas-or-concurrent-execution---https://rocket-science.ru/hacking/2019/01/19/gospel-of-barabbas-or-concurrent-execution</guid>
</item>
<item>
  <title>EventMachine :: Nested Calls</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-01-19T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/01/19/event-machine-nested</link>
  <guid>https://rocket-science.ru/hacking/2013/01/19/event-machine-nested---https://rocket-science.ru/hacking/2013/01/19/event-machine-nested</guid>
</item>
<item>
  <title>Old Hardware I Love</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;Recently I got to thinking about the electronic devices I use regularly and love. I realized that many of the devices I enjoy using are by many people&amp;#8217;s standards pretty old for electronics. I&amp;#8217;ve always been thrifty and don&amp;#8217;t like replacing electronics with new ones until they break. In this blog post I&amp;#8217;m going to highlight three old devices that I enjoy using for one reason or another.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://stratus3d.com/images/posts/old-hardware/old-hardware-collage.jpg" alt="old hardware collage"&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>2022-12-18T17:47:50-05:00</pubDate>
  <link>https://stratus3d.com/blog/2022/12/18/old-hardware-i-love/</link>
  <guid>https://stratus3d.com/blog/2022/12/18/old-hardware-i-love/---https://stratus3d.com/blog/2022/12/18/old-hardware-i-love</guid>
</item>
<item>
  <title>Open Source is Not About You</title>
  <description>From keathley.github.io: Rich Hickey posted this gist back in 2018 about the entitlement of people who use open-source software. I’m not going to re-iterate his points and instead suggest that you give it a read. But, I’ll leave you with one of my favorite quotes:</description>
  <pubDate>2020-01-18T13:32:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/open-source-is-not-about-you.html</link>
  <guid>http://keathley.github.io/blog/open-source-is-not-about-you.html---http://keathley.github.io/blog/open-source-is-not-about-you</guid>
</item>
<item>
  <title>What I Learned Building a CLI App in Rust</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;It&amp;#8217;s been 8 years since I learned Elixir and since then I have not tried to learn another programming language.
I have now decided to learn a systems programming language, and it seems like a good time to be learning about them. New languages like &lt;a href="https://vale.dev/"&gt;Vale&lt;/a&gt; and &lt;a href="https://www.rust-lang.org/"&gt;Rust&lt;/a&gt; combine the memory safety of a high-level language with the performance of C. Rust is very popular today but there are many other new systems languages with similar characteristics. &lt;a href="https://ziglang.org/"&gt;Zig&lt;/a&gt;, &lt;a href="https://vlang.io/"&gt;Vlang&lt;/a&gt;, &lt;a href="https://nim-lang.org"&gt;Nim&lt;/a&gt;  are a few that come to mind.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2023-07-18T07:33:14-04:00</pubDate>
  <link>https://stratus3d.com/blog/2023/07/18/what-i-learned-building-a-cli-app-in-rust/</link>
  <guid>https://stratus3d.com/blog/2023/07/18/what-i-learned-building-a-cli-app-in-rust/---https://stratus3d.com/blog/2023/07/18/what-i-learned-building-a-cli-app-in-rust</guid>
</item>
<item>
  <title>Erlang/OTP 29.0 Release Candidate 2</title>
  <description>From Erlang/OTP | News: OTP 29.0-rc2</description>
  <pubDate>2026-03-18T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/185</link>
  <guid>https://www.erlang.org/news/185---https://www.erlang.org/news/185</guid>
</item>
<item>
  <title>Dynamic Nested Function Call</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-02-18T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/02/18/dynamic-nested-function-calls-with-macro</link>
  <guid>https://rocket-science.ru/hacking/2021/02/18/dynamic-nested-function-calls-with-macro---https://rocket-science.ru/hacking/2021/02/18/dynamic-nested-function-calls-with-macro</guid>
</item>
<item>
  <title>Strong Types Should Have Been Named Strong Hypes</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-01-18T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/01/18/strong-hypes</link>
  <guid>https://rocket-science.ru/hacking/2020/01/18/strong-hypes---https://rocket-science.ru/hacking/2020/01/18/strong-hypes</guid>
</item>
<item>
  <title>Debugging a Slow Starting Elixir Application</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I recently had to fix an Elixir service that was slow to start. I was able to pinpoint the issue with only a few commands and I want to share a couple of the things I learned.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-08-17T21:33:08-04:00</pubDate>
  <link>https://stratus3d.com/blog/2022/08/17/debugging-a-slow-starting-elixir-application/</link>
  <guid>https://stratus3d.com/blog/2022/08/17/debugging-a-slow-starting-elixir-application/---https://stratus3d.com/blog/2022/08/17/debugging-a-slow-starting-elixir-application</guid>
</item>
<item>
  <title>Who Watches Watchmen? - Part 1</title>
  <description>From Hauleth's blog: A lot of application use systems like Kubernetes for their deployment. In my
humble opinion it is often overkill as system, that offers most of the stuff such
thing provide, is already present in your OS. In this article I will try to
present how to utilise the most popular system supervisor from Elixir
applications.</description>
  <pubDate>2022-01-17T21:22:18+01:00</pubDate>
  <link>https://hauleth.dev/post/who-watches-watchmen-i/</link>
  <guid>https://hauleth.dev/post/who-watches-watchmen-i/---https://hauleth.dev/post/who-watches-watchmen-i/</guid>
</item>
<item>
  <title>Going back to RSS</title>
  <description>From keathley.github.io: In the middle of 2019, I rediscovered RSS. I see you rolling your eyes; how could you possibly forget about RSS? I suppose I’d just gotten lazy. I’d allowed Twitter or some crappy news aggregator to dictate what I was reading. But, considering how I could become a more discerning consumer, it occurred to me that RSS hadn’t gone anywhere, and I should start using it again.</description>
  <pubDate>2020-01-17T09:09:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/rss-is-still-great.html</link>
  <guid>http://keathley.github.io/blog/rss-is-still-great.html---http://keathley.github.io/blog/rss-is-still-great</guid>
</item>
<item>
  <title>Creating a Modal LiveView LiveComponent</title>
  <description>From Patrick Thompson: </description>
  <pubDate>2019-12-17T00:09:01-08:00</pubDate>
  <link>http://blog.pthompson.org/phoenix-liveview-livecomponent-modal</link>
  <guid>http://blog.pthompson.org/phoenix-liveview-livecomponent-modal---tag:blog.pthompson.org,2014:Post/phoenix-liveview-livecomponent-modal</guid>
</item>
<item>
  <title>Dialyzer specs: 2 in 1</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-12-17T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/12/17/dialyzer-specs-2-in-1</link>
  <guid>https://rocket-science.ru/hacking/2019/12/17/dialyzer-specs-2-in-1---https://rocket-science.ru/hacking/2019/12/17/dialyzer-specs-2-in-1</guid>
</item>
<item>
  <title>Ruby Memory Pitfalls</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-12-17T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/12/17/ruby-memory-pitfalls</link>
  <guid>https://rocket-science.ru/hacking/2013/12/17/ruby-memory-pitfalls---https://rocket-science.ru/hacking/2013/12/17/ruby-memory-pitfalls</guid>
</item>
<item>
  <title>Ruby metaprogramming for beginners → Elixir-like specs</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-17T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/17/elixir-like-specs-in-ruby</link>
  <guid>https://rocket-science.ru/hacking/2018/10/17/elixir-like-specs-in-ruby---https://rocket-science.ru/hacking/2018/10/17/elixir-like-specs-in-ruby</guid>
</item>
<item>
  <title>Erlang/OTP 28.1 Release</title>
  <description>From Erlang/OTP | News: OTP 28.1</description>
  <pubDate>2025-09-17T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/181</link>
  <guid>https://www.erlang.org/news/181---https://www.erlang.org/news/181</guid>
</item>
<item>
  <title>Interview on Senior Poet Position</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-05-17T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/jokes/2018/05/17/interview-on-senior-poet-position</link>
  <guid>https://rocket-science.ru/jokes/2018/05/17/interview-on-senior-poet-position---https://rocket-science.ru/jokes/2018/05/17/interview-on-senior-poet-position</guid>
</item>
<item>
  <title>Combining multiple sitemaps with a sitemap index</title>
  <description>From Notes to self: What if we need to combine multiple sitemaps for a main domain or subdomain? Here’s how to do it by creating sitemap index.</description>
  <pubDate>2025-02-17T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/multiple-sitemaps-index/</link>
  <guid>https://nts.strzibny.name/multiple-sitemaps-index/---https://nts.strzibny.name/combining-multiple-sitemaps-with-a-sitemap-index</guid>
</item>
<item>
  <title>Either Monad in Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-02-17T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/02/17/either-monad-in-elixir</link>
  <guid>https://rocket-science.ru/hacking/2017/02/17/either-monad-in-elixir---https://rocket-science.ru/hacking/2017/02/17/either-monad-in-elixir</guid>
</item>
<item>
  <title>Wash your dishes</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-01-17T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/01/17/wash-your-disches</link>
  <guid>https://rocket-science.ru/hacking/2023/01/17/wash-your-disches---https://rocket-science.ru/hacking/2023/01/17/wash-your-disches</guid>
</item>
<item>
  <title>Phoenix on Turbo</title>
  <description>From Hostile Developer: </description>
  <pubDate>2021-04-16T16:09:27-04:00</pubDate>
  <link>https://hostiledeveloper.com/2021/04/16/phoenix-on-turbo.html</link>
  <guid>https://hostiledeveloper.com/2021/04/16/phoenix-on-turbo.html---https://hostiledeveloper.com/2021/04/16/phoenix-on-turbo.html</guid>
</item>
<item>
  <title>Phoenix Contexts and Crossing Boundaries</title>
  <description>From Hostile Developer: </description>
  <pubDate>2021-04-16T15:16:13-04:00</pubDate>
  <link>https://hostiledeveloper.com/2020/08/13/phoenix-contexts-and-crossing-boundaries.html</link>
  <guid>https://hostiledeveloper.com/2020/08/13/phoenix-contexts-and-crossing-boundaries.html---https://hostiledeveloper.com/2020/08/13/phoenix-contexts-and-crossing-boundaries.html</guid>
</item>
<item>
  <title>Superb Supervisors. Designing for Failure</title>
  <description>From Hostile Developer: </description>
  <pubDate>2021-04-16T15:16:13-04:00</pubDate>
  <link>https://hostiledeveloper.com/2020/08/12/superb-supervisors-designing-for-failure.html</link>
  <guid>https://hostiledeveloper.com/2020/08/12/superb-supervisors-designing-for-failure.html---https://hostiledeveloper.com/2020/08/12/superb-supervisors-designing-for-failure.html</guid>
</item>
<item>
  <title>Connection Pools and RabbitMQ</title>
  <description>From Hostile Developer: </description>
  <pubDate>2021-04-16T15:16:13-04:00</pubDate>
  <link>https://hostiledeveloper.com/2020/08/09/connection-pools-and-rabbitmq.html</link>
  <guid>https://hostiledeveloper.com/2020/08/09/connection-pools-and-rabbitmq.html---https://hostiledeveloper.com/2020/08/09/connection-pools-and-rabbitmq.html</guid>
</item>
<item>
  <title>Organizing LiveView Logic with Presentation Models</title>
  <description>From Hostile Developer: </description>
  <pubDate>2021-04-16T15:16:13-04:00</pubDate>
  <link>https://hostiledeveloper.com/2020/06/19/organizing-liveview-logic-with-presentation-models.html</link>
  <guid>https://hostiledeveloper.com/2020/06/19/organizing-liveview-logic-with-presentation-models.html---https://hostiledeveloper.com/2020/06/19/organizing-liveview-logic-with-presentation-models.html</guid>
</item>
<item>
  <title>Salesforce Outbound messages and Phoenix</title>
  <description>From Hostile Developer: </description>
  <pubDate>2021-04-16T15:16:13-04:00</pubDate>
  <link>https://hostiledeveloper.com/2020/05/09/salesforce-outbound-messages-and-phoenix.html</link>
  <guid>https://hostiledeveloper.com/2020/05/09/salesforce-outbound-messages-and-phoenix.html---https://hostiledeveloper.com/2020/05/09/salesforce-outbound-messages-and-phoenix.html</guid>
</item>
<item>
  <title>Iteraptable → Swiss Knife For Structs</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-11-16T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/11/16/iteraptable-extending-elixir-structs</link>
  <guid>https://rocket-science.ru/hacking/2018/11/16/iteraptable-extending-elixir-structs---https://rocket-science.ru/hacking/2018/11/16/iteraptable-extending-elixir-structs</guid>
</item>
<item>
  <title>Deploying a Next.js application with Kamal 2</title>
  <description>From Notes to self: Here’s the simplest example to deploy a containerized Next application with Kamal.</description>
  <pubDate>2024-10-16T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/deploying-next-kamal-2/</link>
  <guid>https://nts.strzibny.name/deploying-next-kamal-2/---https://nts.strzibny.name/deploying-a-nextjs-application-with-kamal-2</guid>
</item>
<item>
  <title>Do Not Doughnut Donates</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-05-16T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/05/16/doughnut-donates</link>
  <guid>https://rocket-science.ru/hacking/2021/05/16/doughnut-donates---https://rocket-science.ru/hacking/2021/05/16/doughnut-donates</guid>
</item>
<item>
  <title>Erlang/OTP 28.0 Release Candidate 3</title>
  <description>From Erlang/OTP | News: OTP 28.0-rc3</description>
  <pubDate>2025-04-16T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/178</link>
  <guid>https://www.erlang.org/news/178---https://www.erlang.org/news/178</guid>
</item>
<item>
  <title>Elixir Pipeline Operators</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-03-16T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/03/16/elixir-pipeline-operators</link>
  <guid>https://rocket-science.ru/hacking/2018/03/16/elixir-pipeline-operators---https://rocket-science.ru/hacking/2018/03/16/elixir-pipeline-operators</guid>
</item>
<item>
  <title>Securing Static Websites</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;One of the big benefits of static websites is simplicity. Static websites are simple so there is much less to worry about. There are no login credentials to be compromised. There is no dynamic content that must be stored and displayed. There is no danger of a bug in the code allowing SQL injections. But there are still things that should be done to secure a static website. In this blog post I will list some of the important things that still need to be done to provide a secure static website.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-07-15T16:39:34-04:00</pubDate>
  <link>https://stratus3d.com/blog/2022/07/15/securing-static-websites/</link>
  <guid>https://stratus3d.com/blog/2022/07/15/securing-static-websites/---https://stratus3d.com/blog/2022/07/15/securing-static-websites</guid>
</item>
<item>
  <title>Show All Telemetry Events in Erlang and Elixir</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;&lt;a href="https://hexdocs.pm/telemetry/readme.html"&gt;Telemetry is an Erlang library&lt;/a&gt; for dynamically dispatching events to event handlers. Many popular Erlang and Elixir packages use the Telemetry library to emit events. Telemetry event data typically ends up in logs or metric databases like Prometheus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Events are identified by name. You can register a function as an event handler and get invoked when events with a specific name occur. A name or list of names must be specified when registering a handler using the &lt;code&gt;telemetry:attach/4&lt;/code&gt; and &lt;code&gt;telemetry:attach_many/4&lt;/code&gt; functions.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2023-10-15T14:05:37-04:00</pubDate>
  <link>https://stratus3d.com/blog/2023/10/15/show-all-telemetry-events-in-erlang-and-elixir/</link>
  <guid>https://stratus3d.com/blog/2023/10/15/show-all-telemetry-events-in-erlang-and-elixir/---https://stratus3d.com/blog/2023/10/15/show-all-telemetry-events-in-erlang-and-elixir</guid>
</item>
<item>
  <title>Benefits of Living Without a Smartphone</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;It&amp;#8217;s immediately obvious what the disadvantages are of living without a smartphone. Lots of things in modern life are more difficult without without the help of a smartphone. Smartphones augment our own knowledge and keep us connected to people, systems, and services we rely on. In my previous blog post I wrote about my experience living with only a dumb phone.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this blog post I list the benefits of &lt;a href="https://stratus3d.com/blog/2021/03/30/living-without-a-smartphone/"&gt;living without a smartphone&lt;/a&gt; that I have noted over the last 15 months. Some of these things I experienced myself and others are things I have read about.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2021-05-15T12:11:11-04:00</pubDate>
  <link>https://stratus3d.com/blog/2021/05/15/benefits-of-living-without-a-smartphone/</link>
  <guid>https://stratus3d.com/blog/2021/05/15/benefits-of-living-without-a-smartphone/---https://stratus3d.com/blog/2021/05/15/benefits-of-living-without-a-smartphone</guid>
</item>
<item>
  <title>Stop Spreading Crap at My `$HOME`</title>
  <description>From Hauleth's blog: </description>
  <pubDate>2020-04-15T12:00:19+02:00</pubDate>
  <link>https://hauleth.dev/post/stop-spreading-crap-at-my-home/</link>
  <guid>https://hauleth.dev/post/stop-spreading-crap-at-my-home/---https://hauleth.dev/post/stop-spreading-crap-at-my-home/</guid>
</item>
<item>
  <title>Clarity Over Verbosity Everywhere</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-11-15T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/culture/2017/11/15/clarity-over-verbosity-everywhere</link>
  <guid>https://rocket-science.ru/culture/2017/11/15/clarity-over-verbosity-everywhere---https://rocket-science.ru/culture/2017/11/15/clarity-over-verbosity-everywhere</guid>
</item>
<item>
  <title>Collage directory preview with RMagick</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-08-15T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/08/15/collage-directory-preview-with-rmagick</link>
  <guid>https://rocket-science.ru/hacking/2013/08/15/collage-directory-preview-with-rmagick---https://rocket-science.ru/hacking/2013/08/15/collage-directory-preview-with-rmagick</guid>
</item>
<item>
  <title>Generated Types</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-07-15T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/07/15/generated-types</link>
  <guid>https://rocket-science.ru/hacking/2020/07/15/generated-types---https://rocket-science.ru/hacking/2020/07/15/generated-types</guid>
</item>
<item>
  <title>Common Test for Elixir developers</title>
  <description>From Hauleth's blog: </description>
  <pubDate>2019-07-15T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/common-test-for-elixir/</link>
  <guid>https://hauleth.dev/post/common-test-for-elixir/---https://hauleth.dev/post/common-test-for-elixir/</guid>
</item>
<item>
  <title>Why shouldn’t you use Elixir code in database migrations?</title>
  <description>From Patryk Bak - a programming blog: Elixir code used in migrations can cause troubles when we decide to start up our system with an empty database. To understand the problem, let’s take the same example as the one used in my previous article entitled “How to migrate live production data”.</description>
  <pubDate>2020-06-15T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2020/06/15/why-should-not-you-use-elixir-code-in-database-migrations.html</link>
  <guid>https://patrykbak.com/2020/06/15/why-should-not-you-use-elixir-code-in-database-migrations.html---https://patrykbak.com/2020/06/15/why-should-not-you-use-elixir-code-in-database-migrations</guid>
</item>
<item>
  <title>Adding button loader to Turbo-powered forms</title>
  <description>From Notes to self: Turbo is a great way to build user interfaces, but most Turbo forms have to wait for the server response. Here’s how I am adding a small loading spinner to the submit buttons to improve the UX.</description>
  <pubDate>2025-01-15T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/button-loader-turbo-forms/</link>
  <guid>https://nts.strzibny.name/button-loader-turbo-forms/---https://nts.strzibny.name/adding-button-loader-to-turbo-powered-forms</guid>
</item>
<item>
  <title>Here be (owned) books</title>
  <description>From Hauleth's blog: Simple introduction to Rust's ownership system</description>
  <pubDate>2019-07-14T17:38:48+01:00</pubDate>
  <link>https://hauleth.dev/post/eli5-ownership/</link>
  <guid>https://hauleth.dev/post/eli5-ownership/---https://hauleth.dev/post/eli5-ownership/</guid>
</item>
<item>
  <title>Subclassing STI models in Rails</title>
  <description>From Notes to self: Here’s a short tip on opting out a specific model from Single Table Inheritance (STI).</description>
  <pubDate>2024-11-14T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/subclass-sti-rails-model/</link>
  <guid>https://nts.strzibny.name/subclass-sti-rails-model/---https://nts.strzibny.name/cancel-sti-for-a-rails-model</guid>
</item>
<item>
  <title>Show all running apps on the server with Kamal</title>
  <description>From Notes to self: Kamal 2 can deploy multiple apps on a single server so it’s easy to lose track of what’s deployed. This alias will fix it.</description>
  <pubDate>2024-11-14T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/kamal-show-running-apps/</link>
  <guid>https://nts.strzibny.name/kamal-show-running-apps/---https://nts.strzibny.name/show-all-running-apps-on-the-server-with-kamal</guid>
</item>
<item>
  <title>Who Watches Watchmen? - Part 2</title>
  <description>From Hauleth's blog: Continuation of travel into making systemd to work for us, not against us. This
time we will talk about socket activation and how to make our application run
only when we need it to run.</description>
  <pubDate>2023-11-14T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/who-watches-watchmen-ii/</link>
  <guid>https://hauleth.dev/post/who-watches-watchmen-ii/---https://hauleth.dev/post/who-watches-watchmen-ii/</guid>
</item>
<item>
  <title>Yet Another Markup Parser</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-10-14T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/10/14/md-with-pleasure</link>
  <guid>https://rocket-science.ru/hacking/2021/10/14/md-with-pleasure---https://rocket-science.ru/hacking/2021/10/14/md-with-pleasure</guid>
</item>
<item>
  <title>Two ways to write ruby code</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-10-14T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/10/14/two-ways-to-write-ruby-code</link>
  <guid>https://rocket-science.ru/hacking/2015/10/14/two-ways-to-write-ruby-code---https://rocket-science.ru/hacking/2015/10/14/two-ways-to-write-ruby-code</guid>
</item>
<item>
  <title>FSM Driven Development</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-08-14T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/08/14/fsm-driven-development</link>
  <guid>https://rocket-science.ru/hacking/2022/08/14/fsm-driven-development---https://rocket-science.ru/hacking/2022/08/14/fsm-driven-development</guid>
</item>
<item>
  <title>Tests as First-Class Citizens</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2025-05-14T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2025/05/14/tests-as-first-class-citizens</link>
  <guid>https://rocket-science.ru/hacking/2025/05/14/tests-as-first-class-citizens---https://rocket-science.ru/hacking/2025/05/14/tests-as-first-class-citizens</guid>
</item>
<item>
  <title>YAML Parser Tuning</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-04-14T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/04/14/yaml-parser-tuning</link>
  <guid>https://rocket-science.ru/hacking/2015/04/14/yaml-parser-tuning---https://rocket-science.ru/hacking/2015/04/14/yaml-parser-tuning</guid>
</item>
<item>
  <title>Dumb Elixir VIsual (and iMproved) editor</title>
  <description>From Hauleth's blog: How I have configured Vim for working with Elixir and Erlang projects</description>
  <pubDate>2019-04-13T21:40:05+02:00</pubDate>
  <link>https://hauleth.dev/post/vim-for-elixir/</link>
  <guid>https://hauleth.dev/post/vim-for-elixir/---https://hauleth.dev/post/vim-for-elixir/</guid>
</item>
<item>
  <title>Struct With Hash-like Default Proc</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-12-13T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/12/13/struct-with-default-proc</link>
  <guid>https://rocket-science.ru/hacking/2016/12/13/struct-with-default-proc---https://rocket-science.ru/hacking/2016/12/13/struct-with-default-proc</guid>
</item>
<item>
  <title>Timeo Juniors et ideas ferentes</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-11-13T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/culture/2017/11/13/juniors-and-ideas</link>
  <guid>https://rocket-science.ru/culture/2017/11/13/juniors-and-ideas---https://rocket-science.ru/culture/2017/11/13/juniors-and-ideas</guid>
</item>
<item>
  <title>RFC HTTP API Feedback Proposal</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-13T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2018/10/13/rfc-http-responses</link>
  <guid>https://rocket-science.ru/fun/2018/10/13/rfc-http-responses---https://rocket-science.ru/fun/2018/10/13/rfc-http-responses</guid>
</item>
<item>
  <title>Quotation Marks in XXI Century</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-10-13T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/10/13/quotation-marks-in-XXI-century</link>
  <guid>https://rocket-science.ru/hacking/2015/10/13/quotation-marks-in-XXI-century---https://rocket-science.ru/hacking/2015/10/13/quotation-marks-in-XXI-century</guid>
</item>
<item>
  <title>Banned for not being flattering</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-09-13T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/culture/2018/09/13/banned-from-dev-to</link>
  <guid>https://rocket-science.ru/culture/2018/09/13/banned-from-dev-to---https://rocket-science.ru/culture/2018/09/13/banned-from-dev-to</guid>
</item>
<item>
  <title>DIY Message Broker</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2025-05-13T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2025/05/13/diy-message-broker</link>
  <guid>https://rocket-science.ru/hacking/2025/05/13/diy-message-broker---https://rocket-science.ru/hacking/2025/05/13/diy-message-broker</guid>
</item>
<item>
  <title>Erlang.org planned downtime</title>
  <description>From Erlang/OTP | News: Erlang.org webpage will be down due to an planned power outage at the site.</description>
  <pubDate>2025-03-13T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/176</link>
  <guid>https://www.erlang.org/news/176---https://www.erlang.org/news/176</guid>
</item>
<item>
  <title>Conditional context for macros</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-02-13T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/02/13/conditional-context</link>
  <guid>https://rocket-science.ru/hacking/2021/02/13/conditional-context---https://rocket-science.ru/hacking/2021/02/13/conditional-context</guid>
</item>
<item>
  <title>The dangers of the Single Global Process</title>
  <description>From keathley.github.io: There are a few things in the Elixir/Erlang ecosystem that I consider required reading. To spawn, or not to spawn? by Saša Jurić is definitely one of them. If you haven’t read it, you need to. It’ll change the way you think about building elixir applications.</description>
  <pubDate>2019-08-12T07:53:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/sgp.html</link>
  <guid>http://keathley.github.io/blog/sgp.html---http://keathley.github.io/blog/sgp</guid>
</item>
<item>
  <title>Sigils To The Rescue</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-12-12T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/12/12/sigils-for-the-rescue</link>
  <guid>https://rocket-science.ru/hacking/2018/12/12/sigils-for-the-rescue---https://rocket-science.ru/hacking/2018/12/12/sigils-for-the-rescue</guid>
</item>
<item>
  <title>Define module in Elixir with initial binding</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-05-12T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/05/12/defmodule-with-binding-implementation</link>
  <guid>https://rocket-science.ru/hacking/2017/05/12/defmodule-with-binding-implementation---https://rocket-science.ru/hacking/2017/05/12/defmodule-with-binding-implementation</guid>
</item>
<item>
  <title>A Score for an Invisible Orchestra</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2026-04-12T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2026/04/12/metaast-for-the-rescue</link>
  <guid>https://rocket-science.ru/hacking/2026/04/12/metaast-for-the-rescue---https://rocket-science.ru/hacking/2026/04/12/metaast-for-the-rescue</guid>
</item>
<item>
  <title>Plugins in Elixir Applications</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-02-12T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/02/12/plugins-in-elixir-apps</link>
  <guid>https://rocket-science.ru/hacking/2022/02/12/plugins-in-elixir-apps---https://rocket-science.ru/hacking/2022/02/12/plugins-in-elixir-apps</guid>
</item>
<item>
  <title>Conditional guard for structs of an explicit type</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-02-12T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/02/12/conditional-defguard</link>
  <guid>https://rocket-science.ru/hacking/2021/02/12/conditional-defguard---https://rocket-science.ru/hacking/2021/02/12/conditional-defguard</guid>
</item>
<item>
  <title>Will No One Rid Me Of This Turbulent GUI</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-01-12T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/01/12/will-no-one-rid-me-of-this-turbulent-gui</link>
  <guid>https://rocket-science.ru/hacking/2021/01/12/will-no-one-rid-me-of-this-turbulent-gui---https://rocket-science.ru/hacking/2021/01/12/will-no-one-rid-me-of-this-turbulent-gui</guid>
</item>
<item>
  <title>Pattern matching on binaries takes over Regex</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-01-12T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/01/12/parse-cumbersome-data</link>
  <guid>https://rocket-science.ru/hacking/2018/01/12/parse-cumbersome-data---https://rocket-science.ru/hacking/2018/01/12/parse-cumbersome-data</guid>
</item>
<item>
  <title>The Meaning of Dbg Function Names</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;If you&amp;#8217;ve ever used the dbg module in Erlang you&amp;#8217;ve seen the esoteric names of the functions it exports. I recently watched &lt;a href="https://www.youtube.com/watch?v=sR9h3DZAA74"&gt;Jeffery Utter&amp;#8217;s excellent ElixirConf talk on Debugging Live Systems on the BEAM&lt;/a&gt;. In the talk when he covers the &lt;code&gt;dbg:tpl/3&lt;/code&gt; function he guesses that it might stand for "tuple". This reminded me that I didn&amp;#8217;t know what most of the functions names in the dbg module stood for, even though I learned how to use dbg years ago. This got me searching for the meanings behind the dbg function names.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2021-08-11T19:17:52-04:00</pubDate>
  <link>https://stratus3d.com/blog/2021/08/11/the-meaning-of-dbg-function-names/</link>
  <guid>https://stratus3d.com/blog/2021/08/11/the-meaning-of-dbg-function-names/---https://stratus3d.com/blog/2021/08/11/the-meaning-of-dbg-function-names</guid>
</item>
<item>
  <title>Testing your README.md</title>
  <description>From keathley.github.io: I’ve wanted a good way to test READMEs in elixir. I started building something myself before my good friend Wojtek pointed out that there was a simple solution.</description>
  <pubDate>2021-12-11T11:00:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/testing-readmes.html</link>
  <guid>http://keathley.github.io/blog/testing-readmes.html---http://keathley.github.io/blog/testing-readmes</guid>
</item>
<item>
  <title>Asdf Performance</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I&amp;#8217;ve been a maintainer of asdf for about 6 years now. Most of my work these last several years has been fixing bugs, writing automated tests to catch regressions, and ensuring overall correctness across various operating systems. Maintaining a tool written in Bash isn&amp;#8217;t easy. For most things there are far more edge cases and compatibility issues than happy paths. Certain commands need to be  &lt;a href="https://github.com/asdf-vm/asdf/blob/master/test/banned_commands.bats"&gt;banned from the codebase&lt;/a&gt; to ensure compatibility across operating systems. &lt;a href="http://redsymbol.net/articles/unofficial-bash-strict-mode/"&gt;Bash strict mode&lt;/a&gt; also helps but it introduces &lt;a href="http://stratus3d.com/blog/2019/11/29/bash-errexit-inconsistency/"&gt;other issues&lt;/a&gt; that have to be worked around.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All this to say I haven&amp;#8217;t had a lot of time to step back and assess the other aspects of asdf. Other maintainers have stepped up and helped with documentation and UX, but performance has fallen by the wayside. Over the last couple of months I&amp;#8217;ve noticed some asdf commands that I use often seem to have gotten slower. Many asdf commands are fairly slow but with the way asdf was designed it is seldom a problem. &lt;code&gt;asdf current&lt;/code&gt; was never fast enough to be used as part of a shell prompt showing the current versions, but that was user experience enhancement that wasn&amp;#8217;t critical. But it seems like things are getting worse and I wanted to assess performance. In this post I am going to benchmark several asdf commands and see how their performance has changed over time.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-08-11T08:57:54-04:00</pubDate>
  <link>https://stratus3d.com/blog/2022/08/11/asdf-performance/</link>
  <guid>https://stratus3d.com/blog/2022/08/11/asdf-performance/---https://stratus3d.com/blog/2022/08/11/asdf-performance</guid>
</item>
<item>
  <title>How to use a remote build server with Kamal</title>
  <description>From Notes to self: How and when to create an external build server for your Kamal deploys?</description>
  <pubDate>2024-12-11T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/how-to-use-remote-build-server-kamal/</link>
  <guid>https://nts.strzibny.name/how-to-use-remote-build-server-kamal/---https://nts.strzibny.name/how-to-use-a-remote-builder-with-kamal</guid>
</item>
<item>
  <title>Protocols in Ruby → Allow Implicit Inheritance</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-11T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/11/dry-behaviour-better-error-handling-and-implicit-inheritance</link>
  <guid>https://rocket-science.ru/hacking/2018/10/11/dry-behaviour-better-error-handling-and-implicit-inheritance---https://rocket-science.ru/hacking/2018/10/11/dry-behaviour-better-error-handling-and-implicit-inheritance</guid>
</item>
<item>
  <title>Howto Read Stack Overflow Comments</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-08-11T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/culture/2016/08/11/how-to-read-so-comments</link>
  <guid>https://rocket-science.ru/culture/2016/08/11/how-to-read-so-comments---https://rocket-science.ru/culture/2016/08/11/how-to-read-so-comments</guid>
</item>
<item>
  <title>Log With Pleasure</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-04-11T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/04/11/log-with-pleasure</link>
  <guid>https://rocket-science.ru/hacking/2015/04/11/log-with-pleasure---https://rocket-science.ru/hacking/2015/04/11/log-with-pleasure</guid>
</item>
<item>
  <title>Erlang/OTP 29.0 Release Candidate 1</title>
  <description>From Erlang/OTP | News: OTP 29.0-rc1</description>
  <pubDate>2026-02-11T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/183</link>
  <guid>https://www.erlang.org/news/183---https://www.erlang.org/news/183</guid>
</item>
<item>
  <title>Elixir Structs on Steroids</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-02-11T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/02/11/structs-on-steroids</link>
  <guid>https://rocket-science.ru/hacking/2019/02/11/structs-on-steroids---https://rocket-science.ru/hacking/2019/02/11/structs-on-steroids</guid>
</item>
<item>
  <title>Match or Not Die</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-11T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/02/11/match-or-not-die</link>
  <guid>https://rocket-science.ru/hacking/2013/02/11/match-or-not-die---https://rocket-science.ru/hacking/2013/02/11/match-or-not-die</guid>
</item>
<item>
  <title>Traceroute to Episode IV</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-11T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2013/02/11/traceroute-to-episode-iv</link>
  <guid>https://rocket-science.ru/fun/2013/02/11/traceroute-to-episode-iv---https://rocket-science.ru/fun/2013/02/11/traceroute-to-episode-iv</guid>
</item>
<item>
  <title>Oll Korrect Cartoon</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-11T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2013/02/11/all-correct-cartoon</link>
  <guid>https://rocket-science.ru/fun/2013/02/11/all-correct-cartoon---https://rocket-science.ru/fun/2013/02/11/all-correct-cartoon</guid>
</item>
<item>
  <title>Soft deletion with Ecto</title>
  <description>From keathley.github.io: A common need in web applications is to “undo” a deletion event. This is referred to as a soft-deletion. The record still exists but its hidden from the user. Soft deleting allows the user to restore that data in the event that they need it in the future.</description>
  <pubDate>2019-01-10T04:07:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/soft-deletion-with-ecto.html</link>
  <guid>http://keathley.github.io/blog/soft-deletion-with-ecto.html---http://keathley.github.io/blog/soft-deletion-with-ecto</guid>
</item>
<item>
  <title>Erlang/OTP 28.3 Release</title>
  <description>From Erlang/OTP | News: OTP 28.3</description>
  <pubDate>2025-12-10T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/182</link>
  <guid>https://www.erlang.org/news/182---https://www.erlang.org/news/182</guid>
</item>
<item>
  <title>How does Kamal deploy to multiple hosts</title>
  <description>From Notes to self: How does Kamal deploy to multiple hosts at once? And how to configure it?</description>
  <pubDate>2024-12-10T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/how-kamal-deploy-multiple-servers/</link>
  <guid>https://nts.strzibny.name/how-kamal-deploy-multiple-servers/---https://nts.strzibny.name/how-kamal-deploys-to-multiple-servers</guid>
</item>
<item>
  <title>Adopting Property Testing in Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-10-10T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/10/10/adopting-property-testing-in-elixir</link>
  <guid>https://rocket-science.ru/hacking/2018/10/10/adopting-property-testing-in-elixir---https://rocket-science.ru/hacking/2018/10/10/adopting-property-testing-in-elixir</guid>
</item>
<item>
  <title>Avoiding environment conflicts with Kamal and Dotenv</title>
  <description>From Notes to self: We often use Dotenv in Rails for managing environment variables in development. But both Kamal and Dotenv works with .env files by default. Let’s see how to solve this conflict.</description>
  <pubDate>2024-09-10T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/env-conflict-kamal-dotenv/</link>
  <guid>https://nts.strzibny.name/env-conflict-kamal-dotenv/---https://nts.strzibny.name/avoiding-environment-conflicts-in-kamal-and-dotenv</guid>
</item>
<item>
  <title>Hype Demythified or Pizza With Pineapple Topping</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-09-10T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/09/10/pizza-with-ananas-topping</link>
  <guid>https://rocket-science.ru/hacking/2018/09/10/pizza-with-ananas-topping---https://rocket-science.ru/hacking/2018/09/10/pizza-with-ananas-topping</guid>
</item>
<item>
  <title>Introduction to Minitest Mocks</title>
  <description>From Notes to self: Test doubles likes mocks and stubs can help us with isolating code under test with the rest of the system. Here’s how to mock in Minitest.</description>
  <pubDate>2024-08-10T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/minitest-mocks/</link>
  <guid>https://nts.strzibny.name/minitest-mocks/---https://nts.strzibny.name/mocks-in-minitest</guid>
</item>
<item>
  <title>Why Am I Not To IDE</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2021-08-10T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2021/08/10/why-am-i-not-to-ide</link>
  <guid>https://rocket-science.ru/hacking/2021/08/10/why-am-i-not-to-ide---https://rocket-science.ru/hacking/2021/08/10/why-am-i-not-to-ide</guid>
</item>
<item>
  <title>Upgrading from Kamal 1 to Kamal 2</title>
  <description>From Notes to self: Here’s some possible steps to take and notes from upgrading a single server Kamal setup to the new Kamal 2.</description>
  <pubDate>2024-07-10T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/upgrading-to-kamal-2/</link>
  <guid>https://nts.strzibny.name/upgrading-to-kamal-2/---https://nts.strzibny.name/upgrading-from-kamal-1-to-kamal-2</guid>
</item>
<item>
  <title>How much memory is needed to run 1M Erlang processes?</title>
  <description>From Hauleth's blog: How to not write benchmarks</description>
  <pubDate>2023-06-10T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/beam-process-memory-usage/</link>
  <guid>https://hauleth.dev/post/beam-process-memory-usage/---https://hauleth.dev/post/beam-process-memory-usage/</guid>
</item>
<item>
  <title>Predownloading embedding models in Rails with Kamal</title>
  <description>From Notes to self: If you are building AI-powered applications in Ruby on Rails, you might have come across Informers or Transformers.rb gems for transformer inference. Here’s how to improve the production deployment of their models in Kamal setups.</description>
  <pubDate>2025-03-10T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/predownload-embed-models-kamal/</link>
  <guid>https://nts.strzibny.name/predownload-embed-models-kamal/---https://nts.strzibny.name/predownloading-embedding-models-in-rails-with-kamal</guid>
</item>
<item>
  <title>Review of the Alcatel GO Flip 2 and KaiOS</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I&amp;#8217;ve posted several off-topic blog posts this year and this is another one. Perhaps it is more relevant than the others as I list the issues with KaiOS software and the software limitations of my feature phone.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I&amp;#8217;ve been using an Alcatel Go Flip 2 as my only phone for about a year and a half now. I feel like leaving my smartphone behind was the right choice and feel like my life is better as a result. I&amp;#8217;ve published two previous articles on my move from a high end smartphone to a cheap feature phone. In this blog post I review the hardware and software I now use on my feature phone.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2021-07-09T19:05:23-04:00</pubDate>
  <link>https://stratus3d.com/blog/2021/07/09/review-of-the-alcatel-go-flip-2-and-kaios/</link>
  <guid>https://stratus3d.com/blog/2021/07/09/review-of-the-alcatel-go-flip-2-and-kaios/---https://stratus3d.com/blog/2021/07/09/review-of-the-alcatel-go-flip-2-and-kaios</guid>
</item>
<item>
  <title>Runtime Configuration in Elixir Apps</title>
  <description>From keathley.github.io: I gave a talk last year about how to properly boot elixir applications. In the talk, I showed how to load configuration values into an ETS table on boot, and this was the same pattern that I used initially in Vapor. I now think that this is a bad idea.</description>
  <pubDate>2020-01-09T08:53:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/vapor-and-configuration.html</link>
  <guid>http://keathley.github.io/blog/vapor-and-configuration.html---http://keathley.github.io/blog/vapor-and-configuration</guid>
</item>
<item>
  <title>StackOverflow questions ⇒ Diverse Worlds</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-12-09T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2013/12/09/stackoverflow-questions-diverse-worlds</link>
  <guid>https://rocket-science.ru/fun/2013/12/09/stackoverflow-questions-diverse-worlds---https://rocket-science.ru/fun/2013/12/09/stackoverflow-questions--diverse-worlds</guid>
</item>
<item>
  <title>Divide et Impera</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-11-09T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/11/09/divide-et-impera</link>
  <guid>https://rocket-science.ru/hacking/2023/11/09/divide-et-impera---https://rocket-science.ru/hacking/2023/11/09/divide-et-impera</guid>
</item>
<item>
  <title>Use Hooks in Riak to Create Views</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-09-09T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/09/09/riak-hooks-for-quick-views</link>
  <guid>https://rocket-science.ru/hacking/2016/09/09/riak-hooks-for-quick-views---https://rocket-science.ru/hacking/2016/09/09/riak-hooks-for-quick-views</guid>
</item>
<item>
  <title>Long-lived Process and State Recovery After Crashes</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2025-03-09T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2025/03/09/long-lived-stateful-process</link>
  <guid>https://rocket-science.ru/hacking/2025/03/09/long-lived-stateful-process---https://rocket-science.ru/hacking/2025/03/09/long-lived-stateful-process</guid>
</item>
<item>
  <title>New Hash Syntax for the Rescue</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2016-03-09T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2016/03/09/new-hash-syntax-for-the-rescue</link>
  <guid>https://rocket-science.ru/hacking/2016/03/09/new-hash-syntax-for-the-rescue---https://rocket-science.ru/hacking/2016/03/09/new-hash-syntax-for-the-rescue</guid>
</item>
<item>
  <title>Delightful Logging</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-09T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/02/09/delightful-logging</link>
  <guid>https://rocket-science.ru/hacking/2013/02/09/delightful-logging---https://rocket-science.ru/hacking/2013/02/09/delightful-logging</guid>
</item>
<item>
  <title>Auto-saving Rails forms with Turbo Streams</title>
  <description>From Notes to self: Here’s how to implement autosaving for inline input fields the Hotwire way.</description>
  <pubDate>2025-01-09T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/rails-autosave-form-turbo-stream/</link>
  <guid>https://nts.strzibny.name/rails-autosave-form-turbo-stream/---https://nts.strzibny.name/auto-saving-forms-with-turbo-streams</guid>
</item>
<item>
  <title>The Best Book on Code Reviews</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://smartbear.com/resources/ebooks/best-kept-secrets-of-code-review/"&gt;Best Kept Secrets of Code Review&lt;/a&gt; by SmartBear software has influenced how I create pull requests and perform code reviews more than any other book I have read. I read it back in 2015 and a lot of the findings from the studies in the book have stuck with me.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-06-08T11:34:16-04:00</pubDate>
  <link>https://stratus3d.com/blog/2022/06/08/the-best-book-on-code-reviews/</link>
  <guid>https://stratus3d.com/blog/2022/06/08/the-best-book-on-code-reviews/---https://stratus3d.com/blog/2022/06/08/the-best-book-on-code-reviews</guid>
</item>
<item>
  <title>Reusable Elixir Libraries</title>
  <description>From keathley.github.io: One of my new goals is to try to make my elixir libraries more reusable. It’s an easy mark to hit if you only use modules and functions. But once you start adding processes, ETS tables, and other stateful constructs, the solutions get murky.</description>
  <pubDate>2020-02-08T10:00:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/reusable-libraries.html</link>
  <guid>http://keathley.github.io/blog/reusable-libraries.html---http://keathley.github.io/blog/reusable-libraries</guid>
</item>
<item>
  <title>Reverse Engineering for Poor</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-05-08T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/05/08/reverse-engineering-for-poor</link>
  <guid>https://rocket-science.ru/hacking/2020/05/08/reverse-engineering-for-poor---https://rocket-science.ru/hacking/2020/05/08/reverse-engineering-for-poor</guid>
</item>
<item>
  <title>Bus Factor as Seen by the Bus Driver</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2026-04-08T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2026/04/08/bus-factor-as-seen-by-bus-driver</link>
  <guid>https://rocket-science.ru/hacking/2026/04/08/bus-factor-as-seen-by-bus-driver---https://rocket-science.ru/hacking/2026/04/08/bus-factor-as-seen-by-bus-driver</guid>
</item>
<item>
  <title>Myths of Backward Compatibility</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2026-04-08T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2026/04/08/back-compatibility-myths</link>
  <guid>https://rocket-science.ru/hacking/2026/04/08/back-compatibility-myths---https://rocket-science.ru/hacking/2026/04/08/back-compatibility-myths</guid>
</item>
<item>
  <title>Hack vs Kludge</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-03-08T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/03/08/hack-vs-kludge</link>
  <guid>https://rocket-science.ru/hacking/2017/03/08/hack-vs-kludge---https://rocket-science.ru/hacking/2017/03/08/hack-vs-kludge</guid>
</item>
<item>
  <title>Automate pattern matching for structs</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-01-08T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/01/08/automate-struct-pattern-matching</link>
  <guid>https://rocket-science.ru/hacking/2018/01/08/automate-struct-pattern-matching---https://rocket-science.ru/hacking/2018/01/08/automate-struct-pattern-matching</guid>
</item>
<item>
  <title>Protocols Are Powerful but Beware</title>
  <description>From Timmo's Website: Elixir protocols are powerful for achieving polymorphism. But with great power comes (you've guessed it) great responsibility. I want to share my perspective on this.</description>
  <pubDate>2020-11-07T00:00:00+01:00</pubDate>
  <link>https://timmo.immo/blog/protocols-beware</link>
  <guid>https://timmo.immo/blog/protocols-beware---https://timmo.immo/blog/protocols-beware</guid>
</item>
<item>
  <title>Running multiple apps on a single server with Kamal 2</title>
  <description>From Notes to self: Kamal 2 finally brings the most requested feature to reality and allows people to run multiple applications simultaneously on a single server. Here’s how.</description>
  <pubDate>2024-10-07T00:00:00+00:00</pubDate>
  <link>https://nts.strzibny.name/multiple-apps-single-server-kamal-2/</link>
  <guid>https://nts.strzibny.name/multiple-apps-single-server-kamal-2/---https://nts.strzibny.name/running-multiple-apps-on-a-single-server-with-kamal-2</guid>
</item>
<item>
  <title>Md. Another Word About Markdown</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-08-07T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/08/07/md-under-the-hood</link>
  <guid>https://rocket-science.ru/hacking/2022/08/07/md-under-the-hood---https://rocket-science.ru/hacking/2022/08/07/md-under-the-hood</guid>
</item>
<item>
  <title>Handling failures in background workers with Elixir and supervisors</title>
  <description>From Patryk Bak - a programming blog: Elixir is built on the top of the Erlang Virtual Machine. It allows us to write highly available systems that can run practically forever. Does that mean that we don’t have to do anything to make our systems reliable?</description>
  <pubDate>2020-07-07T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2020/07/07/handling-failures-in-background-workers-with-elixir-and-supervisors.html</link>
  <guid>https://patrykbak.com/2020/07/07/handling-failures-in-background-workers-with-elixir-and-supervisors.html---https://patrykbak.com/2020/07/07/handling-failures-in-background-workers-with-elixir-and-supervisors</guid>
</item>
<item>
  <title>Cloister</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-05-07T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/05/07/cloister</link>
  <guid>https://rocket-science.ru/hacking/2020/05/07/cloister---https://rocket-science.ru/hacking/2020/05/07/cloister</guid>
</item>
<item>
  <title>The Diagnosis ‘SLOP’ Is the New Ad Hominem</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2026-04-07T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2026/04/07/slop-is-the-new-ad-hominem</link>
  <guid>https://rocket-science.ru/hacking/2026/04/07/slop-is-the-new-ad-hominem---https://rocket-science.ru/hacking/2026/04/07/slop-is-the-new-ad-hominem</guid>
</item>
<item>
  <title>Not God but Man Makes Pot and Pan</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-02-07T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/02/07/not-god-but-man-makes-pot-and-pan</link>
  <guid>https://rocket-science.ru/hacking/2020/02/07/not-god-but-man-makes-pot-and-pan---https://rocket-science.ru/hacking/2020/02/07/not-god-but-man-makes-pot-and-pan</guid>
</item>
<item>
  <title>Command line application wrappers problem</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-11-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/11/06/command-line-application-wrappers-problem</link>
  <guid>https://rocket-science.ru/hacking/2013/11/06/command-line-application-wrappers-problem---https://rocket-science.ru/hacking/2013/11/06/command-line-application-wrappers-problem</guid>
</item>
<item>
  <title>Formulæ and Lazy Combinators</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-09-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/09/06/formulae-and-lazy-combinators</link>
  <guid>https://rocket-science.ru/hacking/2019/09/06/formulae-and-lazy-combinators---https://rocket-science.ru/hacking/2019/09/06/formulae-and-lazy-combinators</guid>
</item>
<item>
  <title>Md — еще раз о маркдауне</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-08-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/08/06/md-under-the-hood</link>
  <guid>https://rocket-science.ru/hacking/2022/08/06/md-under-the-hood---https://rocket-science.ru/hacking/2022/08/06/md-under-the-hood</guid>
</item>
<item>
  <title>Vela → Time Series Cache</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-07-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/07/06/vela-time-series-cache</link>
  <guid>https://rocket-science.ru/hacking/2020/07/06/vela-time-series-cache---https://rocket-science.ru/hacking/2020/07/06/vela-time-series-cache</guid>
</item>
<item>
  <title>12 things recruiters look for during interviews with junior devs (which you’re probably NOT doing)</title>
  <description>From Patryk Bak - a programming blog: Don’t be another applicant who doesn’t stand out from the hundreds of candidates.</description>
  <pubDate>2020-06-06T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2020/06/06/how-to-do-well-during-a-recruitment-process-and-a-job-interview-for-a-junior-developer-position.html</link>
  <guid>https://patrykbak.com/2020/06/06/how-to-do-well-during-a-recruitment-process-and-a-job-interview-for-a-junior-developer-position.html---https://patrykbak.com/2020/06/06/how-to-do-well-during-a-recruitment-process-and-a-job-interview-for-a-junior-developer-position</guid>
</item>
<item>
  <title>How Many Paradigms Does It Take to Screw In a Lightbulb?</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2026-04-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2026/04/06/paradigms-for-lightbulb</link>
  <guid>https://rocket-science.ru/hacking/2026/04/06/paradigms-for-lightbulb---https://rocket-science.ru/hacking/2026/04/06/paradigms-for-lightbulb</guid>
</item>
<item>
  <title>How Does Clean Code Work?</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2026-04-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2026/04/06/how-does-clean-code-work</link>
  <guid>https://rocket-science.ru/hacking/2026/04/06/how-does-clean-code-work---https://rocket-science.ru/hacking/2026/04/06/how-does-clean-code-work</guid>
</item>
<item>
  <title>Elixir Iteraptor :: Iterating Nested Terms Like I’m Five</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-04-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/04/06/iteraptor-for-elixir</link>
  <guid>https://rocket-science.ru/hacking/2018/04/06/iteraptor-for-elixir---https://rocket-science.ru/hacking/2018/04/06/iteraptor-for-elixir</guid>
</item>
<item>
  <title>ABC for Fluent Speakers (NM Level)</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-04-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/fun/2013/04/06/abc-for-fluent-speakers-nm-level</link>
  <guid>https://rocket-science.ru/fun/2013/04/06/abc-for-fluent-speakers-nm-level---https://rocket-science.ru/fun/2013/04/06/abc-for-fluent-speakers-nm-level</guid>
</item>
<item>
  <title>Finitomata ❤ Mox</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-03-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/03/06/finitomata-mox-testing</link>
  <guid>https://rocket-science.ru/hacking/2023/03/06/finitomata-mox-testing---https://rocket-science.ru/hacking/2023/03/06/finitomata-mox-testing</guid>
</item>
<item>
  <title>Beware of YAGNI</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-03-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/tips/2018/03/06/beware-of-yagni</link>
  <guid>https://rocket-science.ru/tips/2018/03/06/beware-of-yagni---https://rocket-science.ru/tips/2018/03/06/beware-of-yagni</guid>
</item>
<item>
  <title>Ruby 2.0 Refinements: Totally Useless Crap</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-03-06T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/03/06/ruby-20-refinements-totally-useless-crap</link>
  <guid>https://rocket-science.ru/hacking/2013/03/06/ruby-20-refinements-totally-useless-crap---https://rocket-science.ru/hacking/2013/03/06/ruby-20-refinements-totally-useless-crap</guid>
</item>
<item>
  <title>Introduction to Phoenix LiveView LiveComponents</title>
  <description>From Patrick Thompson: </description>
  <pubDate>2019-12-05T18:00:43-08:00</pubDate>
  <link>http://blog.pthompson.org/liveview-livecomponents-introduction</link>
  <guid>http://blog.pthompson.org/liveview-livecomponents-introduction---tag:blog.pthompson.org,2014:Post/liveview-livecomponents-introduction</guid>
</item>
<item>
  <title>Reserved Backward Compatibility</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-12-05T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/12/05/reserved-backward-compatibility</link>
  <guid>https://rocket-science.ru/hacking/2020/12/05/reserved-backward-compatibility---https://rocket-science.ru/hacking/2020/12/05/reserved-backward-compatibility</guid>
</item>
<item>
  <title>¡AST FTW!</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-11-05T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/11/05/AST-FTW</link>
  <guid>https://rocket-science.ru/hacking/2018/11/05/AST-FTW---https://rocket-science.ru/hacking/2018/11/05/AST-FTW</guid>
</item>
<item>
  <title>Sequentional execution: example of Reactor pattern impl</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-09-05T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/09/05/sequentional-execution-example-of-reactor-pattern-impl</link>
  <guid>https://rocket-science.ru/hacking/2013/09/05/sequentional-execution-example-of-reactor-pattern-impl---https://rocket-science.ru/hacking/2013/09/05/sequentional-execution-example-of-reactor-pattern-impl</guid>
</item>
<item>
  <title>Erlang/OTP 28.0 Release Candidate 4</title>
  <description>From Erlang/OTP | News: OTP 28.0-rc4</description>
  <pubDate>2025-05-05T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/179</link>
  <guid>https://www.erlang.org/news/179---https://www.erlang.org/news/179</guid>
</item>
<item>
  <title>Make Hash Element Access Painless</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-05T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/02/05/make-hash-element-access-painless</link>
  <guid>https://rocket-science.ru/hacking/2013/02/05/make-hash-element-access-painless---https://rocket-science.ru/hacking/2013/02/05/make-hash-element-access-painless</guid>
</item>
<item>
  <title>A War Story - From Failure to Success</title>
  <description>From Timmo's Website: Developing a communication platform is a perfect fit for the BEAM. Learning its characteristics requires users and a significant load. We developed for three years before we added users.</description>
  <pubDate>2021-01-05T00:00:00+01:00</pubDate>
  <link>https://timmo.immo/blog/a-war-story</link>
  <guid>https://timmo.immo/blog/a-war-story---https://timmo.immo/blog/a-war-story</guid>
</item>
<item>
  <title>Running Dialyzer for Elixir Projects in GitHub Actions</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I recently published an article on the Code for RentPath blog about running Dialyzer with GitHub Actions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="quoteblock"&gt;
&lt;blockquote&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;In this blog post I&amp;#8217;ll show you how to set up GitHub Actions to perform type analysis on an Elixir project with &lt;a href="https://erlang.org/doc/man/dialyzer.html"&gt;Dialyzer&lt;/a&gt;. I&amp;#8217;ll also share optimal settings for Dialyzer PLT caching.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Running automated tests for every commit or pull request has become common practice and is a good way to validate the correctness of your Erlang or Elixir software. Type checking with Dialyzer is another way to ensure the quality of your code changes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I have previously written about &lt;a href="http://stratus3d.com/blog/2020/05/01/running-dialyzer-in-jenkins-builds/"&gt;how to run Dialyzer in a Jenkins build&lt;/a&gt; and here I&amp;#8217;ll share how to do the same with a GitHub workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Read the rest of the article on &lt;a href="https://blog.rentpathcode.com/running-dialyzer-for-elixir-projects-in-github-actions-e0594220b272"&gt;blog.rentpathcode.com&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2021-05-04T21:31:32-04:00</pubDate>
  <link>https://stratus3d.com/blog/2021/05/04/running-dialyzer-for-elixir-projects-in-github-actions/</link>
  <guid>https://stratus3d.com/blog/2021/05/04/running-dialyzer-for-elixir-projects-in-github-actions/---https://stratus3d.com/blog/2021/05/04/running-dialyzer-for-elixir-projects-in-github-actions</guid>
</item>
<item>
  <title>Writing Vim Plugin</title>
  <description>From Hauleth's blog: Article about writing Vim plugins, but not about writing Vim plugins. It is
how to conceive plugin, how to go from an idea to the full fledged plugin.</description>
  <pubDate>2019-11-04T18:21:18+01:00</pubDate>
  <link>https://hauleth.dev/post/writing-vim-plugin/</link>
  <guid>https://hauleth.dev/post/writing-vim-plugin/---https://hauleth.dev/post/writing-vim-plugin/</guid>
</item>
<item>
  <title>No Failures Despite Bugs</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-11-04T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/11/04/no-failures-despite-bugs</link>
  <guid>https://rocket-science.ru/hacking/2023/11/04/no-failures-despite-bugs---https://rocket-science.ru/hacking/2023/11/04/no-failures-despite-bugs</guid>
</item>
<item>
  <title>Debug inplace</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-11-04T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/11/04/debug-inplace</link>
  <guid>https://rocket-science.ru/hacking/2013/11/04/debug-inplace---https://rocket-science.ru/hacking/2013/11/04/debug-inplace</guid>
</item>
<item>
  <title>O tempora, o mores!</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-09-04T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/09/04/o-tempora-o-mores</link>
  <guid>https://rocket-science.ru/hacking/2020/09/04/o-tempora-o-mores---https://rocket-science.ru/hacking/2020/09/04/o-tempora-o-mores</guid>
</item>
<item>
  <title>Wolf, Goat, Cabbage… and Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2020-08-04T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2020/08/04/wolf-goat-cabbage-and-elixir</link>
  <guid>https://rocket-science.ru/hacking/2020/08/04/wolf-goat-cabbage-and-elixir---https://rocket-science.ru/hacking/2020/08/04/wolf-goat-cabbage-and-elixir</guid>
</item>
<item>
  <title>YADR for Dummies</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-04-04T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/shell/2013/04/04/yadr-for-dummies</link>
  <guid>https://rocket-science.ru/shell/2013/04/04/yadr-for-dummies---https://rocket-science.ru/shell/2013/04/04/yadr-for-dummies</guid>
</item>
<item>
  <title>Erlang/OTP 28.4 Release</title>
  <description>From Erlang/OTP | News: OTP 28.4</description>
  <pubDate>2026-03-04T00:00:00+00:00</pubDate>
  <link>https://www.erlang.org/news/184</link>
  <guid>https://www.erlang.org/news/184---https://www.erlang.org/news/184</guid>
</item>
<item>
  <title>Ruby Logger :: Temporary Enable Debug for One Class</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-04T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/02/04/ruby-logger-temporary-enable-debug-for-one-class</link>
  <guid>https://rocket-science.ru/hacking/2013/02/04/ruby-logger-temporary-enable-debug-for-one-class---https://rocket-science.ru/hacking/2013/02/04/ruby-logger-temporary-enable-debug-for-one-class</guid>
</item>
<item>
  <title>Asdf Has Been Re-Written in Golang</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;Over the last year I&amp;#8217;ve rewritten asdf in Go. I&amp;#8217;m excited to announce that &lt;strong&gt;the rewrite was released last Wednesday as asdf version 0.16.0!&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;asdf 0.16.0 is now a single binary rather than a collection of Bash scripts. All operations are faster than they were in the previous versions. With improvements ranging from 2x-7x faster than asdf version 0.15.0! Numerous long-standing bugs were also fixed during the rewrite. The codebase is also much more approachable for new contributors, and we can now leverage tools in the Go ecosystems to make future releases of asdf even better!&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2025-02-03T21:33:56-05:00</pubDate>
  <link>https://stratus3d.com/blog/2025/02/03/asdf-has-been-rewritten-in-go/</link>
  <guid>https://stratus3d.com/blog/2025/02/03/asdf-has-been-rewritten-in-go/---https://stratus3d.com/blog/2025/02/03/asdf-has-been-rewritten-in-go</guid>
</item>
<item>
  <title>For F**k Interface</title>
  <description>From Hauleth's blog: </description>
  <pubDate>2025-11-03T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/port-love/</link>
  <guid>https://hauleth.dev/post/port-love/---https://hauleth.dev/post/port-love/</guid>
</item>
<item>
  <title>Software Development in 2023</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-11-03T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/11/03/software-development-in-2023</link>
  <guid>https://rocket-science.ru/hacking/2023/11/03/software-development-in-2023---https://rocket-science.ru/hacking/2023/11/03/software-development-in-2023</guid>
</item>
<item>
  <title>Tarearbol now allows subscriptions to task results</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-08-03T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/08/03/tarearbol-now-allows-subscriptions</link>
  <guid>https://rocket-science.ru/hacking/2018/08/03/tarearbol-now-allows-subscriptions---https://rocket-science.ru/hacking/2018/08/03/tarearbol-now-allows-subscriptions</guid>
</item>
<item>
  <title>Finitomata FTW</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2024-03-03T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2024/03/03/finitomata-for-the-win</link>
  <guid>https://rocket-science.ru/hacking/2024/03/03/finitomata-for-the-win---https://rocket-science.ru/hacking/2024/03/03/finitomata-for-the-win</guid>
</item>
<item>
  <title>Don’t be a language slave</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-03-03T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/03/03/dont-be-a-language-slave</link>
  <guid>https://rocket-science.ru/hacking/2015/03/03/dont-be-a-language-slave---https://rocket-science.ru/hacking/2015/03/03/dont-be-a-language-slave</guid>
</item>
<item>
  <title>Ruby Shorthand to Yield Within Blocks</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-03-03T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/03/03/ruby-shorthand-to-yield-within-blocks</link>
  <guid>https://rocket-science.ru/hacking/2013/03/03/ruby-shorthand-to-yield-within-blocks---https://rocket-science.ru/hacking/2013/03/03/ruby-shorthand-to-yield-within-blocks</guid>
</item>
<item>
  <title>Shorthands in Ruby Code Blocks</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-03T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/02/03/shorthands-in-ruby-code-blocks</link>
  <guid>https://rocket-science.ru/hacking/2013/02/03/shorthands-in-ruby-code-blocks---https://rocket-science.ru/hacking/2013/02/03/shorthands-in-ruby-code-blocks</guid>
</item>
<item>
  <title>Pattern matcher for Protocols</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-01-03T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/01/03/pattern-matcher-for-protocols</link>
  <guid>https://rocket-science.ru/hacking/2018/01/03/pattern-matcher-for-protocols---https://rocket-science.ru/hacking/2018/01/03/pattern-matcher-for-protocols</guid>
</item>
<item>
  <title>Reducing Eye Strain</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;Over the last couple years I&amp;#8217;ve experienced some symptoms of eye strain after a long day at the computer. I spend most of my day in front of the computer and I want my setup to be as healthy for my eyes as possible. I began reading what I could find on the topic. I wrote this post to document the things I learned and the changes I made as a result.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-05-02T20:31:07-04:00</pubDate>
  <link>https://stratus3d.com/blog/2022/05/02/reducing-eye-strain/</link>
  <guid>https://stratus3d.com/blog/2022/05/02/reducing-eye-strain/---https://stratus3d.com/blog/2022/05/02/reducing-eye-strain</guid>
</item>
<item>
  <title>Asdf Performance Improvements</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;asdf was
&lt;a href="https://stratus3d.com/blog/2025/02/03/asdf-has-been-rewritten-in-go/"&gt;rewritten from scratch in Go and released on January 29th&lt;/a&gt;.
No special attention was given to performance during the rewrite. The goal was
feature parity in a more maintainable language. In this post I&amp;#8217;m going to share
some benchmarks I ran across many versions of asdf and show how the Go rewrite
improved performance.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2025-05-02T14:15:30-04:00</pubDate>
  <link>https://stratus3d.com/blog/2025/05/02/asdf-performance-improvements/</link>
  <guid>https://stratus3d.com/blog/2025/05/02/asdf-performance-improvements/---https://stratus3d.com/blog/2025/05/02/asdf-performance-improvements</guid>
</item>
<item>
  <title>Good and Bad Elixir</title>
  <description>From keathley.github.io: I’ve seen a lot of elixir at this point, both good and bad. Through all of that code, I’ve seen similar patterns that tend to lead to worse code. So I thought I would document some of them as well as better alternatives to these patterns.</description>
  <pubDate>2021-06-02T10:00:00+00:00</pubDate>
  <link>http://keathley.github.io/blog/good-and-bad-elixir.html</link>
  <guid>http://keathley.github.io/blog/good-and-bad-elixir.html---http://keathley.github.io/blog/good-and-bad-elixir</guid>
</item>
<item>
  <title>Smart Validation In Elixir With Exvalibur</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2018-11-02T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2018/11/02/smart-validation-in-elixir-with-exvalibur</link>
  <guid>https://rocket-science.ru/hacking/2018/11/02/smart-validation-in-elixir-with-exvalibur---https://rocket-science.ru/hacking/2018/11/02/smart-validation-in-elixir-with-exvalibur</guid>
</item>
<item>
  <title>Pry :: breakpoint or ARGF?</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2015-09-02T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2015/09/02/pry-and-stdin</link>
  <guid>https://rocket-science.ru/hacking/2015/09/02/pry-and-stdin---https://rocket-science.ru/hacking/2015/09/02/pry-and-stdin</guid>
</item>
<item>
  <title>Parser for Markdown Family</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-05-02T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/05/02/md-customizable-parser</link>
  <guid>https://rocket-science.ru/hacking/2022/05/02/md-customizable-parser---https://rocket-science.ru/hacking/2022/05/02/md-customizable-parser</guid>
</item>
<item>
  <title>Finitomata :: The Proper FSM for Elixir</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2022-04-02T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2022/04/02/finitomata</link>
  <guid>https://rocket-science.ru/hacking/2022/04/02/finitomata---https://rocket-science.ru/hacking/2022/04/02/finitomata</guid>
</item>
<item>
  <title>Delete File After Sending in Phoenix</title>
  <description>From Timmo's Website: Sometimes you want to delete a file after sending it with Phoenix (or any other webframework). How can you do that reliably?</description>
  <pubDate>2023-03-02T00:00:00+01:00</pubDate>
  <link>https://timmo.immo/blog/delete-file-after-sending</link>
  <guid>https://timmo.immo/blog/delete-file-after-sending---https://timmo.immo/blog/delete-file-after-sending</guid>
</item>
<item>
  <title>Multiple Match in Ruby</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2013-02-02T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2013/02/02/multiple-match-in-ruby</link>
  <guid>https://rocket-science.ru/hacking/2013/02/02/multiple-match-in-ruby---https://rocket-science.ru/hacking/2013/02/02/multiple-match-in-ruby</guid>
</item>
<item>
  <title>Log all the things</title>
  <description>From Hauleth's blog: logger features. Here I will try to describe
new possibilities and how You can use them to improve your logs.</description>
  <pubDate>2022-01-02T00:00:00+00:00</pubDate>
  <link>https://hauleth.dev/post/log-all-the-things/</link>
  <guid>https://hauleth.dev/post/log-all-the-things/---https://hauleth.dev/post/log-all-the-things/</guid>
</item>
<item>
  <title>Asdf Has Been Re-Written in Golang</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;Over the last year I&amp;#8217;ve rewritten asdf in Go. I&amp;#8217;m excited to announce that &lt;strong&gt;the rewrite was released last Wednesday as asdf version 0.16.0!&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;asdf 0.16.0 is now a single binary rather than a collection of Bash scripts. All operations are faster than they were in the previous versions. With improvements ranging from 2x-7x faster than asdf version 0.15.0! Numerous long-standing bugs were also fixed during the rewrite. The codebase is also much more approachable for new contributors, and we can now leverage tools in the Go ecosystems to make future releases of asdf even better!&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2024-03-01T21:33:56-05:00</pubDate>
  <link>https://stratus3d.com/blog/2024/03/01/asdf-has-been-rewritten-in-go/</link>
  <guid>https://stratus3d.com/blog/2024/03/01/asdf-has-been-rewritten-in-go/---https://stratus3d.com/blog/2024/03/01/asdf-has-been-rewritten-in-go</guid>
</item>
<item>
  <title>Function/Variable Ambiguity in Elixir</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;Variable syntax is one of the big differences between Erlang and Elixir that I encountered when learning Elixir. Instead of having to start each variable name with an uppercase letter, a lowercase letter must be used. This change in syntax seems like an improvement - after all most mainstream programming languages require variables to start with a lowercase letter, and lowercase is generally easier to type. However, a deeper look at this syntax choice reveals some significant downsides that I want to present here.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-04-01T17:25:49-04:00</pubDate>
  <link>https://stratus3d.com/blog/2022/04/01/function-variable-ambiguity-in-elixir/</link>
  <guid>https://stratus3d.com/blog/2022/04/01/function-variable-ambiguity-in-elixir/---https://stratus3d.com/blog/2022/04/01/function-variable-ambiguity-in-elixir</guid>
</item>
<item>
  <title>Integrating Phoenix LiveView with JavaScript and AlpineJS</title>
  <description>From Patrick Thompson: </description>
  <pubDate>2020-07-01T16:00:57-07:00</pubDate>
  <link>http://blog.pthompson.org/alpine-js-and-liveview</link>
  <guid>http://blog.pthompson.org/alpine-js-and-liveview---tag:blog.pthompson.org,2014:Post/alpine-js-and-liveview</guid>
</item>
<item>
  <title>Understanding Vim Insert Completion</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;I&amp;#8217;ve been using Vim for years but Vim&amp;#8217;s built-in insert mode content completion
is something I only recently took the time to learn. In this blog post I&amp;#8217;m
going to share how I&amp;#8217;ve configured insert mode completion using only features
built into Vim 9.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://stratus3d.com/images/posts/vim-completion/vim-completion-menu.jpg" alt="Screenshot of vim insert completion menu open"&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>2025-12-01T09:15:08-05:00</pubDate>
  <link>https://stratus3d.com/blog/2025/12/01/understanding-vim-insert-completion/</link>
  <guid>https://stratus3d.com/blog/2025/12/01/understanding-vim-insert-completion/---https://stratus3d.com/blog/2025/12/01/understanding-vim-insert-completion</guid>
</item>
<item>
  <title>Review of the Kinesis Freestyle Edge Keyboard</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;In August I bought a Kinesis Freestyle Edge keyboard off of eBay. I&amp;#8217;ve been using it for about four months now, and I&amp;#8217;m typing this review on it. I&amp;#8217;d been wanting to try out a split keyboard for a long time but didn&amp;#8217;t want to spend a lot of money on one. Good quality split programmable keyboards start at around $200. The Freestyle Edge I found on eBay was new and cost only $80.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://stratus3d.com/images/posts/kinesis-freestyle/kinesis-freestyle-photo.jpg" alt="Photo of the Kinesis Freestyle Edge after 4 months of use"&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
  <pubDate>2024-01-01T09:15:55-05:00</pubDate>
  <link>https://stratus3d.com/blog/2024/01/01/review-of-the-kinesis-freestyle-edge-keyboard/</link>
  <guid>https://stratus3d.com/blog/2024/01/01/review-of-the-kinesis-freestyle-edge-keyboard/---https://stratus3d.com/blog/2024/01/01/review-of-the-kinesis-freestyle-edge-keyboard</guid>
</item>
<item>
  <title>The Problem With Elixir&amp;#8217;s &lt;code&gt;with&lt;/code&gt;</title>
  <description>From Stratus3D: &lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Summary: Return values from different expressions in a &lt;code&gt;with&lt;/code&gt; block can only be distinguished by the shape of the data returned. This creates ambiguity in &lt;code&gt;with&lt;/code&gt; 's &lt;code&gt;else&lt;/code&gt; clauses that make code harder to understand and more challenging to modify correctly.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;code&gt;with&lt;/code&gt; has existed in &lt;a href="https://elixir-lang.org/blog/2016/01/03/elixir-v1-2-0-released/"&gt;Elixir since version 1.2&lt;/a&gt; and is widely used. But there are downsides to this construct. In this blog post I&amp;#8217;ll explore the problems caused by use of &lt;code&gt;with&lt;/code&gt; in a codebase. I&amp;#8217;ll assume you know how &lt;code&gt;with&lt;/code&gt; works and have &lt;a href="https://elixir-lang.org/getting-started/mix-otp/docs-tests-and-with.html#with"&gt;read the docs&lt;/a&gt; so I will skip over the basics of how it works.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-06-01T07:47:46-04:00</pubDate>
  <link>https://stratus3d.com/blog/2022/06/01/the-problem-with-elixirs-with/</link>
  <guid>https://stratus3d.com/blog/2022/06/01/the-problem-with-elixirs-with/---https://stratus3d.com/blog/2022/06/01/the-problem-with-elixirs-with</guid>
</item>
<item>
  <title>Unveil Erlang Code of Your Elixir Project</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2017-09-01T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2017/09/01/unveil_erlang_code_of_your_elixir_project</link>
  <guid>https://rocket-science.ru/hacking/2017/09/01/unveil_erlang_code_of_your_elixir_project---https://rocket-science.ru/hacking/2017/09/01/unveil_erlang_code_of_your_elixir_project</guid>
</item>
<item>
  <title>Pattern Matching Empty MapSets</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2019-03-01T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2019/03/01/pattern-matching-mapset</link>
  <guid>https://rocket-science.ru/hacking/2019/03/01/pattern-matching-mapset---https://rocket-science.ru/hacking/2019/03/01/pattern-matching-mapset</guid>
</item>
<item>
  <title>10 things you, as a buddy, should know whenever a new developer joins your team</title>
  <description>From Patryk Bak - a programming blog: It’s a day like any other one. Focused on your work, you suddenly notice someone who has walked into the office and is standing confused. At this point, you realize that he’s probably a new developer who’s joining your team today.</description>
  <pubDate>2021-02-01T00:00:00+00:00</pubDate>
  <link>https://patrykbak.com/2021/02/01/10-things-a-buddy-should-know.html</link>
  <guid>https://patrykbak.com/2021/02/01/10-things-a-buddy-should-know.html---https://patrykbak.com/2021/02/01/10-things-a-buddy-should-know</guid>
</item>
<item>
  <title>Syntax Highlighting in PowerPoint</title>
  <description>From Stratus3D: &lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://stratus3d.com/images/posts/syntax-highlighting-powerpoint/powerpoint-screenshot.jpg" alt="screenshot of PowerPoint with code highlighting"&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Back in 2014 I wrote a blog post on &lt;a href="https://stratus3d.com/blog/2014/08/30/inserting_code_samples_into_powerpoint/"&gt;inserting code samples into PowerPoint slides with syntax highlighting&lt;/a&gt;. It&amp;#8217;s been about 8 years and things have changed. Using GitHub Gist no longer works for me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I&amp;#8217;ve found a better way to apply syntax highlighting to code for PowerPoint that also works for &lt;a href="https://stratus3d.com/blog/2015/08/21/inserting-code-samples-into-libreoffice-impress/"&gt;LibreOffice Impress&lt;/a&gt; with no differences. Both applications expect code in Rich Text Format. The only downside to this approach is you&amp;#8217;ll need to have Python and a Python package installed for this to work.&lt;/p&gt;
&lt;/div&gt;</description>
  <pubDate>2022-01-01T00:00:00-05:00</pubDate>
  <link>https://stratus3d.com/blog/2022/01/01/syntax-highlighting-in-powerpoint/</link>
  <guid>https://stratus3d.com/blog/2022/01/01/syntax-highlighting-in-powerpoint/---https://stratus3d.com/blog/2022/01/01/syntax-highlighting-in-powerpoint</guid>
</item>
<item>
  <title>Finitomata Marries Ecto</title>
  <description>From ❖ |&gt; 🌢: </description>
  <pubDate>2023-01-01T00:00:00+00:00</pubDate>
  <link>https://rocket-science.ru/hacking/2023/01/01/finitomata-marries-ecto</link>
  <guid>https://rocket-science.ru/hacking/2023/01/01/finitomata-marries-ecto---https://rocket-science.ru/hacking/2023/01/01/finitomata-marries-ecto</guid>
</item>
</channel>
</rss>
