<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Programming on Grizzlebit</title>
  <subtitle>Ray Grasso's Blog</subtitle>
  <updated>2026-05-05T13:59:36.958565+08:00</updated>
  <id>https://www.grizzlebit.com/tags/programming/feed.xml</id>
  <link rel="alternate" type="text/html" href="https://www.grizzlebit.com/tags/programming/"/>
  <link rel="self" type="application/atom+xml" href="https://www.grizzlebit.com/tags/programming/feed.xml"/>
  <rights>Copyright © 2026, Ray Grasso</rights>
  <author>
    <name>Ray Grasso</name>
  </author>
  <icon>https://www.grizzlebit.com/images/icon.png</icon>
  <logo>https://www.grizzlebit.com/images/icon.png</logo>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2025/10-29-how-i-reversed-amazons-kindle-web-obfuscation-because-their-app-sucked/</id>
    <published>2025-10-29T09:46:23+08:00</published>
    <updated>2025-10-29T09:47:05+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>How I Reversed Amazon&#39;s Kindle Web Obfuscation Because Their App Sucked ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>A story of persistence.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2025/10-29-how-i-reversed-amazons-kindle-web-obfuscation-because-their-app-sucked/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://blog.pixelmelt.dev/kindle-web-drm/"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2025/10-29-how-i-reversed-amazons-kindle-web-obfuscation-because-their-app-sucked/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2025/06-22-field-notes-from-shipping-real-code-with-claude/</id>
    <published>2025-06-22T08:04:59+08:00</published>
    <updated>2025-06-22T08:07:56+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Field Notes From Shipping Real Code With Claude ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>Some practical advice on supporting AI agents from Diwank Tomer.</p>
<p>Two things that stood out to me:</p>
<ol>
<li>Asking the AI to embed bits of context in comments for itself so it can reconstitute context on previous decisions.</li>
<li>Making the tests purely for humans as a way to give the AI more rope to make changes.</li>
</ol>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2025/06-22-field-notes-from-shipping-real-code-with-claude/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/ai/">AI</a>, <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://diwank.space/field-notes-from-shipping-real-code-with-claude"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2025/06-22-field-notes-from-shipping-real-code-with-claude/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/posts/2025/06-05-climbing-further-up-the-stack/</id>
    <published>2025-06-05T14:44:59+08:00</published>
    <updated>2025-06-05T17:02:14+10:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Climbing Further Up the Stack</title>
    <content type="html" xml:base="https://www.grizzlebit.com/posts/" xml:lang="en"><![CDATA[<div>
  <p>As Gen AI programming tools continue to develop, I’m wondering what things will look like when we remove the human from the loop.</p>
<p>At that point, all the code that’s generated is solely for the AI. All the human-focused concerns we care about in code disappear—it becomes a black box. The code effectively becomes another <a href="https://en.wikipedia.org/wiki/Intermediate_representation#Intermediate_language">intermediate language</a> for a new layer on the stack.</p>
<p>As long as the solution fulfils its requirements and fits within the constraints of security and cost for the necessary performance, we’re happy.</p>
<p>Code structures and data schemas don’t matter—so long as the AI can refactor them to meet new requirements as they emerge.</p>
<p>It reminds me of stories of when assembly programmers first saw these flash new C compilers arrive on the scene and generate all this assembly code that no human had directly written.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/posts/2025/06-05-climbing-further-up-the-stack/">🔗</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/ai/">AI</a>, <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://www.grizzlebit.com/posts/2025/06-05-climbing-further-up-the-stack/"></link>
    <link rel="related" href="https://www.grizzlebit.com/posts/2025/06-05-climbing-further-up-the-stack/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/posts/2025/04-23-a-raycast-extension-to-search-my-blog/</id>
    <published>2025-04-23T17:13:26+08:00</published>
    <updated>2025-04-23T19:31:51+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>A Raycast Extension to Search My Blog</title>
    <content type="html" xml:base="https://www.grizzlebit.com/posts/" xml:lang="en"><![CDATA[<div>
  <p>I&rsquo;ve been looking for a way to search through the local copy of my blog using <a href="https://www.raycast.com">Raycast</a>.</p>
<p>I ended up writing a custom extension to do it. ChatGPT helped grease the way—especially in rendering the results.</p>
<p>It uses a brute force grep over the files&rsquo; contents which works fine given the size of the repository.</p>
<p>The two main actions on the extension are opening the post in my editor and copying a Markdown link to the post<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Here is a look at the Raycast command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Command() {</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">query</span><span class="p">,</span> <span class="nx">setQuery</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">&lt;</span><span class="nt">string</span><span class="p">&gt;(</span><span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">results</span><span class="p">,</span> <span class="nx">setResults</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">&lt;</span><span class="nt">SearchResult</span><span class="err">[]</span><span class="p">&gt;([]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span> <span class="o">===</span> <span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">setResults</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="nx">getAllPosts</span><span class="p">(</span><span class="nx">BASE_PATH</span><span class="p">,</span> <span class="nx">BLOG_SUBDIRS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">matches</span> <span class="o">=</span> <span class="nx">searchPosts</span><span class="p">(</span><span class="nx">posts</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">setResults</span><span class="p">(</span><span class="nx">matches</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;Error reading blog posts:&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="p">[</span><span class="nx">query</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">List</span> <span class="na">onSearchTextChange</span><span class="o">=</span><span class="p">{</span><span class="nx">setQuery</span><span class="p">}</span> <span class="na">throttle</span> <span class="na">isShowingDetail</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">results</span><span class="p">.</span><span class="nx">map</span><span class="p">(({</span> <span class="nx">file</span><span class="p">,</span> <span class="nx">snippet</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">filename</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">basename</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">relativePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">relative</span><span class="p">(</span><span class="nx">BASE_PATH</span><span class="p">,</span> <span class="nx">file</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\\/g</span><span class="p">,</span> <span class="s2">&#34;/&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Convert to URL relative from site root based upon Hugo URL config
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kr">const</span> <span class="nx">relativeUrl</span> <span class="o">=</span> <span class="sb">`/</span><span class="si">${</span><span class="nx">relativePath</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\.md$/</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\/\d\d\d\d-/</span><span class="p">,</span> <span class="s2">&#34;/&#34;</span><span class="p">)</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Grab the title from the front matter
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="kr">const</span> <span class="nx">fileContent</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">file</span><span class="p">,</span> <span class="s2">&#34;utf8&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">titleMatch</span> <span class="o">=</span> <span class="nx">fileContent</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/^title:\s*(.*)$/m</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="nx">titleMatch</span> <span class="o">?</span> <span class="nx">titleMatch</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/^[&#39;&#34;]|[&#39;&#34;]$/g</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="o">:</span> <span class="nx">filename</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">markdownLink</span> <span class="o">=</span> <span class="sb">`[</span><span class="si">${</span><span class="nx">title</span><span class="si">}</span><span class="sb">](</span><span class="si">${</span><span class="nx">relativeUrl</span><span class="si">}</span><span class="sb">)`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">List.Item</span>
</span></span><span class="line"><span class="cl">            <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">file</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="na">title</span><span class="o">=</span><span class="p">{</span><span class="nx">snippet</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\*\*/g</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">            <span class="na">detail</span><span class="o">=</span><span class="p">{&lt;</span><span class="nt">List.Item.Detail</span> <span class="na">markdown</span><span class="o">=</span><span class="p">{</span><span class="sb">`**</span><span class="si">${</span><span class="nx">relativeUrl</span><span class="si">}</span><span class="sb">**</span><span class="err">\</span><span class="sb">n</span><span class="err">\</span><span class="sb">n---</span><span class="err">\</span><span class="sb">n</span><span class="err">\</span><span class="sb">n</span><span class="si">${</span><span class="nx">snippet</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span> <span class="p">/&gt;}</span>
</span></span><span class="line"><span class="cl">            <span class="na">actions</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="p">&lt;</span><span class="nt">ActionPanel</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">Action.Open</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Open in VS Code&#34;</span> <span class="na">target</span><span class="o">=</span><span class="p">{</span><span class="nx">file</span><span class="p">}</span> <span class="na">application</span><span class="o">=</span><span class="s">&#34;/Applications/Visual Studio Code.app&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">Action.CopyToClipboard</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Copy Markdown Link&#34;</span> <span class="na">content</span><span class="o">=</span><span class="p">{</span><span class="nx">markdownLink</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">&lt;/</span><span class="nt">ActionPanel</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">})}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">List</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And here is <a href="/files/find-blog-post.tsx">the full file</a>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Which is handy when cross linking while writing other posts.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

  <p>
    
    <a href="https://www.grizzlebit.com/posts/2025/04-23-a-raycast-extension-to-search-my-blog/">🔗</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/automation/">Automation</a>, <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>, <a href="https://www.grizzlebit.com/tags/tools/">Tools</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://www.grizzlebit.com/posts/2025/04-23-a-raycast-extension-to-search-my-blog/"></link>
    <link rel="related" href="https://www.grizzlebit.com/posts/2025/04-23-a-raycast-extension-to-search-my-blog/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2025/04-11-graft/</id>
    <published>2025-04-11T08:50:50+08:00</published>
    <updated>2025-04-11T08:52:27+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Graft ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>A replication focused storage engine.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2025/04-11-graft/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>, <a href="https://www.grizzlebit.com/tags/tools/">Tools</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://sqlsync.dev/posts/stop-syncing-everything/"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2025/04-11-graft/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2024/05-19-multi-level-summarization-in-instapaper/</id>
    <published>2024-05-19T16:06:04+10:00</published>
    <updated>2024-05-19T16:10:00+10:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Multi-Level Summarization in Instapaper ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>Summaries is a feature I&rsquo;ve long wanted in Instapaper.</p>
<p>In this post, Brian Donohue goes through how he implemented it.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2024/05-19-multi-level-summarization-in-instapaper/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>, <a href="https://www.grizzlebit.com/tags/reading/">Reading</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://bthdonohue.com/2024/04/24/multilevel-summarization-instapaper.html"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2024/05-19-multi-level-summarization-in-instapaper/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2024/05-19-a-new-algorithm-for-counting-distinct-objects/</id>
    <published>2024-05-19T16:01:37+10:00</published>
    <updated>2024-05-19T16:10:00+10:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>A New Algorithm for Counting Distinct Objects ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>Steve Nadis:</p>
<blockquote>
<p>computer scientists have described a new way to approximate the number of distinct entries in a long list, a method that requires remembering only a small number of entries.</p></blockquote>
<p>It always fascinates me when introducing randomness enables new approaches.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2024/05-19-a-new-algorithm-for-counting-distinct-objects/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://www.quantamagazine.org/computer-scientists-invent-an-efficient-new-way-to-count-20240516/"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2024/05-19-a-new-algorithm-for-counting-distinct-objects/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2023/04-17-why-and-how-culture-amp-retired-elm/</id>
    <published>2023-04-17T17:26:49+08:00</published>
    <updated>2023-04-17T18:54:28+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Why and How Culture Amp Retired Elm ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>Kevin Yank:</p>
<blockquote>
<p>From time to time someone will ask, “Does Culture Amp still use Elm?” I’ll answer privately that no, we are no longer investing in Elm, and explain why. Invariably, they tell me my answer was super valuable, and that I should share it publicly. Until now, I haven’t.</p></blockquote>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2023/04-17-why-and-how-culture-amp-retired-elm/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://kevinyank.com/posts/on-endings-why-how-we-retired-elm-at-culture-amp/"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2023/04-17-why-and-how-culture-amp-retired-elm/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2023/04-14-the-libraries-evil-martians-use-to-build-rails-apps/</id>
    <published>2023-04-14T11:23:30+08:00</published>
    <updated>2024-11-04T09:50:52+10:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>The Libraries Evil Martians Use to Build Rails Apps ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>A long list of gems and tools.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2023/04-14-the-libraries-evil-martians-use-to-build-rails-apps/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>, <a href="https://www.grizzlebit.com/tags/tools/">Tools</a>.</p>
</div>
]]></content>
    <link rel="alternate" href=""></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2023/04-14-the-libraries-evil-martians-use-to-build-rails-apps/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/posts/2020/04-18-lazily-loading-resized-images-on-a-hugo-photoblog/</id>
    <published>2020-04-18T11:36:00+08:00</published>
    <updated>2024-11-04T09:50:52+10:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Lazily Loading Resized Images on a Hugo Photoblog</title>
    <content type="html" xml:base="https://www.grizzlebit.com/posts/" xml:lang="en"><![CDATA[<div>
  <p>I rebuilt <a href="https://strangemadness.com">A Strange Kind of Madness</a> using <a href="https://gohugo.io">Hugo</a> a month or so ago. As with most photoblogs, it has pages with many images on them, and I was inspired by <a href="https://github.com/maxvoltar/photo-stream/">Photo Stream</a> to load these images lazily.</p>
<h2 id="image-resizing">Image resizing</h2>
<p>If you want Hugo to resize images when it builds a site, you need to place your images alongside posts, so they are considered <a href="https://gohugo.io/content-management/page-resources/">page resources</a>. So, I put each post in a folder with its associated image and reference it in a field called, shockingly, <code>image</code> in the post front matter.</p>
<pre tabindex="0"><code>$ ls content/posts/2020-03-31-my-post
20200331-4491.jpg
index.md

$ cat content/posts/2020-03-31-my-post/index.md
+++
title = My Post
date = 2020-03-31T10:42:00+08:00
image = 20200331-4491.jpg
+++
</code></pre><h2 id="adding-lazyload">Adding Lazyload</h2>
<p>Roll-up pages have many thumbnails and benefit most from lazy loading.</p>
<p>First up, add the <a href="https://github.com/verlok/lazyload">lazyload</a> javascript library to your site build.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Lazyload</span> <span class="nx">from</span> <span class="s2">&#34;lazyload&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Fire up our lazyloading (just initialising it does the job)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">_lazyload</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Lazyload</span><span class="p">();</span>
</span></span></code></pre></div><p>The library&rsquo;s default configuration targets images with the <code>lazyload</code> class and loads the image stored in the <code>data-src</code> attribute. If you place an image on the standard <code>src</code> attribute, it will be treated as a placeholder.</p>
<h2 id="placeholder-images">Placeholder images</h2>
<p>I wanted something more interesting than a sea of grey rectangles for placeholder images. I had a look at using <a href="https://blurha.sh/">BlurHash</a>, but that was going to involve rendering canvas elements for placeholders <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>I want the front end to be as simple as possible <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, so I abandoned that approach and instead created a single-pixel resize of the source image which provides a simplistic average colour placeholder for each image. It does the trick.</p>
<figure><img src="/images/placeholder-images.png"
    alt="Placeholder images">
</figure>

<h2 id="the-markup">The markup</h2>
<p>All of the necessary resizing code and markup is in a <a href="https://gohugo.io/templates/partials/">Hugo partial</a> that renders a thumbnail for each post. Be sure that your image tags include <code>width</code> and <code>height</code> attributes so the browser lays them out correctly such that lazy loading is effective.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{{- with .Resources.GetMatch (.Params.image) -}} {{/* Resize to a single pixel
</span></span><span class="line"><span class="cl">for a placeholder image */}} {{- $placeholder := .Resize &#34;1x1&#34; -}} {{/* Resize
</span></span><span class="line"><span class="cl">to 800 pixels wide for a thumbnail image */}} {{- $thumbnail := .Resize &#34;800x&#34;
</span></span><span class="line"><span class="cl">-}}
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">img</span>
</span></span><span class="line"><span class="cl">  <span class="na">src</span><span class="o">=</span><span class="s">&#34;{{ $placeholder.RelPermalink }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">data-src</span><span class="o">=</span><span class="s">&#34;{{ $thumbnail.RelPermalink }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">width</span><span class="o">=</span><span class="s">&#34;{{ $thumbnail.Width }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">height</span><span class="o">=</span><span class="s">&#34;{{ $thumbnail.Height }}&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">class</span><span class="o">=</span><span class="s">&#34;lazyload&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">{{- end -}}
</span></span></code></pre></div><p>The main downside to this is that two resizes for each image adds a bunch of time to the site&rsquo;s build process but that&rsquo;s a trade off I&rsquo;m willing to make.</p>
<p>Go forth, and embrace laziness.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Or integrating <a href="https://github.com/woltapp/react-blurhash">React components that handle this for you</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>The only Javascript it uses is for this lazy loading.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

  <p>
    
    <a href="https://www.grizzlebit.com/posts/2020/04-18-lazily-loading-resized-images-on-a-hugo-photoblog/">🔗</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/blogging/">Blogging</a>, <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>, <a href="https://www.grizzlebit.com/tags/tools/">Tools</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://www.grizzlebit.com/posts/2020/04-18-lazily-loading-resized-images-on-a-hugo-photoblog/"></link>
    <link rel="related" href="https://www.grizzlebit.com/posts/2020/04-18-lazily-loading-resized-images-on-a-hugo-photoblog/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2019/04-28-immer/</id>
    <published>2019-04-28T09:07:00+08:00</published>
    <updated>2023-04-13T20:33:32+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Immer ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>Using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Javascript Proxies</a> to provide immutable data with a native feel.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2019/04-28-immer/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://hackernoon.com/introducing-immer-immutability-the-easy-way-9d73d8f71cb3"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2019/04-28-immer/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/posts/2018/02-17-event-sourcing-libraries/</id>
    <published>2018-02-17T20:38:00+08:00</published>
    <updated>2023-04-13T20:33:32+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Event Sourcing Libraries</title>
    <content type="html" xml:base="https://www.grizzlebit.com/posts/" xml:lang="en"><![CDATA[<div>
  <p>Creating an event sourced, CQRS application is simple enough conceptually but there is a lot of hidden detail when it comes to building them. There are a couple of event sourcing libraries I&rsquo;ve used that can help.</p>
<p>The first, <a href="https://github.com/envato/event_sourcery">Event Sourcery</a>, is in Ruby and created by my colleagues at Envato. You can use Postgres as your data store and it gives you what you need to build aggregates and events and projectors and process managers.</p>
<p>The immutability and process supervision baked into Elixir makes it a compelling option for implementing these kind of applications as well. <a href="https://github.com/commanded/commanded">Commanded</a> is written in Elixir and follows a very similar approach to Event Sourcery and works a treat.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/posts/2018/02-17-event-sourcing-libraries/">🔗</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://www.grizzlebit.com/posts/2018/02-17-event-sourcing-libraries/"></link>
    <link rel="related" href="https://www.grizzlebit.com/posts/2018/02-17-event-sourcing-libraries/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/posts/2017/06-16-the-convenience-of-_.chain-without-importing-the-world/</id>
    <published>2017-06-16T22:58:00+08:00</published>
    <updated>2023-04-13T20:33:32+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>The Convenience of _.chain Without Importing the World</title>
    <content type="html" xml:base="https://www.grizzlebit.com/posts/" xml:lang="en"><![CDATA[<div>
  <p>I&rsquo;ve been meaning to work out how to maintain the convenience of the <a href="https://lodash.com">Lodash&rsquo;s</a> <code>_.chain</code> function whilst only including the parts of Lodash that I actually need.</p>
<p>Turns out you can cherry pick the <code>fp</code> version of the functions you need and compose them together with <code>_.flow</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">sortBy</span> <span class="nx">from</span> <span class="s2">&#34;lodash/fp/sortBy&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">flatMap</span> <span class="nx">from</span> <span class="s2">&#34;lodash/fp/flatMap&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">uniq</span> <span class="nx">from</span> <span class="s2">&#34;lodash/fp/uniq&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">reverse</span> <span class="nx">from</span> <span class="s2">&#34;lodash/fp/reverse&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">flow</span> <span class="nx">from</span> <span class="s2">&#34;lodash/fp/flow&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">exampleData</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">happenedAt</span><span class="o">:</span> <span class="s2">&#34;2017-06-15T19:00:00+08:00&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">projects</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;Project One&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">happenedAt</span><span class="o">:</span> <span class="s2">&#34;2017-06-16T19:00:00+08:00&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">projects</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;Project One&#34;</span><span class="p">,</span> <span class="s2">&#34;Project Two&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">listOfProjectsByTime</span> <span class="o">=</span> <span class="p">(</span><span class="nx">entries</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">flow</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">sortBy</span><span class="p">(</span><span class="s2">&#34;happenedAt&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">reverse</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">flatMap</span><span class="p">(</span><span class="s2">&#34;projects&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">uniq</span>
</span></span><span class="line"><span class="cl">  <span class="p">)(</span><span class="nx">entries</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>You can read more in <a href="https://github.com/lodash/lodash/wiki/FP-Guide">Lodash&rsquo;s FP Guide</a>.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/posts/2017/06-16-the-convenience-of-_.chain-without-importing-the-world/">🔗</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://www.grizzlebit.com/posts/2017/06-16-the-convenience-of-_.chain-without-importing-the-world/"></link>
    <link rel="related" href="https://www.grizzlebit.com/posts/2017/06-16-the-convenience-of-_.chain-without-importing-the-world/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/posts/2017/05-16-consistent-update-times-for-middleman-blog-articles-with-git/</id>
    <published>2017-05-16T08:00:00+08:00</published>
    <updated>2023-04-13T20:33:32+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Consistent Update Times for Middleman Blog Articles with Git</title>
    <content type="html" xml:base="https://www.grizzlebit.com/posts/" xml:lang="en"><![CDATA[<div>
  <p>The default template for an Atom feed in <a href="https://github.com/middleman/middleman-blog">Middleman Blog</a> uses <a href="https://github.com/middleman/middleman-templates-blog/blob/e3128bcced09258badd1548eba19331f37faf364/template/source/feed.xml.builder#L18">the last modified time of an article&rsquo;s source file as the article&rsquo;s last update time</a>. This means that if I build the site on two different machines I will get different last updated times on articles in the two atom feeds. I&rsquo;d rather the built site look the same regardless of where I build it.</p>
<p>The source code for the site lives in a Git repository which means I have a consistent source for update times that I can rely on. So, I&rsquo;ve added a helper that asks Git for the last commit time of a file and falls back to its last modified time if the file isn&rsquo;t currently tracked in Git.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">helpers</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"> <span class="k">def</span> <span class="nf">last_update_time</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Time</span><span class="o">.</span><span class="n">parse</span> <span class="sb">`git log -1 --format=%cd </span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="sb"> 2&gt;/dev/null`</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">    <span class="no">File</span><span class="o">.</span><span class="n">mtime</span><span class="p">(</span><span class="n">file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">do</span>
</span></span></code></pre></div><p>I now use this helper in my Atom template for each article.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">xml</span><span class="o">.</span><span class="n">entry</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">xml</span><span class="o">.</span><span class="n">published</span> <span class="n">article</span><span class="o">.</span><span class="n">date</span><span class="o">.</span><span class="n">to_time</span><span class="o">.</span><span class="n">iso8601</span>
</span></span><span class="line"><span class="cl">  <span class="n">xml</span><span class="o">.</span><span class="n">updated</span> <span class="n">last_update_time</span><span class="p">(</span><span class="n">article</span><span class="o">.</span><span class="n">source_file</span><span class="p">)</span><span class="o">.</span><span class="n">iso8601</span>
</span></span><span class="line"><span class="cl">  <span class="n">xml</span><span class="o">.</span><span class="n">content</span> <span class="n">article</span><span class="o">.</span><span class="n">body</span><span class="p">,</span> <span class="s2">&#34;type&#34;</span> <span class="o">=&gt;</span> <span class="s2">&#34;html&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></div>
  <p>
    
    <a href="https://www.grizzlebit.com/posts/2017/05-16-consistent-update-times-for-middleman-blog-articles-with-git/">🔗</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>, <a href="https://www.grizzlebit.com/tags/tools/">Tools</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://www.grizzlebit.com/posts/2017/05-16-consistent-update-times-for-middleman-blog-articles-with-git/"></link>
    <link rel="related" href="https://www.grizzlebit.com/posts/2017/05-16-consistent-update-times-for-middleman-blog-articles-with-git/"></link>
    
  </entry>
  
  <entry>
    <id>https://www.grizzlebit.com/links/2017/03-14-shell-scripts-matter/</id>
    <published>2017-03-14T21:03:07+08:00</published>
    <updated>2023-04-13T20:33:32+08:00</updated>
    <author><name>Ray Grasso</name></author>
    
    <title>Shell Scripts Matter ↬</title>
    <content type="html" xml:base="https://www.grizzlebit.com/links/" xml:lang="en"><![CDATA[<div>
  <p>How to follow good practices with Bash.</p>

  <p>
    
    <a href="https://www.grizzlebit.com/links/2017/03-14-shell-scripts-matter/">↬</a>
     ∙ Tagged in <a href="https://www.grizzlebit.com/tags/programming/">Programming</a>.</p>
</div>
]]></content>
    <link rel="alternate" href="https://dev.to/thiht/shell-scripts-matter"></link>
    <link rel="related" href="https://www.grizzlebit.com/links/2017/03-14-shell-scripts-matter/"></link>
    
  </entry>
  
</feed>
