<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Max</title>
    <description>The latest articles on Forem by Max (@klappradla).</description>
    <link>https://forem.com/klappradla</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F389196%2Fb1cb3421-79a4-44ea-8880-09975647b4e5.jpeg</url>
      <title>Forem: Max</title>
      <link>https://forem.com/klappradla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/klappradla"/>
    <language>en</language>
    <item>
      <title>Getting React on Rails to work with Turbo Streams</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Thu, 15 Jan 2026 09:30:54 +0000</pubDate>
      <link>https://forem.com/bitcrowd/getting-react-on-rails-to-work-with-turbo-streams-2c46</link>
      <guid>https://forem.com/bitcrowd/getting-react-on-rails-to-work-with-turbo-streams-2c46</guid>
      <description>&lt;p&gt;On a recent client project, redesigning a large &lt;a href="https://rubyonrails.org/" rel="noopener noreferrer"&gt;Rails&lt;/a&gt; app, we found ourselves having to get Rails, Hotwire's &lt;a href="https://turbo.hotwired.dev/" rel="noopener noreferrer"&gt;Turbo&lt;/a&gt; and &lt;a href="https://www.shakacode.com/react-on-rails/docs/" rel="noopener noreferrer"&gt;React on Rails&lt;/a&gt; to play together nicely. Each of the tools comes with its own amounts of &lt;em&gt;magic™&lt;/em&gt;, so integrating them turned out to be a bit of a juggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario
&lt;/h2&gt;

&lt;p&gt;On this project, we were implementing the new design for a particular page. At it's core, it shows two lists, each with a "Load More" button to asynchronously fetch and append more items to it. The first list is just a stack of items, the second one also includes a grouping of the items. Imagine it roughly like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65l240dz5eyawo54w6dg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65l240dz5eyawo54w6dg.png" alt="Mockup of a page with two lists, List A and List B, each with a " width="800" height="1125"&gt;&lt;/a&gt;" button below. List B is divided into groups Group A, Group B and Group C"/&amp;gt;&lt;/p&gt;

&lt;p&gt;In "List A", clicking "Load More" should append another batch of 5 items to the bottom of the list until the bitter end. For "List B", clicking "Load More" should also append another batch of 5 items, where some may still belong to "Group C" and others will form new groups, each with their respective group header. The list items are heterogeneous and contain complex markup with graphics, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination
&lt;/h2&gt;

&lt;p&gt;We decided to implement the pagination logic using &lt;a href="https://turbo.hotwired.dev/reference/streams" rel="noopener noreferrer"&gt;Turbo Streams&lt;/a&gt;, as they seem to be a good fit for our use case. They also integrate nicely into the Rails ecosystem without being too tightly coupled (e.g. to a specific version of Rails). From Rails 7 on, Turbo also replaced Rails' UJS (Unobtrusive JavaScript) functionality and is the recommended tool for sprinkling asynchrony and SPA (Single Page Application)-feelings. From the Turbo Stream docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Turbo Streams deliver page changes as fragments of HTML wrapped in &lt;code&gt;&amp;lt;turbo-stream&amp;gt;&lt;/code&gt; elements. Each stream element specifies an action together with a target ID to declare what should happen to the HTML inside it. These elements can be delivered to the browser synchronously as a classic HTTP response, or asynchronously […]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And further on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;They can be used to surgically update the DOM after a user action such as removing an element from a list without reloading the whole page […]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sounds like a match for what we are doing!&lt;/p&gt;

&lt;p&gt;By having our server respond with &lt;code&gt;turbo_stream&lt;/code&gt; templates for the pagination requests, we are able to use the same &lt;a href="https://viewcomponent.org/" rel="noopener noreferrer"&gt;View Components&lt;/a&gt; and partials we already have in place from the initial server-rendered page. This is important to us because we're already using our component library throughout the whole project. Reimplementing the same components in a different framework or language just for this single page would be unnecessary duplication, not to mention the constant risk of the two implementations diverging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;For the sake of simplicity, let's focus on the first list, "List A". The initially rendered page would have a markup like this for the list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;List A&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"list-a"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;abc-item/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;abc-item/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;abc-item/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;abc-item/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;abc-item/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"load-more-a"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;load-more-button/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-frame&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user clicks the "Load More" button, we want to retrieve the next batch of items, append them to the list and replace the button with one to request the next batch. If there is no next batch of items, we remove the "Load More button.&lt;/p&gt;

&lt;p&gt;In the controller taking care of the pagination request, we can render a response template of the format &lt;code&gt;turbo_stream&lt;/code&gt;. A simplified version could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;turbo_stream.append&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;list-a&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;render&lt;/span&gt;
&lt;span class="na"&gt;AbcItem.with_collection&lt;/span&gt;&lt;span class="err"&gt;(@&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;as:&lt;/span&gt; &lt;span class="na"&gt;:item&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;pagy.next&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt;
&lt;span class="na"&gt;turbo_stream.replace&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;load-more-a&lt;/span&gt;&lt;span class="err"&gt;',&lt;/span&gt; &lt;span class="na"&gt;method:&lt;/span&gt; &lt;span class="na"&gt;:morph&lt;/span&gt; &lt;span class="na"&gt;do&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;turbo-frame&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"load-more-a"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;render&lt;/span&gt; &lt;span class="na"&gt;LoadMoreButton.new&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="na"&gt;path:&lt;/span&gt; &lt;span class="err"&gt;'/',&lt;/span&gt; &lt;span class="na"&gt;params:&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;page:&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;pagy.next&lt;/span&gt; &lt;span class="err"&gt;})&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-frame&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;else&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%=&lt;/span&gt; &lt;span class="na"&gt;turbo_stream.remove&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;load-more-a&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the example is View Components and the &lt;a href="https://github.com/ddnexus/pagy" rel="noopener noreferrer"&gt;pagy&lt;/a&gt; pagination library. In the template, we're appending items to "List A" and replace the "Load More" button with a new one. If there aren't any items left the "Load More" button gets removed.&lt;/p&gt;

&lt;p&gt;The response from the server would then look roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;turbo-stream&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"append"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"list-a"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;abc-item/&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-stream&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;turbo-stream&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"remove"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"load-more-a"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/turbo-stream&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It appends one item to the list and removes the button to load more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;&amp;lt;abc-item/&amp;gt;&lt;/code&gt; element contains a React component, &lt;code&gt;MyComponent&lt;/code&gt; in its markup, rendered via React on Rails' &lt;code&gt;react_component&lt;/code&gt; helper. In the final HTML those React components appear as three elements each. Two script tags with context and props and one wrapper element for the rendered HTML of the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"js-react-on-rails-context"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"MyComponent-react-component-eaca5b56-0732-4674-b9da-f858c224d410"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- MyComponent markup --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"js-react-on-rails-component"&lt;/span&gt;
  &lt;span class="na"&gt;data-component-name=&lt;/span&gt;&lt;span class="s"&gt;"MyComponent"&lt;/span&gt;
  &lt;span class="na"&gt;data-trace=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;data-dom-id=&lt;/span&gt;&lt;span class="s"&gt;"MyComponent-react-component-eaca5b56-0732-4674-b9da-f858c224d410"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For any &lt;code&gt;&amp;lt;abc-item/&amp;gt;&lt;/code&gt; element added to the page via Turbo Streams, those three elements are also added to the DOM, but the wrapping &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; stayed empty: &lt;strong&gt;the component wasn't actually mounted!&lt;/strong&gt; 🙀&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"js-react-on-rails-context"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"MyComponent-react-component-eaca5b56-0732-4674-b9da-f858c224d410"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"js-react-on-rails-component"&lt;/span&gt;
  &lt;span class="na"&gt;data-component-name=&lt;/span&gt;&lt;span class="s"&gt;"MyComponent"&lt;/span&gt;
  &lt;span class="na"&gt;data-trace=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;data-dom-id=&lt;/span&gt;&lt;span class="s"&gt;"MyComponent-react-component-eaca5b56-0732-4674-b9da-f858c224d410"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turbo is injecting markup into the already fully rendered page and none of the DOM events React on Rails is hooking into to execute the JavaScript to mount React components on the page are being fired.&lt;/p&gt;

&lt;h3&gt;
  
  
  Options
&lt;/h3&gt;

&lt;p&gt;React on Rails already has &lt;a href="https://www.shakacode.com/react-on-rails/docs/building-features/turbolinks/#turbo-streams-requires-react-on-rails-pro" rel="noopener noreferrer"&gt;support for Turbo Streams&lt;/a&gt;, but only as part of their paid &lt;em&gt;pro&lt;/em&gt; plan. Going "pro" wasn't an option for the project as they are on a tight budget and only using small parts of React on Rails in isolated areas of their app. So we're not really qualifying for "pro" here…&lt;/p&gt;

&lt;p&gt;Server-side rendering the React components would also have been an option to tackle the problem. But this again was out of scope for our project and we did not want to run JavaScript on the server.&lt;/p&gt;

&lt;p&gt;So what now?&lt;/p&gt;

&lt;h2&gt;
  
  
  JS to the rescue
&lt;/h2&gt;

&lt;p&gt;With the help of some AI coding agents, we explored the internals of the &lt;a href="https://www.npmjs.com/package/react-on-rails" rel="noopener noreferrer"&gt;&lt;code&gt;react-on-rails&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@hotwired/turbo-rails" rel="noopener noreferrer"&gt;@hotwired/turbo-rails&lt;/a&gt; NPM package to see what was actually happening. The rough idea was that, if any events were emitted by Turbo when Stream elements were injected and rendered, we could hook into them and then manually call React on Rails to mount all the newly inserted components on the page.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Turbo&lt;/code&gt;, we found the &lt;a href="https://github.com/hotwired/turbo/blob/d069101e701f84369689d8fbd521d7a13ca971a0/src/elements/stream_element.js" rel="noopener noreferrer"&gt;&lt;code&gt;StreamElement&lt;/code&gt;&lt;/a&gt;. It has a &lt;code&gt;render&lt;/code&gt; function and a &lt;code&gt;target&lt;/code&gt; pointing to the ID of the DOM-element it's rendered to. Combining these two things, the plan was to extend the &lt;code&gt;render&lt;/code&gt; function to also take care of mounting any React elements within the updated target element. This means, when the &lt;code&gt;StreamElement&lt;/code&gt; contained any React components, we want to make sure they are correctly mounted &lt;strong&gt;after&lt;/strong&gt; the rendering of the &lt;code&gt;StreamElement&lt;/code&gt; itself was done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Gets the current target (an element ID) to which the result will
 * be rendered.
 */&lt;/span&gt;
&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;target&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we wrote our own tiny JS module to patch Turbo with some additional logic for our React on Rails integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// First we get hold of the original render function of Turbo's StreamElement&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Turbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StreamElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Then we override it to extend it with our own additional logic:&lt;/span&gt;
&lt;span class="nx"&gt;Turbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StreamElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// First we call the original render function&lt;/span&gt;
  &lt;span class="nx"&gt;originalRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Then we get hold of the target element&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Then we wait for the next frame, to make sure that the DOM is fully loaded&lt;/span&gt;
  &lt;span class="c1"&gt;// and we call a function to have React on Rails mount all components within&lt;/span&gt;
  &lt;span class="c1"&gt;// the newly inserted DOM snippet.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;mountReactOnRailsComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom &lt;code&gt;mountReactOnRailsComponents&lt;/code&gt; function is pretty much a manual re-implementation of what's happening when React on Rails is client-side rendering components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mountReactOnRailsComponents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;domNode&lt;/span&gt;
    &lt;span class="c1"&gt;// First we get all React on Rails components within our node&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script.js-react-on-rails-component&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Next we get the name of the component and the DOM ID of the placeholder&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;componentName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;domId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldHydrate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hydrate&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Then we put together the component props&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ReactOnRails props JSON parse error for&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Now we need the placeholder element&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// We check whether the component was already mounted&lt;/span&gt;
      &lt;span class="c1"&gt;// in this case we don't want to re-render it.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasMounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reactOnRailsMounted&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMounted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Then we call React on Rails' render function to mount the component&lt;/span&gt;
      &lt;span class="nx"&gt;ReactOnRails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;domId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shouldHydrate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Finally, we mark the placeholder as mounted, so that we don't accidentally&lt;/span&gt;
      &lt;span class="c1"&gt;// re-render it.&lt;/span&gt;
      &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reactOnRailsMounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, our Turbo Streams based pagination works like charm. Any React component newly added to the DOM is mounted as normal 🎉.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Drilling around like this in the internals of Turbo and React on Rails of course yields its risks. Now we're maintaining additional code which may break any time we update our Turbo or React on Rails dependences. But as we're only using the module in a small isolated area of the application, it's a risk we can accept for now. Our test coverage of the feature will inform us early enough.&lt;/p&gt;

&lt;p&gt;Using open source technologies, we have the chance to peak under the hood of the tools and can if necessary adapt them to our needs. And working with legacy codebases and legacy Rails apps, their may be situations with unconventional needs…&lt;/p&gt;

&lt;p&gt;We hope this post was helpful and inspires you to explore the details of the tools you're using the next time you hit a wall with them. Migrating to an entirely different tool is often a way bigger operation, especially in large monoliths.&lt;/p&gt;

</description>
      <category>redesign</category>
      <category>react</category>
      <category>rails</category>
      <category>turbo</category>
    </item>
    <item>
      <title>Loading Chrome extensions for development in 2025</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 03 Dec 2025 08:25:24 +0000</pubDate>
      <link>https://forem.com/bitcrowd/loading-chrome-extensions-for-development-in-2025-1039</link>
      <guid>https://forem.com/bitcrowd/loading-chrome-extensions-for-development-in-2025-1039</guid>
      <description>&lt;p&gt;For loading a development build of our open source browser extension &lt;a href="https://github.com/bitcrowd/tickety-tick" rel="noopener noreferrer"&gt;Tickety-Tick&lt;/a&gt; into Google Chrome during local development, we used to use Chrome's handy &lt;code&gt;--load-extension&lt;/code&gt; flag. It allowed to load a local unpacked build of the extension via the command line. As this feature was &lt;a href="https://developer.chrome.com/blog/extension-news-june-2025#removing_the_--load-extension_flag" rel="noopener noreferrer"&gt;recently removed&lt;/a&gt; from all branded builds of Chrome, we needed to update our workflow to use the-new-way™ - "remote debugging pipes".&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Tickety-Tick is on the nichier side of tech within our fleet of open source projects: it's a browser extension for establishing conventions on naming branches and writing commit messages in Git. And while we're slightly leaning towards team Firefox among us developers, Tickety-Tick is of course also supporting Google Chrome (and others…). For this, we as developers have to be able to run a local build of the extension in Chrome during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old way
&lt;/h2&gt;

&lt;p&gt;For local development, we had a &lt;code&gt;yarn open:chrome&lt;/code&gt; command which would open Chrome with the current local build of the extension already installed, ready to click around. Under the hood, we utilized Google's &lt;a href="https://github.com/GoogleChrome/chrome-launcher" rel="noopener noreferrer"&gt;chrome-launcher&lt;/a&gt; package to start a fresh, unpersonalized instance of Chrome for us. The code looked about like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chromeFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ChromeLauncher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultFlags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disable-extensions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-default-browser-check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--load-extension=/dist/chrome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;ChromeLauncher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;chromeFlags&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chrome running&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is the &lt;code&gt;--load-extension&lt;/code&gt; flag which points to the local Chrome build of the extension.&lt;/p&gt;

&lt;p&gt;Some day, this just stopped working. Chrome would start as usual, but without the browser extension loaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened?
&lt;/h2&gt;

&lt;p&gt;Some debugging and internet research brought us to &lt;a href="https://groups.google.com/a/chromium.org/g/chromium-extensions/c/aEHdhDZ-V0E/m/UWP4-k32AgAJ" rel="noopener noreferrer"&gt;this post&lt;/a&gt; by the Chrome team from March 2025:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;RFC: Removing the &lt;code&gt;--load-extension&lt;/code&gt; flag in branded Chrome builds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Turns out the &lt;code&gt;--load-extension&lt;/code&gt; flag was commonly abused by malicious parties and the Chrome team was looking into ways to further restrict this. It lead them to removing this feature from all branded Chrome builds (so Chromium would still have it…) and instead promote other means for loading extensions for testing and development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What now?
&lt;/h2&gt;

&lt;p&gt;To figure out the new™ way to load an unpacked, locally build extension in Chrome, we just naively searched for the &lt;code&gt;--load-extension&lt;/code&gt; flag on GitHub, hoping to find hints how other people updated their code. A promising hit was in the &lt;code&gt;chrome-launcher&lt;/code&gt; package itself. If somebody knew how to do it, they should be the ones. The searching pointed us to a really helpful code comment in their tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Note: --load-extension in chromeFlags used to be the primary method of&lt;/span&gt;
&lt;span class="c1"&gt;// loading extensions, but this is removed from official stable Chrome builds&lt;/span&gt;
&lt;span class="c1"&gt;// starting from Chrome 137. This shows the officially supported way to load&lt;/span&gt;
&lt;span class="c1"&gt;// extensions, with --remote-debugging-pipe.&lt;/span&gt;
&lt;span class="c1"&gt;// See: "Removing the `--load-extension` flag in branded Chrome builds"&lt;/span&gt;
&lt;span class="c1"&gt;// https://groups.google.com/a/chromium.org/g/chromium-extensions/c/aEHdhDZ-V0E/m/UWP4-k32AgAJ&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As this was in a &lt;code&gt;load-extension-test.ts&lt;/code&gt; file, we figured we'd just try to adopt what they are doing in their test setup for our &lt;code&gt;open:chrome&lt;/code&gt; script. And as the comment indicates, we need to utilize the &lt;code&gt;--remote-debugging-pipe&lt;/code&gt; flag, something we already knew from &lt;a href="https://github.com/bitcrowd/chromic_pdf" rel="noopener noreferrer"&gt;ChromicPDF&lt;/a&gt; our PDF rendering library for Elixir based on headless Chrome.&lt;/p&gt;

&lt;p&gt;Following pretty much exactly what the &lt;code&gt;loadExtension via remote-debugging-pipe&lt;/code&gt; test was doing, we ended up with an updated &lt;code&gt;open-in-chrome&lt;/code&gt; script which would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start Chrome with the respective flags
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chromeFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultFlags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disable-extensions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
       &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--remote-debugging-pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--enable-unsafe-extension-debugging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-first-run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-default-browser-check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;]);&lt;/span&gt;

   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;chromeFlags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;ignoreDefaultFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;startingUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;};&lt;/span&gt;

   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Get hold of the debugging pipes
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remoteDebuggingPipes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chrome did not expose remoteDebuggingPipes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Put together the command to load the extension
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Extensions.loadUnpacked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
   &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Send the request and listen on the pipes
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

     &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
       &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pipe closed before response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
     &lt;span class="p"&gt;);&lt;/span&gt;

     &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;x00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
         &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
           &lt;span class="p"&gt;}&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
           &lt;span class="c1"&gt;// ignore non-JSON noise&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="p"&gt;});&lt;/span&gt;
   &lt;span class="p"&gt;});&lt;/span&gt;

   &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;x00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check that the request was successful
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;firstResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to load extension: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that a fresh Chrome should be running with our extension loaded! 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Our script
&lt;/h3&gt;

&lt;p&gt;In our final script, we also added some emoji-powered checks and debugging statements, so that the CLI output aligns with our other tasks and the webpack build. Here's our final script, maybe you can draw inspiration from it for your own usecase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!/usr/bin/env node
&lt;/span&gt;
&lt;span class="c1"&gt;// usage: open-in-chrome [extension-dir] [starting-url]&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;launcher&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chrome-launcher&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/bitcrowd/tickety-tick&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;launchChrome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chromeFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultFlags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disable-extensions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--remote-debugging-pipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--enable-unsafe-extension-debugging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-first-run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-default-browser-check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chromeFlags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ignoreDefaultFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startingUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;launcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⚠️ Expected remote-debugging-pipe mode on port 0, but got a debug port.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remoteDebuggingPipes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chrome did not expose remoteDebuggingPipes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🚀 Chrome launched with remote-debugging-pipe.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`📂 Loading extension from: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Extensions.loadUnpacked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// --- Send command and wait for response&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pipe closed before response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;x00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// ignore non-JSON noise&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;pipes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outgoing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;x00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;firstResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to load extension: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`✅ Extension loaded (id: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🌐 Opening: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;💨 Chrome closed.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;launchChrome&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;❌ Error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;Comments in code can make other people's lives significantly easier. Think about it the next time you're hesitating to write one!&lt;/p&gt;

&lt;p&gt;And: searching GitHub for something as specific as a Chrome flag, can yield helpful results in any kind of file, even in tests.&lt;/p&gt;

</description>
      <category>chrome</category>
      <category>browserextension</category>
      <category>opensource</category>
      <category>development</category>
    </item>
    <item>
      <title>A RAG for Elixir in Elixir</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Tue, 07 Jan 2025 11:29:07 +0000</pubDate>
      <link>https://forem.com/klappradla/a-rag-for-elixir-in-elixir-4fob</link>
      <guid>https://forem.com/klappradla/a-rag-for-elixir-in-elixir-4fob</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: This post was written by my teammate &lt;a href="https://dev.to/joelpaulkoch"&gt;joel&lt;/a&gt; on &lt;a href="https://bitcrowd.dev/a-rag-for-elixir-in-elixir" rel="noopener noreferrer"&gt;the bitcrowd blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A RAG for Elixir
&lt;/h2&gt;

&lt;p&gt;Recently, we told you about our wonderful &lt;a href="https://bitcrowd.dev/a-rag-for-elixir" rel="noopener noreferrer"&gt;RAG for Elixir&lt;/a&gt; that you can use to chat with your codebase.&lt;br&gt;
It has one single flaw. We built our RAG for Elixir using Python.&lt;br&gt;
Don't get me wrong, Python is alright and there are reasons why as of today it's the &lt;a href="https://github.blog/news-insights/octoverse/octoverse-2024/" rel="noopener noreferrer"&gt;most popular programming language on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But there are also many reasons why Elixir is our &lt;a href="https://bitcrowd.net/en/elixir" rel="noopener noreferrer"&gt;programming language of choice&lt;/a&gt;.&lt;br&gt;
So, wouldn't it be great if we could build our RAG for Elixir &lt;strong&gt;in Elixir&lt;/strong&gt;?&lt;/p&gt;
&lt;h2&gt;
  
  
  The components
&lt;/h2&gt;

&lt;p&gt;Let's start by revisiting the components we chose to build our original system.&lt;/p&gt;

&lt;p&gt;It was based on &lt;a href="https://www.langchain.com/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;.&lt;br&gt;
We contributed a &lt;a href="https://github.com/langchain-ai/langchain/pull/22742" rel="noopener noreferrer"&gt;language parser for Elixir&lt;/a&gt; to the project to be able to chunk our code into tidy pieces.&lt;br&gt;
To capture the semantic meaning of the code chunks, we used the renowned &lt;a href="https://huggingface.co/jinaai/jina-embeddings-v2-base-code" rel="noopener noreferrer"&gt;Jina embeddings model in version 2&lt;/a&gt;.&lt;br&gt;
We persisted the resulting embeddings in a locally running &lt;a href="https://www.trychroma.com/" rel="noopener noreferrer"&gt;&lt;code&gt;chroma&lt;/code&gt;&lt;/a&gt; vector store.&lt;br&gt;
To have an LLM to talk to, we employed &lt;a href="https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct" rel="noopener noreferrer"&gt;&lt;code&gt;llama3:8b&lt;/code&gt;&lt;/a&gt; locally with &lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;&lt;code&gt;ollama&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alright, let's have a look what's available in Elixir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[x] &lt;a href="https://hexdocs.pm/text_chunker/readme.html" rel="noopener noreferrer"&gt;Something to chunk Elixir code&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[x] &lt;a href="https://dev.to/how-to-run-jina-embeddings-in-elixir/"&gt;Jina embeddings v2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[x] &lt;a href="https://github.com/elixir-nx/bumblebee/pull/387" rel="noopener noreferrer"&gt;Llama3&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;[x] &lt;a href="https://hexdocs.pm/chroma/Chroma.html" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we can see, all of the components are available in Elixir, although we will run &lt;code&gt;chroma&lt;/code&gt; using docker as we've done before.&lt;/p&gt;

&lt;p&gt;There is one small change: we will replace Meta's Llama3 with Microsoft's &lt;a href="https://huggingface.co/microsoft/Phi-3.5-mini-instruct" rel="noopener noreferrer"&gt;Phi-3.5 mini instruct&lt;/a&gt;.&lt;br&gt;
While we can run Llama3 in Elixir, you must sign up on Hugging Face, request access from Meta, wait for approval, and get an authentication token to use it.&lt;br&gt;
We don't want this barrier for anyone who wants to follow this blog post (and run the resulting script that's waiting at the end 😉).&lt;br&gt;
With Phi-3.5 you won't need any of that. The only requirement is a machine with enough RAM.&lt;/p&gt;

&lt;p&gt;With all that said, it's time to build a local Retrieval Augmented Generation system in Elixir.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building a RAG system in Elixir
&lt;/h2&gt;

&lt;p&gt;Our RAG system consists of three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An ingestion pipeline to read the codebase, chunk the code, and store it in Chroma&lt;/li&gt;
&lt;li&gt;A retrieval pipeline to accept an input query, find relevant pieces of code and retrieve them from Chroma&lt;/li&gt;
&lt;li&gt;A generation pipeline to feed the relevant information and the query into an LLM and generate a helpful response for the user&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Servings
&lt;/h3&gt;

&lt;p&gt;We will run two models using &lt;code&gt;Bumblebee&lt;/code&gt;. An embedding model that we will configure as &lt;code&gt;RagTime.EmbeddingServing&lt;/code&gt; and an LLM that we will configure as &lt;code&gt;RagTime.LLMServing&lt;/code&gt;.&lt;br&gt;
We create a module that contains two builder functions to build the servings.&lt;br&gt;
You can add servings to your supervision tree as described in the &lt;a href="https://hexdocs.pm/nx/Nx.Serving.html#module-stateful-process-workflow" rel="noopener noreferrer"&gt;&lt;code&gt;Nx&lt;/code&gt; documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Serving&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;build_embedding_serving&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:hf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"jinaai/jina-embeddings-v2-base-code"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_info&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;spec_overrides:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;architecture:&lt;/span&gt; &lt;span class="ss"&gt;:base&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="ss"&gt;params_filename:&lt;/span&gt; &lt;span class="s2"&gt;"model.safetensors"&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_tokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;TextEmbedding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;compile:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;batch_size:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sequence_length:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;defn_options:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;compiler:&lt;/span&gt; &lt;span class="no"&gt;EXLA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;output_attribute:&lt;/span&gt; &lt;span class="ss"&gt;:hidden_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;output_pool:&lt;/span&gt; &lt;span class="ss"&gt;:mean_pooling&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;build_llm_serving&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:hf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"microsoft/phi-3.5-mini-instruct"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_info&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_tokenizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_generation_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;generation_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;max_new_tokens:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Bumblebee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generation_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;compile:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;batch_size:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sequence_length:&lt;/span&gt; &lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;defn_options:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;compiler:&lt;/span&gt; &lt;span class="no"&gt;EXLA&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;stream:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ingestion
&lt;/h3&gt;

&lt;p&gt;In our ingestion pipeline, we want to accept a path to an Elixir codebase.&lt;br&gt;
We find all Elixir files in the codebase using &lt;code&gt;Path.wildcard/1&lt;/code&gt; but ignore every file that's inside &lt;code&gt;_build&lt;/code&gt; or &lt;code&gt;deps&lt;/code&gt;.&lt;br&gt;
Then, we build a map that contains the path of the file at the &lt;code&gt;source&lt;/code&gt; key and the content of the file at the &lt;code&gt;content&lt;/code&gt; key.&lt;br&gt;
We chunk the code into pieces, calculate embeddings which capture the semantics of the chunk of code, and store the chunk and the embedding in Chroma.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ingestion&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="no"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wildcard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_path&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/**/*.{ex, exs}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contains?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/_build/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/deps/"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;files_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zip_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;source:&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk_with_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:elixir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;store_embeddings_and_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;chunk_with_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="no"&gt;TextChunker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;format:&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flat_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_chunks&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;source_chunks&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="p"&gt;%{&lt;/span&gt;
          &lt;span class="ss"&gt;source:&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;start_byte:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;end_byte:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;text:&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;generate_embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;chunk_text_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Serving&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;batched_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;EmbeddingServing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk_text_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;embedding:&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;store_embeddings_and_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;chunk_to_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="no"&gt;Chroma&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;documents:&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ids:&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;embeddings:&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;chunk_to_id&lt;/span&gt;&lt;span class="p"&gt;(%{&lt;/span&gt;&lt;span class="ss"&gt;source:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;start_byte:&lt;/span&gt; &lt;span class="n"&gt;start_byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;end_byte:&lt;/span&gt; &lt;span class="n"&gt;end_byte&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;file_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;start_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;file_content&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;byte_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;end_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;file_content&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;byte_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;start_line&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;end_line&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrieval
&lt;/h3&gt;

&lt;p&gt;Alright, now that we've got the code chunks and corresponding embeddings in our Chroma collection, we want to retrieve relevant code chunks for a given query.&lt;br&gt;
So, we'll build our retrieval pipeline next.&lt;br&gt;
For that, we create a new module with a single &lt;code&gt;retrieve/2&lt;/code&gt; function that takes a Chroma collection and a query and returns relevant chunks and their sources.&lt;/p&gt;

&lt;p&gt;We compute the embeddings that correspond to the query using the same embedding serving we employed for ingesting.&lt;br&gt;
The embedding is an &lt;code&gt;Nx.Tensor&lt;/code&gt;, so we convert it to a list and query Chroma with the list.&lt;br&gt;
We limit the results to the 10 most relevant code chunks and return the code chunks and the sources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Retrieval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;embedding:&lt;/span&gt; &lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Serving&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;batched_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;EmbeddingServing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="no"&gt;Chroma&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;query_embeddings:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;code_chunks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"documents"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ids"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;code_chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generation
&lt;/h3&gt;

&lt;p&gt;We already arrived at the last piece of our system.&lt;br&gt;
It's time to generate a helpful response.&lt;br&gt;
Again, we'll define a new module.&lt;br&gt;
This time it has a single &lt;code&gt;generate_response/3&lt;/code&gt; function that takes the &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;context_documents&lt;/code&gt;, and &lt;code&gt;context_sources&lt;/code&gt;.&lt;br&gt;
We concat the code chunks (our context documents) and inject them into a prompt together with our query.&lt;br&gt;
We pass the resulting prompt to an LLM and return query, context, context source, and the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Generation&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;generate_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context_documents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;code_chunk&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="sd"&gt;"""
        [...]
        #{code_chunk}
        [...]
        """&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="sd"&gt;"""
      &amp;lt;|system|&amp;gt;
      You are a helpful assistant.&amp;lt;/s&amp;gt;
      &amp;lt;|user|&amp;gt;
      Context information is below.
      ---------------------
      #{context}
      ---------------------
      Given the context information and no prior knowledge, answer the query.
      Query: #{query}
      Answer: &amp;lt;/s&amp;gt;
      &amp;lt;|assistant|&amp;gt;
      """&lt;/span&gt;

    &lt;span class="p"&gt;%{&lt;/span&gt;&lt;span class="ss"&gt;results:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Serving&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;batched_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;LLMServing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;%{&lt;/span&gt;
      &lt;span class="ss"&gt;query:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;context:&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;context_sources:&lt;/span&gt; &lt;span class="n"&gt;context_sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;response:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To have a public interface for our RAG system, we define a &lt;code&gt;RagTime&lt;/code&gt; module with &lt;code&gt;ingest/2&lt;/code&gt; and &lt;code&gt;query/2&lt;/code&gt; functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;RagTime&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Ingestion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
   &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Retrieval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="no"&gt;RagTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Generation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it, a fully local RAG system in Elixir.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the RAG system
&lt;/h2&gt;

&lt;p&gt;For demo purposes, we built a simple UI using &lt;a href="https://hexdocs.pm/phoenix_playground/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;phoenix_playground&lt;/code&gt;&lt;/a&gt;, you can download the resulting file from &lt;a href="https://gist.github.com/joelpaulkoch/9192abd23bd2e6ff76be314c24173974" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Remember that we need a running Chroma instance.&lt;br&gt;
You can start it with &lt;code&gt;docker run -p 8000:8000 chromadb/chroma&lt;/code&gt;.&lt;br&gt;
Then you can run &lt;code&gt;elixir rag_time.exs&lt;/code&gt; to start the RAG system.&lt;/p&gt;

&lt;p&gt;In our previous &lt;a href="https://bitcrowd.dev/a-rag-for-elixir#ask-questions" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;, we used the system to get help for a Phoenix PR.&lt;br&gt;
We will ask our Elixir RAG the same question to see if it works.&lt;/p&gt;

&lt;p&gt;We clone the Phoenix repository and checkout the &lt;a href="https://github.com/phoenixframework/phoenix/commit/5d3dc226ec55f1b13b2318bb5be9f1edd4be7036" rel="noopener noreferrer"&gt;commit&lt;/a&gt; right before the PR got merged.&lt;br&gt;
Then, we ingest the codebase into our RAG system using the UI.&lt;/p&gt;

&lt;p&gt;Afterwards, we ask the exact same question as last time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;We&lt;/strong&gt;: Please help me understand the following briefing:&lt;/p&gt;

&lt;p&gt;Currently, if you set &lt;code&gt;remote_ip&lt;/code&gt; during a test when building a &lt;code&gt;conn&lt;/code&gt;, calling a dispatch call, e.g &lt;code&gt;get&lt;/code&gt;, will run the &lt;code&gt;conn&lt;/code&gt; through &lt;code&gt;recycle&lt;/code&gt; which will lose this information. Instead, we want &lt;code&gt;the remote_ip&lt;/code&gt; maintained, similar to &lt;code&gt;host&lt;/code&gt;. Can you see why remote_ip is not preserved in test cases where recycle is used?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqv36l9ki2nhanh8gugk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmqv36l9ki2nhanh8gugk.png" alt="Screenshot of the response of the RAG system" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Chatbot:&lt;/strong&gt; The &lt;code&gt;remote_ip&lt;/code&gt; is not preserved during the recycle process in test cases because the recycling mechanism is designed to emulate the behavior of browsers, which typically recycle connections by using cookies. When a connection is recycled, the response cookies, including the &lt;code&gt;host&lt;/code&gt; header, are saved and sent in subsequent requests. However, the &lt;code&gt;remote_ip&lt;/code&gt; is not automatically included in these cookies. The recycling process, as implemented in the provided context, primarily focuses on preserving and transferring response headers like "accept", "accept-language", and "authorization". It does not explicitly handle the &lt;code&gt;remote_ip&lt;/code&gt; header. This is why when you set &lt;code&gt;remote_ip&lt;/code&gt; during a test and then dispatch a request (e.g., using &lt;code&gt;get&lt;/code&gt;), the &lt;code&gt;recycle&lt;/code&gt; function will process the connection and potentially discard the &lt;code&gt;remote_ip&lt;/code&gt; information. To maintain the &lt;code&gt;remote_ip&lt;/code&gt; during tests, you would need to implement a custom solution that explicitly preserves and transfers this header during the recycling process. This could involve modifying the recycling function or creating a wrapper function that handles the &lt;code&gt;remote_ip&lt;/code&gt; separately. Alternatively, you could avoid using the recycle function altogether and rely on Phoenix's built-in connection recycling, which might preserve the &lt;code&gt;remote_ip&lt;/code&gt; by default. However, this approach might not be suitable for all test scenarios, especially if you need to test specific behaviors related to the &lt;code&gt;remote_ip&lt;/code&gt; header. In summary, the reason &lt;code&gt;remote_ip&lt;/code&gt; is not preserved during recycling is that the recycling mechanism is not designed to handle this header, and it does not automatically include it in the response cookies. To address this issue, you would need to implement a custom solution or adjust your testing approach accordingly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As you can see, we get a different response, but remember that we're using a different LLM.&lt;br&gt;
We also get slightly different sources, which might be due to different chunking.&lt;/p&gt;

&lt;p&gt;You can play around with the RAG system and swap out the embeddings model or LLM.&lt;br&gt;
If your machine does not have enough memory to run the RAG system, try using smaller models, such as &lt;a href="https://huggingface.co/thenlper/gte-small" rel="noopener noreferrer"&gt;thenlper/gte-small&lt;/a&gt; for embeddings and &lt;a href="https://huggingface.co/HuggingFaceTB/SmolLM2-135M-Instruct" rel="noopener noreferrer"&gt;HuggingFaceTB/SmolLM2-135M-Instruct&lt;/a&gt; as LLM.&lt;br&gt;
Remember to adapt the prompt accordingly in that case.&lt;/p&gt;

&lt;p&gt;In any case, start a &lt;a href="https://gist.github.com/joelpaulkoch/9192abd23bd2e6ff76be314c24173974" rel="noopener noreferrer"&gt;discussion with us on GitHub&lt;/a&gt; or reach out via &lt;a href="https://bsky.app/profile/bitcrowd.bsky.social" rel="noopener noreferrer"&gt;bluesky&lt;/a&gt; or &lt;a href="https://elixir-lang.slack.com/team/U3YNJJGRG" rel="noopener noreferrer"&gt;slack&lt;/a&gt; if you need help or just want to let us know what you think.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt;&lt;br&gt;
If you haven't done so already, check the rest of our ongoing series on &lt;a href="https://bitcrowd.dev/tags/rag" rel="noopener noreferrer"&gt;RAG&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>llm</category>
      <category>rag</category>
      <category>ai</category>
      <category>elixir</category>
    </item>
    <item>
      <title>Meet Tickety-Tick</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 04 Nov 2024 11:01:57 +0000</pubDate>
      <link>https://forem.com/bitcrowd/meet-tickety-tick-2j68</link>
      <guid>https://forem.com/bitcrowd/meet-tickety-tick-2j68</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/bitcrowd/tickety-tick" rel="noopener noreferrer"&gt;Tickety-Tick&lt;/a&gt; is a tool so essential and at the same time so natural and obvious for our work at bitcrowd, that we actually never really introduced it much. So here is a very late &lt;em&gt;“meet Tickety-Tick”&lt;/em&gt;, a tool we love, take for granted and whose usage is part of the collective muscle memory at bitcrowd since what feels like forever ❤️.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basics
&lt;/h2&gt;

&lt;p&gt;Tickety-Tick is quite an exotic member of the fleet of &lt;a href="https://github.com/bitcrowd" rel="noopener noreferrer"&gt;open source projects at bitcrowd&lt;/a&gt;: itʼs a &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions" rel="noopener noreferrer"&gt;browser extension&lt;/a&gt; to generate Git branch names and commit messages for the ticket one is working on. It allows us to align branch and commit message naming patterns throughout all projects and have a fixed and easy to follow relationship between the ticket - may it be a bug description or a feature - and its corresponding change in the source code, regardless of the ticketing system used and its possibilities or impossibilities to integrate with code repositories.&lt;/p&gt;

&lt;p&gt;The idea is based on the &lt;a href="https://en.wikipedia.org/wiki/Convention_over_configuration" rel="noopener noreferrer"&gt;&lt;em&gt;“Convention over Configuration”&lt;/em&gt;&lt;/a&gt; design pattern which used to be super popular in the &lt;a href="https://www.ruby-lang.org/en/community/" rel="noopener noreferrer"&gt;Ruby community&lt;/a&gt; where bitcrowd hung out too in 2015 when the project was born. You agree upon a convention on how to name branches and commit messages and then have a tool in place to take care of enforcing this convention without you ever having to think about it or alternative ways to name branches and commit messages ever again.&lt;/p&gt;

&lt;p&gt;If your into history, there is also a &lt;a href="https://speakerdeck.com/bitboxer/tickety-tick" rel="noopener noreferrer"&gt;slidedeck&lt;/a&gt; &lt;a href="https://bodo.tasche.me/" rel="noopener noreferrer"&gt;Bodo&lt;/a&gt;, the original author of the browser extension, used for a lightning talk at &lt;a href="https://2016.rubyconf.pt/" rel="noopener noreferrer"&gt;Ruby Conf Portugal&lt;/a&gt; in 2016. I was in the audience back then and immediately sold on the idea 🥰.&lt;/p&gt;

&lt;h3&gt;
  
  
  Convention
&lt;/h3&gt;

&lt;p&gt;At bitcrowd, branch names follow the format of &lt;code&gt;&amp;lt;type&amp;gt;/&amp;lt;id&amp;gt;-&amp;lt;title&amp;gt;&lt;/code&gt; and the subject of the first commit message on that branch would be &lt;code&gt;[#&amp;lt;id&amp;gt;] &amp;lt;title&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;type&lt;/code&gt;&lt;/strong&gt;: the type of ticket&lt;/p&gt;

&lt;p&gt;This may be &lt;code&gt;feature&lt;/code&gt;, &lt;code&gt;bug&lt;/code&gt;, &lt;code&gt;task&lt;/code&gt;, &lt;code&gt;chore&lt;/code&gt; or whatever else the ticketing system uses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;: the identifier of the ticket in the ticketing system&lt;/p&gt;

&lt;p&gt;This may be &lt;code&gt;TT-5045&lt;/code&gt; in &lt;a href="https://www.atlassian.com/software/jira" rel="noopener noreferrer"&gt;Jira&lt;/a&gt;, a numeric ID like on &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, a UUID on &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt;, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;title&lt;/code&gt;&lt;/strong&gt;: the title or subject of the issue&lt;/p&gt;

&lt;p&gt;On GitHub, this would for instance be the issue title, something like &lt;em&gt;“Add support for Safari”&lt;/em&gt;. When used for the branch name, the title gets lowercased and dasherized to &lt;code&gt;add-support-for-safari&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So for an imaginary feature ticket about adding support for Safari, Tickety-Tick would generate the branch name &lt;code&gt;feature/tt-504-add-support-for-safari&lt;/code&gt; and a commit message subject &lt;code&gt;[#TT-504] Add support for Safari&lt;/code&gt;, when used with the default configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Origin
&lt;/h3&gt;

&lt;p&gt;The convention was derived from existing workflows at bitcrowd. And while having aligned branch names already makes sense for collaboration alone, the aligned commit message subject is a bit more special. Itʼs useful for us at bitcrowd, because we squash-merge 99.9% of our pull requests. This means each merged pull request is represented by a single commit in the Git history. Respectively, each ticket we work on results in a single commit and the history can be read as something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[#504] Add support for Safari
[#503] Fix installation instructions for Chrome
[#501] Remove cookie banner
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also use the generated subject as the title for our pull or merge requests and for time-tracking. But more on that later…&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;Letʼs walk though a possible workflow together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;First we need to add the Tickety-Tick web extension to our browser. Install it via &lt;a href="https://addons.mozilla.org/firefox/addon/tickety-tick/" rel="noopener noreferrer"&gt;Firefox Add-ons&lt;/a&gt;, the &lt;a href="https://chrome.google.com/webstore/detail/ciakolhgmfijpjbpcofoalfjiladihbg" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt; or follow the &lt;a href="https://github.com/bitcrowd/tickety-tick?tab=readme-ov-file#installation" rel="noopener noreferrer"&gt;manual installation instructions&lt;/a&gt; for Chrome, Opera, Firefox or Safari. Then we need an ticket to work on. Tickety-Tick supports a constantly growing range of &lt;a href="https://github.com/bitcrowd/tickety-tick?tab=readme-ov-file#supported-ticket-systems" rel="noopener noreferrer"&gt;ticketing systems&lt;/a&gt; from GitHub to YouTrack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Letʼs say weʼre working on Tickety-Tick itself. We have the project checked out locally, are on the latest changes of the &lt;code&gt;main&lt;/code&gt; branch and have a terminal window open in the projectʼs root directory. Then we browse the open issues on &lt;a href="https://github.com/bitcrowd/tickety-tick/issues" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficvat6mf9dtiu78fv0jc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficvat6mf9dtiu78fv0jc.png" alt="Screenshot of browsing the issues page of the Tickety-Tick repository on GitHub" width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want want to work on the first issue to migrate the ESLint configuration &lt;em&gt;(itʼs already assigned to me…)&lt;/em&gt; and click on the issue to read what needs to be done:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0n39bwsxel9tqxdnzrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0n39bwsxel9tqxdnzrd.png" alt="Screenshot of the issue page for ticket number 427 with the title “Migrate ESLint config to flat format”" width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The description is only pointing to a series of blog posts from the ESLint team explaining the new default configuration format. Letʼs assume we read it and are ready to start working on the ticket. So we open the Tickety-Tick browser extension either by clicking on the ticket icon in our browserʼs tool-bar or, if weʼre already among the power users, use the nifty ctrl t keyboard shortcut to open the extension popup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a2wenb1hp5i62jo9mvt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a2wenb1hp5i62jo9mvt.png" alt="Screenshot of the issue page for ticket number 427 with the title “Migrate ESLint config to flat format” and the Tickety-Tick browse extension popup window open" width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It offers three buttons: one to copy the generated branch name to the clipboard, one to copy the generated commit message and one to copy the &lt;em&gt;“command”&lt;/em&gt;. We click the last button, and paste into our terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="s1"&gt;'feature/427-migrate-eslint-config-to-flat-format'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;--allow-empty&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'[#427] Migrate ESLint config to flat format

https://github.com/bitcrowd/tickety-tick/issues/427
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Power users / keyboard lovers can also tab through the buttons of course. No need to reach the popup window with the mouse.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Broken down into pieces, this will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Check out a new branch with the name &lt;code&gt;feature/427-migrate-eslint-config-to-flat-format&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create an empty commit with this message:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   [#427] Migrate ESLint config to flat format

   https://github.com/bitcrowd/tickety-tick/issues/427
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Check &lt;a href="https://bitcrowd.dev/a-note-on-commit-messages" rel="noopener noreferrer"&gt;our post on commit messages&lt;/a&gt; for more info about commit messages, subject vs. body, formatting, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With that, we can start coding away. Whether we decide to later &lt;a href="https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/changing-a-commit-message#rewriting-the-most-recent-commit-message" rel="noopener noreferrer"&gt;amend&lt;/a&gt; the initial commit, add our changes and write a proper commit message body, or if we just keep the empty commit as a starting point for a series of smaller commits afterwards, that depends on the use case and personal preference.&lt;/p&gt;

&lt;p&gt;I personally only amend the empty commit with my changes if the changes are super small. Otherwise, I prefer to have a series of commits which follow my thought process and just keep the empty commit as a starting point for my pull request.&lt;/p&gt;

&lt;h4&gt;
  
  
  Done?
&lt;/h4&gt;

&lt;p&gt;Once done with the implementation, we can open a pull request with our changes. If our changes are small enough to fit into a single commit, GitHub will even pre-fill the pull request title with the commit messageʼs subject &lt;code&gt;[#427] Migrate ESLint config to flat format&lt;/code&gt; and the description with the commit message body we provided.&lt;/p&gt;

&lt;p&gt;We at bitcrowd reuse the first commit messageʼs subject for the pull request title. It has proven to make browsing the list of open pull requests easier to grasp for our colleagues. So again a convention…&lt;/p&gt;

&lt;h3&gt;
  
  
  Habit
&lt;/h3&gt;

&lt;p&gt;For pretty much every dev team that we introduced to Tickety-Tick as part of our consulting work, using the tool became muscle memory quite fast. Especially the ctrl t shortcut plus tab quickly wired up as the go-to move whenever people would start working on a ticket or want to check out branches of a currently worked on ticket from another team member. Having the naming convention established and enforced, frees the developers from having to spent any attention or brain power onto them. Pick a ticket, read and understand it and paste a command to check out a branch for the ticket to start implementing it.&lt;/p&gt;

&lt;p&gt;When adapting Tickety-Tick as a team with already established rules around branch names and commit messages, it may make sense to look into the &lt;a href="https://github.com/bitcrowd/tickety-tick?tab=readme-ov-file#advanced-configuration" rel="noopener noreferrer"&gt;advanced configuration&lt;/a&gt; of Tickety-Tick. It allows to adjust the naming patterns to the teamʼs needs, so that the tool can be used to enforce already existing conventions. The configuration options are powerful enough to cover any naming pattern your team may have come up with. Yet they are simple and limited enough to not have you spent hours on them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;bitcrowd&lt;/strong&gt; can help you to optimize your workflows and team organization! Just &lt;a href="//mailto:info@bitcrowd.net"&gt;hit us up&lt;/a&gt; and we see what we can do for you!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why use it?
&lt;/h2&gt;

&lt;p&gt;So if you already have a ticketing system and a place to host your repositories, why should especially a browser extension between them be of so much value for you as a developer and/or your team? Arenʼt you already keeping track of your work and juggling around too many tools?&lt;/p&gt;

&lt;p&gt;In that regard, Tickety-Tick is &lt;strong&gt;not &lt;em&gt;another tool™&lt;/em&gt;&lt;/strong&gt; in the garage of tools youʼre already using. Itʼs more like a loose bandage to tie your tools together, so that they donʼt drift apart. As a side-effect, it may also loosely bandage your team members together, so that they donʼt fully drift apart concerning naming conventions…&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforce conventions
&lt;/h3&gt;

&lt;p&gt;Adapting Tickety-Tick helps to enforce conventions, which frees developers from having to think about these naming questions themselves day to day. This reduces the cognitive load when working with ticketing systems and Git. And this freed head-space is something both, a dev team or an individual developer on their own can benefit from.&lt;/p&gt;

&lt;p&gt;Having the naming done automatically via an agreed upon set of rules, also makes the project easier accessible for new people. Plus, it helps to keep things nice and tidy on the long run, without having to constantly have an eye on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traceability
&lt;/h3&gt;

&lt;p&gt;Another huge and probably &lt;strong&gt;the most important benefit&lt;/strong&gt; of using Tickety-Tick is the connection between ticket and code contribution that it helps to establish. Combined with a habit of squash-merging pull requests, the team has a linear and easy to follow Git history: changes in code point to their commit and the commit message points to the ticket on the tracker. Looking at a commit message then allows to quickly dive into further detail about the change on the ticketing system, to find out where a feature originated from, etc. The quality and usefulness of this connection of course also depends on the quality of ticket descriptions, but thatʼs a whole different topic…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96c76bv91bk3barevfvd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F96c76bv91bk3barevfvd.png" alt="Screenshot of a terminal window showing the Git history with pointers to issue IDs in subjects" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having this fixed connection between ticket and source code and having all people in the team know about it, turned out to be extremely beneficial for pretty much every project or team weʼve seen so far. Itʼs an additional mean for knowledge sharing, makes onboarding newcomers on the codebase faster and is generally a trusted reference for any member of the team, newcomer to long term contributer. Itʼs an additional source of documentation for the code with a level of detail which would not be possible within the code itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further usage
&lt;/h3&gt;

&lt;p&gt;The encoded information about the connection between a ticket on the tracker and the code which later was written for it, can be used for further applications, like timetracking, work reports, etc.&lt;/p&gt;

&lt;p&gt;A whole new information heaven can be reached once &lt;a href="//../2024-06-10-how-even-the-simplest-RAG-can-empower-your-team/index.md"&gt;RAG&lt;/a&gt; (retrieval-augmented generation) comes into play. Looking at both, the ticket description and the resulting code contribution, a RAG system can learn a lot about the domain of a project and how the team communicates. Teams who adapted Tickety-Tick early enough in their work, now have a huge individual dataset of how they work right in front of them! This opens up quite some possibilities for knowledge sharing, analysis, automation and what not. We are super excited about the topic ourself and are currently doing a &lt;a href="https://bitcrowd.dev/tags/rag/" rel="noopener noreferrer"&gt;series of blog posts&lt;/a&gt; on it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it!
&lt;/h2&gt;

&lt;p&gt;After all the praise, the only thing left to say is: Just &lt;a href="https://github.com/bitcrowd/tickety-tick?tab=readme-ov-file#installation" rel="noopener noreferrer"&gt;try it&lt;/a&gt; out yourself! Tickety-Tick works in all major browsers people use nowadays (Firefox, Chrome, Opera, Safari). It supports a lot of different ticketing systems and if yours is not &lt;a href="https://github.com/bitcrowd/tickety-tick?tab=readme-ov-file#supported-ticket-systems" rel="noopener noreferrer"&gt;in the list&lt;/a&gt; yet, we can help you to put together a pull request for it. Weʼre always curious to find out which ticketing systems and workflows people use.&lt;/p&gt;

&lt;p&gt;If you have more questions about browser extensions or workflows, just hit us up!&lt;/p&gt;

</description>
      <category>git</category>
      <category>browserextensions</category>
      <category>tooling</category>
    </item>
    <item>
      <title>A RAG for Elixir</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 26 Jun 2024 13:50:49 +0000</pubDate>
      <link>https://forem.com/bitcrowd/a-rag-for-elixir-1da2</link>
      <guid>https://forem.com/bitcrowd/a-rag-for-elixir-1da2</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;This is the second part of a series of blog posts on using a RAG (Retrieval Augmented Generation) information system for your codebase. Together we explore how this can empower your development team. Check out the &lt;a href="https://dev.to/how-even-the-simplest-RAG-can-empower-your-team"&gt;first post&lt;/a&gt; for an introduction into the topic if you haven't already.&lt;/p&gt;

&lt;p&gt;In this episode we explore how we can adapt our RAG system for Ruby codebases from &lt;a href="https://bitcrowd.dev/how-even-the-simplest-RAG-can-empower-your-team"&gt;the first episode&lt;/a&gt; to read and understand Elixir code. We will take a look at &lt;a href="https://www.langchain.com/"&gt;LangChain&lt;/a&gt; and text &lt;em&gt;"splitting"&lt;/em&gt; and &lt;em&gt;"chunking"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's dive right into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Our &lt;a href="https://github.com/bitcrowd/rag_time"&gt;RAG system&lt;/a&gt; was built with the idea to discover Ruby codebases. In order to have conversations about Elixir codebases as well, we need to make sure our LLM &lt;em&gt;"understands"&lt;/em&gt; Elixir code. This is where &lt;a href="https://www.langchain.com/"&gt;LangChain&lt;/a&gt; comes into play.&lt;/p&gt;

&lt;p&gt;LangChain is a toolkit around all things LLMs, including RAG. We use it to parse our documents or codebase and generate a vector database from it. In our &lt;a href="https://github.com/bitcrowd/rag_time"&gt;simple RAG system&lt;/a&gt;, we specify which file endings (.rb) and which programming language (Ruby) our documents have.&lt;/p&gt;

&lt;p&gt;The ingestion of programming source code into an LLM with LangChain was initially only supported for Python, C and a few others languages. Then &lt;a href="https://github.com/langchain-ai/langchain/issues/11229"&gt;this issue&lt;/a&gt; proposed the usage of a parser library like &lt;a href="https://tree-sitter.github.io/tree-sitter/"&gt;Tree-sitter&lt;/a&gt; to facilitate adding support for many more languages. The discussion is worth reading.&lt;/p&gt;

&lt;p&gt;Finally, &lt;a href="https://github.com/langchain-ai/langchain/pull/13318"&gt;this pull request&lt;/a&gt; introduced support for a lot more languages, including Ruby, based on this proposal. It was a school project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I am submitting this for a school project as part of a team of 5. Other team members are @LeilaChr, @maazh10, @Megabear137, @jelalalamy. This PR also has contributions from community members @Harrolee and @Mario928.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Our plan is to use this as a starting point to enable basic parsing of Elixir source code with LangChain. With that, we should be able to have conversations with our RAG system about Elixir codebases as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting / Chunking text
&lt;/h2&gt;

&lt;p&gt;To read our Elixir codebase, the parser needs some rules on where to split the provided source code files at. Generally for RAG, when ingesting (reading in) a text file, PDF, etc. it will try to split it into chunks, ideally along it's semantic meaning. In text documents, the meanings are often grouped along:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;chapters&lt;/li&gt;
&lt;li&gt;paragraphs&lt;/li&gt;
&lt;li&gt;sentences&lt;/li&gt;
&lt;li&gt;words&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your embedding model has enough context capacity, you would try to split along chapters or paragraphs, because human readable text often groups meanings that way. If those are too big, you would try to break between sentences, and, as a last resort, words. One would generally try to avoid splitting inside words. Take for instance "sense" and "nonsense", which carry quite a different meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting / Chunking code
&lt;/h2&gt;

&lt;p&gt;Embedding code is a bit underdeveloped, but the strategy is to break the code into pieces by inserting new lines so that it looks a bit more like natural text, and then let the embedding model deal with the task of making sense (inferring meaning) from it. Interestingly, the models trained on that task do that surprisingly well.&lt;/p&gt;

&lt;p&gt;As said, LangChain has dedicated &lt;a href="https://python.langchain.com/v0.2/docs/integrations/document_loaders/source_code/"&gt;document loaders for source code&lt;/a&gt; and a &lt;a href="https://python.langchain.com/v0.2/docs/integrations/document_loaders/source_code/#adding-languages-using-tree-sitter-template"&gt;guide&lt;/a&gt; on how to add new ones based on Tree-sitter. So we went ahead and implemented a document loader and parser for Elixir source code in LangChain. It only covers the core basics of the language, but it was already enough for our proof-of-concept RAG application. With LangChain now supporting Elixir out of the box, people can use the parser in a variety of different scenarios and will come up with ways to improve it to fit more use cases. Our implementation is only the ground work. You can have a look at &lt;a href="https://github.com/langchain-ai/langchain/pull/22742"&gt;the PR&lt;/a&gt; if you're interested in what's necessary to add parsing support for another programming language in LangChain. Spoiler: not much if you can utilize Tree-sitter.&lt;/p&gt;

&lt;p&gt;The core of LangChain's programming language parsers based on Tree-sitter is their &lt;code&gt;CHUNK_QUERY&lt;/code&gt;. For our Elixir parser it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CHUNK_QUERY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    [
        (call target: ((identifier) @_identifier
            (#any-of? @_identifier &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defmodule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defprotocol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defimpl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;))) @module
        (call target: ((identifier) @_identifier
            (#any-of? @_identifier &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;def&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defmacro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defmacrop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;))) @function
        (unary_operator operator: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; operand: (call target: ((identifier) @_identifier
              (#any-of? @_identifier &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;moduledoc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typedoc&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="s"&gt;doc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)))) @comment
    ]
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using Tree-sitter's own &lt;a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries"&gt;tree query language&lt;/a&gt; here. Without diving into the details, our query makes sure to distinguish top level modules, functions and comments. The document loader will then take care of loading each chunk into a separate document and split the lines accordingly. The approach is the same for all programming languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test drive
&lt;/h2&gt;

&lt;p&gt;Let's take this for a spin in &lt;a href="https://github.com/bitcrowd/rag_time/"&gt;our RAG system scripts&lt;/a&gt; from &lt;a href="//./how-even-the-simplest-RAG-can-empower-your-team"&gt;episode one&lt;/a&gt; of this series.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ztmed88vg80bpez9g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc6ztmed88vg80bpez9g6.png" alt="the scripts used are in the bitcrowd rag_time repo on GitHub" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just as a refresher: the idea is to have a RAG system for your team's codebase using LLMs locally without exchanging any data with third parties like OpenAI and the like. It includes a conversational AI built with &lt;a href="https://docs.chainlit.io/get-started/overview"&gt;Chainlit&lt;/a&gt;, so that members of the team can &lt;em&gt;"chat"&lt;/em&gt; with the LLM about the codebase, for instance to get information about the domain or where to find things for the ticket they are working on.&lt;/p&gt;

&lt;p&gt;For testing purposes we will use our RAG system on a popular open source Elixir package, the &lt;a href="https://github.com/phoenixframework/phoenix"&gt;Phoenix Framework&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get the RAG ready
&lt;/h3&gt;

&lt;p&gt;First we need to get our local RAG system ready for operating on an Elixir codebase. It needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where is the code?&lt;/li&gt;
&lt;li&gt;Which programming language is it?&lt;/li&gt;
&lt;li&gt;Which suffixes have the source code files?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We provide this information via environment variables in a &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OLLAMA_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"llama3:8b"&lt;/span&gt;
&lt;span class="nv"&gt;CODEBASE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./phoenix"&lt;/span&gt;
&lt;span class="nv"&gt;CODEBASE_LANGUAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"elixir"&lt;/span&gt;
&lt;span class="nv"&gt;CODE_SUFFIXES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;".ex, .exs"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just cloned the current state of the Phoenix Git repository right next to our RAG code. We also keep using Meta's &lt;a href="https://llama.meta.com/llama3/"&gt;Llama3&lt;/a&gt; model, and instruct the document loader to look at Elixir files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  ℹ️ Info
&lt;/h4&gt;

&lt;p&gt;At the time of testing &lt;a href="https://github.com/langchain-ai/langchain/pull/22742"&gt;our PR on LangChain&lt;/a&gt; was not released yet. So we were pointing to our fork's local code for the &lt;code&gt;langchain&lt;/code&gt;, &lt;code&gt;langchain-community&lt;/code&gt; and &lt;code&gt;langchain-text-splitter&lt;/code&gt; Python packages from the &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the sake of simplicity we assume the project documentation to be in Markdown and hard-coded this information into our code ingestion logic.&lt;/p&gt;

&lt;p&gt;With that, we can set up our vector database for the Phoenix codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python ingest-code.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we start the chat bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chainlit run main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Chatbot&lt;/strong&gt;:&lt;br&gt;&lt;br&gt;
Hi, Welcome to Granny RAG. Ask me anything about your code!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we are ready to have a conversation about the codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask questions
&lt;/h3&gt;

&lt;p&gt;Let’s take an &lt;a href="https://github.com/phoenixframework/phoenix/pull/5792"&gt;original PR&lt;/a&gt; from Phoenix to evaluate the quality of our solution, similar to what we did with Ruby in the previous post.&lt;/p&gt;

&lt;p&gt;The pull request fixes a small issue in the &lt;code&gt;recycle/1&lt;/code&gt; function of the &lt;code&gt;Phoenix.ConnTest&lt;/code&gt; module at &lt;code&gt;phoenix/lib/phoenix/test/conn_test.ex&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhbka4jd74okwocxo6qn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhbka4jd74okwocxo6qn.png" alt="Screenshot of a pull request" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Currently, if you set &lt;code&gt;remote_ip&lt;/code&gt; during a test when building a conn, calling a dispatch call e.g get will run the conn through &lt;code&gt;recycle&lt;/code&gt; which will lose this information. This PR changes that so it is maintained similar to &lt;code&gt;host&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We reset our clone of the Phoenix repository (and our vector database) to the state right before the PR was merged and then ask the RAG system for help with the issue:&lt;/p&gt;

&lt;h4&gt;
  
  
  Understand the problem
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;We:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Please help me understand the following briefing:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Currently, if you set `remote_ip` during a test when building a `conn`,
calling &amp;gt; a dispatch call, e.g `get`, will run the `conn` through `recycle`
which will &amp;gt; lose this information. Instead, we want `the remote_ip`
maintained, similar to `host`.
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Can you see why &lt;code&gt;remote_ip&lt;/code&gt; is not preserved in test cases where &lt;code&gt;recycle&lt;/code&gt; is used?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftn3klkccj6gpj83z8jbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftn3klkccj6gpj83z8jbv.png" alt="Screnshot of briefing question" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Chatbot:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The briefing explains that when running a test with Phoenix's Conn and Endpoint frameworks, the &lt;code&gt;remote_ip&lt;/code&gt; information is lost during the test. This happens because of the way Phoenix's &lt;code&gt;Conn.recycle/1&lt;/code&gt; function works.&lt;br&gt;
Here's what's happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In your tests, you create a new &lt;code&gt;conn&lt;/code&gt; with a specific IP address (e.g., &lt;code&gt;remote_ip = "192.168.0.100"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;You then dispatch an HTTP request to this connection using something like &lt;code&gt;get("/")&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When you do this, Phoenix's Conn framework calls the &lt;code&gt;Conn.recycle/1&lt;/code&gt; function behind the scenes.&lt;/li&gt;
&lt;li&gt;This &lt;code&gt;recycle&lt;/code&gt; function is designed to clean up and reset the connection for subsequent requests.&lt;/li&gt;
&lt;li&gt;However, as part of this cleanup process, it loses the previously set &lt;code&gt;remote_ip&lt;/code&gt; information.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason why &lt;code&gt;host&lt;/code&gt; is preserved but not &lt;code&gt;remote_ip&lt;/code&gt; is that Phoenix's Conn framework has a special handling for the &lt;code&gt;host&lt;/code&gt; attribute. When you set the &lt;code&gt;host&lt;/code&gt; attribute, it gets stored in the connection's internal state. This means that when you recycle the connection, the &lt;code&gt;host&lt;/code&gt; information is still retained.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;code&gt;remote_ip&lt;/code&gt; is not preserved because it's not explicitly stored in the connection's internal state. Instead, it's just a temporary value set during the test execution.&lt;br&gt;
To maintain the &lt;code&gt;remote_ip&lt;/code&gt; information across tests, you can create a custom Conn module that overrides the default behavior of &lt;code&gt;Conn.recycle/1&lt;/code&gt;. This would involve storing the original &lt;code&gt;remote_ip&lt;/code&gt; value and restoring it after recycling the connection.&lt;/p&gt;

&lt;p&gt;In summary, &lt;code&gt;remote_ip&lt;/code&gt; is lost in test cases because Phoenix's &lt;code&gt;Conn.recycle/1&lt;/code&gt; function doesn't explicitly preserve this information. To keep &lt;code&gt;remote_ip&lt;/code&gt; intact, you'll need to create a custom Conn module that overrides the default behavior of &lt;code&gt;recycle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sources:&lt;br&gt;&lt;br&gt;
/Users/joshuabauer/code/rag_time/phoenix/test/phoenix/test/conn_test.exs,&lt;br&gt;&lt;br&gt;
[…]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98if0pkhle3ac3zcl2ma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98if0pkhle3ac3zcl2ma.png" alt="Screnshot of briefing answer" width="800" height="1136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Implement a solution
&lt;/h4&gt;

&lt;p&gt;This would give us a hint on how to solve the issue in our app if we were &lt;em&gt;"using"&lt;/em&gt; the Phoenix framework. But let's say we want to fix the issue &lt;em&gt;"for good"&lt;/em&gt; in the framework itself. We now know the &lt;code&gt;Conn.recycle/1&lt;/code&gt; function is what we're interested in. So we look at the listed sources, search for the &lt;code&gt;recycle/1&lt;/code&gt; function in the code and find &lt;code&gt;ConnTest/recycle/1&lt;/code&gt; as the test-conn equivalent to &lt;code&gt;Conn.recycle/1&lt;/code&gt;. This is pretty much already be the "custom Conn module" the RAG was referring to and it's just used for tests. And after all, we only want to change the behavior for tests. So let's ask the RAG what to do with it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;We:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Given the following code from &lt;code&gt;phoenix/lib/phoenix/test/conn_test.ex&lt;/code&gt;, what would I need to change to persist &lt;code&gt;conn.remote_ip&lt;/code&gt; in the same way as &lt;code&gt;conn.host&lt;/code&gt;?&lt;/p&gt;


&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;recycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="sx"&gt;~w(accept accept-language authorization)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;build_conn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recycle_cookies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_peer_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_peer_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;copy_headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Chatbot:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A Phoenix ConnTest question!&lt;/p&gt;

&lt;p&gt;To persist &lt;code&gt;conn.remote_ip&lt;/code&gt; in the same way as &lt;code&gt;conn.host&lt;/code&gt;, you would need to add a line to the &lt;code&gt;recycle/2&lt;/code&gt; function to copy the remote IP address.&lt;/p&gt;

&lt;p&gt;Here's how:&lt;/p&gt;


&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;recycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="sx"&gt;~w(accept accept-language authorization)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;build_conn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:remote_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Add this line&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recycle_cookies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Test&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_peer_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Plug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_peer_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;copy_headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Now, when you recycle a connection, both &lt;code&gt;conn.host&lt;/code&gt; and &lt;code&gt;conn.remote_ip&lt;/code&gt; will be preserved.&lt;/p&gt;

&lt;p&gt;Sources:&lt;br&gt;&lt;br&gt;
[…]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3pdem8f2ylgzc8xznla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3pdem8f2ylgzc8xznla.png" alt="Screnshot of dialogue about implementation" width="800" height="1136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Compare solution
&lt;/h4&gt;

&lt;p&gt;Looking at the PR's &lt;a href="https://github.com/phoenixframework/phoenix/pull/5792/files"&gt;file changes&lt;/a&gt;, this is exactly what the person came up with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8259c3h4gdr6mf76l1hh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8259c3h4gdr6mf76l1hh.png" alt="screenshot of the PR file changes on GitHub" width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the conversation with our Elixir RAG was quite helpful for guiding us through the code and finding an adequate solution to the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself!
&lt;/h2&gt;

&lt;p&gt;It is really easy! Just &lt;a href="https://github.com/bitcrowd/rag_time"&gt;clone our repo&lt;/a&gt;, follow the README and tell the script where to find your codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CODEBASE_PATH="./path-to-my-elixir-codebase"
CODEBASE_LANGUAGE="elixir"
CODE_SUFFIXES=".ex, .exs"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We kept the scripts basic, so that they are easy to understand and extend. Depending on your codebase, the results might not always be perfect, but often surprisingly good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

&lt;p&gt;In this post we saw how we can extend a simple off-the-shelf system to better fit the needs of our dev team. We enabled our RAG system to read and understand Elixir code! Text splitting and chunking is just one possible example of where to start when it comes to adjusting a RAG system for your specific needs. What we got is already quite useful, but it's definitely still lacking precision.&lt;/p&gt;

&lt;p&gt;We will explore possibilities for further improvements and fine tuning in the next episodes of this blog post series.&lt;/p&gt;

&lt;p&gt;Or, if you canʼt wait, give the team at bitcrowd a shout via &lt;a href="mailto:granny-rag@bitcrowd.net"&gt;granny-rag@bitcrowd.net&lt;/a&gt; or &lt;a href="https://calendly.com/calendly-6kjr/30min"&gt;book a consulting call here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>rag</category>
      <category>ai</category>
      <category>elixir</category>
    </item>
    <item>
      <title>How even the simplest RAG can empower your team</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 26 Jun 2024 13:34:01 +0000</pubDate>
      <link>https://forem.com/bitcrowd/how-even-the-simplest-rag-can-empower-your-team-15ai</link>
      <guid>https://forem.com/bitcrowd/how-even-the-simplest-rag-can-empower-your-team-15ai</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;This is the first part of a series of blog posts on using a RAG (Retrieval Augmented Generation) information system for coding. Find out how this can empower your development team.&lt;/p&gt;

&lt;p&gt;In this episode, we will discuss at a very simple &lt;a href="https://www.datacamp.com/blog/what-is-retrieval-augmented-generation-rag"&gt;RAG&lt;/a&gt; system for Ruby made with &lt;a href="https://python.langchain.com/v0.2/docs/tutorials/"&gt;LangChain&lt;/a&gt;, &lt;a href="https://jina.ai/news/elevate-your-code-search-with-new-jina-code-embeddings"&gt;JinaAI embeddings&lt;/a&gt; and a very light local LLM served via &lt;a href="https://ollama.com/"&gt;Ollama&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you donʼt know what any of this means, this blog post is for you. You will see how to benefit from the technology without uploading/donating your codebase to external providers like &lt;a href="https://openai.com/"&gt;OpenAI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is an introduction, the coding follows in the next episodes.&lt;/p&gt;

&lt;p&gt;Naming is hard, and I wanted something that goes against the flashy-ai-y. So letʼs call this a &lt;strong&gt;&lt;em&gt;Granny RAG.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The next parts will then move on to the following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;examine different embedding models&lt;/li&gt;
&lt;li&gt;splitting and chunking strategies&lt;/li&gt;
&lt;li&gt;RAG for and with &lt;a href="https://elixir-lang.org/"&gt;Elixir&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Letʼs start.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Use Case
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Imagine you are developer and you just started a new job. The code base looks well maintained, the team is nice. Could be &lt;a href="https://bitcrowd.net/de/careers"&gt;bitcrowd&lt;/a&gt;. You grab your first ticket, and it looks like this:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb3mqli6ae7nbj12v1or.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcb3mqli6ae7nbj12v1or.png" alt="A ticket with a missing description" width="501" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Uff. No description. Normally, you would reach out to your onboarding-buddy and ask for advise. This ticket is clearly written with a developers who knows the application in mind.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But ... you could also quickly investigate. Your team does have a &lt;strong&gt;Granny&lt;/strong&gt; after all!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You go to your local granny. Granny RAG is in fact just some light off-the-shelf application wrapper around a simple RAG system, but you are used to call her Granny RAG, because she is patient, kind and &lt;strong&gt;knows all the stories&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Letʼs ask her:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Hy Granny&lt;/strong&gt;, I need to show the end date of the subscriptions in the “subscription list”. What is a subscription in the application?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Granny&lt;/strong&gt;: To display the end dates of subscriptions in the list of subscriptions, you can modify the COLLECTION_ATTRIBUTES constant in your SubscriptionDashboard class to include the active_until field. Hereʼs an updated version of the SubscriptionDashboard class with the modified COLLECTION_ATTRIBUTES:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SubscriptionDashboard &amp;lt; ApplicationDashboard
 ATTRIBUTE_TYPES = {
   course: Field::BelongsTo.with_options(order: :name),
   user: Field::BelongsTo.with_options(order: :name),
   user_id: Field::Number,
   id: Field::String,
   active_until: Field::DateTime,
   deactivated_at: Field::DateTime,
   active?: Field::Boolean,
   product_type: EnumField,
   created_at: Field::DateTime,
   updated_at: Field::DateTime
 }.freeze

 COLLECTION_ATTRIBUTES = %i[
   course
   user
   id
   active?
   product_type
   active_until
 ].freeze
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Wow, this is magic!
&lt;/h2&gt;

&lt;p&gt;Without knowing the application too well, you came from a non-workable ticket to a plan of action. You can now chose to update the ticket description, or create a PR and use that for the discussion.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h3&gt;
  
  
  ℹ️ Note
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is a real life ticket from a client.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This ticket came from the work at our client &lt;a href="https://medgurus.de"&gt;MedGurus&lt;/a&gt;. When I tried out my Granny RAG, I browsed through the tickets in progress and found this one. I compared the solution Granny RAG proposed with the PR that came out of this ticket. I was actually thrilled how well this works.&lt;/p&gt;

&lt;p&gt;My conversation with Granny RAG was actually a a bit longer. &lt;a href="https://bitcrowd.dev/how-even-the-simplest-RAG-can-empower-your-team/#wow-this-is-magic"&gt;Here&lt;/a&gt; is the full conversation with Granny RAG.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How does it all work?
&lt;/h2&gt;

&lt;p&gt;Granny RAG is a RAG system. That is short for Retrieval Augmented Generation. If you are looking for a quick intro, &lt;a href="https://www.youtube.com/watch?v=T-D1OfcDW1M&amp;amp;t=10s"&gt;here is the nice video by Marina Danilevsky&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In essence, RAG improves the quality of LLM responses by enriching user prompts with relevant contextual information. It retrieves this information from an efficiently searchable index of of your entire project, generated with the help of an &lt;a href="https://huggingface.co/blog/getting-started-with-embeddings"&gt;&lt;em&gt;embedding model&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedding models
&lt;/h3&gt;

&lt;p&gt;Itʼs not easy to say something simple about the embedding process without being incorrect. Embedding models are models that generate a representation of the “meaning” sequence of text. This “meaning” is represented as a vector called “embedding”. It is a long array of numbers that represent semantic meaning within the given context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsakc2pefzvm5bdinec7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsakc2pefzvm5bdinec7.jpg" alt="Embeddings represent meaning as multidimentional vectors" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tokens with a similar &lt;strong&gt;meaning&lt;/strong&gt; in the source document get embedding vectors “close to each other” by some distance measurement.&lt;/p&gt;

&lt;p&gt;A suitable model will place expressions with &lt;em&gt;similar meaning&lt;/em&gt; in &lt;em&gt;similar spaces&lt;/em&gt; of its &lt;em&gt;vector space&lt;/em&gt;. So &lt;code&gt;subscription&lt;/code&gt; will be next to &lt;code&gt;activation&lt;/code&gt; and &lt;code&gt;active_until&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can think of the process as hashing with hashing function that understands the input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieval
&lt;/h3&gt;

&lt;p&gt;Instead, when the user asks a question, we throw it into the same embedding function to get an index for it. With that, we do a lookup what sequences of text occupy a similar space in the memory.&lt;/p&gt;

&lt;p&gt;There are multiple strategies for this similarity criteria. We will explore similarity in more depth in the second post of this series. For now, letʼs assume we found entries “close” to the index we got for the search term.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mzao92zx5nzmmyf7yhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mzao92zx5nzmmyf7yhh.png" alt="a picture showing an embedding in the vector space" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each of those entries carries a piece of text and some metadata. The metadata tells us more about the source, e.g. which file it came from. Until now, we have build a more intelligent search function. It finds &lt;code&gt;active_until&lt;/code&gt; even if you searched for &lt;code&gt;end date&lt;/code&gt;. Something, a classic fulltext index would not find.&lt;/p&gt;

&lt;p&gt;In an “old fashioned” information system, we would output those magical pieces of text and leave it to the reader to go through them, understand their meaning and evaluate their relevance.&lt;/p&gt;

&lt;p&gt;“But wait”, you say, “are there not these new cool kids on the block, &lt;strong&gt;The LLMʼs&lt;/strong&gt;™, that are brilliant at exactly that?”. You are right, this is exactly what RAG systems do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Attention: We will be simplifying heavily.&lt;/em&gt;&lt;/strong&gt; If you would like to get a l ightweight intro head over to this &lt;a href="https://huggingface.co/learn/nlp-course/chapter1/1"&gt;huggingface course&lt;/a&gt;, or this series of videos from &lt;a href="https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi"&gt;three blue one brown&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It boils down to this: When LLMs generate, they find the next word, or gaps in a text. They take this a step at a time, a bit like friends finishing each otherʼs sentences.&lt;/p&gt;

&lt;p&gt;Then, they look at the text created, including the new word, and compile the next word, and the next. Put differently, they try to find the piece of text or the character that is most likely to make sense in the previously generated context.&lt;/p&gt;

&lt;p&gt;Here is an example for a prompt that uses RAG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an assistant for question-answering tasks. Use the following pieces of
retrieved context to answer the question. If you donʼt know the answer, just
say that you donʼt know.
Use three sentences maximum and keep the answer concise. # (1)
--
Question: “What would I need to change to show the active_until date in the list
of subscriptions?” # (2)

Context: {context} # &amp;lt;- The RAG magic happens here

Answer: # (3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;h4&gt;
  
  
  ℹ️ Info
&lt;/h4&gt;

&lt;p&gt;A system prompt tells the LLM what is expected from it (1), then a question is specifying the task (2) and the “please fill in your answer here”-part (3) is what LLMs are used to work with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LLMs do so, again, based on vector representations. Starting from a seed, often the system prompt, and the userʼs instructions.&lt;/p&gt;

&lt;p&gt;The idea of RAG is that if you include facts that you researched into your prompt, the context for the generation is narrowed down significantly compared to a prompt that does not include those facts. Retrieval Augmented Generation is an effective countermeasure against hallucinations. It does not stop them, but makes them less likely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  💡 Tip
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Outline the Important Findings in your follow up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All LLM based systems hallucinate at some point. RAG helps to avoid that, but as you can see in &lt;strong&gt;5. Follow Up&lt;/strong&gt;, even Retrieval Based Systems stray from the truth at times. You can detect that because the information in &lt;strong&gt;5. Follow Up&lt;/strong&gt; does not align with the previous answers.&lt;/p&gt;

&lt;p&gt;If this happens, &lt;strong&gt;it helps to outline the previous facts in the next prompt&lt;/strong&gt;, as I did in &lt;strong&gt;6. Follow Up&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;“You said before that the &lt;code&gt;COLLECTION_ATTRIBUTES&lt;/code&gt; are responsible for the list of &lt;code&gt;Subscriptions&lt;/code&gt; (...)”&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Mentioning previous findings in the new prompt amplifies them in the context.&lt;/strong&gt; This steers the conversation in the direction you like and helps helps the LLM to let go of the hallucinations.&lt;/p&gt;


&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;You can find the code &lt;a href="https://github.com/bitcrowd/rag_time"&gt;on bitcrowdʼs GitHub&lt;/a&gt;, which is probably easier to read than going through the inline version here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddwlxn49hvt0umo226sw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddwlxn49hvt0umo226sw.png" alt="Clone the code from the bitcrowd rag_time repo" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://python.langchain.com/v0.2/docs/tutorials/"&gt;LangChain&lt;/a&gt;, which is a Python library. There are also implementations of it in &lt;a href="https://js.langchain.com/v0.2/docs/tutorials/"&gt;JavaScript&lt;/a&gt; and &lt;a href="https://github.com/brainlid/langchain"&gt;Elixir&lt;/a&gt;. We use Python to start with, because it is the common language for machine learning, so prototyping is easy. Once we know what we want, we will explore the Elixir way. So please make sure you have Python installed.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://jina.ai/news/elevate-your-code-search-with-new-jina-code-embeddings"&gt;JinaAI embeddings&lt;/a&gt;, which is a very capable model that can be used locally under the Apache 2 licence.&lt;/p&gt;

&lt;p&gt;Finally, you need &lt;a href="https://ollama.com/"&gt;Ollama&lt;/a&gt;, or any other tool that letʼs you run a model and expose it to a web endpoint. In the example, we use Meta's &lt;a href="https://llama.meta.com/llama3/"&gt;Llama3&lt;/a&gt; model. Models like CodeLlama:7b-instruct also work. Feel free to change the &lt;code&gt;.env&lt;/code&gt; file and experiment with different models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Drive
&lt;/h2&gt;

&lt;p&gt;You need some Ruby library that we can try out here. Letʼs go for &lt;a href="https://github.com/discourse/discourse"&gt;Discourse&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Weʼre interested in the logic Discourse uses prevent users from re-using an old, expired password and which files in the code are relevant for this. So we ask it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How does Discourse keep track of which passwords a user already used? So that when changing their password, a user does not use the same, expired password again.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitf2i9znzyxx49hmd1xf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fitf2i9znzyxx49hmd1xf.png" alt="A screenshot of the chat UI when entering the question" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Granny RAG&lt;/strong&gt; answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;According to the context you provided, Discourse keeps track of which passwords a user already used by storing the old password hash and algorithm in the &lt;code&gt;user_passwords&lt;/code&gt; table. This is done through the following mechanisms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;user_passwords&lt;/code&gt; table stores the userʼs previous passwords along with their corresponding salts and algorithms.&lt;/li&gt;
&lt;li&gt;When a user changes their password, Discourse generates a new password hash using the new password, salt, and algorithm.&lt;/li&gt;
&lt;li&gt;Discourse then checks if the new password hash is already present in the &lt;code&gt;user_passwords&lt;/code&gt; table for that user. If it is, it means the user has used this password before, and therefore, itʼs considered expired.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By storing the old password hashes and algorithms in the database, Discourse can easily verify whether a user has used a particular password before and prevent them from using an expired password again.&lt;/p&gt;

&lt;p&gt;This approach ensures that users cannot reuse the same password twice, even if they change their password multiple times.&lt;br&gt;
…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes84i77xtbnnit7dvld0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes84i77xtbnnit7dvld0.png" alt="A screenshot of the chat UI conversation" width="800" height="1128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without knowing all the details of Discourseʼ codebase, this seems a perfectly valid answer. It gives us a nice summary of the feature an idea on where to go looking for the implementation in Discourse otherwise overwhelmingly big codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;For our InfoServer Granny RAG we created a sophisticated search index for our codebase. This index allows us to infer meaning. We can now search for an answer by infering the meaning of the question and retrieve relavant information by looking what is close in the vector space.&lt;/p&gt;

&lt;p&gt;We fed this information to an LLM to understand and prioritize the sources. We made the LLM create answers to our questions, based on its training data and the information retrieved.&lt;/p&gt;

&lt;p&gt;So with a few, off-the-shelf, open source components, we manage to get expert knowledge about our code base, and get it delivered to us by our friendly LLM companion, hosted on our own premises.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is this different from Copilot, Duo and friends?
&lt;/h2&gt;

&lt;p&gt;If you ask Granny RAG a question, it can draw &lt;strong&gt;information from the whole codebase&lt;/strong&gt;. It is able to incorporate &lt;strong&gt;all kinds of documents&lt;/strong&gt;, and can be extended to incorporate &lt;strong&gt;additional data-sources&lt;/strong&gt;. Granny RAG operates on &lt;strong&gt;resource effective, local LLMs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No data needs to leaves your control.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The scripts that ingest and embed your data and code can be specific to your needs&lt;/strong&gt; - as is your codebase. That way, you can even specify &lt;strong&gt;what should, and what should not, find its way into your RAG knowledge base&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copilot and GitLab Duo have a much narrower angle of vision.&lt;/strong&gt; Their context is primarily the opened files of the editor, or the PR. That means, once you know where to look, they can be helpful. Both to you and their creators, which can (and probably will) use &lt;em&gt;some&lt;/em&gt; data to improve their models. Even if, per contract, your data and code should not be shared with GitLab or Microsoft, you lost all control once your data leaves the premises.&lt;/p&gt;

&lt;p&gt;If you set theses concerns aside, you still have &lt;strong&gt;little control about what makes its way into the LLMs that are hosted on remote servers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here again, &lt;strong&gt;Granny RAG is different&lt;/strong&gt;. You can collect data from usage and reactions, and you can use that data to &lt;strong&gt;train both, LLM and embedding model, on your data and needs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That way, new arrivals in your dev team get an assistant that is steadily improving. &lt;strong&gt;Granny RAG can integrate into a &lt;a href="https://slack.com/"&gt;Slack&lt;/a&gt; channel to provide a first opinion&lt;/strong&gt;, and take feedback from the more seasoned developers to improve.&lt;/p&gt;

&lt;p&gt;All in all, Granny RAG is a concept that can (and should) be adopted to your use-case and needs. Itʼs not a subscription you buy, but a technique your team learns to master. You invest in consulting or learning time, and you get control and excellent knowledge about the core or your business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself!
&lt;/h2&gt;

&lt;p&gt;It is really easy! Just &lt;a href="https://github.com/bitcrowd/rag_time"&gt;clone our repo&lt;/a&gt;, follow the README and tell the script where to find your codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CODEBASE_PATH="./path-to-my-codebase"
CODEBASE_LANGUAGE="ruby"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We kept the scripts basic, so that they are easy to understand and extend. Depending on your codebase, the results might not always be perfect, but often surprisingly good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

&lt;p&gt;In this introductory post, we saw what a little off-the-shelf system can achieve. Itʼs already impressive, and it only uses local models, namely Llama3 and JinaAI Code.&lt;/p&gt;

&lt;p&gt;You will find that this off-the-shelf solution is lacking precision in some use cases. To improve this, we will explore how changes in the parsing, chunking and embedding strategies will change performance in the next episodes of this blog post series.&lt;/p&gt;

&lt;p&gt;Or, if you canʼt wait, give the team at bitcrowd a shout via &lt;a href="mailto:granny-rag@bitcrowd.net"&gt;granny-rag@bitcrowd.net&lt;/a&gt; or &lt;a href="https://calendly.com/calendly-6kjr/30min"&gt;book a consulting call here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>rag</category>
      <category>ai</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Git, GitHub and your work email</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 09 Aug 2021 16:35:55 +0000</pubDate>
      <link>https://forem.com/bitcrowd/git-github-and-your-work-email-365i</link>
      <guid>https://forem.com/bitcrowd/git-github-and-your-work-email-365i</guid>
      <description>&lt;p&gt;A possible setup for separating private and work-related contributions on &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt;: associate commits with your &lt;em&gt;"private"&lt;/em&gt; vs. your "&lt;em&gt;work"&lt;/em&gt; email.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com"&gt;GitHub&lt;/a&gt; being such a convenient place to collaborate on software, it's likely that both, work and &lt;em&gt;"leassure coding"&lt;/em&gt; take place on the same platform. Do you need two separate accounts for both activities? Eventually yes… But to spare the hassle of switching accounts back and forth and remembering to actually do that, a lot of people - including myself - prefer to use a single account (as long as this is legally aligned with your work place). However, aside from keeping that yin-yang of a work-life-balance, fully blending in your work-related coding into your private ramblings on GitHub and vice versa feels wrong from multiple angles, mainly the legal one. In which role did you contribute this piece of code, who &lt;em&gt;"owns"&lt;/em&gt; this contribution now? You, your employee, a client?&lt;/p&gt;

&lt;p&gt;So a bit of separation can come in handy. But how to get there without juggling accounts? Say hi to your old friend from the 80s: &lt;strong&gt;email&lt;/strong&gt; 📧&lt;/p&gt;

&lt;p&gt;Different to what one would expect on first glance, GitHub does not directly associate contributions with accounts. It's rather just &lt;em&gt;"connecting"&lt;/em&gt; the contribution to the account that happens to own the email address associated with a Git commit &lt;sup&gt;¹&lt;/sup&gt;. Listen to the &lt;a href="https://www.bikeshed.fm/35"&gt;GitHub Ate My Résumé&lt;/a&gt; episode of &lt;a href="https://www.bikeshed.fm"&gt;The Bike Shed&lt;/a&gt; podcast for some wild situations this can bring…&lt;/p&gt;

&lt;p&gt;It's probably save to assume most people have a dedicated email address for work. Using your private email address for work-related communication uncomfortably stresses the yin-yang and should be avoided. And since we're already there, why not apply the same email hygiene to your commits? Use your work email address for work contributions and your &lt;code&gt;riotcat1991@smthng&lt;/code&gt; address for your private coding adventures.&lt;/p&gt;

&lt;p&gt;Here are some tips how I set this up for myself. Disclaimer: people are different. While this works fine for me, other solutions may eventually work better for you - find what feels comfy 🧘.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Config ⚙️
&lt;/h2&gt;

&lt;p&gt;The setup mainly lives in the &lt;a href="https://git-scm.com/docs/git-config"&gt;gitconfig&lt;/a&gt; files in my home directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/
├── .gitconfig
├── .gitconfig_personal
└── .gitconfig_work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gitconfig
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;.gitconfig&lt;/code&gt;, set a name via &lt;code&gt;user.name&lt;/code&gt; and deliberately &lt;strong&gt;not configure&lt;/strong&gt; any email address in &lt;code&gt;user.email&lt;/code&gt;. I combine this with setting &lt;a href="https://git-scm.com/docs/git-config#Documentation/git-config.txt-useruseConfigOnly"&gt;&lt;code&gt;user.useConfigOnly&lt;/code&gt;&lt;/a&gt; to &lt;code&gt;true&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instruct Git to avoid trying to guess defaults for &lt;code&gt;user.email&lt;/code&gt; and &lt;code&gt;user.name&lt;/code&gt;, and instead retrieve the values only from the configuration&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Recent versions of Git don't allow to commit without having a name and email address configured for the repository. The email address is an integral part of a commit. By not configuring one globally and instructing Git to not magically figure one out itself, I can avoid accidentally committing with the &lt;em&gt;"wrong"&lt;/em&gt; email address.&lt;/p&gt;

&lt;p&gt;With this configuration, I now have to manually set &lt;code&gt;user.email&lt;/code&gt; for every repository. For convenience, I have two alias set up for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;alias&lt;/span&gt;]
  &lt;span class="n"&gt;personal&lt;/span&gt; = &lt;span class="s2"&gt;"config user.email 'me@private.internet'"&lt;/span&gt;
  &lt;span class="n"&gt;work&lt;/span&gt; = &lt;span class="s2"&gt;"config user.email 'me@work.internet'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside a Git repository, running &lt;code&gt;git work&lt;/code&gt; configures the email to be used for commits in that repository to my work address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Directories
&lt;/h3&gt;

&lt;p&gt;In most cases, it's obvious which email address I want to use. On disk, my projects are organized in a folder structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/code/
├── work/
│   ├── client_a
│   │   ├── projetc_a
│   │   └── projetc_b
│   ├── internal
│   │   ├── project_c
│   │   └── project_d
│   └── client_b
└── personal/
    ├── project_a
    └── project_a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my &lt;code&gt;.gitconfig&lt;/code&gt; file I include private or work-related additional configuration depending on the directory path of a repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;includeIf&lt;/span&gt; &lt;span class="s2"&gt;"gitdir:code/personal/"&lt;/span&gt;]
  &lt;span class="n"&gt;path&lt;/span&gt; = ~/.&lt;span class="n"&gt;gitconfig_personal&lt;/span&gt;
[&lt;span class="n"&gt;includeIf&lt;/span&gt; &lt;span class="s2"&gt;"gitdir:code/work/"&lt;/span&gt;]
  &lt;span class="n"&gt;path&lt;/span&gt; = ~/.&lt;span class="n"&gt;gitconfig_work&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These &lt;em&gt;personal&lt;/em&gt; and &lt;em&gt;work&lt;/em&gt; sub-configurations only configure &lt;code&gt;user.email&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitconfig_personal
&lt;/span&gt;[&lt;span class="n"&gt;user&lt;/span&gt;]
  &lt;span class="n"&gt;email&lt;/span&gt; = &lt;span class="n"&gt;me&lt;/span&gt;@&lt;span class="n"&gt;work&lt;/span&gt;.&lt;span class="n"&gt;internet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This spares manually running &lt;code&gt;git work&lt;/code&gt; or &lt;code&gt;git personal&lt;/code&gt; in every repo. Cloning a new project at work into a subdirectory of &lt;code&gt;~/code/work&lt;/code&gt;, the configuration already tells Git to use my work email.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub 🐙
&lt;/h2&gt;

&lt;p&gt;Your work email needs to be added to your GitHub account: &lt;a href="https://github.com/settings/emails"&gt;https://github.com/settings/emails&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub UI
&lt;/h3&gt;

&lt;p&gt;Merging pull requests in the GitHub UI or accepting change suggestions also creates a commit. Similar to Git itself, GitHub doesn't associate commits with your account, but associates the account via the email address the commit is made with 🤯. For UI interactions, this is a bit tricky to get right.&lt;/p&gt;

&lt;p&gt;On the first UI interaction in a repository, GitHub shows a dropdown to select the email address to use for the resulting commit. It then stores this email address for that repository and continues to use it for all UI interactions without showing a dropdown to change it ever again. This means, after having used the &lt;em&gt;wrong&lt;/em&gt; email once, it is a bit hard to get out of this again.&lt;/p&gt;

&lt;p&gt;I already reached out to GitHub support for this problem, but they could not help me. For now, the only &lt;em&gt;"hack"&lt;/em&gt; I found to switch the email again is to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the respective repository&lt;/li&gt;
&lt;li&gt;Click on the 🖊 icon to edit a file (e.g. &lt;code&gt;README.md&lt;/code&gt;) in the web editor&lt;/li&gt;
&lt;li&gt;You can choose your email again from a dropdown when commiting&lt;/li&gt;
&lt;li&gt;GitHub will continue to use this email from now on for this repo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; this email juggling in the UI is what worked for me back then, it may already be different now.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;1&lt;/a&gt;: &lt;a href="https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address#about-commit-email-addresses"&gt;https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address#about-commit-email-addresses&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>git</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Script up your projects</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Tue, 02 Feb 2021 18:08:51 +0000</pubDate>
      <link>https://forem.com/bitcrowd/script-up-your-projects-lhn</link>
      <guid>https://forem.com/bitcrowd/script-up-your-projects-lhn</guid>
      <description>&lt;p&gt;At &lt;a href="https://bitcrowd.net/"&gt;bitcrowd&lt;/a&gt;, we &lt;a href="https://github.com/bitcrowd/tickety-tick#readme"&gt;love conventions&lt;/a&gt;. Being an agency, we jump projects, codebases, frameworks and languages quite a lot - to a point where it's impossible to keep all the details about every project one's head. It's not necessary to actually have this knowledge in practise though. &lt;em&gt;Some&lt;/em&gt; people knowing the details of &lt;em&gt;some&lt;/em&gt; projects is enough. They can onboard others who then slowly forget the details of their previous projects… A well-established, semi-stable cycle of knowledge transfer with some silos here and some information lost there - but overall working.&lt;/p&gt;

&lt;p&gt;Still, there is this nice and fluffy feeling of coming to a new project, seamlessly setting it up and immediately finding your way around. Taking developers by their hands upon entry, these &lt;a href="https://en.wikipedia.org/wiki/Hygge"&gt;hygge&lt;/a&gt; codebases appear nice, friendly and motivating. And developer happiness aside, even on the dark and cold business side of things, people coming to these "&lt;em&gt;welcoming"&lt;/em&gt; codebases are probably likely to be more productive with the head start they get there.&lt;/p&gt;

&lt;p&gt;How can we get to this shiny place?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conventions ⚖️
&lt;/h2&gt;

&lt;p&gt;Notorious self-optimization and reaching for ultimate &lt;em&gt;"productivity"&lt;/em&gt; are neoliberal derailments. But decluttering daily tasks from unnecessary cognitive overhead, can actually have a calming, decelerating effect on our personal work life: it gives us a cleaner, virtual &lt;em&gt;"desk"&lt;/em&gt;, less stress and easier time to set details aside and focus on the important things.&lt;/p&gt;

&lt;p&gt;In software development, conventions can be a useful tool to reduce the cognitive effort involved when switching between projects. Backed by a known outline of what to expect where, developers can, for the moment, put aside the nitty-gritty details of which version X of framework Y a project uses and instead focus on things like &lt;em&gt;domain&lt;/em&gt; or &lt;em&gt;datamodel&lt;/em&gt; to easier wrap their head around the new problem space. Utilizing agreed upon patterns, we can take load off peoples' shoulders for the ultimate &lt;em&gt;"hygge"&lt;/em&gt; developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Situation 🚣
&lt;/h2&gt;

&lt;p&gt;In agency work, with its fast and frequent project switches, the &lt;em&gt;"onboarding"&lt;/em&gt; and "&lt;em&gt;getting started"&lt;/em&gt; phase on a codebase are especially crucial. But product teams shouldn't overwhelm new members with days of fiddling with different package managers either.&lt;/p&gt;

&lt;h3&gt;
  
  
  First Contact 🐣
&lt;/h3&gt;

&lt;p&gt;Coming to a new project. What are the first steps you &lt;strong&gt;always&lt;/strong&gt; take?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You most likely have a look at the README first &lt;em&gt;(hopefully it's a good one)&lt;/em&gt; 🔦&lt;/li&gt;
&lt;li&gt;You search the README for installation and setup instructions &lt;em&gt;(the classic "getting started")&lt;/em&gt; 🔎&lt;/li&gt;
&lt;li&gt;You spend a few ~minutes~ hours copy-pasting things from the README into your shell to get to a state where you can finally run the project 🚜&lt;/li&gt;
&lt;li&gt;You nag your colleagues for the steps which are missing, outdated or simply not documented 🕵️&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All in all you pretty much spend around half a day setting up the project and already lost the joy of contributing to it. Time, effort and motivation you could have spent on getting to know the domain better and that will be missing in later phases of the project.&lt;/p&gt;

&lt;p&gt;A sad situation for a group of people aiming to utilize computer to &lt;em&gt;"solve problems"&lt;/em&gt;. Projects with a bad setup and onboarding experience are likely to distract and discourage people. Coming out of a rough setup safari, one may feel insecure and as if one knew &lt;em&gt;"nothing"&lt;/em&gt; about the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day-in day-out 🌚🌝
&lt;/h3&gt;

&lt;p&gt;Once actually working on a project, one has to run certain development tasks now and then: database migrations, the test suite, managing translations, etc. One may want utilize all possible brain capabilities on memorizing those for each possible framework or alternatively just search up and down the shell history for the one command one at some point managed to successfully copy-and-paste. But what if you don't even know what to search for?&lt;/p&gt;

&lt;p&gt;A classic example: You come back to the project after a few days on a different one. You fetch the latest changes on the &lt;code&gt;main&lt;/code&gt; branch and then… Run the migrations? Update your JavaScript version? Update packages and then run migrations? Is it &lt;code&gt;npm install&lt;/code&gt; or &lt;code&gt;yarn install&lt;/code&gt;? A steam of questions and decisions to take before you can even start your editor or run the first test. And there is also the extra cognitive overload of context switches within the project: switching between feature branches, pairing with a colleague on their ticket, etc. &lt;/p&gt;

&lt;p&gt;Agreeing on conventions, on a common way to do and approach things across projects may reduce this overload and give people an easier time thought the day 🐖.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scripts to the Rescue 🚑
&lt;/h2&gt;

&lt;p&gt;People at Github made an attempt to fix this situation: &lt;a href="https://github.com/github/scripts-to-rule-them-all"&gt;scripts to rule them all&lt;/a&gt;. The idea is to have common set of executable scripts for common developer tasks in a &lt;code&gt;script/&lt;/code&gt; directory in the root of every project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A consistent bootstrapping experience across all our projects reduces friction and encourages contribution.¹&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While every project may use different tools or languages, the &lt;code&gt;script&lt;/code&gt; directory consistently following the same pattern everywhere gives developers something to hold on to. Scripts as an anchor in the sea of cognitive overload after checking out a codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;script/bootstrap&lt;/code&gt; to install/update dependencies&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;script/setup&lt;/code&gt; to set up a project for the first time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;script/update&lt;/code&gt; to update a project to run at its current version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;script/test&lt;/code&gt; to run tests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;script/console&lt;/code&gt; to opens a console&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technical setup instructions can of course live as 100+ copy-pasteable steps in the README. Putting them into a script has roughly the same effort but a huge benefit: it can do the work for future developers 🤖. Scripts can work well as a runnable documentation for a project's tooling setup. Imagine switching between Python and JavaScript projects. How to run migrations here, what are the default CLI flags there? Instead of infinitely searching the shell history, simple scripts in the project can wrap and document common developer tasks.&lt;/p&gt;

&lt;p&gt;The selection of files in your &lt;code&gt;script&lt;/code&gt; directory of course depends on every team's individual situation and workflow. Just go with &lt;a href="https://en.wikipedia.org/wiki/Marie_Kondo"&gt;Marie Kondo&lt;/a&gt;, get rid of scripts which don't &lt;em&gt;"spark joy"&lt;/em&gt; and add others where you feel pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommendations 🛒
&lt;/h3&gt;

&lt;p&gt;We adapted this pattern for our own workflows at bitcrowd. Here are some loose recommendations on what we find useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;script/test-e2e&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a lot of projects, high level end-to-end tests are slower. So it's nice to be able to run them with a separate command. With that, &lt;code&gt;script/test&lt;/code&gt; can focus on running unit tests only, finish faster and provide important feedback earlier.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;script/lint&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are obsessed with linters. Running them as part of the test suite does not fit our way of working. For us it makes sense to run tests and linters separately as they hint at different problems in your code. Maybe one wants to not care about linting until one has a working implementation or tests and prefers to do linting and code cosmetics later?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;script/format&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some ecosystems support formatters. If machines can do the formatting for us, why not accept their help?&lt;/p&gt;

&lt;h3&gt;
  
  
  Your own 🎨
&lt;/h3&gt;

&lt;p&gt;Add your own scripts, like &lt;code&gt;script/deploy&lt;/code&gt;, &lt;code&gt;script/psql&lt;/code&gt;, etc., there are no limits. Come up with whatever suits your organization and team. The only thing: &lt;strong&gt;stay consistent&lt;/strong&gt; and have conventions. That's where the real benefits lie.&lt;/p&gt;

&lt;p&gt;For instance &lt;code&gt;script/manage&lt;/code&gt; to facilitate the &lt;a href="https://docs.djangoproject.com/en/3.1/ref/django-admin/"&gt;&lt;code&gt;manage.py&lt;/code&gt;&lt;/a&gt; utility in &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Run manage.py tasks&lt;/span&gt;
&lt;span class="c"&gt;# Usage: scripts/manage [...args]&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; errexit
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nounset

&lt;span class="nv"&gt;SCRIPTDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRIPTDIR&lt;/span&gt;&lt;span class="s2"&gt;/.."&lt;/span&gt;

&lt;span class="nb"&gt;exec &lt;/span&gt;poetry run python manage.py &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or on a project which requires a specific version of a &lt;a href="https://postgis.net/"&gt;PostGIS&lt;/a&gt; database, using a utility script to to conveniently run the database in a &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; container. Developers may run &lt;code&gt;script/db start&lt;/code&gt; to start the database and &lt;code&gt;script/db stop&lt;/code&gt; to stop it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; errexit
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nounset
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail

&lt;span class="nv"&gt;CONTAINER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project_x_db

find_container&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  docker ps &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTAINER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

start_db&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_container&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--tty&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTAINER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C.UTF-8 &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--publish&lt;/span&gt; 5432:5432 &lt;span class="se"&gt;\&lt;/span&gt;
      postgis/postgis:9.6-2.5-alpine &lt;span class="se"&gt;\&lt;/span&gt;
      postgres
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;docker start &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTAINER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

stop_db&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;exec &lt;/span&gt;docker stop &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTAINER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

print_usage&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: db [&amp;lt;option&amp;gt;]"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Options:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  start   Start database."&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  stop    Stop database."&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  --help  Show this message and exit."&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-lt&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;print_usage
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

case&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;start&lt;span class="p"&gt;)&lt;/span&gt;
    start_db
    &lt;span class="p"&gt;;;&lt;/span&gt;
  stop&lt;span class="p"&gt;)&lt;/span&gt;
    stop_db
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    print_usage
    &lt;span class="nb"&gt;exit&lt;/span&gt;
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to do scripts? 🚌
&lt;/h2&gt;

&lt;p&gt;Who would ever blindly run a script on their machine? Better take a look at the contents first. If written, formatted and documented with care, scripts work as a great way to consistently document development workflows. And since we never get things right the first time: continue to update your scripts (e.g. when new people are onboarded and run into issues). Integrating your scripts into your daily workflows and running them regularly also helps to iron out rough edges. We for instance usually run &lt;code&gt;script/test&lt;/code&gt;, &lt;code&gt;script/test-e2e&lt;/code&gt; and &lt;code&gt;script/lint&lt;/code&gt; as part of our CI pipeline.&lt;/p&gt;

&lt;p&gt;When it comes to writing scripts, pick whatever suits your usecase and what you feel comfortable with. For the sake of compatibility, &lt;a href="https://www.gnu.org/software/bash/"&gt;Bash&lt;/a&gt; can be a good choice. But be aware, it can be tricky at times… ⚠️:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The weird thing about shell scripts is that even strong advocates of good practices gladly forget all they know when it comes to shell scripting.²&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Linting your shell scripts with &lt;a href="https://github.com/koalaman/shellcheck"&gt;Shellcheck&lt;/a&gt; can improve the situation a lot though. It is also a great resource and opportunity to learn about best practices.&lt;/p&gt;

&lt;p&gt;Here are some resources we found useful for leveling up our scripting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/koalaman/shellcheck"&gt;https://github.com/koalaman/shellcheck&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/thiht/shell-scripts-matter"&gt;https://dev.to/thiht/shell-scripts-matter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kvz.io/bash-best-practices.html"&gt;https://kvz.io/bash-best-practices.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/shell-script-suggestions-for-speedy-setups"&gt;https://thoughtbot.com/blog/shell-script-suggestions-for-speedy-setups&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion 🍼
&lt;/h2&gt;

&lt;p&gt;Scripting is not a silver bullet, but it can make developer lives a bit easier. And it's a great rabbit hole to get lost in 🕳&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;1&lt;/a&gt;: &lt;a href="https://github.blog/2015-06-30-scripts-to-rule-them-all/"&gt;https://github.blog/2015-06-30-scripts-to-rule-them-all/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;2&lt;/a&gt;: &lt;a href="https://blog.thibaut-rousseau.com/2017/12/03/shell-scripts-matter.html"&gt;https://blog.thibaut-rousseau.com/2017/12/03/shell-scripts-matter.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bash</category>
      <category>scripting</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Prepending Modules to the Rescue</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Thu, 26 Nov 2020 14:26:25 +0000</pubDate>
      <link>https://forem.com/bitcrowd/prepending-modules-to-the-rescue-24h6</link>
      <guid>https://forem.com/bitcrowd/prepending-modules-to-the-rescue-24h6</guid>
      <description>&lt;p&gt;Ruby's ability to &lt;em&gt;"overlay"&lt;/em&gt; default implementations of constants, methods and variables via the &lt;a href="https://ruby-doc.org/core-2.7.1/Module.html#method-i-prepend"&gt;&lt;code&gt;prepend&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://ruby-doc.org/core-2.7.1/Module.html#method-i-prepend_features"&gt;&lt;code&gt;prepend_features&lt;/code&gt;&lt;/a&gt; method on &lt;code&gt;Module&lt;/code&gt; can be a helpful tool when dealing with gems overriding setter methods for attributes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 🧫
&lt;/h2&gt;

&lt;p&gt;On a &lt;a href="https://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt; project, we were recently migrating from the &lt;a href="https://github.com/attr-encrypted/attr_encrypted"&gt;&lt;code&gt;attr_encrypted&lt;/code&gt;&lt;/a&gt; to the &lt;a href="https://github.com/ankane/lockbox"&gt;&lt;code&gt;lockbox&lt;/code&gt;&lt;/a&gt; gem for encrypting database fields. For live applications which cannot accept hours of downtime, the migration path is a multi-step process which includes a period where both gems are used side by side until all data has been migrated from the old columns to the new columns.&lt;/p&gt;

&lt;p&gt;Both gems integrate into Rails models via their own macro-style methods one is supposed to add to the models' class definitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;attr_encrypted&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;
  &lt;span class="n"&gt;attr_encrypted&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;

  &lt;span class="n"&gt;encrypts&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;migrating: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, both gems then dynamically generate the respective getter and setter methods for the attributes (&lt;code&gt;email&lt;/code&gt;, &lt;code&gt;email=&lt;/code&gt;, &lt;code&gt;phone&lt;/code&gt; and &lt;code&gt;phone=&lt;/code&gt; in this example).&lt;/p&gt;

&lt;p&gt;In addition to that, we were also overriding the setter methods &lt;code&gt;email=&lt;/code&gt; and &lt;code&gt;phone=&lt;/code&gt; ourselves to do some normalization on the provided values before assigning them. Combining this with the generated setters from &lt;code&gt;attr_encrypted&lt;/code&gt; introduces a lot of fuzz: in what order are the implementations called - if at all - and what does &lt;code&gt;super&lt;/code&gt; mean in which context? In order to eliminate all this confusion from the start, we previously decided to make our own implementation the &lt;em&gt;"source of truth"&lt;/em&gt; and instead of relying on &lt;code&gt;super&lt;/code&gt; calls, just integrate the respective parts of &lt;code&gt;attr_encrypted&lt;/code&gt;'s implementation into our own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;phone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;normalized_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Normalizers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypted_phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalized_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;instance_variable_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:@phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalized_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Problem 💥
&lt;/h2&gt;

&lt;p&gt;Now, during the migration phase were we utilize both gems in parallel, our own implementation would of course also need to integrate the internals of both gems in our own overriden implementation. Plus we would need to ensure the &lt;code&gt;attr_encrypted&lt;/code&gt; related code is removed again once the migration phase is over 🤯.&lt;/p&gt;

&lt;p&gt;This seemed overwhelming and just way too many things to take care of for our own tiny model implementation. Integrating gems should ideally not interfere too much with our own plans to normalize attributes before assigning them. In addition to that, integrating so deeply with a gem, that understanding the code required reading the gems internals beyond the &lt;em&gt;"normal"&lt;/em&gt; instructions in the README also comes with a high price on maintainability.&lt;/p&gt;

&lt;p&gt;So we needed to find another way to use both gems in parallel while also guaranteeing our values are normalized &lt;strong&gt;before&lt;/strong&gt; assigning and &lt;strong&gt;before&lt;/strong&gt; encrypting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rescue 🚑
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://blog.arkency.com/2016/02/using-anonymous-modules-and-prepend-to-work-with-generated-code/"&gt;post on the arkency blog&lt;/a&gt; describes how Ruby's &lt;a href="https://ruby-doc.org/core-2.7.1/Module.html#method-i-prepend"&gt;&lt;code&gt;prepend&lt;/code&gt;&lt;/a&gt; method on &lt;code&gt;Module&lt;/code&gt; can be utilized to override or better &lt;em&gt;"overlay"&lt;/em&gt; methods added directly onto the class by a gem. One can prepend an anonymous module inline with the own implementation to either fully override the gem's implementation or just &lt;em&gt;"prepend"&lt;/em&gt; one's own implementation and then call &lt;code&gt;super()&lt;/code&gt; to still invoke the code generated by the gem.&lt;/p&gt;

&lt;p&gt;We simply want to &lt;em&gt;"prepend"&lt;/em&gt; our own normalization step before the gems start to do their magic and ideally don't want to get into the details of what they are actually doing. So utilizing &lt;code&gt;super()&lt;/code&gt; after the normalization step fits our use case perfectly. In the model class definition, this could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;attr_encrypted&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;
  &lt;span class="n"&gt;attr_encrypted&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;

  &lt;span class="n"&gt;encrypts&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;migrating: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;phone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;normalized_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Normalizers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;em&gt;"overlays"&lt;/em&gt; or &lt;em&gt;"prepends"&lt;/em&gt; our normalization step before the class' implementation of the setter method, even when it's changed by any of the included gems. So we make sure the value is normalized before it is assigned and the gems' implementation of the setter invoked afterwards without any need for us to fiddle with internal details.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Note&lt;/strong&gt; however, that for the sake of readability and consistency, our model class definition follows Rubocop's &lt;a href="https://github.com/rubocop-hq/rails-style-guide#macro-style-methods"&gt;Rails Style Guide&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Group macro-style methods (&lt;code&gt;has_many&lt;/code&gt;, &lt;code&gt;validates&lt;/code&gt;, etc) in the beginning of the class definition.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Following this class layout, our own implementation is sure to be processed after the gems' methods were defined and we are prepending our normalization step before the final definition of the setter method. Ordering the definitions differently in our class would impact the value of &lt;code&gt;super()&lt;/code&gt; here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup 🧽
&lt;/h2&gt;

&lt;p&gt;While prepending an anonymous module directly inside the class definitions works perfectly fine for our use case here, it still looks very verbose and suspiciously distracting for anyone reading over the model definition. And as we made use of this technique in multiple models within the project, we extracted the boilerplate into a &lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/Concern.html"&gt;model concern&lt;/a&gt;. The goal was to hide the details of the prepending trick while at the same time making the normalization step more visible and explicit to the reader.&lt;/p&gt;

&lt;p&gt;We extracted a more general &lt;code&gt;Normalizable&lt;/code&gt; concern. We already had multiple normalizer classes in the project. They all follow the same pattern and expose a single &lt;code&gt;normalize&lt;/code&gt; class method as their public API. So it just made sense tie the implementation of the &lt;code&gt;Normalizable&lt;/code&gt; model concern close to those normalizers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Normalizable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
      &lt;span class="n"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;define_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="kp"&gt;attr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;normalized_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;public_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:normalize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Utilizing this, we can change the previous example to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Normalizable&lt;/span&gt;

  &lt;span class="n"&gt;attr_encrypted&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;
  &lt;span class="n"&gt;attr_encrypted&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;key: &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;

  &lt;span class="n"&gt;encrypts&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;migrating: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="n"&gt;normalize&lt;/span&gt; &lt;span class="ss"&gt;:phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="no"&gt;Normalizers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PhoneNumber&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hides the internal details of our own implementation of the setter while still making it explicit that we are doing a normalization on the attribute. Similar to the original implementation inlining &lt;code&gt;prepend&lt;/code&gt; with an anonymous module, this approach of course still only works as intended if the &lt;code&gt;normalize&lt;/code&gt; macro in the class definition is defined &lt;strong&gt;after&lt;/strong&gt; any other setter methods generated by gems are defined. However, in our case it seemed most fitting to place the `normalize calls at the end of the macros section anyways.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Improving callable service objects with private constructors in Ruby</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 25 Nov 2020 10:06:41 +0000</pubDate>
      <link>https://forem.com/bitcrowd/improving-callable-service-objects-with-private-constructors-in-ruby-p89</link>
      <guid>https://forem.com/bitcrowd/improving-callable-service-objects-with-private-constructors-in-ruby-p89</guid>
      <description>&lt;p&gt;We recently found a nice practical use case for private class methods in Ruby: the constructor methods of what we will refer to here as &lt;em&gt;"callable services"&lt;/em&gt; in &lt;a href="https://rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt; projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Callable Services ☎️
&lt;/h2&gt;

&lt;p&gt;First off: what do we mean with "&lt;em&gt;callable services"&lt;/em&gt; in Ruby on Rails applications?&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawers 🗄
&lt;/h3&gt;

&lt;p&gt;In the real world, hardly any Ruby on Rails web application only consists of simple, atomic &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; (create, read, update, delete) operations on plain resources. Even the famous &lt;a href="https://www.youtube.com/watch?v=Gzj723LkRJY&amp;amp;feature=youtu.be"&gt;blog engine built in 15 minutes&lt;/a&gt; may at some point incorporate logic beyond the complexity of creating or deleting posts. Take for instance translations, previews, comment moderation, etc. Real world applications model a specific domain and therefore include control flows, procedures and &lt;em&gt;"business logic"&lt;/em&gt; tied to that domain. These bits are in fact what makes an app unique and interesting.&lt;/p&gt;

&lt;p&gt;When it comes to organizing code, Rails people traditionally aim for &lt;a href="http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model"&gt;&lt;em&gt;"skinny controllers and fat models"&lt;/em&gt;&lt;/a&gt;. But as models packed with responsibilities beyond modeling the domain soon tend to get overweight and hard to maintain, the community started to reach for &lt;a href="https://codeclimate.com/blog/7-ways-to-decompose-fat-activerecord-models"&gt;additional concepts and patterns to organize their domain specific code&lt;/a&gt;. One &lt;em&gt;"drawer"&lt;/em&gt; one may come across in a lot of Rails applications are &lt;em&gt;"services"&lt;/em&gt;, also referred to as &lt;a href="https://avdi.codes/service-objects/"&gt;&lt;em&gt;"service objects"&lt;/em&gt;&lt;/a&gt; or &lt;em&gt;"procedures"&lt;/em&gt;. They are usually organized under the &lt;code&gt;app/services/&lt;/code&gt; directory and encapsulate functionality to handle domain-specific logic, such as checking our a cart, registering for the site or starting a subscription. ¹&lt;/p&gt;

&lt;p&gt;Services are usually implemented as POROs ("plain old Ruby objects") which, upon a given input, perform a set of operations and return a predictable response. They are easy to unit test and help developers to maintain a sense for the bigger picture by hiding away the internal details in what appears to the outside as a large black-box-function. That is why people started to implement them as classes exposing only a single method, &lt;code&gt;call&lt;/code&gt; or &lt;code&gt;run&lt;/code&gt;. Depending on its internals and their complexity a service may create a new instance for each call or just utilize a single class method for its work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Authenticator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# complex logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# invoking the service:&lt;/span&gt;
&lt;span class="no"&gt;Authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same service implemented to use a new instance for each call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Authenticator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# complex logic, operate on @user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# invoking the service:&lt;/span&gt;
&lt;span class="no"&gt;Authenticator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conventions 🧘
&lt;/h3&gt;

&lt;p&gt;At bitcrowd we - as you know - &lt;a href="https://github.com/bitcrowd/tickety-tick"&gt;love conventions&lt;/a&gt; and therefore usually strive for a common API for our classes in &lt;code&gt;app/services&lt;/code&gt; in Rails projects. So even if we don't know what's in the box, we at least know we're dealing with a box… Picking up the previous example, this could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Authenticator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# complex logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;private_helper_method&lt;/span&gt;
    &lt;span class="c1"&gt;# some bits of logic, can operate on user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So within the project all services follow the same general structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a constructor takes all the core input data the service needs to do its work&lt;/li&gt;
&lt;li&gt;a single exposed &lt;code&gt;call&lt;/code&gt; method invokes the service to perform the actual work&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Simplifications 🧹
&lt;/h3&gt;

&lt;p&gt;The functionality encapsulated within a service is seen as one operational unit on the outside. So we probably won't interfere between initializing the service and calling it. If the data needed to be manipulated between &lt;code&gt;new&lt;/code&gt; and &lt;code&gt;call&lt;/code&gt;, we should probably rather think about drawing the boundaries between our objects differently instead. But having things clearly encapsulated, we could also simplify the service' API to &lt;code&gt;Service.call(&amp;lt;input-data&amp;gt;)&lt;/code&gt; and hide the implementation details of it using a new instance for each call inside of it. Since Ruby 2.7, we also make use of the 3 dots argument forwarding syntax ² - also referred to as &lt;a href="https://bugs.ruby-lang.org/issues/16253"&gt;&lt;em&gt;"forward everything"&lt;/em&gt;&lt;/a&gt; - for the &lt;code&gt;.call&lt;/code&gt; class method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Authenticator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# complex logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This API makes our service more streamlined and predictable. The service is easier to read and less verbose on the outside, hiding implementation details inside the class itself. We're also less likely to accidentally sneak code between &lt;code&gt;new&lt;/code&gt; and &lt;code&gt;call&lt;/code&gt; when invoking the service. And while it's aesthetically pleasing on the eye, it also allows us to write less verbose expectations in our unit tests for code which interacts with the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before&lt;/span&gt;
&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:service_instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;instance_double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Authenticator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'calls the service'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Authenticator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;and_return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_instance&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# After&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'calls the service'&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Authenticator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:call&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While we previously needed two expectations to ensure both, the service being initialized with the right data and then being invoked, we can now do both in one step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communication 📢
&lt;/h2&gt;

&lt;p&gt;Even with the new simpler API, our services can of course still be called in "the old" way, initializing and calling the service in two steps. The API for this approach is public and aside from examples in the code or documentation, we don't have anything at hand to ensure the service is used as intended. People may still happily do &lt;code&gt;Authenticator.new(user).call&lt;/code&gt; and use instance doubles in their tests… The new API only gives a hint on how to use services in the project, it does not actually encourage or enforce one unified style.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private Constructors to the Rescue 🚒
&lt;/h3&gt;

&lt;p&gt;Turns out we can make use of Ruby's &lt;a href="https://ruby-doc.org/core-2.7.1/Module.html#method-i-private_class_method"&gt;&lt;code&gt;private_class_method&lt;/code&gt;&lt;/a&gt; method on &lt;code&gt;Module&lt;/code&gt; to hide the constructor and make our intentions on how to use the services more obvious:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Makes existing class methods private. Often used to hide the default constructor new.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Adapting our example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Authenticator&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nb"&gt;private_class_method&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# actual logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this we introduce a new problem though: the visual overhead of the additional boilerplate code makes the actual service implementation harder to read and understand. One way to circumvent this and allow the readers to stay focussed on the actual business logic, would be extracting the boilerplate into a &lt;a href="https://api.rubyonrails.org/classes/ActiveSupport/Concern.html"&gt;concern&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Callable&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="n"&gt;included&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;private_class_method&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;class_methods&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then shorten the service to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Authenticator&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Callable&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="c1"&gt;# actual logic&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures our service is now used as intended. Trying to call its parts separately as &lt;code&gt;Authenticator.new(user).call&lt;/code&gt; results in an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NoMethodError: private method `new' called for Authenticator:Class
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing so, we can a clean and concise outer API for our services while not sacrificing readability on its internals. Invoking a service with its single exposed &lt;code&gt;call&lt;/code&gt; class method ensure we pass the initial data to set up the state it needs to perform its work and then immediately trigger the actual &lt;em&gt;"work"&lt;/em&gt; part.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;private_class_method&lt;/code&gt; to hide the initializer of your service objects for a clearer API and less boilerplate in tests 💅.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1: &lt;a href="https://blog.appsignal.com/2020/06/17/using-service-objects-in-ruby-on-rails.html"&gt;https://blog.appsignal.com/2020/06/17/using-service-objects-in-ruby-on-rails.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2: &lt;a href="https://github.com/ruby/ruby/blob/v2_7_0/NEWS#label-Other+miscellaneous+changes"&gt;https://github.com/ruby/ruby/blob/v2_7_0/NEWS#label-Other+miscellaneous+changes&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Folding sections of Markdown in Vim</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Mon, 25 May 2020 07:22:45 +0000</pubDate>
      <link>https://forem.com/bitcrowd/folding-sections-of-markdown-in-vim-23ne</link>
      <guid>https://forem.com/bitcrowd/folding-sections-of-markdown-in-vim-23ne</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL:&lt;/strong&gt; Vim is able to fold sections or Markdown on their headings 💡&lt;/p&gt;

&lt;p&gt;Modern version of &lt;a href="https://www.vim.org/"&gt;Vim&lt;/a&gt; and &lt;a href="https://neovim.io/"&gt;Neovim&lt;/a&gt; support folding &lt;code&gt;.md&lt;/code&gt; &lt;a href="https://en.wikipedia.org/wiki/Markdown"&gt;Markdown&lt;/a&gt; documents on their section headings &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;###&lt;/code&gt; etc. out of the box, even without installing the additional &lt;a href="https://github.com/masukomi/vim-markdown-folding"&gt;vim-markdown-folding&lt;/a&gt; plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is folding 🤔
&lt;/h2&gt;

&lt;p&gt;Vimʼs documentation describes &lt;em&gt;“folding”&lt;/em&gt; as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Folding is used to show a range of lines in the buffer as a single line on the
screen.  Like a piece of paper which is folded to make it shorter:

    +------------------------+
    | line 1         |
    | line 2         |
    | line 3         |
    |_______________________ |
    \            \
     \________________________\
     / folded lines       /
    /________________________/
    | line 12        |
    | line 13        |
    | line 14        |
    +------------------------+

The text is still in the buffer, unchanged.  Only the way lines are displayed
is affected by folding.

The advantage of folding is that you can get a better overview of the
structure of text, by folding lines of a section and replacing it with a line
that indicates that there is a section.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When editing large documents, I personally find it handy to temporarily fold and &lt;em&gt;“hide away”&lt;/em&gt; certain parts I currently donʼt care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Folding Markdown 🗂
&lt;/h2&gt;

&lt;p&gt;Out of the box, Vim and Neovim currently wonʼt know &lt;em&gt;“how”&lt;/em&gt; to fold Markdown sections. Their included default &lt;a href="https://github.com/tpope/vim-markdown"&gt;vim-markdown&lt;/a&gt; filetype and syntax plugin however offers an undocumented setting to enable just this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let g:markdown_folding = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an &lt;a href="https://github.com/tpope/vim-markdown/pull/135"&gt;open pull request&lt;/a&gt; include this in the official documentation, but it is not merged yet.&lt;/p&gt;

&lt;p&gt;Flicking this switch turns my &lt;code&gt;.md&lt;/code&gt; buffers into something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--- 32 lines: ## What is folding 🤔···········································
+--- 23 lines: ## Folding Markdown 🗂···········································
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pressing &lt;code&gt;zR&lt;/code&gt; will recursively unfold all sections again. From there on you may continue to happily fold and unfold. &lt;a href="https://twitter.com/nelstrom"&gt;Drew Neil&lt;/a&gt; from &lt;a href="http://vimcasts.org"&gt;Vimcasts&lt;/a&gt; put together a nice and short &lt;a href="http://vimcasts.org/episodes/how-to-fold/"&gt;overview&lt;/a&gt; over the most important folding commands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;command&lt;/th&gt;
&lt;th&gt;effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;open current fold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;recursively open current fold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;close current fold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;recursively close current fold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;za&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;toggle current fold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;recursively open/close current fold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;reduce &lt;code&gt;foldlevel&lt;/code&gt; by one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;close all folds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;increase &lt;code&gt;foldlevel&lt;/code&gt; by one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;open all folds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  First look 👀
&lt;/h3&gt;

&lt;p&gt;With Markdown-folding enabled, Vim will default to the fully &lt;em&gt;“folded”&lt;/em&gt; view when opening a Markdown buffer. I personally prefer to start with the &lt;em&gt;“full”&lt;/em&gt; document though. According to &lt;a href="https://stackoverflow.com/questions/8316139/how-to-set-the-default-to-unfolded-when-you-open-a-file"&gt;this Stackoverflow thread&lt;/a&gt; there are a few ways out of this behavior.&lt;/p&gt;

&lt;p&gt;I went for setting a custom &lt;code&gt;foldlevelstart&lt;/code&gt; value in my configuration. It instructs Vim how or if a buffer should be folded when one starts editing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'foldlevelstart' 'fdls' number (default: -1)
            global
    Sets 'foldlevel' when starting to edit another buffer in a window.
    Useful to always start editing with all folds closed (value zero),
    some folds closed (one) or no folds closed (99).
    This is done before reading any modeline, thus a setting in a modeline
    overrules this option.  Starting to edit a file for |diff-mode| also
    ignores this option and closes all folds.
    It is also done before BufReadPre autocommands, to allow an autocmd to
    overrule the 'foldlevel' value for specific files.
    When the value is negative, it is not used.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I went for starting with all folds open in Markdown buffers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;au FileType markdown setlocal foldlevel=99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Happy folding 🗺&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>vim</category>
      <category>writing</category>
      <category>productivity</category>
      <category>markdown</category>
    </item>
    <item>
      <title>A note on commit messages</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Wed, 20 May 2020 07:49:52 +0000</pubDate>
      <link>https://forem.com/bitcrowd/a-note-on-commit-messages-14d4</link>
      <guid>https://forem.com/bitcrowd/a-note-on-commit-messages-14d4</guid>
      <description>&lt;p&gt;This post is based on the talk &lt;a href="https://speakerdeck.com/klappradla/my-message-on-commit-messages"&gt;“My Message on Commit Messages”&lt;/a&gt; I gave at the &lt;a href="https://www.rug-b.de/"&gt;Ruby User Group Berlin&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git 🌳
&lt;/h2&gt;

&lt;p&gt;Peeking around the world of software development, one may sooner or later come across the term &lt;a href="https://en.wikipedia.org/wiki/Git"&gt;git&lt;/a&gt;. If you havenʼt yet, you have done so now and are prepared to continue this journey here ⛵️.&lt;/p&gt;

&lt;p&gt;Software development is a collaborative process where we create, edit and delete text files that constitute our project. With multiple people doing this in parallel in different corners of the project, things can get confusing. We need to keep track of what is happening and inform the others about changes which may impact their work and the changes they are planning. Put into technical terms, we need a so called &lt;a href="https://en.wikipedia.org/wiki/Version_control"&gt;Version Control System (VCS)&lt;/a&gt; (or “Revision Control System”) if we want to build software together. Git is one flavour of these:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals.¹&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Git was developed by &lt;a href="https://en.wikipedia.org/wiki/Linus_Torvalds"&gt;Linus Torvalds&lt;/a&gt; to facilitate working on the &lt;a href="https://en.wikipedia.org/wiki/Linux_kernel"&gt;Linux kernel&lt;/a&gt; with other developers. They initially managed changes to their project by passing around patches and archived files before introducing a proprietary version control system named &lt;a href="https://en.wikipedia.org/wiki/BitKeeper"&gt;“Bitkeeper”&lt;/a&gt;. Git was born in 2005 when Bitkeeper stopped offering their tool free of charge to the community.² Git is open source, continuously improved and the de facto (industry-) standard when it comes to version control systems nowadays.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git in 2020 📠
&lt;/h3&gt;

&lt;p&gt;Why are version control systems like Git still relevant in our fast-moving and hyper-modern times of 2020? Building and maintaining software is still a collaborative effort. People need to know about the changes others did to the project and why they did it. Without this, itʼs hard to make good decisions for oneʼs own changes and the project may already be doomed to end up as a digital junkyard. Access to this information at any time without having to ask everyone in the team individually is a key building brick for developing software together.&lt;/p&gt;

&lt;p&gt;Without version control systems, we would send around &lt;a href="https://en.wikipedia.org/wiki/Tar_(computing)"&gt;tarballs&lt;/a&gt; of source code or mob-access the same files on a shared server. The first is tedious and uninclusive and the second turns developing software into an “open heart surgery” with people yelling “donʼt touch file xzy, I am working on it” through offices, living in constant fear of overwriting and breaking each otherʼs work and somewhere in this struggle naming files &lt;code&gt;algorithm_final_final_2.js&lt;/code&gt;. Version control systems give teams structures and processes to &lt;em&gt;“control”&lt;/em&gt; the &lt;em&gt;“version”&lt;/em&gt; of their project.&lt;/p&gt;

&lt;p&gt;Git with its ecosystem of tools and established workflows to document, decide on and reason about code changes has proven to be a good idea. To the point where there is hardly a way around it in todayʼs world of programming.³ &lt;/p&gt;

&lt;h3&gt;
  
  
  A Commit 📸
&lt;/h3&gt;

&lt;p&gt;Before diving into commit &lt;em&gt;“messages”&lt;/em&gt;, we need to know about commits:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A “commit” is a snapshot of your files.⁴&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A commit is a uniquely identifiable snapshot of the project at a certain moment. It contains the projectʼs current state encoded as information about how it differs from a previous state, along with a log message from the author describing the changes.⁵ The latter part is referred to as the commit &lt;em&gt;“message”&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commit messages 💌
&lt;/h2&gt;

&lt;p&gt;Many developers treat commit messages more like an annoying &lt;em&gt;“are you sure”&lt;/em&gt; confirmation dialogue on the otherwise interruption free autobahn to get their code out into the world and can easily get by with that forever. Commit messages arenʼt absolutely necessary for building software, but they can make life so much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Communication cut ✂️
&lt;/h3&gt;

&lt;p&gt;Software projects often have people working on different tasks in parallel. These tasks are rarely fully independent and can affect each other. Not caring about commit messages, one may experience scenarios where important information about a decision or change in the codebase is missing or not communicated yet. This knowledge gap can have negative impact on following tasks.&lt;/p&gt;

&lt;p&gt;Imagine backend and frontend developers collaborating on a feature and neither can read code from the othersʼ domain. The backend people frequently update the codebase with changes labelled &lt;em&gt;“fix”&lt;/em&gt;, &lt;em&gt;“polish”&lt;/em&gt;, &lt;em&gt;“make work”&lt;/em&gt; or &lt;em&gt;“f*ck linters”&lt;/em&gt;. Now imagine yourself in the group of frontend developers, waiting for an API specification. How can you know there is something ready to base your work on? Is the API ready, broken or work in progress? Unable to reason about the raw diff, you constantly have to nag the backend developers about these missing pieces of information. This makes collaboration unnecessarily hard, cuts down possibilities for parallelism and introduces hard dependencies on personal availability, mood and memory. Coming in after the weekend, the backend team doesnʼt know about the current state from looking at their commit history either. They have to dig into the code to find out where they left things.&lt;/p&gt;

&lt;p&gt;Useful commit messages can help to prevent situations of missing important information. Unlike a Slack message or a coffee chat in the kitchen, they are persisted with the code change as an always available way to retrieve contextual information about it - for your teammates or for yourself, coming back after the weekend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Information hunt 🔍
&lt;/h3&gt;

&lt;p&gt;Or imagine you need to adapt a piece of code in a legacy codebase. Looking at it for hours, you still donʼt understand the code and why it is as weird as it is. You donʼt dare to touch it as it could impact other areas of the project. With no one around to ask, you look up the commit that introduced the change to see whether it can enlighten you. Depending on the grade of “legacy-ness”, the message of the commit may read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commit e3aa57ee56e8d9aed89560d1ab4702068ce65d12
Author: Hannah Operator &amp;lt;hannah@dev.internetz&amp;gt;
Date:   Mon May 4 22:44:05 2005 +0200

    make things finally wrk also linter and change UI, fix logging 🍻
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not help. There is no useful contextual information in this commit message. The author of the code may either long be gone or - surprise - was you a couple of years ago. Nevertheless, you have to continue your search for information elsewhere and eventually just leave the piece of code untouched (and rotting). A good commit message would have been helpful here.&lt;/p&gt;

&lt;p&gt;Commit messages can be a useful tool to provide contextual information about a change for others or your future self reading the code. Ignoring them when committing changes is like throwing a boomerang - it will come back at you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Through its lifetime, code is read far more often then it is written…⁶&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Mindset 🧠
&lt;/h2&gt;

&lt;p&gt;Writing good commit messages is more a question of awareness than skill. It needs the right mindset to understand the value and why itʼs worth the effort.&lt;/p&gt;

&lt;p&gt;With collaboration no longer tied to physical presence and teams distributed across the globe, good habits of communication - especially asynchronous communication - are more important than ever. Version control systems act as a medium for asynchronous communication among developers working on the same codebase. A Git commit links changes in the code to the people behind and the plans and intentions they had. It stays with the change forever, even if the authors may have left long ago. It can act as a powerful communication channel, even between generations of developers on a codebase.&lt;/p&gt;

&lt;p&gt;The diff of a commit states &lt;em&gt;“what”&lt;/em&gt; changed, the commit messages the context, the &lt;em&gt;“how”&lt;/em&gt; and &lt;em&gt;“why”&lt;/em&gt;. Both are important for anyone (including the authorʼs future self) reasoning about the code. A clean commit history with clearly scoped commits and meaningful commit messages acts as automated documentation for the life cycle of the project. It allows people to &lt;a href="https://git-scm.com/docs/git-blame"&gt;&lt;code&gt;git blame&lt;/code&gt;&lt;/a&gt; a line of code to find out when, why and by whom it was introduced or changed.&lt;/p&gt;

&lt;p&gt;Unlike API docs or architecture diagrams which usually document a desired or planned state of the project, commit messages reflect the current state of the project and how it was achieved. They can be read as a &lt;em&gt;“process log”&lt;/em&gt; for the project. A project manager may ask: “What did you actually ship in the last two weeks?”. A well maintained Git history is able to answer this question within seconds without having to dig through wikis, issue trackers or Slack conversations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read vs. Write ↩️
&lt;/h3&gt;

&lt;p&gt;Developing awareness for the value of commit messages means empathizing with readers of the code (including yourself). It relates to how high we value the maintainability and liveliness of software: Is it supposed to last for a while or is it a throw-away piece of digital junk? This of course depends on the project, but all in all, code is read far more often than written:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …[Therefore,] making it easy to read makes it easier to write.⁶&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We should therefore aim to cater the needs of developers reading code, not only the single author writing it. A clean commit history with meaningful commit messages facilitates reading, understanding and maintaining code. To read a piece of code, people, including the author themself, often need to re-establish the context around a change at a later point in time. Reasonably scoped commits with meaningful commit messages give them a chance to do so.&lt;/p&gt;

&lt;p&gt;Software we build today usually has an intended lifespan of at least a couple years to decades. This inevitably requires people to read and understand the existing code. In solidarity with future generations of developers, we should aim to this as easy as possible. Writing meaningful commit messages has a significant impact on this and helps preventing frustration. Without this explicit documentation of the commit history, the codebase may easily slide towards big-L legacy, where code is doing things, but itʼs impossible to tell what and why.&lt;/p&gt;

&lt;p&gt;🎥 Also watch Tekin Süleymanʼs awesome talk &lt;a href="https://youtu.be/1NoNTqank_U"&gt;A Branch in Time (a story about revision histories)&lt;/a&gt; from &lt;a href="https://ruby.org.au"&gt;Ruby Australia&lt;/a&gt; for an illustrative example of the benefits of good commit messages for other developers and your future self. Writing good commit messages makes lives easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing commit messages ✍️
&lt;/h2&gt;

&lt;p&gt;Commit messages document &lt;em&gt;“why”&lt;/em&gt; a change was made. Keeping in mind that this information stays with the change forever, the people at &lt;a href="https://thoughtbot.com/"&gt;Thoughtbot&lt;/a&gt; propose three questions as an orientation for what to actually put into a commit message:⁷&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Why is the change necessary?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A commit message informs about the purpose of the change and summarizes what the commit is about on a conceptual level beyond what is already visible in the raw diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. How does this address the issue?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A commit message may also give a high level overview of what has been done. Technical detail should be left out as it is better visualized in the diff. It can also be useful to mention alternative approaches one considered. This helps to focus later discussions and makes the solution more transparent to future readers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What are side effects of this change?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A commit message should also list side effects of a change if there are any. Having this close to the change helps when hunting down eventual regression bugs later. Discussing side effects in the code review or inside the team isnʼt sufficient as itʼs rather difficult to dig up this information again later. Noted in the commit message however, the information stays persisted close to the change where people can easily find it.&lt;/p&gt;

&lt;p&gt;The questions indicate: commit messages are a powerful communication tool. All developers on the team, including yourself in two weeks will thank you for caring and using them. Therefore - and this applies to a lot of things in life - if you can make the lives of others a bit better, just immediately do it!&lt;/p&gt;

&lt;h3&gt;
  
  
  The Looks 💅
&lt;/h3&gt;

&lt;p&gt;A good commit message documents the &lt;em&gt;“why”&lt;/em&gt; and enables others to understand the context of a change. But how should it look?&lt;/p&gt;

&lt;p&gt;Here is an &lt;a href="https://github.com/rails/rails/pull/33770/commits/a572d2283b05c4c05c220ff520732fc570beb2ae"&gt;example&lt;/a&gt; from the &lt;a href="https://rubyonrails.org/"&gt;Rails&lt;/a&gt; codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Convert configs_for to kwargs, add include_replicas

Changes the `configs_for` method from using traditional arguments to
using kwargs. This is so I can add the `include_replicas` kwarg without
having to always include `env_name` and `spec_name` in the method call.

`include_replicas` defaults to false because everywhere internally in
Rails we donʼt want replicas. `configs_for` is for iterating over
configurations to create / run rake tasks, so we really don't ever need
replicas in that case.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line, &lt;code&gt;Convert configs_for to…&lt;/code&gt; is referred to as the &lt;em&gt;“subject”&lt;/em&gt;, the rest as the &lt;em&gt;“body”&lt;/em&gt; of the message. &lt;a href="https://github.com/tpope"&gt;Tim Pope&lt;/a&gt;, of general open source fame and the author of the - at least among commit-message-lovers - &lt;strong&gt;legendary&lt;/strong&gt; &lt;a href="https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html"&gt;Note about Git Commit Messages&lt;/a&gt; with sensible and concise guidelines for formulating and formatting commit messages states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The subject/body distinction may seem unimportant but itʼs one of many subtle factors that makes Git history so much more pleasant to work with than Subversion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Being able to distinguish between subject and body allows for more control about how much detail about a change is displayed in which context.&lt;/p&gt;

&lt;h4&gt;
  
  
  Subject 📬
&lt;/h4&gt;

&lt;p&gt;The subject is supposed to give a short summary of what the commit is about. You see it in various places in Githubʼs UI or command line tools like &lt;code&gt;git rebase&lt;/code&gt; or &lt;code&gt;git reflog&lt;/code&gt; where users need a summary of a list of commits, not the individual details.&lt;/p&gt;

&lt;p&gt;According to Tim Popeʼs de-facto standard guidelines, the subject should contain an around 50 characters long concise but meaningful summary of the change. Given the size limits, this usually wonʼt make a valid full featured sentence and trying to formulate it as one may indeed be hindering. So itʼs recommended to not treat it as a sentence in the first place and also not end it with a dot. If the subject gets too long, most tools truncate it with &lt;code&gt;…&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tim Pope also suggests to use imperative forms, such as &lt;em&gt;“fix bug”&lt;/em&gt; instead of &lt;em&gt;“fixing bug”&lt;/em&gt; and formulate the subject to complete the sentence: &lt;em&gt;“If applied, this commit will …”&lt;/em&gt;. This makes it align nicely with Gitʼs auto-generated commit messages like &lt;code&gt;merge&lt;/code&gt; and &lt;code&gt;revert&lt;/code&gt; and produces a consistent and easy to read commit history.&lt;/p&gt;

&lt;p&gt;The subject line is followed by a blank line separating it from the body. This is absolutely crucial: Without this line the message canʼt be parsed correctly and may generate weird cut offs, line breaks and indentation. This brings us to the most important advice in this post. You may forget everything you read so far, but remembering the following will make you a better human being and spark joy in your and other peopleʼs lives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🚨 Let go of the &lt;code&gt;git commit -m&lt;/code&gt; shortcut forever and for good! 🚨&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--mltmsggt"&gt;&lt;code&gt;git commit -m &amp;lt;msg&amp;gt;&lt;/code&gt;&lt;/a&gt; is a shortcut to inline the commit message with the &lt;code&gt;git commit&lt;/code&gt; command in one go. This is normally a two-step process where &lt;code&gt;git commit&lt;/code&gt; opens your favorite text editor - at least the one configured via the &lt;code&gt;GIT_EDITOR&lt;/code&gt; or &lt;code&gt;EDITOR&lt;/code&gt; environment variable - to let you enter and save the log message before generating the commit.&lt;/p&gt;

&lt;p&gt;It may be challenging to overcome the muscle memory, but youʼll immediately see drastic improvements in your commit message game. A text editor is by far more convenient and inviting for formulating and formatting prose. It gives you spell checking, indentation, line breaks, syntax highlighting and other goodies from the modern world. Multi line commit messages written on the command line will most likely not have the right format and wrapping and end up as a gibberish mess in the Git history. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; Use a text editor for writing commit messages! 💡&lt;/p&gt;

&lt;h4&gt;
  
  
  Body 🎒
&lt;/h4&gt;

&lt;p&gt;For a commit small enough that a subject line is already enough to summarize it, one may of course fully omit the body of the commit message. For instance when fixing a small typo in the &lt;code&gt;README.md&lt;/code&gt; of the project - a rare case for &lt;code&gt;git commit -m&lt;/code&gt;. Most commits are complex enough to require a longer commit message though.&lt;/p&gt;

&lt;p&gt;The commit message body explains the &lt;em&gt;“why”&lt;/em&gt; and the &lt;em&gt;“how”&lt;/em&gt; of the commit in more detail. It can be a multi line text with its paragraphs separated by blank lines and wrapped at 72 characters. Gitʼs default pager &lt;code&gt;less&lt;/code&gt; may choke and show hard to read output on other formats, so this is the agreed upon standard and your text editor will most likely already adhere to it by default.&lt;/p&gt;

&lt;p&gt;The whole commit message could then be formatted like this example from Tim Pope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capitalized, short (50 chars or less) summary

More detailed explanatory text, if necessary.  Wrap it to about 72
characters or so.  In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body.  The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase can get confused if you run the
two together.

Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug."  This convention matches up with commit messages generated
by commands like git merge and git revert.

Further paragraphs come after blank lines.

- Bullet points are okay, too

- Typically a hyphen or asterisk is used for the bullet, followed by a
  single space, with blank lines in between, but conventions vary here

- Use a hanging indent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Extras 🍦
&lt;/h4&gt;

&lt;p&gt;Depending on the tool you are using, commit messages can also include additional meta information. Github for instance supports closing issues and citing co-authors from commit message bodies formatted accordingly. The developers at &lt;a href="https://thoughtbot.com"&gt;thoughtbot&lt;/a&gt; already have the respective lines for pairing partners in their default &lt;a href="https://thoughtbot.com/blog/write-good-commit-messages-by-blaming-others"&gt;&lt;code&gt;.gitmessage&lt;/code&gt;&lt;/a&gt; template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 50-character subject line
#
# 72-character wrapped longer description. This should answer:
#
# * Why was this change necessary?
# * How does it address the problem?
# * Are there any side effects?
#
# Include a link to the ticket, if any.
#
# Add co-authors if you worked on this code with others:
#
# Co-authored-by: Full Name &amp;lt;email@example.com&amp;gt;
# Co-authored-by: Full Name &amp;lt;email@example.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring a &lt;code&gt;.gitmessage&lt;/code&gt; file can be useful to raise awareness and establish certain rules for commit messages within a team. Git uses the contents of the file to pre-fill its commit messages. It can be used as a template, checklist or guideline for writing the actual commit message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 👏
&lt;/h2&gt;

&lt;p&gt;Empathize with the readers of your code and write good commit messages. A clean Git history with meaningful, well formatted commit messages is a great way to show solidarity with fellow developers including your future self and consistently document the life cycle of your project. Adapt a healthy mindset for writing commit messages and make other peopleʼs lives easier ☯️.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a&gt;1&lt;/a&gt;: &lt;a href="https://git-scm.com/docs/git#_description"&gt;https://git-scm.com/docs/git#_description&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;2&lt;/a&gt;: &lt;a href="https://git-scm.com/book/en/v2/Getting-Started-A-Short-History-of-Git"&gt;https://git-scm.com/book/en/v2/Getting-Started-A-Short-History-of-Git&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;3&lt;/a&gt;: &lt;a href="https://www.git-tower.com/learn/git/ebook/en/desktop-gui/basics/why-use-version-control"&gt;https://www.git-tower.com/learn/git/ebook/en/desktop-gui/basics/why-use-version-control&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;4&lt;/a&gt;: &lt;a href="https://www.quora.com/What-does-it-mean-to-commit"&gt;https://www.quora.com/What-does-it-mean-to-commit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;5&lt;/a&gt;: &lt;a href="https://git-scm.com/docs/git-commit"&gt;https://git-scm.com/docs/git-commit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;6&lt;/a&gt;: &lt;a href="https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-time-spent-reading-versus-writing-is"&gt;https://www.goodreads.com/quotes/835238-indeed-the-ratio-of-time-spent-reading-versus-writing-is&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a&gt;7&lt;/a&gt;: &lt;a href="https://thoughtbot.com/blog/5-useful-tips-for-a-better-commit-message"&gt;https://thoughtbot.com/blog/5-useful-tips-for-a-better-commit-message&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>workflow</category>
      <category>collaboration</category>
    </item>
  </channel>
</rss>
