<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Noetic Nought</title>
    <link>https://punchagan.muse-amuse.in/</link>
    <description>Recent content on Noetic Nought</description>
    <generator>Hugo</generator>
    <language>en-US</language>
    <copyright>© 2006-2026 CC-BY-SA-4.0</copyright>
    <lastBuildDate>Fri, 30 Jan 2026 16:58:00 +0530</lastBuildDate>
    <atom:link href="https://punchagan.muse-amuse.in/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Why my Elfeed index was 0 bytes</title>
      <link>https://punchagan.muse-amuse.in/blog/elfeed-index-0-bytes/</link>
      <pubDate>Fri, 30 Jan 2026 16:58:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/elfeed-index-0-bytes/</guid>
      <description>&lt;p&gt;My Linux machine crashed, while I was in the middle of a video call. I&#xA;restarted quickly, and continued the discussion. Later, when I was trying to&#xA;sync &lt;a href=&#34;https://github.com/skeeto/elfeed&#34;&gt;Elfeed&lt;/a&gt; updates onto the &lt;a href=&#34;https://github.com/punchagan/elfeed-offline/&#34;&gt;Elfeed Offline&lt;/a&gt; app on my phone, I found it acting&#xA;weird. All the &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/offline-friendly-elfeed-web-ui/&#34;&gt;bazillion things&lt;/a&gt; needed to get it working were in place, but it&#xA;was still complaining about the Emacs Elfeed server not being accessible. I&#xA;tried opening Elfeed inside Emacs and failed! The index file in &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/elfeed-db-back-up-hooks/&#34;&gt;Elfeed&amp;rsquo;s DB was&#xA;empty&lt;/a&gt;! Gone! Poof!&lt;/p&gt;&#xA;&lt;p&gt;Frustrating, but I didn&amp;rsquo;t have time to look into what happened. I would&amp;rsquo;ve been&#xA;furious if I had been using Elfeed for longer and had a lot more metadata&#xA;saved. But, it was only a few weeks of lost metadata - posts I read, starred,&#xA;etc. I quickly setup a &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/elfeed-db-back-up-hooks/&#34;&gt;Git based backup&lt;/a&gt; to prevent future losses and moved on.&lt;/p&gt;&#xA;&lt;p&gt;Later, I found time to dig into what might have happened&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-breadcrumbs&#34;&gt;The breadcrumbs&lt;/h2&gt;&#xA;&lt;p&gt;Elfeed stores all the metadata for all the posts in an &lt;code&gt;index&lt;/code&gt; file with a&#xA;&lt;a href=&#34;https://en.wikipedia.org/wiki/Content-addressable_storage&#34;&gt;content addressed store&lt;/a&gt; of the contents of each of the posts. The index file is&#xA;simply a dump of the hash-table containing the metadata for the subscribed&#xA;feeds, their entries and metadata like tags, read/unread status, etc.&lt;/p&gt;&#xA;&lt;p&gt;The DB save happens in &lt;a href=&#34;https://github.com/skeeto/elfeed/blob/a39fb78e34ee25dc8baea83376f929d7c128344f/elfeed-db.el#L271&#34;&gt;&lt;code&gt;elfeed-db-save&lt;/code&gt;&lt;/a&gt;, which simply dumps the hash-table to&#xA;disk inside a call to the &lt;code&gt;with-temp-file&lt;/code&gt; macro.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;defun&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;elfeed-db-save&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;; &amp;lt;snip&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;with-temp-file&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;expand-file-name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;index&amp;#34;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;elfeed-db-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;; ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;princ&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;format&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;;;; Elfeed Database Index (version %s)\n\n&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                       &lt;span class=&#34;nv&#34;&gt;elfeed-db-version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;; ...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;prin1&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;elfeed-db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;;...&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;with-temp-file&lt;/code&gt;, as its documentation says, lets you create a new buffer,&#xA;evaluate the &lt;code&gt;body&lt;/code&gt; there, and write the buffer to &lt;code&gt;file&lt;/code&gt;. For a moment, I&#xA;thought there was some temporary file involved, but nope! I guess the name&#xA;comes as an extension from &lt;code&gt;with-temp-buffer&lt;/code&gt; which does create a temporary&#xA;buffer where the &lt;code&gt;body&lt;/code&gt; of the macro gets evaluated. Stripped to its core, &lt;a href=&#34;https://github.com/emacs-mirror/emacs/blob/3b547e4f5dc99dc157b52a059cf234f7a5d15112/lisp/subr.el#L5300-L5318&#34;&gt;this&#xA;function&lt;/a&gt; is:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;temp-file&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;temp-buffer&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;generate-new-buffer&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34; *temp file*&amp;#34;&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;prog1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;with-current-buffer&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;temp-buffer&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         &lt;span class=&#34;o&#34;&gt;,@&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;     &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;with-current-buffer&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;temp-buffer&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;write-region&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;temp-file&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;write-region-and-o-trunc&#34;&gt;&lt;code&gt;write-region&lt;/code&gt; and &lt;code&gt;O_TRUNC&lt;/code&gt;&lt;/h2&gt;&#xA;&lt;p&gt;So, &lt;a href=&#34;https://github.com/emacs-mirror/emacs/blob/3b547e4f5dc99dc157b52a059cf234f7a5d15112/src/fileio.c#L5512&#34;&gt;&lt;code&gt;write-region&lt;/code&gt; is the workhorse&lt;/a&gt; which writes the contents of the temporary&#xA;buffer to disk. It&amp;rsquo;s a roughly 300 line long C function that essentially opens&#xA;the file with the flags &lt;code&gt;O_WRONLY | O_CREAT | O_TRUNC&lt;/code&gt; (in this case) and then&#xA;does something to write the contents to the file, etc. Honestly, I didn&amp;rsquo;t look&#xA;at anything else too carefully after I spotted the &lt;code&gt;O_TRUNC&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The man page of &lt;code&gt;open&lt;/code&gt; explains &lt;code&gt;O_TRUNC&lt;/code&gt; as follows:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;O_TRUNC :: If the file already exists and is a regular file and the access mode&#xA;allows writing (i.e., is O_RDWR or O_WRONLY) it will be truncated to&#xA;length 0.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Voilà!&lt;/p&gt;&#xA;&lt;p&gt;The write is not atomic. The file first gets truncated to length 0, and then we&#xA;hope that the new contents get correctly written before something goes wrong.&lt;/p&gt;&#xA;&lt;p&gt;It now makes sense why the file got truncated to 0 bytes. The crash happened&#xA;after the &lt;code&gt;open&lt;/code&gt;, but before the write. Somehow the crash happened in this&#xA;short window, and poof!&lt;/p&gt;&#xA;&lt;h2 id=&#34;emacs-has-backup-files-doesn-t-it&#34;&gt;Emacs has backup files, doesn&amp;rsquo;t it?&lt;/h2&gt;&#xA;&lt;p&gt;Temporary files with &lt;code&gt;~&lt;/code&gt; in their file extensions have definitely annoyed me in&#xA;the past when Emacs created them where I didn&amp;rsquo;t want them. So, I do know that&#xA;Emacs has back-up mechanisms out of the box. But, it turns out that the backups&#xA;occur in code paths that are more interactive, like &lt;code&gt;save-buffer&lt;/code&gt;,&#xA;&lt;code&gt;write-file&lt;/code&gt;, etc. And not via the programmatic APIs like &lt;code&gt;write-region&lt;/code&gt; or the&#xA;higher level &lt;code&gt;with-temp-file&lt;/code&gt;. &lt;code&gt;save-buffer&lt;/code&gt; calls &lt;code&gt;backup-buffer&lt;/code&gt; before&#xA;writing, but &lt;code&gt;write-region&lt;/code&gt; is much more low-level and doesn&amp;rsquo;t deal with&#xA;backups.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-fix&#34;&gt;The fix&lt;/h2&gt;&#xA;&lt;p&gt;My &amp;ldquo;fix&amp;rdquo; for this is to backup the Elfeed data in a git repository to be able&#xA;to recover from any such corruptions of data, which I already wrote about &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/elfeed-db-back-up-hooks/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I know that any code that uses &lt;code&gt;with-temp-file&lt;/code&gt; (or &lt;code&gt;write-region&lt;/code&gt;) could be&#xA;affected by this, and the right fix for this may be to write to a temporary&#xA;file and rename it. Maybe next time I lose data I&amp;rsquo;ll actually fix it properly.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Elfeed DB backup hooks</title>
      <link>https://punchagan.muse-amuse.in/blog/elfeed-db-back-up-hooks/</link>
      <pubDate>Tue, 20 Jan 2026 23:13:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/elfeed-db-back-up-hooks/</guid>
      <description>&lt;p&gt;I had a bunch of things running on my laptop &amp;ndash; video call with screenshare, my&#xA;Windows VM, Firefox with a lot of tabs, etc. And my laptop crashed! I didn&amp;rsquo;t&#xA;have the time to dig into what, why and how.&lt;/p&gt;&#xA;&lt;p&gt;Later in the day, I discovered my Elfeed&amp;rsquo;s DB was gone &amp;ndash; blown away. :( I&amp;rsquo;m&#xA;guessing the crash happened in the middle of &lt;code&gt;elfeed-db-save&lt;/code&gt;, and the data was&#xA;lost.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve now added some back-up for the DB, since I intend to use Elfeed regularly.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;defvar&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-timer&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s&#34;&gt;&amp;#34;Timer for debounced elfeed database saves.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;defun&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-and-backup&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s&#34;&gt;&amp;#34;Save the elfeed database and commit to git.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;when&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;boundp&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;&amp;#39;elfeed-db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;elfeed-db&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;elfeed-db-save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;default-directory&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;elfeed-db-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;when&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;file-exists-p&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;.git&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;call-process&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;git&amp;#34;&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;*elfeed-db-backup*&amp;#34;&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;add&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;-A&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;call-process&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;git&amp;#34;&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;*elfeed-db-backup*&amp;#34;&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;commit&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;-m&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;auto-backup&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;call-process&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;git&amp;#34;&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;*elfeed-db-backup*&amp;#34;&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;push&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;origin&amp;#34;&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;defun&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-soon&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s&#34;&gt;&amp;#34;Schedule a database save after 10 seconds of idle.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;interactive&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;when&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-timer&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;cancel-timer&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-timer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;setq&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-timer&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;run-with-idle-timer&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;#&amp;#39;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-and-backup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;;; Save and backup when tags change (elfeed-web usage)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;add-hook&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;&amp;#39;elfeed-tag-hooks&lt;/span&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;lambda&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kp&#34;&gt;&amp;amp;rest&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-soon&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;add-hook&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;&amp;#39;elfeed-untag-hooks&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;lambda&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kp&#34;&gt;&amp;amp;rest&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-soon&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;;; Save and backup when new entries are added&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;add-hook&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;&amp;#39;elfeed-db-update-hook&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;#&amp;#39;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;pc/elfeed-db-save-soon&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    <item>
      <title>Safari and invalid HTTP/2 headers</title>
      <link>https://punchagan.muse-amuse.in/blog/safari-and-invalid-http-2-headers/</link>
      <pubDate>Fri, 16 Jan 2026 21:37:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/safari-and-invalid-http-2-headers/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://github.com/punchagan/elfeed-offline/&#34;&gt;Elfeed-offline&lt;/a&gt; currently has a &lt;a href=&#34;https://camlworks.github.io/dream/&#34;&gt;Dream web server&lt;/a&gt; which acts as a proxy server in&#xA;front of Elfeed&amp;rsquo;s Emacs &lt;a href=&#34;https://github.com/skeeto/emacs-web-server/tree/master&#34;&gt;simple-httpd&lt;/a&gt; server.&lt;/p&gt;&#xA;&lt;p&gt;simple-httpd supports HTTP/1.1 protocol, while Dream provides transparent&#xA;upgrading of connections to HTTP/2 — if the client can handle HTTP/2 and the&#xA;connection is using HTTPS, it is transparently upgraded to HTTP/2.&lt;/p&gt;&#xA;&lt;p&gt;My proxying code was too simplistic in forwarding the headers too along with&#xA;the content received from the simple-httpd server. Some of the HTTP/1 headers&#xA;are no longer valid in HTTP/2. And, Safari (and curl) strictly adhere to the&#xA;protocol and fail if there are invalid headers. Curl, for instance, fails with&#xA;the following error:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt; HTTP/2 200&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt; server: simple-httpd (Emacs 30.1)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt; date: Fri, 16 Jan 2026 10:57:25 GMT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;* Invalid HTTP header field was received: frame type: 1, stream: 1, name: [connection], value: [keep-alive]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;* [HTTP2] [1] received invalid frame: FRAME[HEADERS, len=77, hend=1, eos=0], error -531: Invalid HTTP header field was received&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;* HTTP/2 stream 1 was not closed cleanly: unknown (err 4294966765)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;* Connection #0 to host 192.168.1.5 left intact&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl: (92) Invalid HTTP header field was received: frame type: 1, stream: 1, name: [connection], value: [keep-alive]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was causing issues for &lt;a href=&#34;https://github.com/Feyorsh&#34;&gt;@Feyorsh&lt;/a&gt; who was trying out elfeed-offline with&#xA;Safari. Thanks for taking the time to debug the problem and for suggesting a&#xA;fix!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>An offline-friendly Elfeed web UI</title>
      <link>https://punchagan.muse-amuse.in/blog/offline-friendly-elfeed-web-ui/</link>
      <pubDate>Wed, 07 Jan 2026 02:31:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/offline-friendly-elfeed-web-ui/</guid>
      <description>&lt;p&gt;I want to read articles from my RSS subscriptions on my phone without signing&#xA;up to a hosted service or running a public server.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/skeeto/elfeed&#34;&gt;Elfeed&lt;/a&gt;, the Emacs feed reader, already lets me manage my subscriptions and read&#xA;from within Emacs. It comes with an experimental web UI, but needs the server&#xA;(running on my laptop) to be accessible whenever I want to read. My phone&#xA;becomes useless the moment my laptop sleeps.&lt;/p&gt;&#xA;&lt;p&gt;So, I built an alternate web UI with a service worker that caches content on&#xA;the client side for a smooth offline reading experience. It&amp;rsquo;s written in &lt;a href=&#34;https://ocaml.org/&#34;&gt;OCaml&lt;/a&gt;&#xA;and compiled to JavaScript using &lt;a href=&#34;https://ocsigen.org/js_of_ocaml/latest/manual/overview&#34;&gt;&lt;code&gt;js_of_ocaml&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-bother-with-rss-feeds&#34;&gt;Why bother with RSS feeds?&lt;/h2&gt;&#xA;&lt;p&gt;It is annoyingly easy to fall into the trap of short form videos or other&#xA;algorithmically &amp;ldquo;curated&amp;rdquo; feeds and get sucked into scrolling mindlessly. I&#xA;fear this is going to get even worse with all the generative AI stuff.&lt;/p&gt;&#xA;&lt;p&gt;I want to be more deliberate about what I consume. RSS feels calmer and gives&#xA;me a greater sense of control. Or it may just be nostalgia from good old Google&#xA;Reader days.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-emacs-and-elfeed&#34;&gt;Why Emacs and Elfeed?&lt;/h2&gt;&#xA;&lt;p&gt;Since Google Reader was killed, I&amp;rsquo;ve bounced between many readers but none of&#xA;them has really stuck with me. I&amp;rsquo;ve had &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/elfeed-hook-to-fetch-full-content/&#34;&gt;different phases&lt;/a&gt; of using Elfeed, over&#xA;the years, though.&lt;/p&gt;&#xA;&lt;p&gt;I like the fact that everything is local with Elfeed &amp;ndash; no hosted services, no&#xA;public servers. And since it lives inside Emacs, everything from tagging to how&#xA;entries are displayed can be tweaked.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-elfeed-offline&#34;&gt;Why Elfeed offline?&lt;/h2&gt;&#xA;&lt;p&gt;Elfeed also ships a basic web UI that lets me read my feeds from a different&#xA;device, but only when my laptop is online and reachable. I&amp;rsquo;d like to be able to&#xA;read these posts even when I&amp;rsquo;m &amp;ldquo;on the road&amp;rdquo;, say, while waiting for a train or&#xA;while taking a cab ride.&lt;/p&gt;&#xA;&lt;p&gt;I knew it should be possible to do this with some client side caching. And, I&#xA;considered improving the web UI of Elfeed itself but it doesn&amp;rsquo;t seem to be&#xA;&lt;a href=&#34;https://github.com/skeeto/elfeed/pulls&#34;&gt;actively maintained&lt;/a&gt; in the last couple of years. Creating a separate project&#xA;would also give me more freedom to experiment.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-does-it-work&#34;&gt;How does it work?&lt;/h2&gt;&#xA;&lt;p&gt;Elfeed offline comes with a tiny &lt;a href=&#34;https://camlworks.github.io/dream/&#34;&gt;Dream&lt;/a&gt; based webserver that acts as a proxy in&#xA;front of the Emacs Elfeed webserver for the API requests. The Dream web server&#xA;also serves the static assets like the HTML, JavaScript, stylesheets and the&#xA;Service Worker for the web app. The diagram below shows how data flows between the different components.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-artist&#34; data-lang=&#34;artist&#34;&gt;+---------------+     +-----------------+&#xA;|  Emacs Elfeed |&amp;lt;---&amp;gt;|     Dream       |&#xA;|     Server    |     |   Web-server    |&#xA;+---------------+     +-----------------+&#xA;                               ^&#xA;                               |&#xA;                               v&#xA; +------------+        +----------------+       +---------+&#xA; |    Web     |        |    Service     |       | Browser |&#xA; |  Client    |&amp;lt;------&amp;gt;|     Worker     |&amp;lt;-----&amp;gt;|  Cache  |&#xA; +------------+        +----------------+       +---------+&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The Service Worker intercepts all the &lt;code&gt;GET&lt;/code&gt; requests from the client and&#xA;responds using a &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Caching#cache_first_with_cache_refresh&#34;&gt;cache-first-with-cache-refresh&lt;/a&gt; strategy. This makes the&#xA;reading experience feel very responsive. The web client also implements a &amp;ldquo;good&#xA;enough&amp;rdquo; clone of the original search functionality of Elfeed to allow searching&#xA;and filtering content while offline.&lt;/p&gt;&#xA;&lt;p&gt;The web app also lets me mark entries as read or star them when I&amp;rsquo;m offline.&#xA;The Service Worker caches these operations and updates the server when the&#xA;server becomes reachable. There&amp;rsquo;s no smart conflict resolution here, and the&#xA;API requests just overwrite and update the state on the server.&lt;/p&gt;&#xA;&lt;p&gt;I have been using this for a couple of weeks now, and am quite happy with how&#xA;responsive the UI feels and the number of posts I managed to read so far.&lt;/p&gt;&#xA;&lt;p&gt;But, there are definitely some rough edges to smoothen out.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;It&amp;rsquo;s a pain to make sure that both the servers are running when I&amp;rsquo;m online&#xA;and want to sync new updates to my phone. I&amp;rsquo;m considering adding a tiny Emacs&#xA;helper to manage this.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I need to remember to open the app on my phone to trigger a cache update&#xA;before stepping away from my laptop. I want to explore using the &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Background_Synchronization_API&#34;&gt;Background&#xA;Sync API&lt;/a&gt; (not available on Firefox) to possibly make this easier.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The UI could use some improvements to indicate pending syncs when offline and&#xA;also indicate the last sync with the server to help ensure everything is&#xA;synced before stepping away from my laptop.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Service workers need the server to be accessible via https. I have a wrapper&#xA;script around mkcert to make this easy, but some apps on my phone &lt;a href=&#34;https://www.reddit.com/r/UPI/comments/1p4le9x/what_happened_to_my_bhim_app/&#34;&gt;refuse to&#xA;work&lt;/a&gt; correctly when there are user installed certificates.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;why-ocaml&#34;&gt;Why OCaml?&lt;/h2&gt;&#xA;&lt;p&gt;At work, I write OCaml every day and enjoy it. This project seemed like a good&#xA;one to try out &lt;code&gt;js_of_ocaml&lt;/code&gt; since everything is local and I don&amp;rsquo;t need to&#xA;worry about bundle sizes, etc.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;js_of_ocaml&lt;/code&gt; makes it quite convenient to share code between the server and&#xA;the client side. And given there&amp;rsquo;s also a &amp;ldquo;third&amp;rdquo; Service Worker component in&#xA;the mix, I thought it would make life easier. For instance, the message types&#xA;and the serialization code for the messages between the client and the service&#xA;worker are shared.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m also using this project to experiment with Dune&amp;rsquo;s &amp;ldquo;soon to be released&amp;rdquo;™&#xA;&lt;a href=&#34;https://dune.readthedocs.io/en/stable/tutorials/dune-package-management/index.html&#34;&gt;package management&lt;/a&gt; that our team at Tarides is working on building.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-can-you-use-it&#34;&gt;How can you use it?&lt;/h2&gt;&#xA;&lt;p&gt;You can try out Elfeed offline&amp;rsquo;s UI &lt;a href=&#34;https://elfeed-offline.muse-amuse.in/&#34;&gt;here&lt;/a&gt; as a static site (with some posts from&#xA;Planet Emacslife and The OCaml Planet). You should be able to read posts,&#xA;search and filter for posts, mark them as read or star them. Any changes you&#xA;make should be preserved between reloads.&lt;/p&gt;&#xA;&lt;p&gt;If you are an Emacs user but don&amp;rsquo;t use Elfeed, you can checkout the &lt;a href=&#34;https://github.com/skeeto/elfeed&#34;&gt;Elfeed&amp;rsquo;s&#xA;README&lt;/a&gt; for instructions to set it up. It also has links to a bunch of blog&#xA;posts and videos that show off the features of Elfeed.&lt;/p&gt;&#xA;&lt;p&gt;If you are already using Elfeed, you should be able to set it up quite easily&#xA;if you&amp;rsquo;re happy installing some OCaml tooling. The installations instructions&#xA;are available in the &lt;a href=&#34;https://github.com/punchagan/elfeed-offline/blob/main/README.org&#34;&gt;README&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;outro&#34;&gt;Outro&lt;/h2&gt;&#xA;&lt;p&gt;I have been enjoying using Elfeed offline for the past couple of weeks. And I&amp;rsquo;m&#xA;hoping I&amp;rsquo;ll end up spending much more time reading in it, than fixing and&#xA;building it. It currently definitely feels like something I built for myself,&#xA;but I&amp;rsquo;d love to hear from others who try it out.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Some useful Git configuration for Windows</title>
      <link>https://punchagan.muse-amuse.in/blog/some-useful-git-configuration-for-windows/</link>
      <pubDate>Fri, 24 Jan 2025 01:25:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/some-useful-git-configuration-for-windows/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve recently been working on Windows with a relatively involved git repository&#xA;and ran into a bunch of issues. Setting these configuration values turned out&#xA;to be very helpful!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Allow symlinks&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git config --global core.symlinks &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Don&amp;#39;t automatically change file endings to \r\n (carriage return + line feed)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git config --global core.autocrlf &lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Use line feed for line endings&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git config --global core.eol lf&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Allow long paths in the repo&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git config --global core.longpaths &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s also useful to set these as configuration values in &lt;code&gt;.gitattributes&lt;/code&gt; in a&#xA;repository to share this configuration with other people working on it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;related-posts&#34;&gt;Related posts&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://punchagan.muse-amuse.in/blog/git-resources/&#34;&gt;Git resources&lt;/a&gt; : A post with some resources to better understand git, along&#xA;with some useful git configuration.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Restoring a broken Ubuntu upgrade</title>
      <link>https://punchagan.muse-amuse.in/blog/restoring-broken-ubuntu-upgrade/</link>
      <pubDate>Wed, 20 Nov 2024 15:17:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/restoring-broken-ubuntu-upgrade/</guid>
      <description>&lt;p&gt;A couple of weeks ago, &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; and I were discussing Ubuntu upgrades &amp;ndash; his&#xA;upgrade was borked and he ended up re-installing his system. I was celebrating&#xA;the fact that I never really had to throw away my OS installation entirely&#xA;since I switched to Ubuntu after getting tired of &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/numpy-pacman-and-me/&#34;&gt;Arch Linux in 2011-12&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;And on cue, I had a really broken update on my dad&amp;rsquo;s laptop! It was really&#xA;broken &amp;ndash; no WiFi, no Ethernet connection, no GUI packages, etc.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-crash&#34;&gt;The crash&lt;/h2&gt;&#xA;&lt;p&gt;I started the update but forgot about it, promptly. My dad was semi-supervising&#xA;it, since he was using the laptop on-and-off. I came back to it a couple of&#xA;days later, and I ran an autoremove, assuming everything else in the install&#xA;went ok. Everything continued to work alright until some time later, we had to&#xA;restart the X server because it had hung up. And it never came back. And on a&#xA;full restart, no network &amp;ndash; both Ethernet and Wifi.&lt;/p&gt;&#xA;&lt;h2 id=&#34;chroot-to-the-rescue&#34;&gt;&lt;code&gt;chroot&lt;/code&gt; to the rescue!&lt;/h2&gt;&#xA;&lt;p&gt;Thankfully these are not the days when I had &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/not-so-floppix/&#34;&gt;access to a single computer&lt;/a&gt;, and&#xA;crashing it meant running to a friend and using their computer to download&#xA;stuff. I quickly made a bootable USB on my laptop.&lt;/p&gt;&#xA;&lt;p&gt;I boot my dad&amp;rsquo;s laptop using the live distro and chroot to the installed OS&#xA;using the live distro. Live Ubuntu has Ethernet working, thankfully.&lt;/p&gt;&#xA;&lt;p&gt;For &lt;code&gt;apt-get install&lt;/code&gt; and &lt;code&gt;dist-upgrade&lt;/code&gt; to work correctly with the &lt;code&gt;chroot&lt;/code&gt;, I&#xA;had to &lt;a href=&#34;https://forum.manjaro.org/t/howto-chroot-from-or-into-any-linux-distribution/34071&#34;&gt;mount a bunch of volumes&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Due to some network manager changes in the update, the &lt;code&gt;chroot&lt;/code&gt; system still&#xA;wasn&amp;rsquo;t connecting to the internet. I probably should&amp;rsquo;ve looked at the&#xA;&lt;code&gt;resolv.conf&lt;/code&gt; file first, but ended up going the &lt;a href=&#34;https://wiki.ubuntu.com/OfflinePackageDownload&#34;&gt;offline package install&lt;/a&gt;&#xA;route&amp;hellip;&lt;/p&gt;&#xA;&lt;h2 id=&#34;offline-updates&#34;&gt;Offline updates&lt;/h2&gt;&#xA;&lt;p&gt;I generate a list of missing packages to be downloaded using the &lt;code&gt;--print-uris&lt;/code&gt;&#xA;flag to &lt;code&gt;apt-get&lt;/code&gt; on the &lt;code&gt;chroot&lt;/code&gt; system; And use &lt;code&gt;wget&lt;/code&gt; to download the&#xA;packages on the &amp;ldquo;live&amp;rdquo; system; Then copying these packages to&#xA;&lt;code&gt;/var/cache/apt/archives/&lt;/code&gt; gets the update to finish.&lt;/p&gt;&#xA;&lt;h2 id=&#34;but-more-missing-packages&#34;&gt;But more missing packages!&lt;/h2&gt;&#xA;&lt;p&gt;I rebooted into the installed OS, once &lt;code&gt;apt-get dist-upgrade&lt;/code&gt; seems to have&#xA;everything resolved. But, I still didn&amp;rsquo;t have a network! I had to &lt;code&gt;chroot&lt;/code&gt;&#xA;again into the system, and this time I figured out that there was an issue with&#xA;&lt;code&gt;resolv.conf&lt;/code&gt; being a broken soft-link which seemed to cause the broken network&#xA;(with Ethernet).&lt;/p&gt;&#xA;&lt;p&gt;For WiFi, I had to install proprietary drivers for the Broadcom wireless card&#xA;(&lt;code&gt;BCM43142 802.11b/g/n&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;p&gt;I had to also install the &lt;code&gt;ubuntu-desktop&lt;/code&gt; and related packages that seem to&#xA;have gone missing, and on reboot everything was up and running!&lt;/p&gt;&#xA;&lt;h2 id=&#34;outro&#34;&gt;Outro&lt;/h2&gt;&#xA;&lt;p&gt;When I told Shantanu, I didn&amp;rsquo;t have to reinstall my Ubuntu from scratch doesn&amp;rsquo;t&#xA;mean that all my updates were flawless, and things never broke. I have had such&#xA;broken updates a few times &amp;ndash; network updates leaving the system in a broken&#xA;state after network connections died, Ubuntu upgrade being in a borked state&#xA;after the &lt;a href=&#34;https://ubuntu.com/security/CVE-2024-3094&#34;&gt;xz vulnerability was discovered&lt;/a&gt;, etc.&lt;/p&gt;&#xA;&lt;p&gt;Updates do fail. But, more often than not I&amp;rsquo;ve been able to restore things&#xA;thanks to &lt;code&gt;dpkg&lt;/code&gt; &amp;rsquo;s robustness and awesomness! And in some extreme cases,&#xA;reaching for &lt;code&gt;chroot&lt;/code&gt; to save myself.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s probably nothing new to learn or discover in this post, but just some&#xA;help forum links that might come in handy, in future. In this age of AI&#xA;answers, it seemed nice to look through some old forum QAs and work things out&#xA;using them.&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; and &lt;a href=&#34;https://x.com/kamalx&#34;&gt;Kamal&lt;/a&gt; for reading drafts of this post.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Responsive Auto Export for Org Hugo</title>
      <link>https://punchagan.muse-amuse.in/blog/responsive-auto-export-for-org-hugo/</link>
      <pubDate>Wed, 13 Nov 2024 00:17:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/responsive-auto-export-for-org-hugo/</guid>
      <description>&lt;p&gt;I use &lt;a href=&#34;https://github.com/kaushalmodi/ox-hugo/&#34;&gt;ox-hugo&lt;/a&gt; to &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/deploying-hugo-drafts-simplified/&#34;&gt;write blog posts&lt;/a&gt; in org-mode and publish them using &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;. I&#xA;&lt;a href=&#34;https://punchagan.muse-amuse.in/blog/why-i-like-org-as-a-markup/&#34;&gt;enjoy using org-mode&lt;/a&gt; for any writing that I do, including blog posts (when I am&#xA;able to get myself to write them). After a long hiatus, I&amp;rsquo;ve been trying to get&#xA;back to blogging, as this post might have given away. But, ox-hugo&amp;rsquo;s &lt;a href=&#34;https://ox-hugo.scripter.co/doc/auto-export-on-saving/&#34;&gt;auto&#xA;export&lt;/a&gt; seemed much slower than I remember it being.&lt;/p&gt;&#xA;&lt;p&gt;Each time I hit save on my &lt;code&gt;blog-posts.org&lt;/code&gt; file, Emacs gets busy for about&#xA;10ish seconds exporting the post to Hugo markdown. And this is annoying because&#xA;I tend to hit &lt;code&gt;save-buffer&lt;/code&gt; multiple times while writing in &lt;code&gt;Emacs&lt;/code&gt; &amp;ndash; thanks,&#xA;muscle memory!&lt;/p&gt;&#xA;&lt;h2 id=&#34;enter-emacs-profiler&#34;&gt;Enter Emacs profiler&lt;/h2&gt;&#xA;&lt;p&gt;I was on a flight and had some time to dig into this. If I was online, I&#xA;probably would have looked through the README and/or the issue tracker, but I&#xA;&lt;a href=&#34;https://punchagan.muse-amuse.in/blog/how-i-learnt-to-use-emacs-profiler/&#34;&gt;jumped in&lt;/a&gt; with the handy &lt;a href=&#34;https://www.gnu.org/software/emacs/manual/html_node/elisp/Profiling.html&#34;&gt;Emacs profiler&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The profiler-report showed that a big chunk of time was being spent in&#xA;&lt;code&gt;org-id-update-id-locations&lt;/code&gt; even before the actual export started. And then,&#xA;during the export, a bulk of the time was spent in&#xA;&lt;code&gt;org-hugo--get-pre-processed-buffer&lt;/code&gt;. Once I knew the problem areas, I started&#xA;poking around in the &lt;code&gt;ox-hugo&lt;/code&gt; code to &amp;ldquo;fix&amp;rdquo; these issues.&lt;/p&gt;&#xA;&lt;h2 id=&#34;turn-off-org-id-location-update&#34;&gt;Turn off Org ID location update&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;org-id-update-id-locations&lt;/code&gt; scans a bunch of org-mode files and stores a&#xA;mapping of all the IDs of subtrees to their filenames. If you have a lot of org&#xA;subtrees this can take a while, even if none of them actually have IDs. It&#xA;turns out that I didn&amp;rsquo;t have any ID properties set, and this caused the &lt;a href=&#34;https://github.com/kaushalmodi/ox-hugo/blob/98421a1298adc6d80ce21b3cb5c951af818b27bf/ox-hugo.el#L4881-L4882&#34;&gt;update&#xA;function to run before every export&lt;/a&gt;!&lt;/p&gt;&#xA;&lt;p&gt;I simply added a new ID property on one of the subtrees in my blog-posts file&#xA;to prevent the ID updation from running on every export! I&amp;rsquo;m not sure how a&#xA;stale &lt;code&gt;org-id-locations&lt;/code&gt; value affects cross links, but at this stage of&#xA;writing I don&amp;rsquo;t care about the cross links. (spoiler: The next hack actually&#xA;nullifies any impact a stale value might have had!)&lt;/p&gt;&#xA;&lt;p&gt;Cutting down auto export time by 4-5 seconds is great! But, I&amp;rsquo;m still not happy&#xA;to wait for 5-6 seconds in the middle of writing my posts. Let&amp;rsquo;s look at the&#xA;other hotspot &amp;ndash; the buffer preprocessing!&lt;/p&gt;&#xA;&lt;h2 id=&#34;turn-off-buffer-preprocessing-maybe&#34;&gt;Turn off buffer preprocessing, maybe?&lt;/h2&gt;&#xA;&lt;p&gt;Currently, I don&amp;rsquo;t have any cross links between posts in my org-mode source.&#xA;So, I can turn off this feature completely by setting&#xA;&lt;code&gt;org-hugo--preprocess-buffer&lt;/code&gt; to &lt;code&gt;nil&lt;/code&gt;. Viola! Hitting &lt;code&gt;save-buffer&lt;/code&gt; doesn&amp;rsquo;t&#xA;freeze my Emacs any more. I can compose &amp;ldquo;100s of blog posts&amp;rdquo;™ in a flurry! ;)&lt;/p&gt;&#xA;&lt;p&gt;But, if I&amp;rsquo;m going to have these &amp;ldquo;100s of blog posts&amp;rdquo;, wouldn&amp;rsquo;t it be better to&#xA;have cross links? But, with preprocessing turned off when there are&#xA;cross-links, the &amp;ldquo;auto-export and build&amp;rdquo; workflow breaks. The variable&#xA;&lt;code&gt;org-hugo--preprocess-buffer&lt;/code&gt; MUST be non-nil to produce posts with &lt;strong&gt;valid&lt;/strong&gt;&#xA;cross-links. If not, the exported markdown file processed by &lt;code&gt;hugo&lt;/code&gt; ends up&#xA;having broken cross-links, which crashes &lt;code&gt;hugo serve&lt;/code&gt; and/or &lt;code&gt;hugo build&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Unsetting and setting the &lt;code&gt;org-hugo--preprocess-buffer&lt;/code&gt; variable for the&#xA;writing vs publishing phase, respectively, isn&amp;rsquo;t an ergonomic workflow. It&amp;rsquo;s&#xA;not an improvement over disabling and enabling the auto-export mode as needed.&#xA;I want to enjoy auto export with Hugo&amp;rsquo;s &lt;a href=&#34;https://gohugo.io/getting-started/usage/#livereload&#34;&gt;live reload&lt;/a&gt; feature.&lt;/p&gt;&#xA;&lt;h2 id=&#34;moar-workaround&#34;&gt;Moar workaround!&lt;/h2&gt;&#xA;&lt;p&gt;Looking through the code some more, I learnt &lt;a href=&#34;https://github.com/kaushalmodi/ox-hugo/blob/98421a1298adc6d80ce21b3cb5c951af818b27bf/ox-hugo.el#L2723&#34;&gt;&lt;code&gt;org-hugo-link&lt;/code&gt;&lt;/a&gt; first uses a&#xA;custom export handler, if one exists for a link&amp;rsquo;s protocol. I decided to piggy&#xA;back on this functionality and made up &lt;code&gt;hugo:&lt;/code&gt; protocol for cross-links.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;hugo&lt;/code&gt; link simply contains the &lt;code&gt;EXPORT_FILE_NAME&lt;/code&gt; of the linked blog post&#xA;i.e., name of the exported markdown file (without the .md extension) as the&#xA;&amp;lsquo;path&amp;rsquo; of the link. The custom protocol export handler can then generates a&#xA;&lt;code&gt;relref&lt;/code&gt; shortcode for Hugo to process in the exported markdown file.&lt;/p&gt;&#xA;&lt;p&gt;This nicely works around the need to preprocess my entire &lt;code&gt;blog-posts.org&lt;/code&gt;&#xA;buffer to generate &lt;strong&gt;valid&lt;/strong&gt; cross-links!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;org-link-set-parameters&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;hugo&amp;#34;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;:export&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;#&amp;#39;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;pc/org-hugo-link-export-to-md&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Export Hugo blog link to markdown file&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;defun&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;pc/org-hugo-link-export-to-md&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;backend&lt;/span&gt; &lt;span class=&#34;kp&#34;&gt;&amp;amp;optional&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s&#34;&gt;&amp;#34;Export a link to a Hugo blog link in markdown format.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;message&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;format&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;path: %s, desc: %s, backend: %s&amp;#34;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;backend&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;cond&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;backend&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;&amp;#39;md&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;equal&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;org-export-current-backend&lt;/span&gt; &lt;span class=&#34;ss&#34;&gt;&amp;#39;hugo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;format&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;[%s]({{&amp;lt; relref \&amp;#34;%s\&amp;#34; &amp;gt;}})&amp;#34;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ne&#34;&gt;error&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Cannot export Hugo link to non-Hugo backend&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;no&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;ne&#34;&gt;error&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Cannot export Hugo link to non-Hugo backend&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;outro&#34;&gt;Outro&lt;/h2&gt;&#xA;&lt;p&gt;I made a &lt;a href=&#34;https://github.com/punchagan/dot-doom/blob/c45f01e6b20275ce1df943855626af5c40cb626c/config.el#L541-L564&#34;&gt;simple helper&lt;/a&gt; to make it easier to insert these cross-links with the&#xA;&lt;code&gt;hugo:&lt;/code&gt; protocol. It simply looks through all the headlines with an&#xA;&lt;code&gt;EXPORT_FILE_NAME&lt;/code&gt; property, and allows it to be inserted as a link with the&#xA;&lt;code&gt;hugo:&lt;/code&gt; protocol.&lt;/p&gt;&#xA;&lt;p&gt;Now, cross-linking posts and publishing posts with cross-links are both a&#xA;breeze. Stay tuned for the &amp;ldquo;100s of blog posts&amp;rdquo; ™ I&amp;rsquo;m going to write!&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; and &lt;a href=&#34;https://x.com/kamalx&#34;&gt;Kamal&lt;/a&gt; for reading drafts of this post.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Made in 2022</title>
      <link>https://punchagan.muse-amuse.in/blog/made-in-2022/</link>
      <pubDate>Wed, 08 Mar 2023 23:35:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/made-in-2022/</guid>
      <description>&lt;p&gt;Keeping with last year&amp;rsquo;s tradition, this post comes when we are almost 1/5th&#xA;through this year.&lt;/p&gt;&#xA;&lt;h2 id=&#34;2022-projects&#34;&gt;2022 projects&lt;/h2&gt;&#xA;&lt;dl&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/punchagan/expense-tracker&#34;&gt;Expense Tracker&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;I built a personal Expense Tracker over the month of&#xA;October using SQLite and Streamlit, because I wasn&amp;rsquo;t comfortable sharing my&#xA;financial data with a 3rd party. I&amp;rsquo;ve tried to keep it simple, so it&amp;rsquo;s not a&#xA;pain to use.  But, I&amp;rsquo;ve also tried to keep it extensible, for others to try&#xA;and use.&#xA;&lt;p&gt;The last few months have been quite busy personally, and I haven&amp;rsquo;t been able&#xA;to use it as much as I would like to, but hopefully things will settle down&#xA;soon.  A couple of things that I&amp;rsquo;d like to improve:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The Sqlite file is stored locally, and the data is not synced between&#xA;different computers.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The tagging/categorization of expenses is very manual, and a little bit&#xA;more automation would be handy.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/dd&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/punchagan/artful-dodger/&#34;&gt;artful-dodger&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;A Next.js project for hosting an art/photo gallery with&#xA;metadata stored in a Google Spreadsheet and images in a Google Drive (with an&#xA;optional CDN, recommended). I built this for a friend, who is experimenting&#xA;with creating &amp;ldquo;a more equitable model for curating, buying, and selling&#xA;art&amp;rdquo;. The gallery is currently in &amp;ldquo;private beta&amp;rdquo;, and I can&amp;rsquo;t link to&#xA;it. But, here&amp;rsquo;s a sample gallery &lt;a href=&#34;https://punchagan.github.io/artful-dodger/&#34;&gt;here&lt;/a&gt;, whose configuration lives &lt;a href=&#34;https://github.com/punchagan/artful-dodger/blob/main/.env.local.default&#34;&gt;here&lt;/a&gt;.&lt;/dd&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/punchagan/ox-gist&#34;&gt;ox-gist&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;I released my first Emacs MELPA package &amp;ndash; &lt;a href=&#34;https://melpa.org/#/ox-gist&#34;&gt;ox-gist&lt;/a&gt; &amp;ndash; an Orgmode&#xA;backend to export and update sub-trees and buffers to GitHub gists.  It was a&#xA;great experience contributing to MELPA.  I wrote a blog post about it &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/ox-gist/&#34;&gt;too&lt;/a&gt;.&lt;/dd&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/punchagan/android-dotfiles/tree/main/bin&#34;&gt;Termux scripts&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;I&amp;rsquo;ve started using Termux on my Android phone and have&#xA;found the Text-to-Speech (TTS) API quite handy.  I&amp;rsquo;ve a couple of scripts on&#xA;my phone that lets me use TTS to read out contents on the clipboard or an&#xA;article from my browser.&lt;/dd&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/india-ultimate/fantasy&#34;&gt;Fantasy League Site&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;With help from a bunch of volunteers collecting and&#xA;validating data, &lt;a href=&#34;https://github.com/Joe2k&#34;&gt;Jonny&lt;/a&gt; and I built a &lt;a href=&#34;https://fantasy.indiaultimate.org/&#34;&gt;website&lt;/a&gt; for the Fantasy League for&#xA;Indian Ultimate State Championships. There was nothing technically&#xA;challenging about the project, but it just involved a lot of data cleaning&#xA;and co-ordination between the volunteers. The data was collected using pen&#xA;and paper, instead of a digital app for collecting the stats.  This made the&#xA;data cleaning and validation significantly harder. I&amp;rsquo;d definitely push for&#xA;digital data collection if people try to do something like this again.&lt;/dd&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/punchagan/bookmarklets&#34;&gt;Bookmarklets&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;A &lt;a href=&#34;https://punchagan.github.io/bookmarklets/&#34;&gt;web-page&lt;/a&gt; with a bunch of Bookmarklets from my browser&amp;rsquo;s&#xA;Bookmarks bar. I&amp;rsquo;ve had most of them around for a while, but putting them on&#xA;a web page makes it easy to share and find myself on other&#xA;browsers/computers.&lt;/dd&gt;&#xA;&lt;dt&gt;Music&lt;/dt&gt;&#xA;&lt;dd&gt;Unlike 2021, I&amp;rsquo;ve only been able to record a couple of songs. And&#xA;this year (2023) hasn&amp;rsquo;t been any better, so far. I hope I&amp;rsquo;ll be able to make&#xA;amends to this, soon, though.&lt;/dd&gt;&#xA;&lt;/dl&gt;&#xA;&lt;h2 id=&#34;review-of-2021-projects&#34;&gt;Review of 2021 projects&lt;/h2&gt;&#xA;&lt;dl&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/punchagan/earworm/&#34;&gt;Earworm&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;I didn&amp;rsquo;t make any updates to the package, not because it&amp;rsquo;s perfect&#xA;but because I didn&amp;rsquo;t use it much in 2022 &amp;ndash; thanks to how little I&amp;rsquo;ve been&#xA;able to record.&#xA;&lt;p&gt;While, writing this blog post I realized that workflow had a couple of&#xA;annoyances. I improved the code to fix them.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I need to move an audio file back-and-forth between my phone and my&#xA;laptop, one full round trip to add ID3 tags and cover image.  This makes&#xA;me put off uploading recorded files to the site.  Also, the new media file&#xA;needs to be named in a specific format.  I&amp;rsquo;ve added a new CLI subcommand&#xA;to automate all of this for me.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I always need the full collection of the music files on the computer,&#xA;where I&amp;rsquo;m updating the site from. I can just &lt;code&gt;scp&lt;/code&gt; the existing site onto&#xA;my computer and merge the media directories.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/dd&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://ukulele.muse-amuse.in/&#34;&gt;Ukulele Tutorials Aggregator&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;The job of manually updating the site is a&#xA;lot of work, and I haven&amp;rsquo;t had the interest to keep doing it.&lt;/dd&gt;&#xA;&lt;dt&gt;RPi Server and Phone Backup&lt;/dt&gt;&#xA;&lt;dd&gt;The physical setup was accidentally dismantled&#xA;by folks I share my physical space with, and I haven&amp;rsquo;t been able to put back&#xA;everything in place, yet.  I&amp;rsquo;m hoping I&amp;rsquo;ll be able to get back to doing this&#xA;at some point this year.&lt;/dd&gt;&#xA;&lt;/dl&gt;&#xA;&lt;h2 id=&#34;outro&#34;&gt;Outro&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve already built a couple of small things in 2023, and I hope I am able to&#xA;find some interesting things to do this year.&lt;/p&gt;&#xA;&lt;p&gt;Link to the &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/made-in-2021/&#34;&gt;2021&lt;/a&gt; post.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Moving from Heroku to Fly.io</title>
      <link>https://punchagan.muse-amuse.in/blog/moving-from-heroku-to-fly-dot-io/</link>
      <pubDate>Sun, 06 Nov 2022 12:19:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/moving-from-heroku-to-fly-dot-io/</guid>
      <description>&lt;p&gt;A couple of months ago Heroku &lt;a href=&#34;https://blog.heroku.com/next-chapter&#34;&gt;announced&lt;/a&gt; their plan to &amp;ldquo;Focus on Mission&#xA;Critical&amp;rdquo; and shut down the free product plan. The ideas is to free up their&#xA;developer time to focus on the important stuff instead of fighting abuse of the&#xA;free plans. I&amp;rsquo;m surprised they invested in the free plans for so many years!&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-heroku-apps&#34;&gt;My Heroku Apps&lt;/h2&gt;&#xA;&lt;p&gt;I have a bunch of apps on the free (Hobby dev) plan, and only app on the Hobby&#xA;Basic plan because it needed a bigger DB. All of these apps are small enough&#xA;that I could easily host all of them on my shared &lt;a href=&#34;https://www.hetzner.com/&#34;&gt;Hetzner server&lt;/a&gt;, where this&#xA;blog is hosted. But, I like using Heroku for services that I run for other&#xA;people. It makes access control and deployment much easier, and lets me invite&#xA;other people to help maintain apps easily, despite constraints like &lt;a href=&#34;https://devcenter.heroku.com/articles/heroku-postgres-plans#hobby-tier&#34;&gt;10k rows&lt;/a&gt; in&#xA;the Postgres DB and &lt;a href=&#34;https://devcenter.heroku.com/articles/free-dyno-hours&#34;&gt;Dyno sleeping&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Over the years of using Heroku, I ended up accumulating a bunch of small apps&#xA;on the service. Most of these apps aren&amp;rsquo;t actively being used any more and&#xA;didn&amp;rsquo;t feel worth the effort of moving.&lt;/p&gt;&#xA;&lt;p&gt;I decided to let most of the apps die, since they aren&amp;rsquo;t actively being&#xA;used. Only a couple of apps are used regularly enough to be worth keeping alive&#xA;&amp;ndash; &lt;a href=&#34;https://sotg.indiaultimate.org/&#34;&gt;SOTG Calculator&lt;/a&gt; and &lt;a href=&#34;https://rsvp.tiks-ultimate.in/features&#34;&gt;RSVP app&lt;/a&gt;. I turned on &lt;a href=&#34;https://devcenter.heroku.com/articles/maintenance-mode&#34;&gt;maintenance mode&lt;/a&gt; on the RSVP app&#xA;for a couple of days, as an experiment, and I had to bring back the services&#xA;due to &amp;ldquo;popular&amp;rdquo; demand.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-not-pay-heroku&#34;&gt;Why not pay Heroku?&lt;/h2&gt;&#xA;&lt;p&gt;I did consider just moving up to the Hobby basic plan and paying for running&#xA;the app. I wasn&amp;rsquo;t keen on investing numerous hours to get these apps to run&#xA;again. &lt;a href=&#34;https://instagram.com/akilesh.m&#34;&gt;Akilesh&lt;/a&gt; convinced me that the amount to pay Heroku could be collected&#xA;from the team and wouldn&amp;rsquo;t amount to much per person. I almost took this route.&lt;/p&gt;&#xA;&lt;p&gt;But, it bugged me that the amount we&amp;rsquo;d be paying Heroku could fund a couple of&#xA;turf practice sessions for the team. I could host the apps on my Hetzner server&#xA;&amp;ndash; we already pay enough for it. But, giving other people maintainer access to&#xA;apps is important and this was not straightforward with Hetzner.&lt;/p&gt;&#xA;&lt;p&gt;Also, the SOTG app couldn&amp;rsquo;t be funded this way.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-fly-dot-io&#34;&gt;Why Fly.io?&lt;/h2&gt;&#xA;&lt;p&gt;I ranted about this to my friends on Zulip and &lt;a href=&#34;https://github.com/rajaboja/&#34;&gt;Rajesh&lt;/a&gt; offered to help me as a&#xA;way to learn some devops stuff. I was more than happy to have someone to&#xA;discuss this stuff with and get some help.&lt;/p&gt;&#xA;&lt;p&gt;We decided to give &lt;a href=&#34;https://fly.io/&#34;&gt;Fly.io&lt;/a&gt; a try despite being skeptical about using yet another&#xA;free service. Their free Hobby plan includes 3GB of persistent storage space&#xA;(which neither of the apps currently use, but potentially could). They also&#xA;built &lt;a href=&#34;https://fly.io/blog/new-turboku/&#34;&gt;Turboku&lt;/a&gt; &amp;ndash; a tool to move apps from Heroku. It seemed worth giving a try,&#xA;and we could always fall back on Hetzner if it didn&amp;rsquo;t work out.&lt;/p&gt;&#xA;&lt;p&gt;We had a couple of months before the Heroku shutdown date, when I ranted about&#xA;this. Rajesh and I exchanged a few links and messages on how to potentially go&#xA;about doing this, when we got down to it. But, he got busy with other things&#xA;and now the deadline was only a month away. I decided to use the notes and do&#xA;the migration myself. I was glad I had the notes to refer to. The initial&#xA;&amp;ldquo;research&amp;rdquo; made the task seem less daunting.&lt;/p&gt;&#xA;&lt;h2 id=&#34;migrating-a-simple-app&#34;&gt;Migrating a simple app&lt;/h2&gt;&#xA;&lt;p&gt;I started with the simpler app first &amp;ndash; the &lt;a href=&#34;https://sotg.indiaultimate.org/&#34;&gt;SOTG app&lt;/a&gt;. I used the &lt;a href=&#34;https://fly.io/launch/heroku&#34;&gt;Turboku web&#xA;page&lt;/a&gt; and it worked pretty seemlessly! It copied over the environment variables&#xA;from Heroku and deployed the app.&lt;/p&gt;&#xA;&lt;p&gt;But, using the Turboku web page meant that I didn&amp;rsquo;t have a &lt;code&gt;fly.toml&lt;/code&gt; file to&#xA;do any future deployments. I used the &lt;code&gt;fly launch&lt;/code&gt; command to create a new app&#xA;and edited the &lt;code&gt;fly.toml&lt;/code&gt; file to point to the migrated app.&lt;/p&gt;&#xA;&lt;p&gt;Setting up the custom domain was pretty simple too, using the CLI. I just had&#xA;to run &lt;code&gt;fly certs add sotg.indiaultimate.org&lt;/code&gt; after changing the DNS entries to&#xA;point to &lt;code&gt;sotg-calculator.fly.dev&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The whole thing felt quite simple and I was done in under an hour &amp;ndash; reading&#xA;the docs, installing the tool, trying out different things, getting it all&#xA;working. Everything. Quite impressive!&lt;/p&gt;&#xA;&lt;h2 id=&#34;migrating-a-bigger-app&#34;&gt;Migrating a bigger app&lt;/h2&gt;&#xA;&lt;p&gt;Encouraged by this, I started with the RSVP app. I didn&amp;rsquo;t think it would be&#xA;much harder than this, but I guess when there are a few more moving parts, it&amp;rsquo;s&#xA;hard to tell what could pop-up.&lt;/p&gt;&#xA;&lt;p&gt;I used the same approach for the migration &amp;ndash; Turboku web page to migrate from&#xA;Heroku and then ran the launch command to generate a &lt;code&gt;fly.toml&lt;/code&gt; file that I&#xA;edited to point to the deployed app.&lt;/p&gt;&#xA;&lt;p&gt;The deployment seemed to go fine, but the app wouldn&amp;rsquo;t come up. I looked at the&#xA;logs to find out that the app was getting &lt;a href=&#34;https://neo4j.com/developer/kb/linux-out-of-memory-killer/&#34;&gt;OOM killed&lt;/a&gt;. Fly.io provides half the&#xA;memory (256MB) that Heroku provided. But, I wasn&amp;rsquo;t expecting this tiny app to&#xA;need much more.&lt;/p&gt;&#xA;&lt;p&gt;I removed some default imports of large packages, but it didn&amp;rsquo;t really help&#xA;that much. I did consider reducing the Gunicorn&amp;rsquo;s &lt;code&gt;WEB_CONCURRENCY&lt;/code&gt; setting to&#xA;1, but didn&amp;rsquo;t want to take that route unless absolutely required. I looked&#xA;around the forums a little, and &lt;a href=&#34;https://community.fly.io/t/question-about-weird-deployment-size-on-fly/5677/2&#34;&gt;found&lt;/a&gt; that using a Dockerfile instead of the&#xA;buildpacks might improve the memory situation. And it did!&lt;/p&gt;&#xA;&lt;p&gt;Next, I moved to setting up the custom domain and it worked from the Fly.io&#xA;side seemlessly. But, the &lt;code&gt;flask_dance&lt;/code&gt; library was generating &lt;code&gt;http&lt;/code&gt; URLs in&#xA;it&amp;rsquo;s OAuth redirect URLs. The app already uses FlaskSSLify, but that didn&amp;rsquo;t&#xA;seem to be sufficient. After reading some &lt;a href=&#34;https://flask-dance.readthedocs.io/en/latest/proxies.html&#34;&gt;Flask dance&lt;/a&gt; and &lt;a href=&#34;https://flask.palletsprojects.com/en/latest/deploying/proxy_fix/&#34;&gt;Flask&lt;/a&gt; docs, I found&#xA;that Werkzeug&amp;rsquo;s &lt;code&gt;ProxyFix&lt;/code&gt; exists exactly for this!&lt;/p&gt;&#xA;&lt;p&gt;After dealing with these two unexpected problems, I got to the problem that I&#xA;was aware I had to tackle &amp;ndash; Fly.io doesn&amp;rsquo;t have support for cron jobs. The&#xA;RSVP app uses some scheduled jobs to periodically sync some metadata from&#xA;Google Drive, Calendar, etc.&lt;/p&gt;&#xA;&lt;p&gt;I took a hackish route to work around this. Using the &lt;a href=&#34;https://schedule.readthedocs.io/en/stable/&#34;&gt;schedule&lt;/a&gt; library, I wrote&#xA;a &lt;a href=&#34;https://github.com/thatte-idli-kaal-soup/rsvpapp/blob/master/scripts/cron.py&#34;&gt;&amp;ldquo;cron&amp;rdquo; script&lt;/a&gt; that would run the jobs on the desired schedule. I created a&#xA;duplicate app to run the cron service, even though it could potentially be run&#xA;as a different process on the same app. I didn&amp;rsquo;t want to risk any more OOM&#xA;kills on the web app. But, it can be a little bit more work to keep the&#xA;environment variables and other such things in sync between the two apps.&lt;/p&gt;&#xA;&lt;p&gt;I was quite excited about Fly.io having a datacenter in Madras and was swayed&#xA;by Fly.io&amp;rsquo;s marketing about running the app close to the users. I tried this&#xA;but the performance was terrible since the Mongo DB was in North Virginia. I&#xA;quickly switched back to using an East Coast region for Fly.io app too.&lt;/p&gt;&#xA;&lt;p&gt;But, while writing this blog post, I realized that I could move the DB to&#xA;India. I moved the DB to the AWS Mumbai datacenter, and the Fly.io app to the&#xA;Madras datacenter.&lt;/p&gt;&#xA;&lt;h2 id=&#34;all-s-well-that-ends-well&#34;&gt;All&amp;rsquo;s well that ends well&lt;/h2&gt;&#xA;&lt;p&gt;The app has been chugging along for a few days without any new hiccups. I&amp;rsquo;m&#xA;hoping things stay this way for the weeks and months to come, if not years.&lt;/p&gt;&#xA;&lt;p&gt;On the whole, I found the whole experience more beta-ish than deploying&#xA;something to Heroku. But, I&amp;rsquo;m quite happy with how the whole thing turned&#xA;out. I think things will get smoother with time &amp;ndash; with my growing familarity&#xA;with Fly.io and more people using their tools suggesting improvements.&lt;/p&gt;&#xA;&lt;p&gt;I am wary of depending on yet another Hobby plan, but neither of these apps&#xA;make any revenue and I&amp;rsquo;m not (yet) comfortable charging users for the small set&#xA;of features they provide.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d like to hear stories of other people moving away from Heroku! Feel free to&#xA;reach out!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Old Talk: Write Prose, Not Just Programs</title>
      <link>https://punchagan.muse-amuse.in/blog/old-talk-write-prose-not-just-programs/</link>
      <pubDate>Wed, 15 Jun 2022 09:54:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/old-talk-write-prose-not-just-programs/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve recently signed up for the &lt;a href=&#34;https://indiauncut.com/clear-writing/&#34;&gt;Art of Clear Writing&lt;/a&gt; course by Amit Verma. It&#xA;is still early days and I can&amp;rsquo;t comment on the course itself.  But, the&#xA;discussion after the first class reminded me of a talk I had given a couple of&#xA;years ago &amp;ndash; Write Prose, Not Just Programs.&lt;/p&gt;&#xA;&lt;p&gt;A friend, &lt;a href=&#34;https://twitter.com/vivekv&#34;&gt;Vivek&lt;/a&gt;, had nudged me to do a talk on &amp;ldquo;Writing for Developers&amp;rdquo; and I&#xA;ended up talking to his team about a week later. The PyCon India CFP was open a&#xA;couple of months later, where I submitted this talk and it was accepted.&lt;/p&gt;&#xA;&lt;p&gt;Even if a couple of years late, I thought it might be helpful to leave links to&#xA;the talk video and slides on my blog. So, here they are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://punchagan.github.io/talks/pycon-2020-writing/writing-for-devs.html&#34;&gt;Slides&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=0TucAxOO68c&#34;&gt;Video&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;PS: Ironically, my blog hasn&amp;rsquo;t seen much activity after giving that talk due to&#xA;various reasons.  I&amp;rsquo;ve been meaning and trying to change that off-late, though.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>GitHub Gists from Emacs Orgmode</title>
      <link>https://punchagan.muse-amuse.in/blog/ox-gist/</link>
      <pubDate>Tue, 26 Apr 2022 12:37:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/ox-gist/</guid>
      <description>&lt;p&gt;TL;DR: I released my first Emacs MELPA package &amp;ndash; &lt;a href=&#34;https://melpa.org/#/ox-gist&#34;&gt;ox-gist&lt;/a&gt; &amp;ndash; An Orgmode backend&#xA;to export and update sub-trees and buffers to GitHub gists.  It was a great&#xA;experience contributing to MELPA.&lt;/p&gt;&#xA;&lt;h2 id=&#34;motivation&#34;&gt;Motivation&lt;/h2&gt;&#xA;&lt;p&gt;I often share bits and pieces of my &lt;code&gt;journal.org&lt;/code&gt; file with others, mostly for&#xA;reading, some times for comments. The file contains different subtrees for each&#xA;day with subtrees for different topics, meetings, articles, etc.&lt;/p&gt;&#xA;&lt;p&gt;Orgmode lets you export subtrees to specific file names using the&#xA;&lt;code&gt;EXPORT_FILE_NAME&lt;/code&gt; property (and &lt;code&gt;FILE_NAME&lt;/code&gt; buffer export option when&#xA;exporting full buffers). It is easy to create these exported files on a&#xA;publicly accessible webserver using the power of &lt;a href=&#34;https://www.gnu.org/software/tramp/&#34;&gt;Emacs TRAMP mode&lt;/a&gt;.  For&#xA;instance, I often set the property to some thing like&#xA;&lt;code&gt;/ssh:muse-amuse.in:~/public_html/&amp;lt;filename&amp;gt;&lt;/code&gt; to publish stuff.&lt;/p&gt;&#xA;&lt;p&gt;But, exporting to HTML is a distraction some times because I start fiddling&#xA;with CSS and styling. Exporting to a simple org file doesn&amp;rsquo;t seem to cut it&#xA;because there&amp;rsquo;s no styling at all, and making longer exports hard to read in a&#xA;web-browser.  Exporting to a GitHub Gist is a nice middle ground because GitHub&#xA;renders Orgmode files reasonably well. Additionally, the comment functionality&#xA;is pretty handy.&lt;/p&gt;&#xA;&lt;p&gt;I could also directly use &lt;a href=&#34;https://github.com/defunkt/&#34;&gt;@defunkt&lt;/a&gt;&amp;rsquo;s excellent &lt;a href=&#34;https://github.com/defunkt/gist.el&#34;&gt;&lt;code&gt;gist.el&lt;/code&gt;&lt;/a&gt; to share stuff as&#xA;Gists.  I&amp;rsquo;ve used it in the past, and it works well when I don&amp;rsquo;t care about&#xA;having the contents of the Gist locally on my machine. It&amp;rsquo;s also quite easy to&#xA;update existing/old Gists in this scenario, since there&amp;rsquo;s a single &amp;ldquo;source of&#xA;truth&amp;rdquo; and &lt;code&gt;gist.el&lt;/code&gt; makes it quite easy to update Gists.&lt;/p&gt;&#xA;&lt;p&gt;But, this isn&amp;rsquo;t good enough when I want to have a &amp;ldquo;synced&amp;rdquo; copy of the text on&#xA;my machine.  Also, it doesn&amp;rsquo;t work very well when I want to share just a&#xA;subtree from my notes file, and not the entire file.  I&amp;rsquo;ve to export the&#xA;subtree to a temporary Orgmode buffer and then create a Gist from that buffer.&lt;/p&gt;&#xA;&lt;h2 id=&#34;hackish-wrapper-around-gist-dot-el&#34;&gt;Hackish wrapper around &lt;code&gt;gist.el&lt;/code&gt;&lt;/h2&gt;&#xA;&lt;p&gt;A couple or so years ago, I wrote a wrapper around &lt;code&gt;gist.el&lt;/code&gt; to let me post&#xA;Orgmode subtrees as a Gist and also update them when I edited these subtrees&#xA;locally.  There was no support for exporting entire buffers since that wasn&amp;rsquo;t&#xA;my use-case.  This wrapper code was just a part of my &lt;code&gt;.emacs&lt;/code&gt; and worked&#xA;reasonably well for me.&lt;/p&gt;&#xA;&lt;p&gt;A few weeks ago, I ended up procrastinating on writing and sharing notes for a&#xA;discussion with a friend, and extracted this wrapper as a separate package &amp;ndash;&#xA;&lt;code&gt;org2gist&lt;/code&gt; &amp;ndash; to make it easy for other people to use.&lt;/p&gt;&#xA;&lt;h2 id=&#34;melpa-submission-and-feedback&#34;&gt;MELPA submission and feedback&lt;/h2&gt;&#xA;&lt;p&gt;I &lt;a href=&#34;https://github.com/melpa/melpa/pull/7940&#34;&gt;submitted&lt;/a&gt; this as a package to &lt;a href=&#34;https://melpa.org/&#34;&gt;MELPA&lt;/a&gt; while being skeptical about whether it&#xA;would be accepted.  I wasn&amp;rsquo;t sure if my thin wrapper around &lt;code&gt;gist.el&lt;/code&gt; would&#xA;meet MELPA&amp;rsquo;s criteria for a &amp;ldquo;Reasonably innovative package&amp;rdquo;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;MELPA provides a curated set of Emacs Lisp packages, not an exhaustive list&#xA;of every single Emacs Lisp file ever created. By default, MELPA maintainers&#xA;will reject packages that duplicate functionality provided by existing&#xA;packages. Please try to improve existing packages instead of creating new&#xA;ones when possible.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It was a pleasant surprise to get very helpful reviews from the MELPA&#xA;maintainers.  They not only thought the package was good enough to include in&#xA;MELPA, but also looked at it&amp;rsquo;s code and gave great feedback &amp;mdash; not just basic&#xA;quality checks before accepting the package into MELPA.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m pretty sure mainting a package repository like MELPA is a lot of work, but&#xA;as an end-user it mostly just worked for me and I never really gave it much&#xA;thought.  It was eye-opening to submit the package to MELPA and see first-hand&#xA;the care and effort the maintainers put into what goes into the repository.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/riscy/&#34;&gt;@riscy&lt;/a&gt; (a MELPA maintainer) &lt;a href=&#34;https://github.com/melpa/melpa/pull/7940#issuecomment-1080036663&#34;&gt;suggested&lt;/a&gt; a different approach that I hadn&amp;rsquo;t&#xA;thought of &amp;mdash; turning the wrapper into an &lt;a href=&#34;https://orgmode.org/manual/Exporting.html&#34;&gt;Orgmode Export backend&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;current-version-ox-gist&#34;&gt;Current version: &lt;code&gt;ox-gist&lt;/code&gt;&lt;/h2&gt;&#xA;&lt;p&gt;It turned out to be a great idea which I ended up implementing.  The user&#xA;interface became much cleaner, since it used the Orgmode Export UI.  And it&#xA;also forced me to &amp;ldquo;complete&amp;rdquo; the feature-set by adding support for exporting&#xA;entire Orgmode buffers to GitHub Gists, not just subtrees.&lt;/p&gt;&#xA;&lt;p&gt;The package now adds an Orgmode export backend that lets you export a subtree&#xA;or the whole buffer as a GitHub Gist, similar to how you&amp;rsquo;d export to HTML or&#xA;LaTeX from Orgmode. The GitHub Gist ID is saved for each exported buffer or&#xA;subtree and is to update a Gist when re-exporting an already exported subtree&#xA;or buffer.&lt;/p&gt;&#xA;&lt;h2 id=&#34;outro&#34;&gt;Outro&lt;/h2&gt;&#xA;&lt;p&gt;The package has now been added to MELPA as &lt;a href=&#34;https://github.com/punchagan/ox-gist&#34;&gt;&lt;code&gt;ox-gist&lt;/code&gt;&lt;/a&gt;.  Feel free to give it a&#xA;spin and give feedback directly to me or on the &lt;a href=&#34;https://github.com/punchagan/ox-gist/issues&#34;&gt;issue tracker&lt;/a&gt;!&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t know if anyone else would find the package useful and use it, though,&#xA;MELPA tells me that 20-odd people atleast downloaded it.  I&amp;rsquo;m happy this code&#xA;may be to a handful of people.&lt;/p&gt;&#xA;&lt;p&gt;Also, I now have a new found appreciation for MELPA and the work put into it by&#xA;the maintainers.  I&amp;rsquo;m glad I took the time out to submit this package to MELPA.&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; for reading drafts and helping me restructure this post.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Made in 2021</title>
      <link>https://punchagan.muse-amuse.in/blog/made-in-2021/</link>
      <pubDate>Thu, 24 Feb 2022 02:23:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/made-in-2021/</guid>
      <description>&lt;p&gt;Some things I made in 2021:&lt;/p&gt;&#xA;&lt;dl&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://github.com/punchagan/earworm/&#34;&gt;Earworm&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;A static site generator to listen to audio (and video?) files in a&#xA;directory. I use it to host a couple of simple websites for music files. It&#xA;also happens to be the first package I uploaded to PyPI despite writing&#xA;Python for many years.&lt;/dd&gt;&#xA;&lt;dt&gt;&lt;a href=&#34;https://ukulele.muse-amuse.in/&#34;&gt;Ukulele Tutorials Aggregator&lt;/a&gt;&lt;/dt&gt;&#xA;&lt;dd&gt;Inspired by &lt;a href=&#34;https://ukealong.com/&#34;&gt;Ukealong.com&lt;/a&gt;, I set out to create&#xA;an aggregator for Ukulele Tutorials for Indian songs. I&amp;rsquo;ve, with help from&#xA;&lt;a href=&#34;https://twitter.com/kamalx&#34;&gt;Kamal&lt;/a&gt;, populated about 300 songs on the site. I haven&amp;rsquo;t been able to keep up&#xA;with maintaing it, after an initial bunch of work. I&amp;rsquo;ve wanted to automate&#xA;more of the workflow, but haven&amp;rsquo;t yet.&lt;/dd&gt;&#xA;&lt;dt&gt;RPi Server and Phone Backup&lt;/dt&gt;&#xA;&lt;dd&gt;I&amp;rsquo;ve started to use a Raspberry Pi to backup&#xA;data from my phone and laptop to a personal Hard disk. I&amp;rsquo;ve &lt;a href=&#34;https://github.com/punchagan/ansible-raspberry-pi&#34;&gt;ansible scripts&lt;/a&gt;&#xA;to reproduce the RPi&amp;rsquo;s setup, and some &lt;a href=&#34;https://github.com/punchagan/sync-rpi&#34;&gt;scripts to &amp;ldquo;sync&amp;rdquo;&lt;/a&gt; my phone&amp;rsquo;s data&#xA;every few hours, when it&amp;rsquo;s on the same network. There&amp;rsquo;s a lot more that I&#xA;need here, before I can ditch cloud backups of important things.&lt;/dd&gt;&#xA;&lt;dt&gt;Music&lt;/dt&gt;&#xA;&lt;dd&gt;I made covers of about 10 songs on the Ukulele, that I shared with&#xA;some friends. I&amp;rsquo;ve ways to go before I&amp;rsquo;m comfortable sharing them on the web,&#xA;publicly.&lt;/dd&gt;&#xA;&lt;/dl&gt;&#xA;&lt;p&gt;I&amp;rsquo;m glad to have had the energy, time and resources to be able to spend time&#xA;working on some fun things despite everything that 2021 had in store for&#xA;us. Let&amp;rsquo;s see what 2022 has in store!&lt;/p&gt;&#xA;&lt;p&gt;PS: This post is quite late into 2022, but it&amp;rsquo;s an excuse to post something to&#xA;a blog that&amp;rsquo;s not seen any updates since the middle of 2020.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Hat tip to Bryan Braun whose &lt;a href=&#34;https://www.bryanbraun.com/2022/01/11/made-in-2021/&#34;&gt;post&lt;/a&gt; inspired me to write this&lt;/em&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Instagram&#39;s user-friendliness</title>
      <link>https://punchagan.muse-amuse.in/blog/instagram-user-friendliness/</link>
      <pubDate>Wed, 24 Jun 2020 20:13:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/instagram-user-friendliness/</guid>
      <description>&lt;p&gt;TL; DR: I&amp;rsquo;m a video content creator on Instagram, and don&amp;rsquo;t tell me Instagram is&#xA;user friendly. Being able to preview posts before making them viewable for&#xA;everyone will go a long way, though.&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve been using Instagram quite regularly from around the beginning of this&#xA;year, posting about one video a week on an average. Previously, I used it&#xA;primarily as a content consumer for about an year, before getting off it for&#xA;quite long since it started to turn into a big time-suck. In the current stint&#xA;of using Instagram as a content-creator, I&amp;rsquo;ve had quite a few frustrating user&#xA;experiences. On a platform that comes with a giant reputation of being&#xA;user-friendly, this is a surprise.&lt;/p&gt;&#xA;&lt;p&gt;A lot of my posts this year have been video content. While posting them, I ran&#xA;into a bunch of things that didn&amp;rsquo;t &amp;ldquo;just work&amp;rdquo;. For instance, in the first 3 or&#xA;4 posts of the &lt;a href=&#34;https://www.instagram.com/tiks_ultimate/channel/&#34;&gt;Humans of TIKS&lt;/a&gt; series, every post had some thing broken, leading&#xA;to a poor viewing experience.&lt;/p&gt;&#xA;&lt;p&gt;Some of my friends, though, found it surprising that I was frustrated with&#xA;Instagram. &amp;ldquo;Isn&amp;rsquo;t it very user-friendly?&amp;rdquo;, I was asked by many. I think&#xA;Instagram is just riding on their reputation from the past. The newer features,&#xA;especially around video content, aren&amp;rsquo;t exactly &amp;ldquo;user-friendly&amp;rdquo;. Instagram does&#xA;focus on giving the content consumer a nice (read: addictive) experience. But,&#xA;as a creator, there are a bunch of hoops to jump. Content creators probably put&#xA;up with it just for the reach Instagram gives.&lt;/p&gt;&#xA;&lt;p&gt;In this post, I&amp;rsquo;ve listed down everything that I see as broken. I&amp;rsquo;ve also tried&#xA;to suggest improvements to each broken feature that would make the experience&#xA;more friendly. I start with the more common problem scenarios and progress&#xA;towards the edge cases.&lt;/p&gt;&#xA;&lt;h2 id=&#34;caption-formatting-is-needlessly-hard&#34;&gt;Caption formatting is needlessly hard&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve always had to fiddle with the caption of a post, after publishing it, if it&#xA;had multiple paragraphs. There&amp;rsquo;s a magic algorithm that Instagram uses to decide&#xA;if multiple line-breaks in the text actually mean separate paragraphs or not.&lt;/p&gt;&#xA;&lt;p&gt;After some trial and error, I&amp;rsquo;ve figured out a few things about it. For&#xA;instance, leaving a trailing space at the end of a paragraph causes paragraphs&#xA;to be joined together. But, when the second &amp;ldquo;paragraph&amp;rdquo; starts with a non-text&#xA;character, it doesn&amp;rsquo;t seem to matter what the previous paragraph ends with, they&#xA;always are joined together as a single one.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t want to find out these details by trial and error. Adding previews for&#xA;captions would make this so much nicer. Some documentation for the &amp;ldquo;magic&amp;rdquo; would&#xA;probably help too. Instagram could even look at WhatsApp, its messaging cousin,&#xA;for some lessons on how to make writing text easier. Switching to a more common&#xA;markup language would be a nice bonus.&lt;/p&gt;&#xA;&lt;h2 id=&#34;albums-are-restrictive&#34;&gt;Albums are restrictive&lt;/h2&gt;&#xA;&lt;p&gt;Posting multiple images in a single post (an album) is a pain. All images are&#xA;&lt;a href=&#34;https://help.instagram.com/269314186824048&#34;&gt;cropped to the same orientation&lt;/a&gt; &amp;ndash; square, portrait or landscape.&lt;/p&gt;&#xA;&lt;p&gt;Until a few years ago, Instagram required every image to be square. If you want&#xA;to keep things dead simple for your users, the square image restriction is as&#xA;simple as it can get. At least it&amp;rsquo;s easy to understand and remember, even though&#xA;I can&amp;rsquo;t post my pictures exactly the way I want.&lt;/p&gt;&#xA;&lt;p&gt;The current same orientation restriction is worse on both fronts. I&amp;rsquo;m still left&#xA;with not being able to post my pictures exactly the way I want. But, also the&#xA;restriction is not easy to infer from the UI. I had to fiddle with the UI and my&#xA;pictures for a while, to understand that there&amp;rsquo;s no way around this, and that&#xA;Instagram is actually enforcing this restriction.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t want to be thinking about Instagram&amp;rsquo;s restrictions while clicking my&#xA;pictures. I want to take the best pictures I can, and upload them with the best&#xA;possible orientation/crop.&lt;/p&gt;&#xA;&lt;p&gt;Instagram may have these restrictions in place to ensure the best possible&#xA;&amp;ldquo;viewing&amp;rdquo; experience. But, for the creators the user-friendly thing to do here&#xA;would be to remove these restrictions. I think, even going back to the universal&#xA;square image restriction would feel more user-friendly!&lt;/p&gt;&#xA;&lt;h2 id=&#34;public-profiles-really&#34;&gt;Public profiles, really?&lt;/h2&gt;&#xA;&lt;p&gt;I think, a public profile should be viewable by anybody without having to login,&#xA;or having to use a specific kind of device. But, Instagram&amp;rsquo;s idea of &amp;ldquo;public&amp;rdquo;&#xA;profiles is quite different.&lt;/p&gt;&#xA;&lt;p&gt;If you are not logged into Instagram, and are viewing a public profile, you are&#xA;prompted to login after scrolling down through a few posts on the profile. If&#xA;you are on a desktop browser, you can&amp;rsquo;t open up any post from a public profile.&#xA;You are prompted to login, when you try to view a post. Even on a mobile&#xA;browser, you are prompted to login after opening 3 or 4 posts. Opening direct&#xA;links (or opening posts in a new tab) works, though.&lt;/p&gt;&#xA;&lt;p&gt;Instagram obviously wants to grow as much as it can, and it doesn&amp;rsquo;t really care&#xA;about people not on the platform. As a content creator, though, I care about my&#xA;audience, irrespective of whether they have an Instagram account or not. A user&#xA;friendly platform would keep their users&amp;rsquo; interests ahead of their growth goals&#xA;and not do such shady stuff. Or at least, tell us the truth and stop calling it&#xA;&amp;ldquo;public&amp;rdquo; profiles.&lt;/p&gt;&#xA;&lt;h2 id=&#34;so-many-video-types&#34;&gt;So many video types!&lt;/h2&gt;&#xA;&lt;p&gt;When I want to post a video on Instagram, I&amp;rsquo;ve to first decide what kind of a&#xA;&amp;ldquo;post&amp;rdquo; to make. If I want to post a &amp;ldquo;short&amp;rdquo; video, it can go into a normal&#xA;post/album if it is under a minute. If not, I&amp;rsquo;ll either need to split it up into&#xA;smaller segments, or post it as an IGTV video. Stories can only be 15 seconds&#xA;long, or they get chopped into multiple shorter stories.&lt;/p&gt;&#xA;&lt;p&gt;IGTV supports longer videos &amp;ndash; from 1 minute up to 60 minutes long. The videos&#xA;need to be portrait videos with an aspect ratio of 4:5 or smaller, 9:16 is&#xA;recommended, while normal posts prefer square (1:1) videos.&lt;/p&gt;&#xA;&lt;p&gt;Just like with pictures, I don&amp;rsquo;t want to care about Instagram&amp;rsquo;s restrictions&#xA;when capturing a video. I want to shoot my video, and let Instagram do what is&#xA;required to share that video. I find it strange that there are hundreds of blog&#xA;posts documenting how to upload an already recorded video to IGTV.&lt;/p&gt;&#xA;&lt;p&gt;A user-friendly platform would let users upload the videos as they shot them,&#xA;and prompt about adding some padding, or allowing users to crop and select a&#xA;part of the video, to match the aspect ratio requirements.&lt;/p&gt;&#xA;&lt;h2 id=&#34;9-16-igtv-videos-are-broken&#34;&gt;9:16 IGTV videos are broken&lt;/h2&gt;&#xA;&lt;p&gt;The documented aspect ratio for an IGTV video is 9:16, but guess what! They&#xA;aren&amp;rsquo;t displayed correctly in the mobile app.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://user-images.githubusercontent.com/315678/85221173-6017dc00-b3cf-11ea-8b84-43d44cf89d20.png&#34; width=&#34;150px&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;They are displayed correctly in mobile and desktop browsers, though. But, given&#xA;how Instagram nudges everyone to use the app, I would guess that 90-95% of&#xA;viewers would be using the app, if not more.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://user-images.githubusercontent.com/315678/85098070-21d7bc80-b217-11ea-9432-bfb8ebe3ab0b.png&#34; width=&#34;150px&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;After a lot of trial and error, I figured out that I need to upload my videos at&#xA;a ratio of 9:21 or lower to be able to prevent the sides from being chopped off.&#xA;Instagram&amp;rsquo;s UI or docs don&amp;rsquo;t mention this at all. Also, this workaround only&#xA;works when uploading from a destkop browser. I haven&amp;rsquo;t yet figured out how to&#xA;prevent mobile uploads from looking broken.&lt;/p&gt;&#xA;&lt;p&gt;For a visual platform that is so bullish on everything looking perfect, this is&#xA;horrendous! I don&amp;rsquo;t know where to place this - is this a bug in Instagram&amp;rsquo;s&#xA;processing pipeline? Or is this a part of attempts to provide the best viewing&#xA;experience? Just display my videos as advertised, please!&lt;/p&gt;&#xA;&lt;h2 id=&#34;video-post-processing-is-magic&#34;&gt;Video post-processing is magic&lt;/h2&gt;&#xA;&lt;p&gt;Any video I upload goes through a processing pipeline, that is undocumented,&#xA;before it gets displayed to the users. This is possibly done to optimize for&#xA;space, etc., but it has led to broken user experience for at least a couple of&#xA;my videos.&lt;/p&gt;&#xA;&lt;p&gt;On one occassion, it made the audio and video go out of sync. To be fair, the&#xA;video did have some issues with the audio stream data missing for the first 3&#xA;seconds of the video, due to a &lt;a href=&#34;https://github.com/thatte-idli-kaal-soup/humans/commit/77f10800ce6663e1e118b425421662206e3f50c4&#34;&gt;bug in my video processing pipeline&lt;/a&gt;. The same&#xA;video worked perfectly fine in &lt;a href=&#34;http://www.mplayerhq.hu/design7/news.html&#34;&gt;MPlayer&lt;/a&gt;, &lt;a href=&#34;https://mpv.io/&#34;&gt;mpv&lt;/a&gt;, &lt;a href=&#34;https://www.videolan.org/vlc/&#34;&gt;VLC&lt;/a&gt;, and on YouTube. Noticing that&#xA;Instagram&amp;rsquo;s post-processing made the audio and video go out of sync, after about&#xA;50 people watched it, is such a shame!&lt;/p&gt;&#xA;&lt;p&gt;To avoid such problems, we decided to make a demo account and upload every video&#xA;there, before posting it from the real account. Needing to do this seems super&#xA;broken to me. I still did it, since I wanted everything to work just right. But,&#xA;that wasn&amp;rsquo;t to be.&lt;/p&gt;&#xA;&lt;p&gt;We had a video where we added some background music on the video, and it ended&#xA;up having a 4-channel audio stream. It turns out the Instagram app on iOS won&amp;rsquo;t&#xA;play such videos, and we only detected that after some people in our audience&#xA;let us know. Instagram has this documented on their IGTV documentation under&#xA;&lt;a href=&#34;https://help.instagram.com/1038071743007909&#34;&gt;additional technical details&lt;/a&gt;. But, I only read that because things weren&amp;rsquo;t&#xA;working. Again, our followers had a broken experience because of this.&lt;/p&gt;&#xA;&lt;p&gt;It can be hard to &amp;ldquo;document&amp;rdquo; all the technical details of the post-processing&#xA;pipeline. But, it should be fairly easy to detect things like 4-channel audio,&#xA;and warning users about it!&lt;/p&gt;&#xA;&lt;p&gt;Even simpler, but user-friendlier thing to do here would be to allow users to&#xA;view the video after it has been uploaded and processed, before making it&#xA;viewable by everyone. Currently, draft posts cannot be shared with friends. I&amp;rsquo;m&#xA;not sure if they can even be found and viewed from multiple devices &amp;ndash; it wasn&amp;rsquo;t&#xA;intuitive to do this, when I tried. Just making it easy to preview posts before&#xA;sharing it publicly would make things a lot lot more user-friendly. It feels&#xA;quite backward to be using a private account for this to ensure no broken&#xA;content is published.&lt;/p&gt;&#xA;&lt;p&gt;This may sound too extreme, but just give me the exact &lt;a href=&#34;https://www.ffmpeg.org/&#34;&gt;ffmpeg&lt;/a&gt; command being run&#xA;on the uploaded videos somewhere in the docs! I would then be able to verify&#xA;that my videos are Instagram compliant, without having to upload a dozen videos&#xA;and checking on multiple devices. With a lot of big brands using Instagram, I&#xA;think there&amp;rsquo;d be other &amp;ldquo;expert&amp;rdquo; users who would appreciate these details.&lt;/p&gt;&#xA;&lt;h2 id=&#34;published-content-is-uneditable&#34;&gt;Published content is uneditable&lt;/h2&gt;&#xA;&lt;p&gt;I would find the brokenness more tolerable if I could go back and change the&#xA;uploaded videos. But, Instagram doesn&amp;rsquo;t let you edit published posts. It does&#xA;seem like a reasonable restriction, since this content may have been referenced&#xA;elsewhere. This seems to be Twitter&amp;rsquo;s take too, for instance.&lt;/p&gt;&#xA;&lt;p&gt;For something as &amp;ldquo;heavy&amp;rdquo; as a video, though, it seems a little too restrictive.&#xA;It may not be a great idea to allow changing content behind people&amp;rsquo;s backs. But,&#xA;I&amp;rsquo;m not happy about my followers having to watch a broken post. Deleting a&#xA;broken post and making a new one doesn&amp;rsquo;t really cut it &amp;ndash; it feels spammy.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d like to see some kind of versioning added to the videos. Let user upload a&#xA;newer version, and make it possible for users to see older versions too, if they&#xA;chose to. Google Drive and Dropbox seem to do this, for instance.&lt;/p&gt;&#xA;&lt;h2 id=&#34;outro&#34;&gt;Outro&lt;/h2&gt;&#xA;&lt;p&gt;Instagram started off by allowing users to only post square pictures. It was&#xA;very easy to undestand and use, even if not very accomodating of different use&#xA;cases. Everyone and their dog could use it!&lt;/p&gt;&#xA;&lt;p&gt;A whole bunch of new features have been added in the last few years, trying to&#xA;compete with the other platforms. Doing the simplest thing - posting a single&#xA;picture - may still be easy to do, but trying to do anything more than that&#xA;leads to a frustrating experience quite often. I think the reputation of being&#xA;user-friendly is now just a vestige of the past.&lt;/p&gt;&#xA;&lt;p&gt;People who use it regularly get used to its quirks and probably put up with the&#xA;annoyances, because it gives them a way to reach their audiences with whom&#xA;Instagram is popular. Instagram is &amp;ldquo;friendly&amp;rdquo; to the content consumer, and tries&#xA;its best to keep the content extremely consumable to keep them coming back for&#xA;more. The burden of figuring out and making the content fit the restrictions is&#xA;left on the content-creators. This can often be frustrating.&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;https://balu.muse-amuse.in/&#34;&gt;Kartik&lt;/a&gt;, Meghana, Nitin, Pratiksha, &lt;a href=&#34;https://a-travelers-tales.blogspot.com/&#34;&gt;Ranjan&lt;/a&gt;, &lt;a href=&#34;https://www.lightstalking.com/author/riteshsaini/&#34;&gt;Ritesh&lt;/a&gt;, &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; and &lt;a href=&#34;https://vkrishnaswam.github.io/&#34;&gt;Vivek&lt;/a&gt;&#xA;for reading drafts (or parts) of this post and helping by boucing off ideas.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Deploying Hugo Drafts, simplified</title>
      <link>https://punchagan.muse-amuse.in/blog/deploying-hugo-drafts-simplified/</link>
      <pubDate>Sun, 31 May 2020 17:19:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/deploying-hugo-drafts-simplified/</guid>
      <description>&lt;p&gt;This is a follow-up post to my last post, &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/deploying-hugo-drafts&#34;&gt;Deploying Hugo Drafts&lt;/a&gt;. Kaushal Modi,&#xA;the author of &lt;a href=&#34;https://github.com/kaushalmodi/ox-hugo&#34;&gt;ox-hugo&lt;/a&gt; (a tool that lets you compose in org-mode and use Hugo to&#xA;export), let me know about the &lt;a href=&#34;https://gohugo.io/content-management/build-options/&#34;&gt;build options&lt;/a&gt; in Hugo.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Have you looked at the new _build options: &lt;a href=&#34;https://t.co/Nj8Uefis6Q&#34;&gt;https://t.co/Nj8Uefis6Q&lt;/a&gt;. I haven&amp;#39;t used them yet, but looks like they fit your use case.&lt;/p&gt;&amp;mdash; Kaushal (@kaushalmodi) &lt;a href=&#34;https://twitter.com/kaushalmodi/status/1266761525286653952?ref_src=twsrc%5Etfw&#34;&gt;May 30, 2020&lt;/a&gt;&lt;/blockquote&gt;&#xA;&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;&#xA;&#xA;&#xA;&lt;p&gt;The build options in Hugo let you configure building a post for different use&#xA;cases like render a post but hide it in lists, or don&amp;rsquo;t render separate pages&#xA;for posts and only show them in lists, etc.&lt;/p&gt;&#xA;&lt;p&gt;To use these build options for my use case &amp;ndash; being able to share draft post&#xA;links with friends, without them appearing in the index page or RSS feed &amp;ndash; I&#xA;just need to add the following to my draft blog posts!&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;_build&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But, how do I do this with &lt;code&gt;ox-hugo&lt;/code&gt;? I know &lt;code&gt;ox-hugo&lt;/code&gt; doesn&amp;rsquo;t yet have this&#xA;feature, for sure, because Kaushal mentioned that he hasn&amp;rsquo;t tried it out yet.&lt;/p&gt;&#xA;&lt;p&gt;I checked to see if I could use the &lt;code&gt;EXPORT_HUGO_CUSTOM_FRONT_MATTER&lt;/code&gt; property&#xA;to get it to generate this extra metadata for each blog post. But that didn&amp;rsquo;t&#xA;work. The property assumes all the arguments to it are top level front matter&#xA;keys and values, which is good enough for &amp;ldquo;normal&amp;rdquo; use cases.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;EDIT &lt;span class=&#34;timestamp-wrapper&#34;&gt;&lt;span class=&#34;timestamp&#34;&gt;[2020-05-31 Sun]&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;: I previously manually created the &lt;code&gt;drafts&lt;/code&gt; directory with&#xA;the &lt;code&gt;_index.md&lt;/code&gt;, but Kaushal pointed me to &lt;a href=&#34;https://twitter.com/kaushalmodi/status/1267066389996724229&#34;&gt;more docs&lt;/a&gt;, again! :) There&amp;rsquo;s also an&#xA;&lt;a href=&#34;https://github.com/kaushalmodi/ox-hugo/issues/358&#34;&gt;issue&lt;/a&gt; for adding an easier way to add these build options from ox-hugo.&lt;/p&gt;&#xA;&lt;p&gt;I created a subtree like this in my org file, that creates the&#xA;&lt;code&gt;drafts/_index.md&lt;/code&gt; file with the required front matter and build options.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-org&#34; data-lang=&#34;org&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gh&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;gs&#34;&gt; Drafts&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;:PROPERTIES:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&lt;/span&gt;&lt;span class=&#34;cs&#34;&gt;:EXPORT_FILE_NAME: _index&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cs&#34;&gt;:EXPORT_HUGO_SECTION: drafts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cs&#34;&gt;:EXPORT_HUGO_CUSTOM_FRONT_MATTER: yaml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cs&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;:END:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;#+begin_src &lt;/span&gt;&lt;span class=&#34;cs&#34;&gt;yaml&lt;/span&gt;&lt;span class=&#34;c&#34;&gt; :front_matter_extra t&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;_build&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;publishResources&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;cascade&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;_build&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;#+end_src&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The build options for the index page turn off publishing and showing it in&#xA;lists. With the &lt;code&gt;cascade&lt;/code&gt; option, we can get the options to all the children &amp;ndash;&#xA;any files created in the &lt;code&gt;drafts&lt;/code&gt; directory, but also override the &lt;code&gt;render&lt;/code&gt;&#xA;option to be true. In other words, the configuration above would translate to&#xA;adding the following to &lt;em&gt;every draft post&lt;/em&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;A&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;How-to&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;on&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Hugo&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Drafts&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;_build&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;publishResources&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, I just need to make sure that all my draft posts are created in this&#xA;&lt;code&gt;drafts&lt;/code&gt; directory. &lt;code&gt;ox-hugo&lt;/code&gt; allows doing this by simply using the&#xA;&lt;code&gt;EXPORT_HUGO_SECTION&lt;/code&gt; property on my post subtree. I added this additional&#xA;property to my helper function that I use to create hugo blog posts from any&#xA;subtree.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-org&#34; data-lang=&#34;org&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;gh&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;gs&#34;&gt; DRAFT A How-to on Hugo Drafts&lt;/span&gt;&lt;span class=&#34;ge&#34;&gt; :blogging:hack:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;:PROPERTIES:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&lt;/span&gt;&lt;span class=&#34;cs&#34;&gt;:EXPORT_FILE_NAME: how-to-hugo-drafts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cs&#34;&gt;:EXPORT_HUGO_SECTION: drafts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cs&#34;&gt;:EXPORT_DATE: [2020-05-31 Sun 16:51]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cs&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c&#34;&gt;:END:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m now all set to publish draft posts without requiring to use &lt;code&gt;rsync&lt;/code&gt; and two&#xA;&lt;code&gt;hugo&lt;/code&gt; builds. :) Publishing a draft post would need me to remove the &lt;code&gt;DRAFT&lt;/code&gt;&#xA;todo keyword, like before, but additionally also remove the&#xA;&lt;code&gt;:EXPORT_HUGO_SECTION:&lt;/code&gt; property on the subtree. This should be quite quick to&#xA;do, or pretty easy to put into an interactive function if required.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Deploying Hugo Drafts</title>
      <link>https://punchagan.muse-amuse.in/blog/deploying-hugo-drafts/</link>
      <pubDate>Sat, 30 May 2020 19:07:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/deploying-hugo-drafts/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update &lt;span class=&#34;timestamp-wrapper&#34;&gt;&lt;span class=&#34;timestamp&#34;&gt;[2020-05-31 Sun 17:23]&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;: I published a &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/deploying-hugo-drafts-simplified&#34;&gt;simpler workflow&lt;/a&gt; for this, using&#xA;Hugo&amp;rsquo;s build options that I wasn&amp;rsquo;t aware of, because they are relatively new.&lt;/p&gt;&#xA;&lt;p&gt;A few friends and I run a weekly writing club similar to &lt;a href=&#34;https://github.com/sursh/writing-club&#34;&gt;this&lt;/a&gt;. Once we have our&#xA;drafts ready, we usually share them for reviews, etc.&lt;/p&gt;&#xA;&lt;p&gt;Previously, I used to use &lt;a href=&#34;https://getnikola.com&#34;&gt;Nikola&lt;/a&gt;, and it&amp;rsquo;s default behaviour is what worked well&#xA;for this workflow &amp;ndash; share the posts URL with reviewers, but the post shouldn&amp;rsquo;t&#xA;appear in the feed, post list, etc.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;If you set the status metadata field of a post to &lt;code&gt;draft&lt;/code&gt;, it will not be shown&#xA;in indexes and feeds. It will be compiled, and if you deploy it it will be made&#xA;available, so use with care. If you wish your drafts to be not available in your&#xA;deployed site, you can set &lt;code&gt;DEPLOY_DRAFTS = False&lt;/code&gt; in your configuration.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I moved to using Hugo a few years ago. By default, it doesn&amp;rsquo;t publish the draft&#xA;posts, but you could ask it to do so using the &lt;code&gt;--buildDrafts&lt;/code&gt; option. But, that&#xA;doesn&amp;rsquo;t do what I want &amp;ndash; draft posts are treated just like any other post, and&#xA;they appear in the feeds, post list, etc.&lt;/p&gt;&#xA;&lt;p&gt;Given how fast &lt;code&gt;hugo&lt;/code&gt; builds are, though, it is pretty easy to hack-up the&#xA;behaviour I want.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Run a build with drafts, say to &lt;code&gt;dev&lt;/code&gt; directory.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;DEV_DIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;dev&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Build drafts to dev dir&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hugo --cleanDestinationDir -D -d &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;DEV_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &amp;gt; /dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Run a build without drafts, to &lt;code&gt;public&lt;/code&gt; directory.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PUBLIC_DIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;public&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hugo --cleanDestinationDir -d &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;PUBLIC_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Copy the drafts from the &lt;code&gt;dev&lt;/code&gt; build to the &lt;code&gt;public&lt;/code&gt; build&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rsync -ri --ignore-existing &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;DEV_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;/ &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;PUBLIC_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Deploy the &lt;code&gt;public&lt;/code&gt; build&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The code above is &lt;a href=&#34;https://github.com/punchagan/dotfiles/blob/3652a6be42c776c6d1771e6cd46acadb2cafe295/bin/publish-hugo-drafts.sh&#34;&gt;here&lt;/a&gt; as a single script. &lt;a href=&#34;https://github.com/punchagan/punchagan.muse-amuse.in/blob/fdc80a61e28290c1c4a48a437bc77ec3fda811f5/deploy.sh&#34;&gt;Here&lt;/a&gt; is the full deployment script&#xA;for my blog.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>An innocent query that caused Zulip downtime</title>
      <link>https://punchagan.muse-amuse.in/blog/zulip-downtime-postmortem/</link>
      <pubDate>Thu, 28 May 2020 11:34:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/zulip-downtime-postmortem/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;Recently, &lt;a href=&#34;https://github.com/zulip/zulip/commit/8f32db81a1a1230b425aef8f7c6d2b7fc4543ee7&#34;&gt;a change&lt;/a&gt; that I contributed caused some downtime on the Zulip cloud&#xA;(zulipchat.com). Here&amp;rsquo;s a user &lt;a href=&#34;https://twitter.com/zulip/status/1255005502599131137&#34;&gt;tweeting at us&lt;/a&gt;, about this.&lt;/p&gt;&#xA;&lt;p&gt;I shared this with some friends (who use Zulip) and they asked me to share more&#xA;about what happened. I began writing an explanation, and it eventually turned&#xA;into a blog post. The post tries to explain what caused the down-time, without&#xA;assuming too much knowledge of the Zulip DB schema, but it would help if you&#xA;used Zulip a little.&lt;/p&gt;&#xA;&lt;p&gt;The goal of my change was to change how Zulip backend calculates the&#xA;&lt;code&gt;furthest_read_time&lt;/code&gt; for a user &amp;ndash; the time stamp indicating when the last&#xA;message was read.&lt;/p&gt;&#xA;&lt;p&gt;Previously, the code used simply kept track of the message ID of the messages&#xA;being read by a user in a field called the &amp;ldquo;pointer&amp;rdquo;. My change was to move away&#xA;from this &lt;code&gt;pointer&lt;/code&gt; approach to using the actual timestamp of when a user last&#xA;read a message.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-is-the-pointer&#34;&gt;What is the pointer?&lt;/h2&gt;&#xA;&lt;p&gt;Put very simply, the pointer is just a message ID&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; &amp;ndash; ideally, the ID of the&#xA;&amp;ldquo;last message&amp;rdquo; a user has read. Message IDs in Zulip are incremental &amp;ndash; a&#xA;message that has arrived after another one, would have a higher ID than the&#xA;older message. So, it would&amp;rsquo;ve been a great way to keep track of the last read&#xA;message, if all the messages were in &amp;ldquo;one continuous feed&amp;rdquo;. But, guess what,&#xA;Zulip has streams and topics which are precisely what make Zulip such a valuable&#xA;communication tool!&lt;/p&gt;&#xA;&lt;p&gt;The pointer can be set to the ID of the message that is currently being read,&#xA;but only when we are in the &amp;ldquo;All messages&amp;rdquo; view. In this view, all messages are&#xA;ordered chronologically, so any message that is after another message would have&#xA;arrived after it, and would have an ID greater than the other message. So&#xA;reading a message in this view means, any message with a lower ID must&amp;rsquo;ve&#xA;already been read.&lt;/p&gt;&#xA;&lt;p&gt;When you are reading messages in a stream/topic (narrowed) view, the &lt;code&gt;pointer&lt;/code&gt;&#xA;is not updated. We can&amp;rsquo;t be sure that all messages with ID lower than the&#xA;current message&amp;rsquo;s ID have already been read. There could be a whole bunch of&#xA;unread messages in other streams/topics than the current one you are reading.&#xA;So, the &lt;code&gt;pointer&lt;/code&gt; doesn&amp;rsquo;t get updated until you go to the &amp;ldquo;All messages&amp;rdquo; view&#xA;and scroll over the messages, even if you have already read them in a narrowed&#xA;view.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-change-this&#34;&gt;Why change this?&lt;/h2&gt;&#xA;&lt;p&gt;Using the &lt;code&gt;pointer&lt;/code&gt; to compute the &lt;code&gt;furthest_read_time&lt;/code&gt; doesn&amp;rsquo;t work very well,&#xA;because it is only updated in the &amp;ldquo;All messages&amp;rdquo; view. In other words,&#xA;&lt;code&gt;furthest_read_time&lt;/code&gt; turned out to actually mean something like&#xA;&lt;code&gt;furthest_read_time_in_all_messages_view&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;furthest_read_time&lt;/code&gt; is used in the UI to display the bankruptcy banner &amp;ndash; a&#xA;prompt shown to a user to mark all messages as read, if they have &amp;ldquo;too many&amp;rdquo;&#xA;unreads, and haven&amp;rsquo;t read new messages in the last 48 hours.&lt;/p&gt;&#xA;&lt;p&gt;But, this is a problem, especially in Zulip instances with a lot of traffic,&#xA;where people leave a lot of messages unread until they have the time to&#xA;catch-up. It is common for people to communicate only in a handful of&#xA;streams/topics, and leave a lot of messages unread in other streams or topics,&#xA;that they are hoping to catch-up on, when they have more time at hand. Such&#xA;users would avoid using the &amp;ldquo;All messages&amp;rdquo; view at any cost.&lt;/p&gt;&#xA;&lt;p&gt;Even in not-so-high-traffic realms, some users just prefer not to use the &amp;ldquo;All&#xA;messages&amp;rdquo; view, and just stick to narrowed views.&lt;/p&gt;&#xA;&lt;p&gt;So, for these users the &lt;code&gt;furthest_read_time&lt;/code&gt; would get updated very rarely, and&#xA;could potentially remain at a date way back in the past. This meant the users&#xA;would see the bankruptcy banner despite actively participating in the Realm,&#xA;even if only in a few topics/streams. This is annoying UX, and what something we&#xA;wanted to fix.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-did-we-change-this-to&#34;&gt;What did we change this to?&lt;/h2&gt;&#xA;&lt;p&gt;We decided to use the last read message time as the message to consider for the&#xA;&lt;code&gt;furthest_read_time&lt;/code&gt;. The following &amp;ldquo;innocent looking&amp;rdquo; query should do that for&#xA;us, we thought&amp;hellip;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;UserMessage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;objects&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_profile&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_profile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flags&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;UserMessage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flags&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well, it does, but not without bringing down the whole of &lt;code&gt;zulipchat.com&lt;/code&gt; after&#xA;a deploy! 🙈 But, why?!&lt;/p&gt;&#xA;&lt;p&gt;Some background on how data is stored would be useful to see why.&lt;/p&gt;&#xA;&lt;h2 id=&#34;usermessage-table-is-huge-my-friend&#34;&gt;UserMessage table is huge, my friend&lt;/h2&gt;&#xA;&lt;p&gt;Zulip&amp;rsquo;s data model has a &lt;code&gt;Message&lt;/code&gt; table that stores the content of each&#xA;message, when it was sent, by whom, to which topic, etc. The &lt;code&gt;UserMessage&lt;/code&gt; table&#xA;is then used to keep track of what the status of this message is, for each user&#xA;that has received the message.  A 32 bit flag field is used to keep track of&#xA;things like, whether the user has read a message, starred a message, has been&#xA;mentioned in a message, etc. So, if a messages is sent to a stream with N users,&#xA;it creates 1 row in the &lt;code&gt;Message&lt;/code&gt; table and N rows in the &lt;code&gt;UserMessage&lt;/code&gt; table.&#xA;If M such messages are sent, it would create M*N rows in the &lt;code&gt;UserMessage&lt;/code&gt;&#xA;table. So, pretty soon, the &lt;code&gt;UserMessage&lt;/code&gt; table becomes huge! &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;p&gt;To make common queries on this table efficient, the DB has indexes for them.&#xA;Some of the indexes on the &lt;code&gt;UserMessage&lt;/code&gt; table are documented &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/production/expensive-migrations.html#running-expensive-migrations-early&#34;&gt;here&lt;/a&gt;, though the&#xA;documentation was created in the migration context for production deployments.&#xA;There are indexes for various things, including &lt;strong&gt;unread&lt;/strong&gt; messages.&lt;/p&gt;&#xA;&lt;p&gt;And here&amp;rsquo;s our innocent looking query, as a reminder.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;UserMessage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;objects&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_profile&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_profile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flags&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;UserMessage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flags&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;read&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This query searches through the &lt;code&gt;read&lt;/code&gt; messages for a particular user, and tries&#xA;to figure out the last message they read. Note that the DB only has indexes for&#xA;unread messages, not read messages! So, this query isn&amp;rsquo;t using DB indexes to&#xA;search for this needle in the haystack, and ends up being pretty damn slow!&lt;/p&gt;&#xA;&lt;p&gt;Additionally, this query is run for every user when the &amp;ldquo;home page&amp;rdquo; is loaded&#xA;since we want to calculate their &lt;code&gt;furthest_read_time&lt;/code&gt; to decide if should show&#xA;the bankruptcy banner.&lt;/p&gt;&#xA;&lt;p&gt;Also, Zulip&amp;rsquo;s architecture is designed to allow using the same app server and DB&#xA;to host multiple different realms. And zulipchat.com is such a deployment where&#xA;a few servers are used to serve all the realms. (I don&amp;rsquo;t know too much about the&#xA;deployment setup, though).&lt;/p&gt;&#xA;&lt;p&gt;When a new change is deployed to zulipchat.com and the server is restarted, all&#xA;the clients of all the users reload. Having an expensive query in that path,&#xA;would just kill the DB. And this innocent looking query &lt;a href=&#34;https://twitter.com/zulip/status/1255005502599131137&#34;&gt;didn&amp;rsquo;t disappoint&lt;/a&gt;!&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-did-we-fix-this&#34;&gt;How did we fix this?&lt;/h2&gt;&#xA;&lt;p&gt;Well, this change was &lt;a href=&#34;https://github.com/zulip/zulip/commit/976e554799a03ff9d82d7b75d77d45985cd25df4&#34;&gt;reverted&lt;/a&gt; immediately by &lt;a href=&#34;https://github.com/timabbott&#34;&gt;Tim&lt;/a&gt;, and things were kicked back&#xA;into a usable state.&lt;/p&gt;&#xA;&lt;p&gt;Later, we decided to take a different approach. We decided to look at a&#xA;different table (&lt;code&gt;UserActivity&lt;/code&gt;) that tracks user activity, mainly for the&#xA;purpose of analytics. This table keeps track of the last time a user performed&#xA;an action based on the API calls made.&lt;/p&gt;&#xA;&lt;p&gt;Among other things, changing any of the flags in the 32-bit flag field is one of&#xA;the actions that is tracked. Marking a message as read was being tracked too. We&#xA;use the last time this action was performed to compute the last time a user was&#xA;&amp;ldquo;active&amp;rdquo;, and based on that decide if we should show the bankruptcy banner or&#xA;not.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the &lt;a href=&#34;https://github.com/zulip/zulip/commit/ded3b0076070365dddccd43d7f05fd627c2637f7&#34;&gt;actual commit&lt;/a&gt; that makes this change. But, it turns out that needed a&#xA;&lt;a href=&#34;https://github.com/zulip/zulip/commit/734d651b45542e9a1bdea10cd290637f73be30d9&#34;&gt;follow-up fix&lt;/a&gt; too. 🙈&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;  &lt;div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;https://web.mit.edu/tabbott/www/&#34;&gt;Tim Abbott&lt;/a&gt; and &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; for reading drafts of this post.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;The pointer was used to do a &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/subsystems/pointer.html&#34;&gt;bunch of useful things&lt;/a&gt;, but we are currently in the process of &lt;a href=&#34;https://github.com/zulip/zulip/issues/8994&#34;&gt;removing it&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Zulip&amp;rsquo;s code base uses a concept called &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html#soft-deactivation&#34;&gt;soft-deactivation&lt;/a&gt; to mitigate this problem on Realms where there are a lot of users who log in very infrequently, or don&amp;rsquo;t login after an initial period of activity. So, the number of rows are not exactly &lt;code&gt;M*N&lt;/code&gt; in all the cases, but in the worst case they are.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>My Favorite Git Resources</title>
      <link>https://punchagan.muse-amuse.in/blog/git-resources/</link>
      <pubDate>Fri, 22 May 2020 16:07:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/git-resources/</guid>
      <description>&lt;p&gt;I often help friends who are new to using &lt;code&gt;git&lt;/code&gt; with using it. This blog post is&#xA;going to serve as a single link to share with them, instead of trying to find&#xA;individual links over and over.&lt;/p&gt;&#xA;&lt;h2 id=&#34;understanding-git&#34;&gt;Understanding Git&lt;/h2&gt;&#xA;&lt;p&gt;I got started with using &lt;code&gt;git&lt;/code&gt; after using &lt;code&gt;svn&lt;/code&gt; and &lt;code&gt;hg&lt;/code&gt; for a few months. But,&#xA;I would often be lost in the myriad of command line options, putting myself in&#xA;situations I couldn&amp;rsquo;t come out of without some scars. I didn&amp;rsquo;t really understand&#xA;how &lt;code&gt;git&lt;/code&gt; worked, and that was the real problem.&lt;/p&gt;&#xA;&lt;p&gt;Improving my mental model of how &lt;code&gt;git&lt;/code&gt; works, and then trying to fit the&#xA;commands into this mental model is what made &lt;code&gt;git&lt;/code&gt; finally click for me. The &lt;a href=&#34;http://tom.preston-werner.com/2009/05/19/the-git-parable.html&#34;&gt;Git&#xA;Parable&lt;/a&gt; really helped me understand &lt;code&gt;git&lt;/code&gt; from the inside out, and gave me a&#xA;much clearer mental model of &lt;code&gt;git&lt;/code&gt;. There was no looking back from there.&lt;/p&gt;&#xA;&lt;p&gt;If parables are not your thing, and you&amp;rsquo;d rather read something that takes a&#xA;less flowery and more straighforward approach to explaining these concepts, I&#xA;really liked John Wiegley&amp;rsquo;s &lt;a href=&#34;https://jwiegley.github.io/git-from-the-bottom-up/&#34;&gt;Git from the Bottom Up&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;day-to-day-git&#34;&gt;Day-to-day git&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://zulipchat.com&#34;&gt;Zulip&lt;/a&gt; (an open source group chat application that I spend a lot of time&#xA;contributing to) uses a rebase-heavy workflow, where good commits are merged&#xA;from different PRs, as and when they are reviewed, instead of waiting for all&#xA;the commits in a PR to be good to be merged. Developers often have to pull from&#xA;branches that have been force-pushed to, rebase their commits, squash commits,&#xA;learn to change &lt;code&gt;git&lt;/code&gt; history, etc.&lt;/p&gt;&#xA;&lt;p&gt;Zulip&amp;rsquo;s &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/git/index.html&#34;&gt;Git Guide&lt;/a&gt; is top-class documentation that gives guidelines on using git&#xA;from a practical stand-point. A lot of new developers use it to get upto speed&#xA;with Zulip&amp;rsquo;s workflow that involves a lot of things that I&amp;rsquo;ve seen devs who have&#xA;been using git for many years struggle with.&lt;/p&gt;&#xA;&lt;p&gt;The section on &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/git/pull-requests.html&#34;&gt;pull request workflow&lt;/a&gt; is quite helpful, and should work&#xA;reasonably well for contributing to other FOSS projects too.&lt;/p&gt;&#xA;&lt;p&gt;I also like the sections with recipes for common &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/git/fixing-commits.html&#34;&gt;&lt;code&gt;git&lt;/code&gt; history edit actions&lt;/a&gt;, and&#xA;the &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/git/troubleshooting.html&#34;&gt;troubleshooting section&lt;/a&gt; with recipes to get out of common troublesome&#xA;situations. &lt;a href=&#34;https://ohshitgit.com/#accidental-commit-master&#34;&gt;Oh shit, git!&lt;/a&gt; is a very similar resource to this, that has a bunch&#xA;of useful &amp;ldquo;recipes&amp;rdquo; to recover from different scenarios.&lt;/p&gt;&#xA;&lt;p&gt;Julia Evans&amp;rsquo;s &lt;a href=&#34;https://jvns.ca/blog/2019/08/30/git-exercises--navigate-a-repository/&#34;&gt;exercises&lt;/a&gt; approach to learning to explore a &lt;code&gt;git&lt;/code&gt; repository is&#xA;quite interesting, though I haven&amp;rsquo;t run through all the exercises myself.&lt;/p&gt;&#xA;&lt;h2 id=&#34;git-as-a-communication-tool&#34;&gt;Git as a communication tool&lt;/h2&gt;&#xA;&lt;p&gt;I like to look at &lt;code&gt;git&lt;/code&gt; history as telling the story of evolution of a code&#xA;base. The advice that is sometimes given to writers about doing the hardwork, so&#xA;that the readers can do less of it, holds true for git history too. Communicate&#xA;the story as clearly as possible for your readers to understand, and don&amp;rsquo;t just&#xA;publish a &amp;ldquo;stream of consciousness&amp;rdquo; piece.&lt;/p&gt;&#xA;&lt;p&gt;What does this mean in practical terms, though?&lt;/p&gt;&#xA;&lt;p&gt;I think editing git history to present a clearer story is completely acceptable.&#xA;If one tries a handful of different things, and then finally finds the solution&#xA;to do something, it&amp;rsquo;s not necessary to have all the failed attempts as commits.&#xA;Just the final approach can be a commit/pull-request, while documenting the&#xA;other approaches the commit message or as a comment in the code, if that makes&#xA;sense.&lt;/p&gt;&#xA;&lt;p&gt;Expect your collaborators (including future you) to look at not just older&#xA;versions of code, but the commit history for understanding how a piece of code&#xA;evolved, for undertsanding the history. Make your git &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/contributing/version-control.html#commit-messages&#34;&gt;commit messages&lt;/a&gt; as useful&#xA;as you can. Writing a good summary line goes a long way in understanding a&#xA;change. In most cases a body would be useful to explain the how and the why of&#xA;the change, while the summary line explains the what.&lt;/p&gt;&#xA;&lt;p&gt;I like this &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/contributing/version-control.html#commit-discipline&#34;&gt;commit discipline&lt;/a&gt; that the Git project itself uses, and was adopted&#xA;by the Zulip project: &amp;ldquo;Each commit is a minimal coherent idea&amp;rdquo;. Simply put, you&#xA;want each commit to be such that they can be can be deployed (and/or reverted by&#xA;itself), without depending on any other commits alongside them. You can read&#xA;more about what minimal and coherent mean in this context in the &lt;a href=&#34;https://zulip.readthedocs.io/en/latest/contributing/version-control.html#commit-discipline&#34;&gt;Zulip&#xA;documentation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;tools-for-git&#34;&gt;Tools for Git&lt;/h2&gt;&#xA;&lt;p&gt;Some of the confusion in using &lt;code&gt;git&lt;/code&gt; even after understanding how it works is&#xA;the inconsitent CLI API that it often exposes. There has been some work going&#xA;into this, to make this more consistent. But, using a nice client can help with&#xA;this. I use the excellent Emacs client for &lt;code&gt;git&lt;/code&gt; called &lt;a href=&#34;https://magit.vc/screenshots/&#34;&gt;&lt;code&gt;magit&lt;/code&gt;&lt;/a&gt;. It is an&#xA;intuitive UI on top of &lt;code&gt;git&lt;/code&gt;&amp;rsquo;s data model, that allows me to do things that I&#xA;commonly want to do, with just a few hot-keys, instead of typing out elaborate&#xA;recipes to do things. I highly recommend it, if you are an Emacs user.&lt;/p&gt;&#xA;&lt;p&gt;It is also helpful to change you shell&amp;rsquo;s prompt to tell you more about the&#xA;status of the current directory&amp;rsquo;s repository. There are a bunch of&#xA;configurations out there (Stackoverflow, Gists, etc.) that let you do this. The&#xA;git repo itself comes with a &lt;a href=&#34;https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh&#34;&gt;prompt customisation&lt;/a&gt;. I like being able to see at&#xA;least the current branch and dirty/clean state of the repo.&lt;/p&gt;&#xA;&lt;p&gt;Here are some simple changes to the global &lt;code&gt;git&lt;/code&gt; config, off the top of my head.&#xA;These could make your &lt;code&gt;git&lt;/code&gt; workflow more pleasant.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-gitconfig&#34; data-lang=&#34;gitconfig&#34;&gt;[push]&#xA;  # Push to the branch to the same remote branch as the one we are working on,&#xA;  # locally. This is the default in Git &amp;gt;= 2.0, so you may not need to set it.&#xA;  default = simple&#xA;&#xA;[diff]&#xA;  # You could configure a GUI tool to use here. (I haven&amp;#39;t used one in a long&#xA;  # time, and I don&amp;#39;t have recommendations on what to use)&#xA;  tool = icdiff&#xA;&#xA;[merge]&#xA;  # Shows common ancestor in a merge conflict. Makes it easier to understand&#xA;    # the conflict and merge, for me.&#xA;  conflictstyle = diff3&#xA;&#xA;# Never garbage collect commits/blobs that are unreachable&#xA;# The cost of keeping this data around is negligble compared losing data&#xA;[gc]&#xA;  reflogExpire = never&#xA;  reflogExpireUnreachable = never&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;outro&#34;&gt;Outro&lt;/h2&gt;&#xA;&lt;p&gt;If you have other recommendations or tools that you&amp;rsquo;d like to share with me,&#xA;please do! Using &lt;code&gt;git&lt;/code&gt; better, and understanding it better, is something that&#xA;I&amp;rsquo;m always excited about. I&amp;rsquo;d also be happy to answer questions, or help you&#xA;with specific things that you are trying to do with &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>The tools we build change us</title>
      <link>https://punchagan.muse-amuse.in/blog/the-tools-we-build-change-us/</link>
      <pubDate>Wed, 22 Apr 2020 12:54:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/the-tools-we-build-change-us/</guid>
      <description>&lt;p&gt;A couple of weeks ago, I spent some time building a couple of &amp;ldquo;automation tools&amp;rdquo;&#xA;for myself. It is still early days to talk about their impact, but I have&#xA;already noticed some changes, and have some thoughts to share.&lt;/p&gt;&#xA;&lt;p&gt;Thanks to the lock-down, I felt like I needed to do something to get more&#xA;fresh-air and sunlight. I figured out a nice setup to work from my balcony, when&#xA;I feel like it. It&amp;rsquo;s nice to sit in the balcony, since there&amp;rsquo;s not as much&#xA;traffic, and there&amp;rsquo;s enough fresh-air and sunlight.&lt;/p&gt;&#xA;&lt;p&gt;I usually work with dark themes in all my tools - code editor, terminal, OS,&#xA;chat clients, etc. But, switching to the balcony during the day makes it harder&#xA;to read text in dark mode. I have to switch all my tools to light mode. And then&#xA;switch back, when I come back in. It takes me about a minute or so, each time to&#xA;make this switch. In a few days, this started to seem like work! And I could see&#xA;myself needing to push myself to step out, or step back in.&lt;/p&gt;&#xA;&lt;p&gt;A few days later, I spent about half a day writing a tool that could switch&#xA;modes for all of these programs with one command. If I was doing the switches&#xA;manually each time, it would take 2-4 months for the switching time to add up to&#xA;the 4-6 hours, I spent on getting everything working correctly. So, it&amp;rsquo;s not&#xA;really a bad investment. But, what matters more is that I don&amp;rsquo;t feel a mental&#xA;block to alternate between indoors and outdoors because of all the extra work&#xA;that manual theme switching felt like. I am getting some fresh air and sunlight&#xA;everyday. :)&lt;/p&gt;&#xA;&lt;p&gt;The other tool I wrote recently was for generating a daily summary of what I&#xA;worked on from my org-mode based clock entries. I add more detail to the&#xA;bulleted-list summary that gets generated, and then post it on our team&amp;rsquo;s Zulip.&#xA;I noticed a couple of changes because of this.&lt;/p&gt;&#xA;&lt;p&gt;First, I clock things more diligently than I did before. If I forget to clock&#xA;into something, I go back and add some manual entries, to maintain a proper log.&#xA;Second, a couple of other people on my team also have started posting a summary&#xA;of their day&amp;rsquo;s work, which is quite nice. This tool may have triggered a change&#xA;in my teammates&amp;rsquo; behavior, though I didn&amp;rsquo;t really intend it to.&lt;/p&gt;&#xA;&lt;p&gt;We are creatures of habit, often going through the motions, without needing to&#xA;think about what we are doing. This is evolution helping us save energy for the&#xA;more critical tasks. But, our environments can have a big impact on our habits.&#xA;Having tools around us, that are easy to use, can help us nudge ourselves in&#xA;directions that we want to be nudged in.&lt;/p&gt;&#xA;&lt;p&gt;The tools we build can change us; if we let them.&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;  &lt;div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Thanks to Rajesh, &lt;a href=&#34;https://www.lightstalking.com/author/riteshsaini/&#34;&gt;Ritesh&lt;/a&gt;, &lt;a href=&#34;https://tcluri.github.io/&#34;&gt;Tejaa&lt;/a&gt; and &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; for reading drafts of this post.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Django ModelFormSet and multiple saves</title>
      <link>https://punchagan.muse-amuse.in/blog/django-modelformset-multiple-saves/</link>
      <pubDate>Wed, 08 Jan 2020 09:45:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/django-modelformset-multiple-saves/</guid>
      <description>&lt;p&gt;A friend and I were trying to &lt;a href=&#34;https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#using-a-model-formset-in-a-view&#34;&gt;use the Django ModelFormSets&lt;/a&gt; in a project, and we&#xA;ran into a subtle bug that took a little big of digging into the Django code to&#xA;identify and work around.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-bug&#34;&gt;The Bug&lt;/h2&gt;&#xA;&lt;p&gt;We weren&amp;rsquo;t really familiar with Django&amp;rsquo;s &lt;code&gt;FormSet&lt;/code&gt; or &lt;code&gt;ModelFormSet&lt;/code&gt;, when we&#xA;started. We began by reading the documentation to get started.&lt;/p&gt;&#xA;&lt;p&gt;The Django docs had &lt;a href=&#34;https://docs.djangoproject.com/en/2.2/topics/forms/modelforms/#using-a-model-formset-in-a-view&#34;&gt;an example&lt;/a&gt; that looked straight-forward, and showed how to&#xA;use &lt;code&gt;ModelFormSet&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;manage_authors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;AuthorFormSet&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;modelformset_factory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Author&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fields&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;method&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AuthorFormSet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;POST&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FILES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_valid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;c1&#34;&gt;# do something.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AuthorFormSet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;manage_authors.html&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;formset&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;You create a &lt;code&gt;ModelFormSet&lt;/code&gt; class using the &lt;code&gt;modelformset_factory&lt;/code&gt; using the&#xA;required model, and the required fields.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Use the data from a &lt;code&gt;POST&lt;/code&gt; to create an instance of the class from above.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Save the data if the formset validates, and we are done!&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;We tweaked the example to our needs, and it worked for us. Mostly.&lt;/p&gt;&#xA;&lt;p&gt;A user reported that after saving the form once with some data, trying to save&#xA;more data didn&amp;rsquo;t work! The form would try to save the data already saved in the&#xA;last save, and fail validation that required some of the fields to be unique.&lt;/p&gt;&#xA;&lt;p&gt;On the second save, the data which was saved during the last save was still&#xA;being treated as newly filled in data!&lt;/p&gt;&#xA;&lt;p&gt;This also caused newly filled-in data during the second save to be lost forever.&#xA;The user was forced to reload the page and re-enter the data.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-why&#34;&gt;The Why&lt;/h2&gt;&#xA;&lt;p&gt;The way Django keeps track of newly filled-in data in the form along with&#xA;multiple levels of caching causes this bug to manifest.&lt;/p&gt;&#xA;&lt;h3 id=&#34;modelformset-queryset-argument&#34;&gt;&lt;code&gt;ModelFormSet&lt;/code&gt; &lt;code&gt;queryset&lt;/code&gt; argument&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;AuthorFormSet&lt;/code&gt; (or any &lt;code&gt;ModelFormSet&lt;/code&gt; created using the &lt;code&gt;modelformset_factory&lt;/code&gt;)&#xA;takes an additional optional argument &lt;code&gt;queryset&lt;/code&gt;. This argument can be used to&#xA;choose specific rows in the DB that need to be used to populate the initial form&#xA;rows, if any. If no &lt;code&gt;queryset&lt;/code&gt; is specified, all the objects in the DB are used.&lt;/p&gt;&#xA;&lt;h3 id=&#34;formset-metadata&#34;&gt;&lt;code&gt;FormSet&lt;/code&gt;  metadata&lt;/h3&gt;&#xA;&lt;p&gt;First, metadata about the form is saved in a few hidden input fields (called the&#xA;&lt;code&gt;ManagementForm&lt;/code&gt;). This hidden form keep tracks of metadata like the total&#xA;number of forms to show, initial form count and the minimum &amp;amp; maximum number of&#xA;forms to display.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;input&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;hidden&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;form-TOTAL_FORMS&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;19&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;id_form-TOTAL_FORMS&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;input&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;hidden&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;form-INITIAL_FORMS&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;7&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;id_form-INITIAL_FORMS&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;input&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;hidden&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;form-MIN_NUM_FORMS&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;id_form-MIN_NUM_FORMS&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;input&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;hidden&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;form-MAX_NUM_FORMS&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;id_form-MAX_NUM_FORMS&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Initial form count is the count of the number of forms that are pre-filled using&#xA;data from the DB &amp;ndash; essentially, the length of the &lt;code&gt;queryset&lt;/code&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;metadata-does-not-change&#34;&gt;Metadata does not change&lt;/h3&gt;&#xA;&lt;p&gt;When the &lt;code&gt;ModelFormSet&lt;/code&gt; is used as described in the documentation, the metadata&#xA;in the &lt;code&gt;ManagementForm&lt;/code&gt; doesn&amp;rsquo;t get updated when the form is re-rendered in the&#xA;response to a successful &lt;code&gt;POST&lt;/code&gt; request. If one form with new data was&#xA;successfully saved, the number of initial forms in the re-rendered form must go&#xA;up by 1, but it doesn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;h3 id=&#34;queryset-caching-problem&#34;&gt;Queryset caching problem&lt;/h3&gt;&#xA;&lt;p&gt;Django has caching at the queryset level which means that the queryset doesn&amp;rsquo;t&#xA;get updated with the newly created instances, even if they match the query that&#xA;the queryset uses.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AuthorFormSet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;POST&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FILES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;queryset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;existing_authors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;existing_authors&lt;/code&gt; is a queryset that looks for a specific set of authors in the&#xA;DB, or may be all the authors in the DB, if that&amp;rsquo;s what the form needs to do.&lt;/p&gt;&#xA;&lt;p&gt;So, when a formset has been submitted, and some new rows have been created in&#xA;the model&amp;rsquo;s table, the new rows don&amp;rsquo;t automatically get added to the &lt;code&gt;queryset&lt;/code&gt;&#xA;since it the results fetched from the DB are cached by the &lt;code&gt;queryset&lt;/code&gt;. We need&#xA;to create a new &lt;code&gt;queryset&lt;/code&gt; for it to include the newly created rows.&lt;/p&gt;&#xA;&lt;p&gt;These two levels of caching combined make the bug manifest, as it does. I&amp;rsquo;m not&#xA;yet sure what the correct &amp;ldquo;fix&amp;rdquo; for this is, but we ended up using a work-around&#xA;&amp;ndash; create a new formset when returning from a successful &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-work-around&#34;&gt;The Work-Around&lt;/h2&gt;&#xA;&lt;p&gt;We instantiate a new &lt;code&gt;ModelFormSet&lt;/code&gt; instance when returning the form, on a&#xA;successful &lt;code&gt;POST&lt;/code&gt;. This means, the &lt;code&gt;ManagementForm&lt;/code&gt; is recreated for the new&#xA;formset and has the correct metadata about the form, even when being rendered in&#xA;the response of a successful &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The code in the example in Django docs, would look something like the one below:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;manage_authors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;AuthorFormSet&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;modelformset_factory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Author&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fields&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;method&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AuthorFormSet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;POST&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FILES&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;is_valid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;manage_authors.html&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;formset&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;              &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;AuthorFormSet&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;manage_authors.html&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;formset&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;formset&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This blog post should probably become a PR or an issue, but I&amp;rsquo;m not familiar&#xA;with how contributing to Django works. This blog post is an attempt to&#xA;understand the issue better, and potentially help others who get bit by it.&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;  &lt;div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; for reading drafts of this post.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Been there, done that, and still doing it!</title>
      <link>https://punchagan.muse-amuse.in/blog/been-there-done-that-and-still-doing-it/</link>
      <pubDate>Thu, 05 Sep 2019 12:23:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/been-there-done-that-and-still-doing-it/</guid>
      <description>&lt;p&gt;I was talking to a friend a few days ago about a new internship she got. She&#xA;wasn&amp;rsquo;t sure if and how much she was going to get paid for it. Unpaid internships&#xA;aren&amp;rsquo;t cool, and experience doesn&amp;rsquo;t pay the bills, &lt;a href=&#34;https://twitter.com/aoc/status/1154466530500812800&#34;&gt;as they say&lt;/a&gt;. Eventually, it&#xA;turned out that she was indeed going to get paid for it.&lt;/p&gt;&#xA;&lt;p&gt;But, when she found out that she&amp;rsquo;ll be paid, she felt way more pressure to &amp;ldquo;not&#xA;screw up&amp;rdquo;. I think working in this mode of &amp;ldquo;don&amp;rsquo;t screw up&amp;rdquo; isn&amp;rsquo;t very productive.&#xA;Staying in the mindset of &amp;ldquo;learn as much as I can&amp;rdquo; would still be the best way&#xA;to go about the internship.&lt;/p&gt;&#xA;&lt;p&gt;Screwing up is a part of the job. People make mistakes and learn from them.&#xA;Screwing up is a part of gaining experience, and learning. To let people feel&#xA;comfortable and safe while they are learning, it&amp;rsquo;s important to have better&#xA;fallback and recovery mechanisms when things go wrong. The productivity gains&#xA;are worth investing time into such systems.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to write down a couple of times I screwed up &amp;ndash; one of them in my very&#xA;first job after college, and the other just a month or so ago. People screw up&#xA;every day, irrespective of how much experience they have.&lt;/p&gt;&#xA;&lt;h2 id=&#34;corrupted-a-server-s-hard-disk&#34;&gt;Corrupted a server&amp;rsquo;s hard-disk&lt;/h2&gt;&#xA;&lt;p&gt;In my &lt;a href=&#34;https://python.fossee.in/&#34;&gt;first job&lt;/a&gt;, I had screwed up big time in the first couple of weeks of my&#xA;job. It&amp;rsquo;s been a while, and a lot of the details escape me now.&lt;/p&gt;&#xA;&lt;p&gt;I was supposed to resize partitions on a server&amp;rsquo;s hard disk so that there&amp;rsquo;s more&#xA;space available for the media that we were going to store on the server. The&#xA;server was pretty newly setup, and there wasn&amp;rsquo;t all that much data on it, but my&#xA;bosses (&lt;a href=&#34;https://in.linkedin.com/in/pasokan&#34;&gt;Asokan&lt;/a&gt; and &lt;a href=&#34;https://www.aero.iitb.ac.in/~prabhu/&#34;&gt;Prabhu&lt;/a&gt;) had spent some time on configuring it, and setting up&#xA;user accounts, etc., for all the people that were going to join the project.&lt;/p&gt;&#xA;&lt;p&gt;I tested the resize operation on a machine locally in the office that I worked&#xA;in, and it seemed to work. I replicated this on the server, and it went kaput!&#xA;All the data was lost and I could no longer SSH into it. I was totally freaking&#xA;out!&lt;/p&gt;&#xA;&lt;p&gt;But my boss ended up being really calm about it, and asked me to just write to&#xA;the server hosting service to get a new machine with a bigger media disk. And&#xA;no, I didn&amp;rsquo;t get fired from the job. I worked with him for 5-6 years after that,&#xA;including being hired again at a company he started working for.&lt;/p&gt;&#xA;&lt;h2 id=&#34;almost-deleted-7-8-years-of-email&#34;&gt;Almost Deleted 7-8 years of email&lt;/h2&gt;&#xA;&lt;p&gt;A few weeks ago, I screwed up again. It could&amp;rsquo;ve been pretty bad, but luckily&#xA;for me it wasn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;p&gt;I administer a &lt;a href=&#34;https://support.google.com/a/answer/2855120?hl=en&#34;&gt;legacy Google Suite&lt;/a&gt; for &lt;a href=&#34;https://indiaultimate.org&#34;&gt;UPAI&lt;/a&gt;. We have only 10 email IDs to&#xA;allocate, and we often recycle them. Recently, I had to merge 3 accounts into a&#xA;single one to free up 2 new accounts. I transferred data from two of them to the&#xA;third, and went ahead and deleted them. I did this with a couple of other email&#xA;accounts before, but they were barely used and nobody really cared about them.&lt;/p&gt;&#xA;&lt;p&gt;I hadn&amp;rsquo;t paid attention to what data gets transferred, and what doesn&amp;rsquo;t. It&#xA;turns out &lt;a href=&#34;https://support.google.com/a/answer/33314?hl=en&#34;&gt;emails don&amp;rsquo;t get transferred&lt;/a&gt;, and only data in the Google Drive gets&#xA;transferred. This meant I had lost years of important email communication for&#xA;the organisation. I freaked out!&lt;/p&gt;&#xA;&lt;p&gt;My &amp;ldquo;&lt;a href=&#34;https://indiaultimate.org/u/manickam-narayanan&#34;&gt;boss&lt;/a&gt;&amp;rdquo; was again pretty calm, and asked me to write to Google to see if I&#xA;could get them to restore the data. I looked around, and found that Google&#xA;allowed restoring deleted accounts for 20 days! I &lt;a href=&#34;https://support.google.com/a/answer/1397578&#34;&gt;restored those accounts&lt;/a&gt;,&#xA;&lt;a href=&#34;https://support.google.com/a/answer/6351475?hl=en&amp;amp;ref%5Ftopic=6351498&#34;&gt;transferred the email&lt;/a&gt; and then deleted the accounts again.&lt;/p&gt;&#xA;&lt;h2 id=&#34;learning-from-screw-ups&#34;&gt;Learning from screw-ups&lt;/h2&gt;&#xA;&lt;p&gt;Learning and improving the systems in-place to prevent such screw-ups is the&#xA;best thing that we can do from such screw-ups. Just like a piece of software&#xA;gets hardened by bug reports that are acted upon, over the course of time, other&#xA;systems can and should too.&lt;/p&gt;&#xA;&lt;p&gt;As described in this &lt;a href=&#34;https://about.gitlab.com/2015/01/23/how-to-turn-screw-ups-to-your-advantage/&#34;&gt;blog post by GitLab&lt;/a&gt; on how to take advantage of screw-ups,&#xA;acknowledging mistakes, fixing them, and improving the systems in-place to&#xA;prevent such mistakes is a good way to go about it.&lt;/p&gt;&#xA;&lt;p&gt;Systems needn&amp;rsquo;t always be super complicated things, but just a &lt;a href=&#34;https://punchagan.muse-amuse.in/blog/automation-and-habits/&#34;&gt;little bit of&#xA;automation&lt;/a&gt;, and even &lt;a href=&#34;http://scattered-thoughts.net/blog/2016/01/28/notes-on-the-checklist-manifesto/&#34;&gt;simple checklists&lt;/a&gt; go a long way in improving work-flows and&#xA;making processes less error prone. Google&amp;rsquo;s recovery mechanism definitely&#xA;helped, and gives me confidence to continue working with these user accounts.&#xA;Our server had a system admin pretty soon, and he had automated back-ups and the&#xA;usual stuff in place, for us!&lt;/p&gt;&#xA;&lt;p&gt;The way a team deals with mistakes sets the tone for how the team works, takes&#xA;responsibility, and feels about working together. I still screw up after working&#xA;for about a decade as a developer, and being paid for it. I&amp;rsquo;m sure I&amp;rsquo;ll continue&#xA;to have my screw-ups, even though I&amp;rsquo;d like to have none. What was nice in both&#xA;the cases was the way in which my mentors/bosses reacted, and how calm they were&#xA;about the whole situation.&lt;/p&gt;&#xA;&lt;p&gt;May you have screw-ups that you learn and grow from!&lt;/p&gt;&#xA;&lt;p&gt;PS: Google&amp;rsquo;s &lt;a href=&#34;https://support.google.com/a/answer/6351475?hl=en&amp;amp;ref%5Ftopic=6351498&#34;&gt;Data Migration Service&lt;/a&gt; doesn&amp;rsquo;t support legacy Google Suite&#xA;accounts, and I ended up using &lt;a href=&#34;https://github.com/jay0lee/got-your-back&#34;&gt;Got Your Back&lt;/a&gt; to move email from the accounts I&#xA;wanted to delete to the other accounts. It worked pretty smoothly!&lt;/p&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;  &lt;div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Thanks to Aishwarya, Meghana, &lt;a href=&#34;https://twitter.com/cst2bicycle/&#34;&gt;Tejaa&lt;/a&gt; and &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; for reading drafts of this&#xA;post.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Isolating flaky Django tests</title>
      <link>https://punchagan.muse-amuse.in/blog/isolating-flaky-django-tests/</link>
      <pubDate>Wed, 05 Jun 2019 18:37:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/isolating-flaky-django-tests/</guid>
      <description>&lt;p&gt;The Zulip repository had a bunch of flaky tests that were fixed a few weeks ago.&#xA;I learnt a couple of tricks that I wasn&amp;rsquo;t aware of during this.&lt;/p&gt;&#xA;&lt;p&gt;Django&amp;rsquo;s test runner has an &lt;a href=&#34;https://docs.djangoproject.com/en/1.11/ref/django-admin/#cmdoption-test-reverse&#34;&gt;option&lt;/a&gt; to run tests in the reverse order. The&#xA;documentation of this option explicitly mentions that this option is useful for&#xA;debugging side-effects of tests that are not properly isolated. I wasn&amp;rsquo;t aware&#xA;of this option, and it&amp;rsquo;s a pretty neat tool.&lt;/p&gt;&#xA;&lt;p&gt;The test runner also has a &lt;a href=&#34;https://docs.djangoproject.com/en/1.11/ref/django-admin/#cmdoption-test-parallel&#34;&gt;&lt;code&gt;--parallel&lt;/code&gt; option&lt;/a&gt; to run tests in parallel, and the&#xA;Zulip server uses this option when running the back-end tests. I never really&#xA;bothered to look into how test cases are assigned to different processes. The&#xA;Django docs explain:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Django distributes test cases — &lt;code&gt;unittest.TestCase&lt;/code&gt; subclasses — to subprocesses.&#xA;If there are fewer test cases than configured processes, Django will reduce the&#xA;number of processes accordingly.&lt;/p&gt;&#xA;&lt;p&gt;Each process gets its own database. You must ensure that different test cases&#xA;don’t access the same resources. For instance, test cases that touch the&#xA;filesystem should create a temporary directory for their own use.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;When running tests in parallel, the set of tests running in the same process can&#xA;vary quite a bit based on the execution speed of tests in different processes.&#xA;To add more variation to the order in that tests are run, the number of&#xA;processes being used could also be changed.&lt;/p&gt;&#xA;&lt;p&gt;These two tools combined together can get tests to run in different orders than&#xA;the usual order in which they usually get run, and can help find tests which are&#xA;not well isolated.&lt;/p&gt;&#xA;&lt;p&gt;It could be a good idea to run tests in parallel (with a randomly chosen,&#xA;reasonable number of processes) and with the &lt;code&gt;--reverse&lt;/code&gt; flag, once in every few&#xA;CI runs, or as a cron job periodically.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;  &lt;div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;https://baali.muse-amuse.in&#34;&gt;Shantanu Choudhary&lt;/a&gt; for nudging me to write this post, and reading&#xA;drafts of this post. Thanks also to &lt;a href=&#34;https://blog.zulip.org/author/tabbott/&#34;&gt;Tim Abbott&lt;/a&gt; for making me aware of these&#xA;tricks.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Thoughts on Technical Debt</title>
      <link>https://punchagan.muse-amuse.in/blog/thoughts-on-technical-debt/</link>
      <pubDate>Tue, 23 Apr 2019 07:52:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/thoughts-on-technical-debt/</guid>
      <description>&lt;p&gt;I recently watched &lt;a href=&#34;https://www.youtube.com/watch?v=pqeJFYwnkjE&#34;&gt;this short video by Ward Cunningham&lt;/a&gt; where he reflects on the&#xA;history, motivation for, and common misunderstandings of the term Technical&#xA;debt.&lt;/p&gt;&#xA;&lt;p&gt;This post is just some of my reflections on Technical debt, based on ideas and&#xA;thoughts from &lt;a href=&#34;https://en.wikipedia.org/wiki/Ward%5FCunningham&#34;&gt;Ward Cunningham&lt;/a&gt;, &lt;a href=&#34;https://martinfowler.com/&#34;&gt;Martin Fowler&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Robert%5FC.%5FMartin&#34;&gt;Uncle Bob&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-is-technical-debt&#34;&gt;What is Technical Debt?&lt;/h2&gt;&#xA;&lt;p&gt;Ward Cunningham coined the &amp;ldquo;Techincal Debt&amp;rdquo; metaphor &amp;ndash; he used the metaphor to&#xA;explain the idea of shipping something with a limited understanding of the&#xA;problem, in the hope of gaining a better understanding as more people interact&#xA;with the shipped code.&lt;/p&gt;&#xA;&lt;p&gt;The difference between the understanding of the problem and how the code models&#xA;it is going to slow down the developers. This &amp;ldquo;slowing down&amp;rdquo; is equivalent to&#xA;paying interest, until we take out the time to refactor the code to accurately&#xA;reflect the current understanding of the problem.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-take-on-technical-debt&#34;&gt;Why take on Technical Debt?&lt;/h2&gt;&#xA;&lt;p&gt;When Cunningham says debt, he means shipping code with a partial, unclear or&#xA;even incorrect understanding of the problem and how it should be modeled. You&#xA;are willing to get the code out of the door, to improve this understanding.&lt;/p&gt;&#xA;&lt;p&gt;With borrowed money, sometimes you can do something sooner than you would&#xA;otherwise. Similarly, rushing software out the door can help us get some&#xA;experience with it, which would help gain a better understanding and improve the&#xA;software.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-mess-is-not-a-debt-or-is-it&#34;&gt;A mess is not a debt, or is it?&lt;/h2&gt;&#xA;&lt;p&gt;Cunningham says that people confuse messily written code with technical debt. He&#xA;is of the opinion that messy code isn&amp;rsquo;t technical debt. He&amp;rsquo;s never in favor of&#xA;shipping bad or messy code. He emphasizes that code that&amp;rsquo;s shipped should always&#xA;be clear enough to refactor easily, when required, even if it doesn&amp;rsquo;t model our&#xA;current understanding of the problem very accurately.&lt;/p&gt;&#xA;&lt;p&gt;Uncle Bob also has a post, on similar lines, where he says, &lt;a href=&#34;https://sites.google.com/site/unclebobconsultingllc/a-mess-is-not-a-technical-debt&#34;&gt;a mess is not a&#xA;technical debt&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Martin Fowler, on the other hand, argues that the debt metaphor is a useful&#xA;metaphor to think about and to communicate problems in code &amp;mdash; especially to&#xA;non-technical people &amp;mdash; irrespective of the reason for the problem.&lt;/p&gt;&#xA;&lt;p&gt;Not shipping messy code makes sense. But, the debt metaphor is a useful tool to&#xA;talk about existing &amp;ldquo;messy&amp;rdquo; code, compared to just calling it messy code.&#xA;Looking at it as a debt lets you think about whether or not it&amp;rsquo;s worth paying&#xA;off, and if so when, etc. The Technical Debt Quadrant (explained below) further&#xA;classified this debt into 4 classes, which makes it even more useful to identify&#xA;bad code as debt.&lt;/p&gt;&#xA;&lt;h2 id=&#34;technical-debt-quadrant&#34;&gt;Technical Debt Quadrant&lt;/h2&gt;&#xA;&lt;p&gt;Martin Fowler came up with a &lt;a href=&#34;https://martinfowler.com/bliki/TechnicalDebtQuadrant.html&#34;&gt;Technical Debt Quadrant&lt;/a&gt; which incorporates other&#xA;kinds of debt, along with the one Ward Cunningham originally defined.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&#34;https://martinfowler.com/bliki/images/techDebtQuadrant.png&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;Fowler comes up with two kinds of divisions of debt &amp;mdash; Reckless vs Prudent and&#xA;Inadvertent vs Deliberate.&lt;/p&gt;&#xA;&lt;p&gt;When Ward says he&amp;rsquo;s against writing bad code, I think he means he&amp;rsquo;s never for&#xA;taking on reckless debt. Uncle Bob&amp;rsquo;s mess is also what Fowler would call&#xA;Reckless debt.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Reckless-Deliberate&lt;/strong&gt; debt is code written out of not caring enough about the&#xA;code, or not stopping to think about everything that&amp;rsquo;s going on with the code,&#xA;or just being mentally lazy. Code-reviews seem like one good way to avoid this&#xA;kind of debt.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Reckless-Inadvertent&lt;/strong&gt; debt is something that&amp;rsquo;s hard to prevent. It sounds like&#xA;&lt;a href=&#34;https://www.youtube.com/watch?v=GiPe1OiKQuk&#34;&gt;unknown-unknowns&lt;/a&gt; territory to me. The developers or the team would probably&#xA;benefit from improving their general design skills and/or getting some external&#xA;help as consultants, etc.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Prudent-Deliberate&lt;/strong&gt; debt is what Ward talks about, I think, in his video. You&#xA;know your understanding is not good enough, but you aren&amp;rsquo;t yet sure what&amp;rsquo;s the&#xA;right design is. You may gather more knowledge about the problem while the&#xA;software is out there in the wild and being used.&lt;/p&gt;&#xA;&lt;p&gt;Ward talks about better understanding. But, you may not always realize that your&#xA;understanding is incomplete, when you are shipping the code. This I think would&#xA;put it in the 4th quadrant - &lt;strong&gt;Prudent-Inadvertent&lt;/strong&gt; debt.&lt;/p&gt;&#xA;&lt;h2 id=&#34;is-everything-tech-debt&#34;&gt;Is everything tech debt?&lt;/h2&gt;&#xA;&lt;p&gt;The Tech Debt quadrant seems to make a lot of things seem to fit into the debt&#xA;metaphor, but there are some things that I&amp;rsquo;m not sure about.&lt;/p&gt;&#xA;&lt;p&gt;For example, it can be a significant time investment to update dependencies.&#xA;Often, teams don&amp;rsquo;t update dependencies until it becomes absolutely essential &amp;mdash;&#xA;&amp;ldquo;don&amp;rsquo;t fix what ain&amp;rsquo;t broken&amp;rdquo;. This strategy of postponing of updates, to use&#xA;the time for other things, seems like taking on a debt. But, I&amp;rsquo;m not sure which&#xA;quadrant to put it into.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-should-i-care&#34;&gt;Why should I care?&lt;/h2&gt;&#xA;&lt;p&gt;Just calling code messy isn&amp;rsquo;t really qualifying the mess, and helping us talk&#xA;about how to tackle it. Thinking about debt while taking decisions seems&#xA;helpful, especially with further classification of debt into the 4 quadrants.&lt;/p&gt;&#xA;&lt;p&gt;It helps think about whether we&amp;rsquo;d like to pay off debts we have taken, and to&#xA;communicate with non-technical people the kind of &amp;ldquo;maintenance&amp;rdquo; or refactoring&#xA;work we plan to take up, and why it is important to do so. The deliberate debt&#xA;concept can be used to think and communicate about things we aren&amp;rsquo;t dealing&#xA;with, right now, in the interest of shipping software quicker.&lt;/p&gt;&#xA;&lt;h2 id=&#34;keeping-track-and-repaying-debt&#34;&gt;Keeping track and repaying debt&lt;/h2&gt;&#xA;&lt;p&gt;If a debt is something we can take on deliberately, and we can take on multiple&#xA;such debts, what would be a good way to keep track of these debts?&lt;/p&gt;&#xA;&lt;p&gt;Keeping a list of these debts that we periodically review, seems like a good&#xA;idea. We check if our understanding has improved from the time we took on a&#xA;debt, and take out time to incorporate it back into the code.&lt;/p&gt;&#xA;&lt;p&gt;This list could just be a bunch of FIXMEs in the code with some detailed&#xA;explanation of the debt, and why it was required, or just a list of issues in&#xA;the issue tracker with a special tag to make it easy to find them.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;div style=&#34;font-size:small;&#34; class=&#34;reviewers&#34;&gt;&#xA;  &lt;div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;https://baali.muse-amuse.in&#34;&gt;Shantanu Choudhary&lt;/a&gt; and &lt;a href=&#34;https://vkrishnaswam.github.io/&#34;&gt;Vivek Krishnaswamy&lt;/a&gt; for reading drafts of this post&#xA;and giving helpful suggestions.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Heroku apps with custom-domain and Cloudflare SSL</title>
      <link>https://punchagan.muse-amuse.in/blog/heroku-apps-custom-domain-cloudflare-ssl/</link>
      <pubDate>Thu, 14 Mar 2019 13:03:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/heroku-apps-custom-domain-cloudflare-ssl/</guid>
      <description>&lt;h2 id=&#34;motivation&#34;&gt;Motivation&lt;/h2&gt;&#xA;&lt;p&gt;I have a bunch of small (toy?) apps hosted on Heroku. They are probably used by&#xA;a few dozen people, at most. For these apps, I&amp;rsquo;d like the following setup:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Use a custom domain for the apps&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Heroku provides domains of the form &lt;code&gt;appname.herokuapp.com&lt;/code&gt; for all apps.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;But, it also has the option to add one or more custom domains&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I usually already have a domain under which I want to add a sub-domain,&#xA;where these apps would run.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Use HTTPS for all connections&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The domains or the sub-domains usually don&amp;rsquo;t already already have an SSL&#xA;certificate. With lets-encrypt, I guess this can change in future.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Heroku provides SSL certificates for the default domain&#xA;(&lt;code&gt;appname.herokuapp.com&lt;/code&gt;) for free.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;They also have an option to buy SSL certificates for custom domains, but&#xA;they are expensive!&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;I&amp;rsquo;m going to use &lt;a href=&#34;https://www.cloudflare.com/ssl/&#34;&gt;Cloudflare&amp;rsquo;s free SSL service&lt;/a&gt; instead. I&amp;rsquo;d like to have&#xA;SSL for the full domain, and not just the app&amp;rsquo;s subdomain.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Redirect all the requests to the Heroku domain to the custom domain&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Nobody should really be using the &lt;code&gt;appname.herokuapp.com&lt;/code&gt; domain, in case&#xA;I&amp;rsquo;d like to move away from it.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;A few lines of app (Flask) code can do this for us.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;&#xA;&lt;h3 id=&#34;setup-dns-to-use-cloudflare&#34;&gt;Setup DNS to use Cloudflare&lt;/h3&gt;&#xA;&lt;p&gt;Since we plan to use Cloudflare for the SSL certificates, we need to change DNS&#xA;settings at our domain registrar to use Cloudflare. We first need to create a&#xA;Cloudflare account, and &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/201720164-Step-2-Create-a-Cloudflare-account-and-add-a-website&#34;&gt;let Cloudflare do a DNS scan&lt;/a&gt; to add all the existing&#xA;domain settings automatically. Next we point to the &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/205195708&#34;&gt;Cloudflare DNS servers&lt;/a&gt; on&#xA;our domain registrar.&lt;/p&gt;&#xA;&lt;h3 id=&#34;enable-https-always-in-cloudflare&#34;&gt;Enable HTTPS always in Cloudflare&lt;/h3&gt;&#xA;&lt;p&gt;Using &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/218411427#always-use-https&#34;&gt;page rules in Cloudflare&lt;/a&gt;, add a rule for the whole domain to &amp;ldquo;Always use&#xA;HTTPS&amp;rdquo;. For example, use the url &lt;code&gt;http://*example.com/*&lt;/code&gt; and select the &lt;code&gt;Always use HTTPS&lt;/code&gt; option.&lt;/p&gt;&#xA;&lt;h3 id=&#34;select-the-ssl-option-to-use&#34;&gt;Select the SSL option to use&lt;/h3&gt;&#xA;&lt;p&gt;Cloudflare provides &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/200170416-What-do-the-SSL-options-mean-&#34;&gt;different options for the SSL setting&lt;/a&gt; which changes whether&#xA;or not traffic is encrypted between Cloudflare and the Heroku app.&lt;/p&gt;&#xA;&lt;p&gt;Choose &lt;code&gt;Full (strict)&lt;/code&gt; to enable encryption between Cloudflare and Heroku.&lt;/p&gt;&#xA;&lt;p&gt;If you are not so concerned about encryption between Cloudflare and Heroku, you&#xA;could select the &lt;code&gt;Flexible&lt;/code&gt; option too.&lt;/p&gt;&#xA;&lt;h3 id=&#34;add-a-custom-domain-in-heroku&#34;&gt;Add a custom domain in Heroku&lt;/h3&gt;&#xA;&lt;p&gt;Add a &lt;a href=&#34;https://devcenter.heroku.com/articles/custom-domains&#34;&gt;custom domain&lt;/a&gt; &amp;ndash; &lt;code&gt;app.example.com&lt;/code&gt; &amp;ndash; in Heroku&amp;rsquo;s settings.&lt;/p&gt;&#xA;&lt;p&gt;Heroku provides a DNS Target that needs to be used as the destination for a&#xA;CNAME setting in the DNS provider (Cloudflare). The DNS Target looks something&#xA;like &lt;code&gt;foo-bar-123abcdef.herokudns.com&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;If you chose &lt;code&gt;Full (strict)&lt;/code&gt; SSL option, this DNS target cannot be used and can&#xA;be ignored.&lt;/p&gt;&#xA;&lt;h3 id=&#34;add-a-cname-for-the-subdomain&#34;&gt;Add a CNAME for the subdomain&lt;/h3&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/360019093151-#h%5F60566325041543261564371&#34;&gt;Add a new CNAME setting&lt;/a&gt; in Cloudflare for &lt;code&gt;app.example.com&lt;/code&gt; and use the&#xA;app&amp;rsquo;s Heroku domain name (&lt;code&gt;appname.herokuapp.com&lt;/code&gt;) as the destination value/IP&#xA;address.&lt;/p&gt;&#xA;&lt;p&gt;It is important to use the &lt;code&gt;appname.herokuapp.com&lt;/code&gt; value if the SSL settings you&#xA;chose above was &lt;code&gt;Full (strict)&lt;/code&gt;. Using the DNS Target provided by Heroku instead&#xA;(&lt;code&gt;foo-bar-123abcdef.herokudns.com&lt;/code&gt;) would give an &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/200278659&#34;&gt;SSL Handshake error&lt;/a&gt; since the&#xA;SSL certificate provided by Heroku only works for the &lt;code&gt;appname.herokuapp.com&lt;/code&gt;&#xA;domain, and not for &lt;code&gt;foo-bar-123abcdef.herokudns.com&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;If you chose &lt;code&gt;Flexible&lt;/code&gt;, though, you can use the DNS Target provided by Heroku&#xA;&amp;ndash; &lt;code&gt;foo-bar-123abcdef.herokudns.com&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Also, ensure that the Cloudflare Proxy Toggle is toggled on &amp;ndash; &lt;em&gt;the cloud icon&#xA;is orange, not grey&lt;/em&gt;!&lt;/p&gt;&#xA;&lt;h3 id=&#34;redirect-all-requests&#34;&gt;Redirect all requests&lt;/h3&gt;&#xA;&lt;p&gt;We redirect all the requests coming to the old domain to the new one.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nd&#34;&gt;@app.before_request&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;redirect_heroku&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Redirect herokuapp requests.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;urlparts&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;urlparse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;urlparts&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;netloc&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;appname.herokuapp.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;urlparts_list&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;urlparts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;urlparts_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;app.example.com&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;redirect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;urlunparse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;urlparts_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;code&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;301&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;enforce-https-on-heroku&#34;&gt;Enforce HTTPS on Heroku&lt;/h2&gt;&#xA;&lt;p&gt;If you don&amp;rsquo;t care about a custom domain and just wish to enforce SSL for the&#xA;Heroku domain (&lt;code&gt;appname.herokuapp.com&lt;/code&gt;), &lt;code&gt;Flask-SSLify&lt;/code&gt; is the way to go. The&#xA;app can be hosted on Heroku and we can use the certs provided for free.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;flask_sslify&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SSLify&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;app&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Flask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;vm&#34;&gt;__name__&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;DYNO&amp;#34;&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;environ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Always use SSL if the app is running on Heroku (not locally)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;sslify&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SSLify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve done this setup, or some parts of it quite a few times, but each time I&#xA;seem to need to look up the documentation. Every time, it seems to take longer&#xA;than it needs to! Hopefully, this post will make it quick and reproducible.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Catch of the Year Poll on Ultiworld: A pointless exercise</title>
      <link>https://punchagan.muse-amuse.in/blog/catch-of-the-year-poll-on-ultiworld-a-pointless-exercise/</link>
      <pubDate>Thu, 07 Mar 2019 20:23:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/catch-of-the-year-poll-on-ultiworld-a-pointless-exercise/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://ultiworld.com/2019/02/19/ultiworlds-2018-catch-year-bracket-presented-friction-gloves/&#34;&gt;Ultiworld&lt;/a&gt;, a popular Ultimate related magazine did a poll for it&amp;rsquo;s &amp;ldquo;readers&amp;rdquo; to&#xA;&lt;a href=&#34;https://ultiworld.com/2019/02/19/ultiworlds-2018-catch-year-bracket-presented-friction-gloves/&#34;&gt;pick the Catch of the Year&lt;/a&gt;. I wouldn&amp;rsquo;t have noticed it normally, but this time&#xA;around Pranav Rajan, a player from India, featured in it. A lot of people in the&#xA;Indian Ultimate community tried to ensure the whole community took notice and&#xA;voted in large numbers!&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;[&amp;hellip;] we studied many hours of tape and solicited your submissions to create a&#xA;list of 16 finalists. Over the next week, you the Ultiworld readers will have a&#xA;chance to weigh in and select the Catch of the Year.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Studying all these hours of tape and picking some of the best catches seems like&#xA;a great contribution to the Ultimate community. It gives these players a good&#xA;platform of recognition and it also gives all of us people to look up to.&lt;/p&gt;&#xA;&lt;p&gt;But, I find this whole exercise of popular voting quite pointless. What exactly&#xA;is Ultiworld trying to achieve here? It seems like it is just a competition to&#xA;see how much popularity and support each player can garner.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Perhaps unsurprising to those who have followed along with our brackets in the&#xA;past, Pranav managed by far the most votes in the first round, buoyed by a&#xA;continent’s worth of support. If past brackets are any indication, it will take&#xA;massive domestic support to take the young Indian star down in this competition.&#xA;Can Sovell-Fernandez muster it?&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;!--quoteend--&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;In one of the tighter matchups we’ve seen in this year’s competition, Pranav’s&#xA;U24 World Championships snag captured 56% of the vote to surpass another young&#xA;star in the making, Allyn Suzuki of California Current. With the margin always&#xA;close, Suzuki led as late as with 18 hours to go, but Pranav saw a late surge to&#xA;claim victory.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The poll is unauthenticated. People can vote multiple times, with simple cookie&#xA;tweaking. Even if we are relying on people to vote in the &lt;a href=&#34;http://wfdf.org/sotg/about-sotg&#34;&gt;&amp;ldquo;Spirit of the Game&amp;rdquo;&lt;/a&gt;,&#xA;I don&amp;rsquo;t see what the community gains out of this exercise.&lt;/p&gt;&#xA;&lt;p&gt;The players, who featured in the poll, themselves are left wondering if they&#xA;deserved to win given the poll wasn&amp;rsquo;t fool-proof and given that humans with all&#xA;their biases easily vote for players they know, or players from their country,&#xA;etc.&lt;/p&gt;&#xA;&lt;p&gt;Ultiworld gets lots of page views and the like. The link to the vote was shared&#xA;with lots of people who probably hadn&amp;rsquo;t heard of Ultiworld before or don&amp;rsquo;t care&#xA;to read it. May be they found a few new readers?&lt;/p&gt;&#xA;&lt;p&gt;I would&amp;rsquo;ve liked the whole exercise a lot more if Ultiworld just stopped at&#xA;picking the 8 or 16 top catches, and putting them all in a post. But, may be&#xA;that wouldn&amp;rsquo;t have got so many page views!&lt;/p&gt;&#xA;&lt;p&gt;I refuse to participate in any such future popularity contests. And I think we&#xA;all should.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Thanks to Meghana Iyer for sharing her strong views on this Ultiworld poll and&#xA;for reading a draft of this post. Thanks to &lt;a href=&#34;https://baali.muse-amuse.in&#34;&gt;Shantanu Choudhary&lt;/a&gt; for encouraging&#xA;me to write this post.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Glue Highlight Reels</title>
      <link>https://punchagan.muse-amuse.in/blog/glue-highlight-reels/</link>
      <pubDate>Wed, 06 Feb 2019 14:33:00 +0530</pubDate>
      <guid>https://punchagan.muse-amuse.in/blog/glue-highlight-reels/</guid>
      <description>&lt;p&gt;As a leader on my Ultimate team, I spend a lot of time talking to my team mates.&#xA;I get to hear out their complaints, their joys and everything in between. All&#xA;these conversations got me thinking about what makes someone feel like an&#xA;integral part of the team, where they are making a meaningful contribution.&lt;/p&gt;&#xA;&lt;h2 id=&#34;metrics-to-measure-contribution&#34;&gt;Metrics to measure contribution&lt;/h2&gt;&#xA;&lt;p&gt;How do we measure our contributions to the team. What do we look at, to tell&#xA;ourselves and others, whether we have made a contribution or not? I&amp;rsquo;m still&#xA;trying to make sense of this, but here are a few metrics I found.&lt;/p&gt;&#xA;&lt;h3 id=&#34;highlight-or-debacle-reels&#34;&gt;Highlight or Debacle reels&lt;/h3&gt;&#xA;&lt;p&gt;A lot of us tend to remember things as highlight or debacle reels - we make&#xA;stories out of these events. If you got that layout grab or D, you tend to&#xA;remember it and that leaves you with a feeling of satisfaction. If you&amp;rsquo;ve more&#xA;highlights to remember than the number of debacles, you feel like you made a&#xA;positive contribution.&lt;/p&gt;&#xA;&lt;p&gt;But, I think games are not as &amp;ldquo;eventful&amp;rdquo; as we think [citation needed!] &amp;ndash;&#xA;especially when a team is playing good Ultimate. When everyone is making good&#xA;decisions and executing them well, the game is quite uneventful. The credit for&#xA;the success is distributed over the team, rather than one or two individuals.&#xA;Are we capable of identifying this, acknowledging it as a meaningful personal&#xA;contribution?&lt;/p&gt;&#xA;&lt;h3 id=&#34;tiredness&#34;&gt;Tiredness&lt;/h3&gt;&#xA;&lt;p&gt;When we are done with a day&amp;rsquo;s play, we only have a few moments stuck in our&#xA;memory. What remains as a physical manifestation is how tired we are, physically&#xA;and mentally. A lot of us tend to use this metric, probably even subconsciously.&lt;/p&gt;&#xA;&lt;p&gt;But, with a big enough squad that is being rotated well, I&amp;rsquo;m not sure this is a&#xA;good metric.&lt;/p&gt;&#xA;&lt;h3 id=&#34;mvp-awards&#34;&gt;MVP awards&lt;/h3&gt;&#xA;&lt;p&gt;At the end of every game, teams have a spirit circle where they discuss each&#xA;others&amp;rsquo; play, and pick a player in the opposition who made a difference to the&#xA;game - Most Valuable Player. Being identified the MVP is definitely one of the&#xA;easiest things that is used as a metric for contribution. Only one person gets&#xA;it in a game, and often, the same person gets it in a few matches over the&#xA;course of a tournament.&lt;/p&gt;&#xA;&lt;p&gt;Sure, the MVP played well, but what about the dozen others? If you weren&amp;rsquo;t the&#xA;best player, doesn&amp;rsquo;t mean you didn&amp;rsquo;t make a difference. Would not being&#xA;identified by the opposition make you forget those contributions? Is there&#xA;something the team can do to help recognize and identify them?&lt;/p&gt;&#xA;&lt;h3 id=&#34;being-challenged&#34;&gt;Being Challenged&lt;/h3&gt;&#xA;&lt;p&gt;When a game was very challenging, win or loss, people tend to feel happy and&#xA;satisfied about it.&lt;/p&gt;&#xA;&lt;p&gt;Just being challenged to their limits seems to make people feel like they have&#xA;contributed. Or may be just makes them forget about asking this question.&lt;/p&gt;&#xA;&lt;h3 id=&#34;contributing-in-other-ways&#34;&gt;Contributing in other ways&lt;/h3&gt;&#xA;&lt;p&gt;If I&amp;rsquo;m not having a good day, or I&amp;rsquo;m injured, I don&amp;rsquo;t mind filling water bottles&#xA;and taking it easy. If I&amp;rsquo;ve been able to alert someone from the sideline and&#xA;they make a difference, that&amp;rsquo;s a contribution I&amp;rsquo;m making.&lt;/p&gt;&#xA;&lt;h2 id=&#34;better-metrics&#34;&gt;Better metrics&lt;/h2&gt;&#xA;&lt;h3 id=&#34;stats&#34;&gt;Stats&lt;/h3&gt;&#xA;&lt;p&gt;Stats might help in a small way. For instance, just using a rotating scheme for&#xA;calling lines, a lot of the not-enough game time problems seemed to go away.&#xA;Just being able to assure people that there&amp;rsquo;s a system in place to ensure they&#xA;get enough game time, took their minds away from it.&lt;/p&gt;&#xA;&lt;p&gt;The usual kinds of stats - scores, assists, drops, etc. - might actually only&#xA;end-up reinforcing the outlook of highlight and debacle reels. But, it may help&#xA;with triggering some faded memories, which might alter the story we make up from&#xA;the events recorded.&lt;/p&gt;&#xA;&lt;p&gt;I wonder, though, if there can be other kinds of metrics that might help bring&#xA;out some of the &amp;ldquo;grind&amp;rdquo; aspects of the game, rather than events. These stats&#xA;will be harder to collect, but would there be a way to reward someone on how&#xA;well they made space for the next cutter, or how they shutdown the person they&#xA;were defending and took them out of the game?&lt;/p&gt;&#xA;&lt;h3 id=&#34;culture&#34;&gt;Culture&lt;/h3&gt;&#xA;&lt;p&gt;I am beginning to think the problem is primarily cultural. In a world where we&#xA;care so much about the number of likes and retweets, I think it&amp;rsquo;s just hard to&#xA;remember the &amp;ldquo;grind&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;Sarah Griffith &lt;a href=&#34;https://soundcloud.com/user-809702254/why-every-team-needs-glue-by-surge-griffith&#34;&gt;shared an interesting idea&lt;/a&gt; that she calls Glue players - players&#xA;who just do the basics right, and aren&amp;rsquo;t necessarily involved in spectacles like&#xA;a layout D or a super difficult throw.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Matty&amp;rsquo;s shout outs are for glue plays - reliable fill cuts for big yards, or&#xA;somebody who committed to fronting their matchup because that was the game plan&#xA;even though it’s scary as heck to give that cutter the chance to go deep on you.&lt;/p&gt;&#xA;&lt;p&gt;And anyone who has ever played for Matty knows that these shout outs are a&#xA;coveted thing, because you know that what you’ve done was selfless and helped&#xA;the team succeed, and that’s an honor.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;http://weheartmatty.tumblr.com/&#34;&gt;Matty Tsang is a great coach&lt;/a&gt; who has some revolutionary ideas on how to play and&#xA;coach Ultimate. I&amp;rsquo;m not surprised the idea of glue plays goes back to him.&lt;/p&gt;&#xA;&lt;p&gt;But, as a team without a non-playing coach, I wonder how to bring about this&#xA;culture of identifying and encouraging glue players. May be having a handful of&#xA;players who make it their job to do this for a few weeks or months, will set the&#xA;ball rolling.&lt;/p&gt;&#xA;&lt;p&gt;Sarah has some advice on how to go about doing this in her article.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;When you’re holding tryouts for your team, look for who is facilitating scores&#xA;even if they’re not always catching them. Look for who’s eliminating their&#xA;person from the offense with great positioning rather than bidding all over the&#xA;place on D. Look for who shows up, and who you can count on. And then let other&#xA;people know. Celebrate it. Glue playing is contagious - make people aware of&#xA;when it’s happening by calling it out and focusing on it, instead of the glory&#xA;plays.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The idea of &amp;ldquo;Glue highlights&amp;rdquo; replacing the (figurative) highlight reels seems&#xA;like a very promising idea. I&amp;rsquo;d like to give this a shot, as a team!&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;https://vimeo.com/tariqthekaekara&#34;&gt;Tariq&lt;/a&gt;, &lt;a href=&#34;https://twitter.com/cst2bicycle/&#34;&gt;Tejaa&lt;/a&gt; and &lt;a href=&#34;http://baali.muse-amuse.in&#34;&gt;Shantanu&lt;/a&gt; for being excited reading drafts of this&#xA;post, and encouraging me to publish it.&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
