<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>msfjarvis.dev</title>
    <description>Systems Engineer at Cloudflare. Recovering Android developer, amateur Rustacean.</description>
    <link>https://msfjarvis.dev/</link>
    <atom:link href="https://msfjarvis.dev/rss.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Tue, 23 Jun 2026 21:27:44 GMT</lastBuildDate>
    <language>en-us</language>
    <item>
      <title>Weeknotes: Week #25 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-25-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-25-2026/</guid>
      <description>Ow, my neck…</description>
      <pubDate>Sun, 21 Jun 2026 13:27:02 GMT</pubDate>
      <content:encoded><![CDATA[<p>Rough week in the health department but the brain juices have held up against all odds. Also games!</p>
<h1 id="personal">Personal</h1>
<p>I woke up on Monday with a lot of pain in my neck and decided to take the day off for some rest and relaxation. In the midst of aforementioned R&amp;R <a href="https://yashgarg.dev/">Yash</a> asked me if I wanted to meet up with his girlfriend and him for breakfast at Toit, where we got some pancakes and waffles. Not the typical idea of resting an injury, but it was nice to be out and about in the sun. From there we walked the length of the 100 Feet road to end up at Lenskart for some new glasses for Yash. I got one of their complementary eye checkups which confirmed that despite my best efforts to the contrary I still have a 5/5 eyesight, having two bespectacled friends with me and receiving that result was funny.</p>
<p>The neck problem did not improve throughout the day, so I had to get a hot watter bottle and a neck brace, which have helped with the problem as the week progressed. Really starting to feel like a proverbial unc with these random aches, and had to skip the gym for the whole week.</p>
<p>I’m planning to finally take a proper holiday sometime next month, and have already taken some recommendations from <a href="https://rohanarthur.com">Rohan Arthur</a> for lake houses near Bengaluru. Reach out to me if you have ideas for any idyllic places close by where I can be <em>mostly</em> offline for a couple of weeks!</p>
<p>I am also working on my passport application in anticipation of a trip to Singapore for the PGL Counter Strike 2 Major with Yash, <a href="https://github.com/anunaym14">Anunay</a>, and <a href="https://github.com/akhilnarang">Akhil</a>. I have one failed application under my belt and no other real knowledge of the matter to speak of, so I am also open to someone helping me out here as well!</p>
<h1 id="media">Media</h1>
<p>Continued reading <a href="https://bookwyrm.social/book/1586797/s/the-ask-and-the-answer">The Ask and the Answer</a>, no more power cuts to help but illness has covered for that.</p>
<p>Started watching <a href="https://www.imdb.com/title/tt33332385/">Widow’s Bay</a> on Yash’s recommendation as well as an <a href="https://aftermath.site/widows-bay-apple-tv-matthew-rhys/">Aftermath post</a> I had read a month ago. With all the downtime this week I was able to finish the first season quickly, albeit in Italian for 9 of 10 episodes because I am a dumbass. Genuinely could not tell you how I came to think the show is meant to be Italian but at least not understanding the language made me actually watch instead of it being second monitor content.</p>
<p>Watched <a href="https://www.imdb.com/title/tt26657236/">Backrooms</a> with Yash, <a href="https://fknil.pages.dev/">Fenil</a>, and <a href="https://wantguns.dev/">Gunwant</a>. The movie was <em>awesome</em>, I fully get why these new age directors are rocking the boat in the upper echelons of Hollywood. Make some non-horror movies now!!</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/backrooms-movie.DL0OUNbu_1KXvRO.avif" type="image/avif"><source srcset="/_astro/backrooms-movie.DL0OUNbu_Z2p8qt0.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/backrooms-movie.DL0OUNbu_Z2p8qt0.webp" alt="A tilted shot of a yellow backrooms hallway on a screen" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Found footage is one of my favorite narrative devices</figcaption> </figure>  </div>  
<h1 id="meta">Meta</h1>
<p>The site has had some minor changes this week. <a href="https://github.com/msfjarvis/msfjarvis.dev/commit/99325e9a151f220e59c80dc2af3de30f700d3bde">Share buttons have been removed</a> since they don’t get used, and all text on the home page is now about <a href="https://github.com/msfjarvis/msfjarvis.dev/commit/db047f21b6101184abacc8dfe8f6fae296d5fafd">1.3x larger</a>, since I was starting to feel very old trying to squint at the text. I also noticed a bunch of my received webmentions were being incorrectly cut off during parsing, and weren’t being rendered as proper HTML; this has been <a href="https://github.com/msfjarvis/msfjarvis.dev/commit/450ec0d14f2f46b4ce6c69b71e85358872233515">fixed retroactively</a> on all posts.</p>
<p>I wrote <a href="https://msfjarvis.dev/weeknotes/week-24-2026">last week</a> about being overwhelmed by my <a href="https://miniflux.app">Miniflux</a> RSS reader, and my attempts at clanking something to identify the problem. That was unsurprisingly useless, but I did find out some good quality of life changes to improve the scenario which have been documented in <a href="https://msfjarvis.dev/posts/taming-my-intimidating-rss-reader-queue/">this blog post</a> I wrote this week.</p>
<h1 id="projects">Projects</h1>
<p><a href="https://github.com/msfjarvis/compose-lobsters">Claw</a> had a minor change to add a smoke-tests JVM-only CLI that runs daily to validate the HTML parsing code, hopefully to catch any regressions early.</p>
<p><a href="https://github.com/android-password-store/kage">kage</a> received an external PR this week <a href="https://github.com/android-password-store/kage/pull/539">to improve memory usage</a> in the streaming API, which motivated me to catch up on some pending work there. Kotlin is <a href="https://github.com/android-password-store/kage/pull/540">upgraded to 2.4.0</a>, the upstream test vectors <a href="https://github.com/android-password-store/kage/pull/541">have been updated</a> and the bugs uncovered by that fixed, and a longstanding TODO to <a href="https://github.com/android-password-store/kage/pull/542">properly handle some invalid inputs</a> been resolved.</p>
<h2 id="-clank-clank-clank-">🤖 CLANK CLANK CLANK 🤖</h2>
<p>The <a href="https://github.com/rohitg00/agentmemory">agentmemory</a> experiment has concluded in <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/44e880d63bf897502f35425ac36e169e7f7ccff0">failure</a>. Clankers failed to find any meaningful information in the saved memories because they couldn’t figure out how to get them in a non-truncated form. I stole some <a href="https://pi.dev">Pi</a> extensions from <a href="https://lucumr.pocoo.org">Armin Ronacher</a>, adding <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/ec1802ee1b8334f8da5ac0790944896507a12363">some whimsy</a>, a <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/b524ccd460a7e94d2984a3639cbed8c901cf1531">session analyser</a>, and a <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/dee979282d5d67997741c627d0c78b349d8199d6">Q&amp;A widget</a>. I also added a <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/82e1980f2c7044fcbb8817d7a2391e27565ebec6">Markdown viewer overlay</a> to make it easier to review specs and plans inline.</p>
<h1 id="gaming">Gaming</h1>
<p><a href="https://store.steampowered.com/app/553850/HELLDIVERS_2/">Helldivers 2</a> had a big update this week with a ton of buffs across the board, had a great time playing the Illuminate faction with the new Supply FRV.</p>
<p>I played even more <a href="https://controlgame.com/">CONTROL</a> this week, and finally finished the Foundation DLC just before writing this weeknote. In celebration of this feat I pre-ordered CONTROL Resonant, something I typically don’t do. The mêlée hack and slash combat simply looks sick, and I am fully invested in the story.</p>
<h1 id="food">Food</h1>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/paper-dosa-the-filter-coffee.BKh2pfIA_2nmmHq.avif" type="image/avif"><source srcset="/_astro/paper-dosa-the-filter-coffee.BKh2pfIA_ZXJVhe.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/paper-dosa-the-filter-coffee.BKh2pfIA_ZXJVhe.webp" alt="A crisp paper dosa on a banana leaf with chutneys and sambar" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Been a while since I've been back at The Filter Coffee</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/pizza-toast.DdtlO_AW_1qE3q5.avif" type="image/avif"><source srcset="/_astro/pizza-toast.DdtlO_AW_Z2jjniQ.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/pizza-toast.DdtlO_AW_Z2jjniQ.webp" alt="A slice of pizza toast topped with chicken sausages and herbs" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The air fryer browns the sausages a little too much for my preference</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/egg-sandwich.B-m7RANL_Z292HTD.avif" type="image/avif"><source srcset="/_astro/egg-sandwich.B-m7RANL_Z1xEXU7.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/egg-sandwich.B-m7RANL_Z1xEXU7.webp" alt="Two toasted egg sandwiches on a steel plate with ketchup" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">A simple egg sandwich just hits some days</figcaption> </figure>  </div>   </div>
<p>For all the Toit breakfast images, head to <a href="https://yashgarg.dev/weekly-notes-25-2026/">Yash’s weeknote</a> who is much more diligent about these things than I ever will be.</p>
<p>Since I was by myself on Monday I made myself a pretty sloppy salad of corn, cucumbers, onions, and tomatoes all doused in some crumbled feta cheese and a Greek yogurt dressing.</p>
<p>On Tuesday the whole team at work went out to Meghna Foods for lunch since our PM <a href="https://www.linkedin.com/in/korinne-alpers">Korinne Alpers</a> was getting read to head back to the US and wanted to sample some great biryani. For dinner, we just made our regular okra.</p>
<p>Thursday was feeling like a sandwich kind of day, so I just made an egg sandwich at home during our morning standup. Turns out there’s a lot of downtime in a 10-person meeting :P</p>
<p>I had been craving pizza toasts this week, so I decided to make some on Friday morning, I kinda screwed up by accidentally ordering sausages instead of pepperonis, but it was delicious regardless.</p>
<p>This morning I joined Mayank, Yash, and <a href="https://github.com/swapnilmadavi">Swapnil</a> for some dosas at The Filter Coffee which were great.</p>]]></content:encoded>
    </item>
    <item>
      <title>Taming my intimidating RSS reader queue</title>
      <link>https://msfjarvis.dev/posts/taming-my-intimidating-rss-reader-queue/</link>
      <guid>https://msfjarvis.dev/posts/taming-my-intimidating-rss-reader-queue/</guid>
      <description>How I regained control of my reading habits</description>
      <pubDate>Thu, 18 Jun 2026 07:22:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I talked in <a href="https://msfjarvis.dev/weeknotes/week-24-2026/#:~:text=I%20checked%20my%20Miniflux%20unread%20count%20at%20the%20start%20of%20this%20week">last week’s weeknote</a> about how I was struggling with getting my Miniflux reader situation under control, and what I was attempting to identify the problem. Today, I came across <a href="https://michal.sapka.pl/2026/no-more-rss/">this blog post</a> from Michał Sapka (via <a href="https://mastodon.neilzone.co.uk/@neil/116769624379243503">this Fediverse post</a>) talking about the exact feeling, and choosing to entirely drop RSS and rely on manually checking up on bookmarks instead.</p>
<p>I think that was quite saddening to read, that the thing they had switched to as a way to get out of mindless algorithmic scrolling had ended up feeling like that itself. I’m personally not quite at the giving up stage, and over the weekend ended up making a bunch of changes within <a href="https://miniflux.app">Miniflux</a> that have significantly improved my experience already.</p>
<p>I’m going to preempt the weeknote and say this: the whole data driven analysis approach I was trying out last week was a complete waste of time and mental faculties. I had misdiagnosed my problem of not reading things as being tied to individual feeds which wasn’t <em>quite</em> accurate. “Bad” feeds do contribute to and exacerbate the problem, but ultimately the thing I found to be the real issue was <em>perception</em>.</p>
<hr>
<p>The feeds I subscribe to can broadly be categorized under the following labels</p>
<ol>
<li>News outlets: high volume, mix of intriguing and uninteresting content, leaning towards the latter on most days</li>
<li>Prolific individuals posting multiple times a week, sometimes multiple times a day. <a href="https://unsung.aresluna.org/">Unsung</a> is a perfect fit here.</li>
<li>Slower paced and longer length posters.</li>
<li><a href="https://www.wheresyoured.at/">Ed Zitron</a>. He writes long insightful newsletters at frightening pace, and I am scared to put a label on this madman.</li>
</ol>
<p>The overwhelming feeling for me comes from the first category. It crowds the sorted-by-new unread queue and makes it look like the whole pile is just uninteresting things so you either close the tab/app or mark all as read and move on. The second problem is that cheeky <em>unread</em> count at the top of the screen which keeps reminding you that you’re a failure for not keeping up with this free knowledge you asked for.</p>
<p>At least on Miniflux, these are both solvable problems, and it has helped me immensely in just the 3-4 days since I made these changes.</p>
<p>For the first: Exclude the high volume/low signal feeds from the global unread list. Head over to Feeds &gt; Click ‘Edit’ on the one you want &gt; Check ‘Hide entries in global unread list’</p>
<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/hide-entries-in-global-queue.2bRLQ3Aq_TuIFq.avif" type="image/avif"><source srcset="/_astro/hide-entries-in-global-queue.2bRLQ3Aq_Z8R7Fp.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/hide-entries-in-global-queue.2bRLQ3Aq_Z8R7Fp.webp" alt="Feed settings page with the option to hide entries in the global unread list checked." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="398" height="713"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Hiding high volume feeds does wonders for your unread page</figcaption> </figure>  </div>  
<p>You can also hide entire categories if your feeds are already categorized</p>
<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/hide-category-in-global-queue.DZXgWlo6_Z19cPua.avif" type="image/avif"><source srcset="/_astro/hide-category-in-global-queue.DZXgWlo6_1Iyy8s.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/hide-category-in-global-queue.DZXgWlo6_1Iyy8s.webp" alt="Category settings page with the option to hide entries in the global unread list checked." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="738" height="267"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Gone, just like that</figcaption> </figure>  </div>  
<p>This removes the feed/category’s entries from the homepage while still letting me browse the posts individually in the feeds/category page when I am ready for my daily dose of Biz &amp; IT.</p>
<p>Now the second problem: the ever looming unread counter haunting your dreams can simply be hidden with a bit of CSS. Click over to the ‘Settings’ option at the top and then paste snippet in the ‘Custom CSS’ section on that page, then click the ‘Update’ button.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="css"><code><span class="line"><span style="color:#B392F0">.unread-counter-wrapper</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#B392F0">#page-header-title</span><span style="color:#F97583"> &gt;</span><span style="color:#85E89D"> span</span><span style="color:#B392F0">:nth-child</span><span style="color:#E1E4E8">(</span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#79B8FF">  display</span><span style="color:#E1E4E8">: </span><span style="color:#79B8FF">none</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>With these changes you should find your default view of Miniflux a much more approachable space</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/unread-before-hiding.DP9Y6Ie__aP9GP.avif" type="image/avif"><source srcset="/_astro/unread-before-hiding.DP9Y6Ie__1zhCUm.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/unread-before-hiding.DP9Y6Ie__1zhCUm.webp" alt="Unread list in Miniflux before hiding one feed, showing more articles and a higher unread count." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="913" height="1340"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The unread feed before changes, a big looming number over your head and a lot of relatively uninteresting things taking up most of your screen</figcaption> </figure>  </div>  <div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/unread-sans-counts.CChK6Ro3_2cnxa4.avif" type="image/avif"><source srcset="/_astro/unread-sans-counts.CChK6Ro3_ZjaurY.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/unread-sans-counts.CChK6Ro3_ZjaurY.webp" alt="Unread list in Miniflux with article titles shown and unread count badges removed from the top bar." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="913" height="1340"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">A much cleaner experience with more focus on writing by individuals, and more attuned to what you'd like to naturally discover in a casual browsing</figcaption> </figure>  </div>   </div>
<p>No unread counter screaming at you, and your default view now has a significantly better signal-to-noise ratio. Hope this helps someone out in creating a more approachable feed reader experience for themselves!</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #24 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-24-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-24-2026/</guid>
      <description>Another lazy week dedicated entirely to gaming and programming</description>
      <pubDate>Sun, 14 Jun 2026 12:22:08 GMT</pubDate>
      <content:encoded><![CDATA[<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/angy-cat.C9v_lvFD_Srbhh.avif" type="image/avif"><source srcset="/_astro/angy-cat.C9v_lvFD_1Mwmdc.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/angy-cat.C9v_lvFD_1Mwmdc.webp" alt="Ginger cat resting on a mat beside a plaid cushion." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Saw this angry fuzzball after a long while</figcaption> </figure>  </div>  
<h1 id="life">Life</h1>
<p>I didn’t do much this week, owing to busy times at $DAYJOB and then having my semiannual burst of motivation which had me doing a lot of work on <a href="https://git.msfjarvis.dev/msfjarvis/compose-lobsters">Claw</a>.</p>
<p>I finished reading <a href="https://bookwyrm.social/book/192589/s/the-knife-of-never-letting-go">The Knife of Never Letting Go</a>, after which <a href="https://bookwyrm.social">Bookwyrm</a> helpfully reminded me that I was halfway through the year but only a quarter of the way through my reading goal of 24 books a year 😔✊</p>
<p>That said I have been reading more frequently (started the sequel: <a href="https://bookwyrm.social/book/1586797/s/the-ask-and-the-answer">The Ask and The Answer</a>) during the multiple hour-long power cuts throughout the week (thanks Indiranagar) so hopefully I’ll catch up at some point. Or cheat by reading short stories, I don’t know.</p>
<h1 id="meta">Meta</h1>
<p>This site has seen some minor tweaks: the “divider” between the header and the content should be slightly better aligned now, and we can now render Mermaid diagrams!</p>
<p>I’m working on a blog post about the stuff I’ve been doing on Claw where those will come in handy, stay tuned for that to be published soon™ <sub>psst you can read the current draft <a href="https://drafts.msfjarvis.dev/posts/rewriting-claw-html-parser-for-over-the-air-updates/">here</a></sub></p>
<h1 id="work">Work</h1>
<p>I’m finally free of my self-inflicted deadline jail, and went right back to my cosy janitorial duties of refactoring ugly code and accidentally stumbling upon latent bugs. All in all a great week at work!</p>
<h1 id="personal-projects">Personal Projects</h1>
<p>Energized by the aforementioned periodic burst of motivation, I managed to wrap up a handful of large changes to Claw.</p>
<ul>
<li>The search button now expands in-place rather than taking you to a separate full screen destination [<a href="https://github.com/msfjarvis/compose-lobsters/pull/1197">PR</a>]</li>
<li>All the HTML parsing logic is now delegated to a <a href="https://github.com/cashapp/zipline">Zipline</a>-based module, which means I can now do over-the-air updates of that code without a new app release. This will significantly improve the time-to-fix for site markup changes, and also ended up being a pretty interesting exercise in general. The draft post I linked above covers this work, and I hope to finish it off in a few days. <a href="https://github.com/msfjarvis/compose-lobsters/pull/1183">[PR]</a></li>
<li>I <a href="https://androiddev.social/@msfjarvis/116687510122725010">talked on the Fediverse</a> some time ago about how the ability to log into the app was causing my pre-login tagging feature to break down. This week I implemented a simple solution to it after being <a href="https://androiddev.social/@msfjarvis/116692105799028538">massively disappointed by Codex’s</a> attempt at it. [<a href="https://github.com/msfjarvis/compose-lobsters/pull/1203">PR</a>]</li>
</ul>
<p>I checked my <a href="https://miniflux.app">Miniflux</a> unread count at the start of this week: it had breached the 100 posts mark again, which made me wonder how that keeps happening because I look at it at least once a day, often more. To determine the issue, I sketched up a very basic “algorithm” involving gathering all entries for a specified period, calculating the time it takes for them to go from created → read and bucket those based on feed and tags. This gives me a very rough idea of what kinds of posts stay unread for long, which was good enough for what I had in mind. I let Codex loose on the problem, and obviously it was unable to follow a detailed spec and implementation plan, instead writing an empty shell that just had a bunch of no-op methods where the actual analysis would go 🙂</p>
<p>It took a lot of prodding to get it all to a proper spot, and I’m still tweaking it as I’m writing this weeknote because I like to suffer or something. You can find the slop code <a href="https://git.msfjarvis.dev/msfjarvis/acceptable-vibes/src/branch/main/miniflux-analyzer">here</a> if this seems interesting for any reason. My own instance is behind Cloudflare Access because I don’t trust even this fully static HTML by a clanker.</p>
<h1 id="gaming">Gaming</h1>
<p>This week was even more <a href="https://controlgame.com/">CONTROL</a> with <a href="https://kat.bio">Amogh</a> as we finished the base game and explored the DLC, getting my ass kicked by random side missions and continuing to unravel the mysteries of The Oldest House. I enjoyed this playthrough a <em>lot</em> more than any of the previous attempts by a huge margin, who knew companionship makes things more fun. Since Amogh also hasn’t done much of the DLC it’s been a fun time for the both of us to come across abominations beyond mortal comprehension.</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/control-warped-rooms.C4hP-eZG_1fUiEv.avif" type="image/avif"><source srcset="/_astro/control-warped-rooms.C4hP-eZG_Z7UDbY.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/control-warped-rooms.C4hP-eZG_Z7UDbY.webp" alt="A character stands in a warped office space with tilted rooms and floating papers." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="2560" height="1440"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">This game has some of the most mindbending visuals I've encountered</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/control-final-boss.Dk39bQSJ_1jGWWO.avif" type="image/avif"><source srcset="/_astro/control-final-boss.Dk39bQSJ_Z2m2GCA.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/control-final-boss.Dk39bQSJ_Z2m2GCA.webp" alt="A lone figure walks through a huge red-lit hallway toward a bright opening." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="2560" height="1440"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The final fight was honestly pretty easy but the atmosphere kept me on my toes the whole time</figcaption> </figure>  </div>   </div>
<h1 id="food">Food</h1>
<p><a href="https://msfjarvis.dev/weeknotes/week-23-2026/#food">As promised</a>, the pasta from last Sunday in all its glory (I ate a second serving on Monday so it counts as this week, hush). With Yash <a href="https://yashgarg.dev/weekly-notes-23-2026/">back from his Europe trip</a> we were back in the kitchen, and made a big pot of Shakshouka with 6 eggs instead of our usual 4. I was working from home on Friday, so I whipped together some plain ham and cheese sandwiches for breakfast and a simple garlic-based Greek yogurt dip, with a nice Coke Zero to round it all off. Sandwiches are awesome 😄</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/chicken-spinach-pasta.oXIogctX_Z4V0QJ.avif" type="image/avif"><source srcset="/_astro/chicken-spinach-pasta.oXIogctX_Z2vJfYv.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/chicken-spinach-pasta.oXIogctX_Z2vJfYv.webp" alt="Pot of creamy chicken and spinach pasta on a stovetop." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">My first time cooking spinach and I'm blown away by how quickly it wilts and how little you get out of it</figcaption> </figure>  </div>  <div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/shakshouka.D9QUIlIp_Z1G2GCB.avif" type="image/avif"><source srcset="/_astro/shakshouka.D9QUIlIp_1CGWSL.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/shakshouka.D9QUIlIp_1CGWSL.webp" alt="Bowl of shakshouka topped with herbs, served with toast on the side." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Protein maxxing</figcaption> </figure>  </div>  <div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/ham-and-cheese-sandwich.hu161OHk_sgXqJ.avif" type="image/avif"><source srcset="/_astro/ham-and-cheese-sandwich.hu161OHk_qcio4.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/ham-and-cheese-sandwich.hu161OHk_qcio4.webp" alt="Ham sandwich, yogurt, and a can of Coke on a metal plate at a desk." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Now that I think about it this kind of looks like a struggle meal...</figcaption> </figure>  </div>   </div>
<p>I was feeling the feels this morning for some reason so decided to treat myself to a nice breakfast at the nearby Cafe Muziri’s and also get some reading done under the pleasant sunlight. Eternally grateful to <a href="https://rohanarthur.com/">Rohan</a> for introducing me to this place, the vibe is always nice and cosy and the hash browns are to die for.</p>
<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/breakfast-at-cafe-muziris.CMNU_JK-_ZGsrTe.avif" type="image/avif"><source srcset="/_astro/breakfast-at-cafe-muziris.CMNU_JK-_2biVIo.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/breakfast-at-cafe-muziris.CMNU_JK-_2biVIo.webp" alt="Cafe breakfast plate with toast, eggs, vegetables, fruit, and coffee on a table." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The scrambled tofu in the center was just the right amount of rich</figcaption> </figure>  </div>  
<p>See you next week!</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #23 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-23-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-23-2026/</guid>
      <description>The most productive yet unhealthy week in a while</description>
      <pubDate>Sun, 07 Jun 2026 11:25:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://yashgarg.dev/">Yash</a> has been out this week for his trip to Europe, so I’ve been by myself and mostly filled that time with work and games.</p>
<h2 id="personal">Personal</h2>
<p>With all the free time this week I decided to sort out the discrepancies in my budgeting software and made some rather upsetting discoveries. The egregious one was me having flubbed my SIP changes last month, which meant I hadn’t actually ramped up my investments and was just blindly counting money as invested which was still sitting in my savings account. Thank you, ICICI Direct for a definitely-not-confusing UI.</p>
<p>The other one was mostly just annoying, but was caused by a very subtle interaction within Actual Budget that I wasn’t aware of. To make a long story short; when using enveloped budgeting and overspending a category, even if you add money back in the next month to zero it out, Actual will continue seeing it as overspent until you explicitly roll over the overspent month into the next one where you reimbursed it. I had a very confused 3 days after having to recreate an old account and its transactions.</p>
<p>I also watched the first season of <a href="https://en.wikipedia.org/wiki/Spider-Noir">Spider-Noir</a> this week, was surprised by how good it was. Definitely a little miffed that Amazon dumped it all in one go instead of letting the suspense build week over week but 🤷</p>
<h2 id="projects">Projects</h2>
<p>This website received some important updates:</p>
<ul>
<li>Webmentions are now <a href="https://git.msfjarvis.dev/msfjarvis/msfjarvis.dev/commit/bab9074b33d9a152a08ebc4509916477212d1e6f">grouped by type</a> to make the UI more compact</li>
<li>As part of that change, I discovered that I wasn’t capturing the full content of Fediverse posts being sent via <a href="https://brid.gy">brid.gy</a> in my Webmentions server, which has <a href="https://git.msfjarvis.dev/msfjarvis/acceptable-vibes/commit/b1110c8f115c0dd71d39c9a4d12c6c3becc41460">since been fixed</a>.</li>
<li>I discovered the <a href="https://www.brailleinstitute.org/freefont/">Atkinson Hyperlegible</a> font family last week, and finally <a href="https://git.msfjarvis.dev/msfjarvis/msfjarvis.dev/commit/792f1ae90afe73d43a18428a226951d1689f9152">switched over</a> the site to them. As part of the change I’ve also made the site text a bit bigger since I had started to feel like I needed to squint to read some of it.</li>
<li>The dark theme is now using a <a href="https://git.msfjarvis.dev/msfjarvis/msfjarvis.dev/commit/68b48529e20f3b5e05d1ac84f0ea272920162488">slightly lighter tone</a> to improve readability.</li>
</ul>
<p><a href="https://github.com/msfjarvis/compose-lobsters/">Claw</a> had a new release and some new features added later.</p>
<ul>
<li>The app now has the ability to upvote and reply to comments, provided you’re logged in.</li>
<li>The release also fixed a bug caused by the use of certificate pinning by foolish past me.</li>
<li>The comment UI has been tweaked to match the now-defunct app <a href="https://www.reddit.com/r/BoostForReddit/">Boost for Reddit</a>, which does a far better job of using mobile screen real estate than I ever did.</li>
<li>The search UI is now a proper “search bar” style experience instead of being a separate navigation destination.</li>
</ul>
<h2 id="work">Work</h2>
<p>Things at work were pretty chill. I went to one of the client meetings I <a href="https://msfjarvis.dev/weeknotes/week-22-2026/">mentioned last week</a>, and let <a href="https://github.com/mayankofficial999">Mayank</a> sub in for me on the other one. After my visit we ended up at Burma Burma for lunch, where I focused too much on the food to actually take any pictures. Unsurprisingly, the food was incredible.</p>
<p>Other than that I mostly focused on a bunch of refactoring work and dealing with customer reports.</p>
<h2 id="gaming">Gaming</h2>
<p>I spent a <em>lot</em> of time gaming this week since I was home alone, and it was raining every time I wanted to do anything else.</p>
<p>I found this short and emotionally moving pixel art game called <a href="https://store.steampowered.com/app/4515660/Control_Im_Not_Coming_Back/">Control, I’m Not Coming Home</a> and played through it in around 30-40 minutes which was on par with their 45-minute estimate. The game and its story are too beautiful to not be experienced, so I’m not going to explain anything here. The only thing I can share is that the game does not have a keybind to walk backwards, make of it what you will :)</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/control-im-not-coming-back-1.BllL-OZh_Z1Lp5R2.avif" type="image/avif"><source srcset="/_astro/control-im-not-coming-back-1.BllL-OZh_Z3kg2K.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/control-im-not-coming-back-1.BllL-OZh_Z3kg2K.webp" alt="Pixel art astronaut gloves reaching toward a floating person near a green planet." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="2560" height="1440"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">&nbsp;</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/control-im-not-coming-back-2.DM-uO5RA_Z180ilS.avif" type="image/avif"><source srcset="/_astro/control-im-not-coming-back-2.DM-uO5RA_A4wso.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/control-im-not-coming-back-2.DM-uO5RA_A4wso.webp" alt="Pixel art view of Earth in space with text about drifting farther away." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="2560" height="1440"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">&nbsp;</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/control-im-not-coming-back-3.CpUe6XBu_ZcUki1.avif" type="image/avif"><source srcset="/_astro/control-im-not-coming-back-3.CpUe6XBu_1v9uwg.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/control-im-not-coming-back-3.CpUe6XBu_1v9uwg.webp" alt="Bright pink and teal game scene with a small figure facing a dark tree." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="2560" height="1440"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">&nbsp;</figcaption> </figure>  </div>   </div>
<p>After seeing the gameplay trailer for <a href="https://store.steampowered.com/app/3669870/CONTROL_Resonant/">CONTROL Resonant</a> I decided to finally best my oldest foe, and beat the original game: <a href="https://store.steampowered.com/app/870780/CONTROL_Ultimate_Edition/">CONTROL</a>.</p>
<p>Some backstory: CONTROL was the first game I bought after being cyberbullied by my friends into getting a GPU, and I was so dogshit at it that I got stuck on a minor boss for <em>weeks</em> until I just gave up. I tried again a few years later, and got my ass beat again by the same boss, and the game was uninstalled in shame once more.</p>
<p>This week I decided to play it on a Discord stream with <a href="https://kat.bio">Amogh</a> watching alongside who could at least help me since he’s beaten the game already. Shockingly I was no longer absolute ass at the game, so things have been smooth sailing so far. Hopefully I’ll be done long before the release of Resonant 🤞</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/control.DsZO9y41_f0AjR.avif" type="image/avif"><source srcset="/_astro/control.DsZO9y41_Z2biSak.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/control.DsZO9y41_Z2biSak.webp" alt="Titular character Jesse Faden aims a gun at enemies known as The Mold in a dark green cave." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="2560" height="1440"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The Mold gives me the creeps</figcaption> </figure>  </div>  
<p>I also got mildly addicted to this spin off on Werewolf called <a href="https://store.steampowered.com/app/3803820/Demon_Bluff_Demo/">Demon Bluff</a>. It’s only available as a demo, but I’ve sunk 5-6 hours into it in less than 10 days, and it makes for a great screen share game in a Discord call.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/demon-bluff.Cq2ebJZO_ZHWMed.avif" type="image/avif"><source srcset="/_astro/demon-bluff.Cq2ebJZO_BfTPM.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/demon-bluff.Cq2ebJZO_BfTPM.webp" alt="Card-based puzzle game board with numbered characters and speech bubble clues." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="2560" height="1440"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Every character has its own unique abilities, and the game packs in a lot of depth through those even in this limited demo</figcaption> </figure>  </div>  
<h2 id="food">Food</h2>
<p>With Yash away I kept finding excuses to not cook since it was boring to just cook for myself. I finally decided today that it was enough dilly-dallying and finally made some air fried chicken nuggets with the chicken breast that had been sitting in the freezer for 5 days.</p>
<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/chicken-nuggets.E2_tNwhe_1yRf5o.avif" type="image/avif"><source srcset="/_astro/chicken-nuggets.E2_tNwhe_2sWqxv.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/chicken-nuggets.E2_tNwhe_2sWqxv.webp" alt="Breaded chicken nuggets on a steel plate with a bowl of creamy dipping sauce." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I had to do something to remind myself I can cook</figcaption> </figure>  </div>  
<p>I’m also finally cooking a proper dinner tonight, but that’ll be revealed next week :D</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #22 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-22-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-22-2026/</guid>
      <description>The first anniversary of IndieWebClub!</description>
      <pubDate>Sun, 31 May 2026 13:05:36 GMT</pubDate>
      <content:encoded><![CDATA[<p>Pretty “happening” week this time around! Let’s get right into it…</p>
<p>After a couple of weeks of frustrating silence, <a href="https://yashgarg.dev/">Yash</a> got <a href="https://yashgarg.dev/weekly-notes-22-2026/">his Visa</a> and will be heading out to Europe for a couple of weeks. Last time he was away for a similar trip I found myself somewhat struggling being by myself, but I’m much better prepared this time around so hopefully things will go smoothly back here. I got a sneak peek at his itinerary, and you should definitely be keeping an eye out for his next weeknote!</p>
<p>My colleague Tarush is leaving Cloudflare at the end of this week, so we had an impromptu farewell party followed by a trip to Karachi Bakery with the boys on our way back.</p>
<p>In some more fun news, it was the first anniversary of <a href="https://blr.indiewebclub.org">IndieWebClub</a> this week! There was a <a href="https://blr.indiewebclub.org/2026-se/">special session</a> on Saturday where we had a series of 13 lightning talks across the whole spectrum of the IndieWeb. Most talks were recorded, so hopefully there will be a public playlist on YouTube or PeerTube at some point 🤞</p>
<p>I took some awful pictures which I’ll attach here, but you can find the ones taken by smarter people in the <a href="https://photos.app.goo.gl/YNR9iQCzWV3kaD6X6">shared Google Photos album</a> and this <a href="https://lightroom.adobe.com/shares/836ab564df1e468c80ed1d3570dd66b5">Adobe Lightroom album</a>.</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/iwc_arpit.Dpwroror_ZCzpkI.avif" type="image/avif"><source srcset="/_astro/iwc_arpit.Dpwroror_1V3pu4.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/iwc_arpit.Dpwroror_1V3pu4.webp" alt="Arpit speaking at IndieWebClub, standing beside a projected slide showing his website." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Arpit talking about obsessively building a website with Claude but forgetting to actually use it</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/iwc_sagrika_1.D-IMsmVC_1GotFW.avif" type="image/avif"><source srcset="/_astro/iwc_sagrika_1.D-IMsmVC_Z9PdOw.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/iwc_sagrika_1.D-IMsmVC_Z9PdOw.webp" alt="Slide from Sagrika Padha’s talk about finding one’s writing voice, with notes on collecting sentences and writing in a continuous flow." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Sagrika's guide to finding your writing voice. Let the hate flow through you...</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/iwc_sagrika_2.w6K1nr2h_1AFgUT.avif" type="image/avif"><source srcset="/_astro/iwc_sagrika_2.w6K1nr2h_Zfyqzz.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/iwc_sagrika_2.w6K1nr2h_Zfyqzz.webp" alt="Sagrika Padha speaking into a microphone beside a slide about specific, emotional writing and developing a distinct voice." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The importance of honesty in writing feels very underappreciated these days</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/iwc_yash.DqHnR1sg_U8Kim.avif" type="image/avif"><source srcset="/_astro/iwc_yash.DqHnR1sg_1OdVeh.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/iwc_yash.DqHnR1sg_1OdVeh.webp" alt="Yash giving a talk titled “Owning Conversations on the Open Web” at IndieWebClub." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">That's my roommate!</figcaption> </figure>  </div>   </div>
<p>After the talks everyone got together to hang out on the terrace and there was cake! And also some very ominous clouds, forcing Yash and I to dip back home to avoid getting stuck in the rain.</p>
<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/post_iwc_rain.C0QCfE5o_1foNv8.avif" type="image/avif"><source srcset="/_astro/post_iwc_rain.C0QCfE5o_ZAOT0l.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/post_iwc_rain.C0QCfE5o_ZAOT0l.webp" alt="Very gray clouds overhead, some light gray buildings and the top of a very lush tree are visible towards the bottom of the image" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Anything for some rain in the AM instead of evenings...</figcaption> </figure>  </div>  
<h2 id="work-stuff">Work Stuff™</h2>
<p>Work has been somewhat chaotic this week as I chased self-imposed tight deadlines, but I was ultimately able to get everything that was within my control to the finish line. I did however fully exhaust myself due to compulsively working late nights, and by Friday I just needed to take an afternoon nap or risk falling asleep at my home desk. Definitely not the healthiest week I’ve had with respect to work but these should be very rare, since it was my own decision to take on just a <em>little</em> more than I was really capable of delivering 😅</p>
<p>Next week is going to involve visiting some clients which has the potential to be interesting, looking forward to the experience.</p>
<h2 id="personal-projects">Personal Projects</h2>
<p>Big week for the tens of users of my <a href="https://lobste.rs">lobsters</a> app <a href="https://msfjarvis.dev/g/compose-lobsters">Claw</a> as I had to make two releases in quick succession. The first release had been baking for nearly 2 months, and added the ability to log into the app to lay the groundwork for building interactive features in the future. I also switched the app over to not rely on the JSON API the site exposes due to a Ruby on Rails misfeature (according to the site admin) and instead just scrape the HTML itself. As my luck would have it, the markup changed literally 2 days after I made the release after having been perfectly stable for <em>weeks</em> since I made the initial swap, so I had to do an emergency hotfix. Grrr</p>
<p>I’ve continued hacking on the app, making a big range of changes to bring it up to parity with the website experience. In no particular order:</p>
<ul>
<li>Screenshot testing to make it easier to do iterative improvements</li>
<li>Changed the comments UI and UX to closely resemble the defunct Reddit client <em>Boost</em> — big thanks to Yash for that suggestion</li>
<li>(Unsuccessfully) <a href="https://github.com/msfjarvis/compose-lobsters/pull/1183">attempt</a> to move the parsing logic to a <a href="https://github.com/cashapp/zipline">Zipline</a>-backed module that would let me hot patch it without making users update the app</li>
<li>(In progress) add the ability for users to <a href="https://github.com/msfjarvis/compose-lobsters/pull/1184">reply to comments</a>. I paused work on it to write this weeknote!</li>
</ul>
<h2 id="clanker-things">Clanker things</h2>
<p>I talked <a href="https://msfjarvis.dev/weeknotes/week-21-2026">last week</a> about experimenting with <code>agentmemory</code> to remove the need for <code>AGENTS.md</code> files, so far that has yielded exactly 0 value. I haven’t had any luck in steering LLMs into autonomously persisting facts and decisions into memory, and <code>AGENTS.md</code> files themselves have been feeling useless as LLMs seem to mostly ignore them even in projects that do have the files. I did some tweaks to the prompt today to improve <code>agentmemory</code> usage, and will report back next week on how that went.</p>
<p>I also made one major-but-not-really change to the harness by <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/3045be0afc8bff4cea4fb148e39840bd360b9861">vendoring every single skill I use</a> into the Git repository itself rather than having them be symlinks to an untracked <code>~/.agents</code> directory. This makes the setup more portable and easier to make modifications to should I need to.</p>
<p>I don’t always agree with <a href="https://lucumr.pocoo.org">Armin Ronacher</a>, but I like to keep up with his excellent writing regardless. This week he posted a <a href="https://lucumr.pocoo.org/2026/5/26/clankers/">great article</a> about his persistent use of the word “Clankers” for what the AI pilled call <em>agents</em>, and I found myself nodding along to everything he was saying. I’m not going to do him the injustice of distilling his argument down to a couple of lines, so please go give it a read.</p>
<h2 id="the-obligatory-food-section">The obligatory food section</h2>
<p>This week had more Instagram recipes and me finally going back to my scoopable veggie salad. Expect more salads in coming weeks when I only have myself to feed :D</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/instagram_recipe_onions.D5d5_RCN_1OaouE.avif" type="image/avif"><source srcset="/_astro/instagram_recipe_onions.D5d5_RCN_1M5IrY.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/instagram_recipe_onions.D5d5_RCN_1M5IrY.webp" alt="Creamy onion gravy or masala cooking in a frying pan with a spoon and spatula." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Still can't believe this was just onions in a pan</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/instagram_recipe_chicken.SJCbKqKM_Z24XGGb.avif" type="image/avif"><source srcset="/_astro/instagram_recipe_chicken.SJCbKqKM_1VPzKU.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/instagram_recipe_chicken.SJCbKqKM_1VPzKU.webp" alt="Chicken pieces browning in a frying pan on a stovetop during cooking" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">For how few steps were involved in this recipe it was incredibly delicious, can't do better in &lt;15 minutes</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/veggie_salad.BeNa7W3q_ZSM3af.avif" type="image/avif"><source srcset="/_astro/veggie_salad.BeNa7W3q_ZipjaI.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/veggie_salad.BeNa7W3q_ZipjaI.webp" alt="A bowl of chopped vegetable salad with cucumber, tomato, onion, and sweet corn, held in one hand with a spoon inside." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">My beloved bowl of chopped veggies...</figcaption> </figure>  </div>   </div>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #21 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-21-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-21-2026/</guid>
      <description>Honestly not even sure how I feel about this week</description>
      <pubDate>Sun, 24 May 2026 12:58:09 GMT</pubDate>
      <content:encoded><![CDATA[<p>As I’m sitting down to write this weeknote and looking through <a href="https://msfjarvis.dev/notes/collating-entries-in-my-obsidian-journal-for-week-notes/">my outline</a>, I’m realizing that I remember basically nothing I am reading. I can’t remember the last time a week has felt this long, but hey at least it’s over and I have words to prove it happened.</p>
<p>This week I had decided to be in the office every single day instead of our usual 3, so obviously I got rewarded for it on day 1 itself with very heavy rains. Thankfully an expensive taxi saved my hide before the traffic jams really started to materialize, and I was very grateful to be back inside before it really started pouring.</p>
<video controls="" loading="lazy"><source src="https://media.msfjarvis.dev/rain_on_monday-2026-05-24.mp4" type="video/mp4"></video>
<p>This was admittedly a little scary, but I came back home to the news that <a href="https://yashgarg.dev">Yash</a> had finished printing the Rocktopus I had requested last week. Here it is, guarding my workstation at the office the next day.</p>
<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/the_rocktopus.9DfEGoKi_Z1usqS.avif" type="image/avif"><source srcset="/_astro/the_rocktopus.9DfEGoKi_Z1RJaWm.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/the_rocktopus.9DfEGoKi_Z1RJaWm.webp" alt="A 3D printed octopus with the head of Dwayne 'The Rock' Johnson, perched neatly on top of my monitor" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">He's perfect</figcaption> </figure>  </div>  
<p>A few weeks ago, Yash had come across a Reddit post from someone wanting to sell the mangoes they were growing, and we had decided to get some. They got delivered on Wednesday and goddamn were they huge. Unfortunately still raw and will take a few days to be ready to eat, but I feel it’ll be worth the wait.</p>
<div data-image-modal-root="" class="half" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/massive_mango.BT1tpmnj_ZWpDfW.avif" type="image/avif"><source srcset="/_astro/massive_mango.BT1tpmnj_2mk1gq.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/massive_mango.BT1tpmnj_2mk1gq.webp" alt="A large raw mango in Yash's hand, him barely managing to hold on to it because of how huge it is" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="960" height="1280"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">That's a thick boy</figcaption> </figure>  </div>  
<p><a href="https://github.com/swpanilmadavi">Swapnil</a> and I got stuck debugging something after a late work meeting, after wrapping it up Swapnil decided to start a group call with the lads. We did a solid half hour plus of just shooting the shit which was a nice way to wind down before bed. I had forgotten how much I love group calls with friends, it’s so hard to get people together these days. Damn you capitalism!</p>
<p>On Friday the entire RealtimeKit team got together for lunch at <a href="https://thepizzabakery.in/">Pizza Bakery</a>, where I was too focused on the pizza to actually take more than one picture. As usual, Yash <a href="https://yashgarg.dev/weekly-notes-21-2026/">has you covered</a> on that front.</p>
<p>I try to keep Sundays free so I can just exist for a day, no thoughts head empty. Unfortunately Mayank had other plans, so we went and watched The Mandalorian and Grogu today. I found the movie to be a fine spectacle, but it doesn’t really advance the wider world too much which seems to be my constant problem with Star Wars.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/the_mandalorian_and_grogu.BNOxHuqB_Z1tPy3J.avif" type="image/avif"><source srcset="/_astro/the_mandalorian_and_grogu.BNOxHuqB_1nUPyS.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/the_mandalorian_and_grogu.BNOxHuqB_1nUPyS.webp" alt="A frame from the movie The Mandalorian And Grogu" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4064" height="3056"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I am convinced Grogu being a puppet instead of CGI is the entire reason this character ever got traction</figcaption> </figure>  </div>  
<h2 id="reading">Reading</h2>
<p>It took me 2 months of <em>very</em> distracted reading, but I managed to finishing reading <a href="https://www.couldshouldmightdont.com">Could Should Might Don’t</a>. I think the book itself is great, but with so many distractions available I found myself struggling to read it regularly. According to the statistics on my Kobo I averaged 9 minutes per session on the book which is significantly lower than my usual of 15-20 minutes. My usual fare of Science Fiction works better here by having continual suspense drawing me in, so I started reading <a href="https://bookwyrm.social/user/msfjarvis/generatednote/11327057">The Knife of Never Letting Go</a> instead of going for more non-fiction books. I’ll see if I can piece together a book review for Could Should Might Don’t, and definitely see what I can change to make non-fiction stick better. I can’t have great books take me 2 months to go through 400 pages when I typically manage that in under 2 weeks.</p>
<h2 id="food">Food</h2>
<p>With a lot of busy evenings we didn’t get to cook a lot this week. Yash has finally started whipping out the Instagram reels, and we had a couple of successes. It was nice to break up our usual cycle of the same 4-5 dishes in rotation, I think we’ll keep pulling one or two items off his evergrowing list every week.</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/classic_chhole_rice.C4df4Q6D_ZH7ISX.avif" type="image/avif"><source srcset="/_astro/classic_chhole_rice.C4df4Q6D_22eWpO.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/classic_chhole_rice.C4df4Q6D_22eWpO.webp" alt="A bowl of chickpea curry and rice" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Another staple of this household, just can't beat the ease.</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/instagram_egg_rice_recipe.Cyfv1uXm_Z1Lvjf4.avif" type="image/avif"><source srcset="/_astro/instagram_egg_rice_recipe.Cyfv1uXm_16g5ny.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/instagram_egg_rice_recipe.Cyfv1uXm_16g5ny.webp" alt="A pan with rice that has been cooked in a bunch of spices to an almost biryani like appearance" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">This one's off Instagram and a bit of a sleeper hit, go pester Yash for a link</figcaption> </figure>  </div>   </div>
<h2 id="random-llm-experiments">Random LLM experiments</h2>
<p>Just wanted to have some place to dump info about what shenanigans I have been up to, so I’ll probably just make this an ongoing section.</p>
<p>I’ve written previously about <a href="https://msfjarvis.dev/weeknotes/week-19-2026/">switching to OpenAI Codex</a> and the associated GPT-5 laziness problems that I had to deal with. In the two weeks since, I’ve had multiple incidents of the extension I wrote either not firing when it should or misfiring when it shouldn’t so I decided to make it entirely too complicated. As I say <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/b243e6caf15ae2bfd648a65a2143429e9da1eda7">in the commit message</a>; what more RegEx can’t solve, more AI definitely can. Now instead of putting every message through an impenetrably complicated RegEx, the extension instead pipes it through an impenetrably complicated text classifier LLM. This approach also hasn’t been foolproof so far either, but iterating over the prompt (<a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/7347bf2b607ca71d8682e853f0db95844660422a">here</a> and <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/commit/59d79e9892ac6c04c9ee02a0e09daf2db300dc71">here</a>), has been helpful when paired with a proper test suite.</p>
<p><a href="https://agents.md/">AGENTS.md</a> files have been the de-facto way of giving LLMs persistent knowledge of codebases, but I find it somewhat annoying to have a bunch of them in my repositories. This week I’ve been looking at alternatives, and unfortunately mostly came up empty. Only one thing stood out to me, which was <a href="https://github.com/rohitg00/agentmemory">agentmemory</a>. So far I’ve only put “memories” into it, and I’ll see if during the next week LLMs actually use this or continue reading all files from scratch.</p>
<p>I’ve been going all in on <a href="https://agentskills.io">skills</a> as a way of steering LLMs, and found an adversarial reviewer skill from my colleague <a href="https://mk.gg/">Matt Kane</a> in the <a href="https://github.com/emdash-cms/emdash/blob/283bcf059e8c6889d14292e822953f13eaf6a6b0/skills/adversarial-reviewer/SKILL.md">EmDash CMS repo</a>. I had it run across the full diff of <a href="https://github.com/msfjarvis/compose-lobsters">Claw</a> since the last release, and it was able to identify <a href="https://git.msfjarvis.dev/msfjarvis/compose-lobsters/commit/1b6ae68e0d4a27d99709a09b7ef759f6bcda5b17">a couple of valid problems</a>. Obviously like any LLM usage, it also fully made up how to properly detect login sessions but that wasn’t too difficult to rectify early on.</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #20 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-20-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-20-2026/</guid>
      <description>Ignorance is bliss</description>
      <pubDate>Sun, 17 May 2026 12:10:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This week was relatively quiet all things considered, all the “excitement” was at work related to things I cannot share yet :)</p>
<p>We had two big meetings this week with leadership, first the R&amp;D All Hands with our engineering leadership and then celebrating the Bengaluru office’s 1st year with the people who helped make it happen. The All Hands was used to peddle some more apologia for the <a href="https://msfjarvis.dev/weeknotes/week-19-2026/#work">layoffs</a> last week and lay the path forward. Wasn’t super convincing, and I think I was better off not hearing any of it. The other call was less depressing, but I could’ve done without that one too. Not great for the brainworms :)</p>
<p>What I can share though, is that our Product Manager <a href="https://www.linkedin.com/in/korinne-alpers">Korinne Alpers</a> is visiting the India office, and we’ve been having a lot of productive discussions about the team and product’s future. I’ve come out of the week feeling much more hopeful about what’s in store for my team and I.</p>
<p>Other than that, I started the week off by discovering the <a href="https://www.genesispc.in/products/wobkey-rainy75-mechanical-keyboard?variant=43852369068085">keyboard</a> that I <a href="https://msfjarvis.dev/weeknotes/week-12-2026/">bought for my birthday</a> did have wireless support, hidden behind a switch whose position I can only describe as chaotic evil.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/the-betrayal-of-the-keyboard.BxcfvAPL_1IL8Qz.avif" type="image/avif"><source srcset="/_astro/the-betrayal-of-the-keyboard.BxcfvAPL_Z1Cla85.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/the-betrayal-of-the-keyboard.BxcfvAPL_Z1Cla85.webp" alt="A keyboard with a light blue aluminium frame, with its Caps Lock keycap removed. Below where the key would sit, next to the key switch there is a physical on/off switch that enables the keyboard's wireless features." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3278" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I was so mad at this</figcaption> </figure>  </div>  
<p>Surprisingly I received <a href="http://msfjarvis.dev/apsg/kage/pull/517">a pull request</a> for a project of mine: <a href="https://msfjarvis.dev/apsg/kage">kage</a>. It’s a Kotlin library that implements the <a href="https://c2sp.org/age">age</a> cryptography protocol, originally written for use in <a href="https://passwordstore.app">Android Password Store</a>. <em>Unsurprisingly</em>, the CI pipeline needed some fixing :) Glad to see people using this library though, given I don’t get to work on this too much.</p>
<p>On Friday, the boys got together for a movie in a very long time! <a href="https://yashgarg.dev">Yash</a>, <a href="https://github.com/mayankofficial999">Mayank</a>, <a href="https://github.com/swapnilmadavi">Swapnil</a> and I went to see <a href="https://en.wikipedia.org/wiki/Hokum_(film)">Hokum</a>. We all liked it a lot, the horror elements felt very well done without relying too much on the trite jumpscares that seem to be the only way horror gets done these days.</p>
<p>Having read the <a href="https://en.wikipedia.org/wiki/Silo_(series)">Silo books</a> this year I was excited to finally pick up the <a href="https://en.wikipedia.org/wiki/Silo_(TV_series)">Silo TV series</a> this week. I got through a couple of episodes, and I’m starting to realise why so many people say they felt checked out pretty quickly. Already knowing the story makes it easy to maintain some suspense for what’s coming but for someone who hasn’t read the books there seems to be next to nothing to hook them into it. I’m told Season 2 is even worse at this so maybe I won’t end up finishing the whole thing.</p>
<p>Not a lot of cooking this week, contributed in part by cooking bigger portions, being lazy, and just not being home on time. You’re still owed pictures though :)</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/good-old-dal-chawal.CGxAEXbC_dELMg.avif" type="image/avif"><source srcset="/_astro/good-old-dal-chawal.CGxAEXbC_Z279EGS.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/good-old-dal-chawal.CGxAEXbC_Z279EGS.webp" alt="A bowl of hearty lentils and white rice. The lentils were cooked with some sauted onions and tomatoes." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Some days a good old bowl of dal chawal just hits right</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/rajma-makes-a-return.0YMKhsLs_1oUa1a.avif" type="image/avif"><source srcset="/_astro/rajma-makes-a-return.0YMKhsLs_14d9F.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/rajma-makes-a-return.0YMKhsLs_14d9F.webp" alt="A spicy curry made with red kidney beans, sitting in a big saucepot. It clearly looks half empty, since we had already served ourselves." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The rajma is just a staple at this point</figcaption> </figure>  </div>   </div>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #19 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-19-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-19-2026/</guid>
      <description>Super busy week of work, fun, not-fun work and a lot in between.</description>
      <pubDate>Sun, 10 May 2026 13:12:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I’m dreading starting this weeknote because the cliff notes I auto-generate from my daily journal (explained <a href="https://msfjarvis.dev/notes/collating-entries-in-my-obsidian-journal-for-week-notes/">here</a>) are effectively two pages long. Either a lot more happened than I remember or I was feeling <em>very</em> strongly about a lot of inane things at the start of the week. Only one way to find out…</p>
<h1 id="meta">Meta</h1>
<p>The week started with an RSS feed mishap on this very site that caused mismatched GUIDs to spam all subscribers of the feed. I got rather fed up with trying to patch this all, so I took <a href="https://yashgarg.dev">Yash’s</a> help and copied the code for his RSS feeds. Massive thank you to him for that, I was at the end of my rope.</p>
<p>Since then, I’ve also added Atom and JSONFeed support, and brought back the per-category and per-tag feeds that had gone missing in the transition away from Hugo. I’ve manually checked as much as I could, please reach out if things are still out of place.</p>
<h1 id="life-and-people">Life and People</h1>
<p>This week I’ve been trying out going to the gym early in the morning instead of the evening. So far that has made a very positive impact in both my schedule for the day and how I feel at the gym itself, so I intend to stick with it. This also forces me to sleep at a better time which is always a win.</p>
<p>I did however roll my ankle <em>pretty</em> badly on my way back from the gym on Wednesday, which continued to be a problem well into Friday. Never felt older in my life.</p>
<p><a href="https://rohanarthur.com/">Rohan Arthur</a> took us out this week to <a href="https://rageroom.in/">Rage Room</a> which was shockingly close to our house in a rather secluded corner. Wrecking some shit on Friday evening (context in next section) was very cathartic and couldn’t have been any more well-timed, despite being a complete accident.</p>
<h1 id="work">Work</h1>
<p>Hoo boy, was this a week. I spent the first half of the week debugging what seemed like an endless stream of bugs while trying to cut a new release. One of these debugging sessions ran for a full 5 hours focused on a single bug, which I thought was gonna be my worst problem this week…</p>
<p>Lo and behold, <a href="https://blog.cloudflare.com/building-for-the-future">Cloudflare announces layoffs</a> for nearly 20% of the workforce bright and early on Friday. Nobody in my team was affected, but holy fuck did this completely annihilate the vibes in the office and company chat room. Seeing so many profile pictures just disappear overnight felt like walking through a graveyard. My heart goes out to everyone affected, they’re all incredibly talented people who did not deserve this treatment.</p>
<h1 id="personal-projects">Personal projects</h1>
<p>Talking about AI and LLMs after Cloudflare cited it as the reason for decimating the workforce and organisational structure feels rather tone-deaf, but I wanted to come out and be honest about where my own head is at. It’s a well accepted sentiment that continued usage of AI and LLMs causes some amount of skill atrophy and the formation of unhealthy dependence, and I never felt that more than I did this week.</p>
<p>So far I’ve been relying on unsustainably subsidised access to GitHub Copilot Pro via GitHub’s Open Source program for coding-related LLM usage, which recently received <a href="https://github.blog/news-insights/company-news/changes-to-github-copilot-individual-plans/">changes</a> that reduce usage limits significantly. While working on this very site I ran afoul of those limits pretty quickly in the first week, and then again in the second week. I had to go without access to an LLM for a few days, and I found myself being unreasonably anxious about not having this tool that I’ve lived without just fine for nearly a decade of programming.</p>
<p>I wasn’t liking how this made me feel, and the layoffs didn’t exactly help, but I did ultimately concede and admit to myself that like a rat to cheese, I have been trapped. For the first time, I had to take out my wallet and shell out the cash for a monthly subscription to OpenAI’s Codex. My sole reason for preferring it over Claude was that it lets you use third-party harnesses, so that I could use it with <a href="https://pi.dev">pi</a>. Pi is far from the most featureful coding agent you will come across, but it does that by choice and instead focuses on being incredibly extensible so you can build something exactly as per your needs. You can check out my customisations on top of pi in <a href="https://git.msfjarvis.dev/msfjarvis/my-pi">this repository</a>.</p>
<p>This transition was obviously not sunshine and rainbows, after all there’s a reason Claude Sonnet is my consistent workhorse choice at work over GPT models. GPT-5 has a widely known problem of not executing tasks and just kind of sitting around, which is something OpenClaw has worked around, so I <a href="https://git.msfjarvis.dev/msfjarvis/my-pi/src/commit/90b58cf63a7d3a411b0f84ba1d1fef0517a27214/pi/extensions/openai-codex-execution-bias.ts">implemented a Pi extension</a> to replicate those fixes. Since adding this, things have been much more reliable on longer sessions.</p>
<p>This week I’ve shipped changes in <a href="https://git.msfjarvis.dev/msfjarvis/compose-lobsters">Claw</a> to switch out the use of unsupported JSON endpoints on lobste.rs to scraping the actual HTML instead, which is hopefully more in line with the site admin’s preferences. I will reach out and verify before doing a wide rollout.</p>
<h1 id="food">Food</h1>
<p>Thankfully this week had rather quiet evenings so a lot of home cooked food was had, but obviously I was terrible at documenting it all. We made pulao one day, a high protein salad the next, and a beautifully executed shakshouka on Thursday. On Saturday we did burritos as per our usual and managed to not make too much of a mess.</p>
<p>On Friday we ate dinner at the Amicii Bar and Restaurant where I had a citrus-y mocktail and a chicken burger that was honestly kind of mid. On Sunday I went to Cafe Mizuri’s to try out their English breakfast platter, which was also very delicious and the portion size was just right. I followed it up with a Vietnamese Coffee as a treat to myself :)</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/boring-protein-shake.Cdq3tJp3_Zw3r07.avif" type="image/avif"><source srcset="/_astro/boring-protein-shake.Cdq3tJp3_Z1TTnQB.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/boring-protein-shake.Cdq3tJp3_Z1TTnQB.webp" alt="A lavender colored protein shake in a plastic shaker cup" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I finally had the totally original idea of blending frozen fruits into my protein shakes, total game changer.</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/usual-burrito-bowl.B-kt3nE7_Zn2PYV.avif" type="image/avif"><source srcset="/_astro/usual-burrito-bowl.B-kt3nE7_11oCdA.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/usual-burrito-bowl.B-kt3nE7_11oCdA.webp" alt="A simple burrito bowl of rice, beans, corn, salsa and jalapeno and some cilantro" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Nothing super exciting about this burrito bowl, but it keeps me fed and that's all that matters.</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/shakshuka.BFcAOjRH_Zp1J0.avif" type="image/avif"><source srcset="/_astro/shakshuka.BFcAOjRH_Z2vXkI9.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/shakshuka.BFcAOjRH_Z2vXkI9.webp" alt="A pan of bright red shakshouka and two eggs nestled into it. The pan is clearly half empty." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I only remembered to take a picture after Yash had served himself :'D</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/mocktail-at-amicii.DV3X7-RX_Z2dFBx5.avif" type="image/avif"><source srcset="/_astro/mocktail-at-amicii.DV3X7-RX_ZOe8jy.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/mocktail-at-amicii.DV3X7-RX_ZOe8jy.webp" alt="A pink/red drink in a short glass cup. There's a single basil leaf on top with the name Amicii branded onto it" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">This hit all the right citrus notes but was a little too sweet for my liking. The branding on the basil leaf was a nice touch!</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/english-breakfast-at-mizuri.Z-DseHkM_Z1TKnJb.avif" type="image/avif"><source srcset="/_astro/english-breakfast-at-mizuri.Z-DseHkM_UJ2p9.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/english-breakfast-at-mizuri.Z-DseHkM_UJ2p9.webp" alt="An English breakfast spread of sausages, hashbrows, salami, baked beans, toasted bread, baked tomato, an egg, some fruits and a cappuccino on the side." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The bread didn't feel like it belonged but everything else on that plate was perfectly done.</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/vietnamese-coffee-at-mizuri.jXlfH9cn_ZysGdH.avif" type="image/avif"><source srcset="/_astro/vietnamese-coffee-at-mizuri.jXlfH9cn_2h1IUC.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/vietnamese-coffee-at-mizuri.jXlfH9cn_2h1IUC.webp" alt="A tall glass of Vietnamese coffee with a thick layer of white chocolate at the bottom, and filled up the rest of the way with an Americano coffee" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Yep, that's white chocolate at the bottom.</figcaption> </figure>  </div>   </div>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #18 (2026)</title>
      <link>https://msfjarvis.dev/weeknotes/week-18-2026/</link>
      <guid>https://msfjarvis.dev/weeknotes/week-18-2026/</guid>
      <description>A brand new website, powered by Astro and Cloudflare! And a lot of other things...</description>
      <pubDate>Sat, 02 May 2026 21:02:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Welcome to the new website! It’s powered by <a href="https://astro.build">Astro</a> but still fully static to the highest extent possible, and performs impeccably on synthetic benchmarks. I was growing increasingly frustrated with both Hugo and Netlify, so this was a great opportunity to kill 2 birds with one stone and also move the site to Cloudflare.</p>
<p>I’ve painstakingly tried to keep all old links working even as I reorganized things, but please please let me know if you find anything out of place. There are still missing features due to technical and human limitations, which will be restored slowly over time:</p>
<ul>
<li>Automatically generated OpenGraph images</li>
<li>GitHub Discussions powered comments (Not sure I want to bring this back TBH)</li>
<li>~~The glorius post footer that let you share my writing to LinkedIn, boosting my street cred~~ Back without LinkedIn, RIP my king</li>
<li>~~Incoming Webmentions viewer~~</li>
<li>~~The build-time integration that I had on Netlify to queue pages for outgoing Webmentions~~ Cron is good enough for this.</li>
</ul>
<p>Onwards to the weeknotes!</p>
<h1 id="life-and-people">Life And People</h1>
<p>HOLY SHIT WE FINALLY HAD RAIN BLESSED BY THE GOD WHO RELENTED FOR A SINGLE DAY</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/thunderstorms.BtmnEozG_sABNv.avif" type="image/avif"><source srcset="/_astro/thunderstorms.BtmnEozG_Z1nD5GX.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/thunderstorms.BtmnEozG_Z1nD5GX.webp" alt="A thunderstorm outside my office building, the picture shows trees being buffeted by strong winds and heavy rainfall that's started to waterlog the area a bit" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Seeing this right outside the office building felt almost surreal after the incessant heat we've had for the past month</figcaption> </figure>  </div>  
<p>This however did cause our taxi back home to take a total of 2.5 hours from being booked to dropping us off, which usually takes 15 minutes to arrive and 15 more to reach our home.</p>
<p>Not a lot of socializing happened during the week until Saturday when <a href="https://yashgarg.dev">Yash</a> and I invited <a href="https://hachyderm.io/@vi_mi">Fenil</a> to IndieWebClub. I was a little reluctant since I didn’t feel that the discussion topics were going to be particularly interesting to me but I was pleasantly surprised and had a great time.</p>
<p>Fenil convinced us to do a watch party of <a href="https://www.imdb.com/title/tt1327035/">Dev D</a> at our apartment, we invited over Kunwar to join us and it ended up being quite the fun night.</p>
<p>Also since there’s no better place than this, my childhood friend Prajwal got a raise, promotion and bonus; go friendo 🎉</p>
<h1 id="work-and-career">Work And Career</h1>
<p>Things at work have been moving along quite well, after a long period of working exclusively on documentation and such we’re back to doing good old engineering and it’s nice to be back. I’ve been managing my increased responsibilities okay so far but it’s barely been 2 weeks so who’s to say things will stay the same.</p>
<p>Our last sprint was planned around culling our backlog and I’m pleased to report it has gone under 100 issues for the first time in maybe 2 or 3 years. A lot of important but low-key improvements have landed and we’re on track to ship in the coming week with a casual 60 or so tickets shuffled out of the backlog.</p>
<p>We had CI problems the whole week with macOS runners failing to boot up which was causing a lot of trouble in merging backlogs, so I had Claude write a bash script that just waits for jobs to fail and retries them until the pipeline is green. I jokingly spun out a side business of lending this service to my colleagues and made my first sale: ₹5 off Mayank to get his release PR through. If I still used LinkedIn this could make an excellent motivational post.</p>
<p>On Thursday Yash, Mayank and I decided to work together out of Cafe Grey again which was a fun and surprisingly productive time. Mayank was shocked we actually got so much shit done! We tried some new items off the menu this time and they were all pretty solid.</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/breakfast-sandwiches.CLEwKyIH_ZcLqlO.avif" type="image/avif"><source srcset="/_astro/breakfast-sandwiches.CLEwKyIH_Z1ACndj.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/breakfast-sandwiches.CLEwKyIH_Z1ACndj.webp" alt="" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">This sandwich was delicious, and surprisingly the thick cut chips they gave on this plate had more seasoning than the actual Peri Peri fries we ordered later</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/breakfast-french-toast.C0Qg-4Ml_1Gl7ut.avif" type="image/avif"><source srcset="/_astro/breakfast-french-toast.C0Qg-4Ml_2liAOB.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/breakfast-french-toast.C0Qg-4Ml_2liAOB.webp" alt="" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Mayank really wanted french toast and we all sampled the goods, looking at it almost convinced me that I could pull this off at home</figcaption> </figure>  </div>   </div>
<h1 id="games">Games</h1>
<p><a href="https://www.playstation.com/games/helldivers-2">Helldivers 2</a> dropped a new update this week with a new biome and enemy class, we were getting wiped pretty frequently on difficulty 10 where we usually play so we toned it down to 8 and have been having a much more acceptable time since. The new Bullet Storm stratagem is absolutely busted and will definitely catch a nerf in the next patch.</p>
<p><a href="https://store.steampowered.com/app/3509230/Gambonanza/">Gambonanza</a> also released this week which is a chess roguelike that looks super interesting, I’ve been meaning to give it a try but I already have enough roguelikes taking over my life at the moment…</p>
<h1 id="personal-projects-and-tech-stuff">Personal Projects And Tech Stuff</h1>
<p>Rebuilding this website occupied my whole week and I’m still not remotely done, which is just going to be a constant problem I suppose. No wonder people say rewrites don’t fix anything.</p>
<p>My Forgejo instance was being absolutely pummeled this whole week by spammers which was a problem because I rely on it constantly and can’t afford downtime. I tried using <a href="https://anubis.techaro.lol">Anubis</a> which didn’t make much of a dent and ended up just letting it be and accepting that Cloudflare will have to take care of it.</p>
<p>I finally caved and ordered a new set of earbuds since my existing ones have gotten very unreliable in terms of charging both buds at the same time and pairing properly. I went with the OnePlus Nord Buds 4 Pro which are a massive upgrade from my CMF Buds. The fast pairing feature actually works, they have a Spotify integration and the active noise cancelation blows the CMF Buds out of the water. I am very impressed!</p>
<h1 id="food">Food</h1>
<p>I am writing this weeknote after attempting to pressure cook a batch of chickpeas with no water in the cooker so please excuse my lack of enthusiasm.</p>
<p>On Tuesday I was too tired to cook and Yash too hungry, so we decided to not do any cooking. I still needed to eat though, so I channeled my inner <a href="https://www.youtube.com/@coachjohnnoel/">John Noel</a> and mixed together whatever I could find in the fridge, into a bowl and ripped open a pack of nachos. The beans were pre-boiled and from a packet, which I didn’t know was a thing that was sold here and was pleasantly surprised by.</p>
<p>We had gotten some potatoes after I complained about its lack in our diets so I decided to make a classic aloo bhujia for us, which was also when I discovered how intensely Bihari/Western UP this dish is because Yash had never heard of it. It was hard having to find words for something you’ve gone 27 years assuming people just knew. Came out nice though! Could’ve made Mom proud.</p>
<div class="gallery" style="--gallery-min-width: 250px; --gallery-gap: 1.5rem" data-astro-cid-ihllb3az=""> <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/bean-and-corn-nacho-dip.CFVQqX3Z_ZGQM4o.avif" type="image/avif"><source srcset="/_astro/bean-and-corn-nacho-dip.CFVQqX3Z_ZIVs74.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/bean-and-corn-nacho-dip.CFVQqX3Z_ZIVs74.webp" alt="A bowl of beans, corns, cheese and some salsa that was used as a nacho dip" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Bar none the coldest nacho dip I've ever eaten, next time I'm throwing some of this shit in a pan.</figcaption> </figure>  </div>  <div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/aloo-bhujia.BEHzPCwW_Z1ftEfM.avif" type="image/avif"><source srcset="/_astro/aloo-bhujia.BEHzPCwW_4J2Od.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/aloo-bhujia.BEHzPCwW_4J2Od.webp" alt="A glass bowl of the aloo bhujia sabzi" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Next time I'll have to remember to slice the onions instead of dicing them</figcaption> </figure>  </div>   </div>
<p>And that’s kind of all! I will continue fucking around with this site as I find more nooks and crannies where things are broken, but hopefully it all functions well enough for people watching at home. Thanks for reading!</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #17 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-17-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-17-2026/</guid>
      <description>Adapting to changes at work and doing a bunch of gaming</description>
      <pubDate>Sun, 26 Apr 2026 10:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>On Wednesday 22nd April this site was down for a few hours in the morning India time thanks to Netlify failing to renew its certificate. Why they thought it was not an operation that should be retried is beyond me, because when I logged into the dashboard and pressed the renew button it was able to provision a certificate in under 2 minutes. Netlify has been a good host for nearly 4 years now (logs say I signed up on Sep&nbsp;23,&nbsp;2022), but at this point I’ve had one too many problems to continue here. I’ve set up a Cloudflare Pages project with this site already, and will cut over the DNS in a week or so when I’ve ported the <a href="https://github.com/msfjarvis/netlify-webmentions-extension/">WebMentions Publisher</a> build plugin I wrote to a Cloudflare native solution. Onward to the actual week notes now!</p>
<h1 id="work">Work</h1>
<p>This was my first week assuming full responsibilities of my team as part of the <a href="https://msfjarvis.dev/posts/weeknotes-week-16-2026/#work">transition</a> I talked about last week. There’s still a bit of chaos and new processes we’re having to work up to but it has not been as disruptive as I had expected. I’ve continued to mostly focus on my engineering work and burn down our backlog as part of a bug bash sprint, inspired by a similar thing Lalit Maganti <a href="https://lalitm.com/fixits-are-good-for-the-soul/">wrote about</a> some time ago. LLMs have been quite useful here, both for identifying low hanging fruit to work through as well as actually implementing solutions. One of the tickets I worked on this week was so old that half the code it was meant to change is gone already.</p>
<p>On the flip side I had an absolutely abysmal time getting release PRs through our mandatory AI reviewer since it tries to understandably be extra paranoid for those. The issue is that these are PRs that go from one protected branch to another, so I have to raise <em>another</em> PR to fix its (often benign) complaints for multiple rounds. There’s a lot of complaints across the org about this specific issue so hopefully there’s some improvements on this front soon.</p>
<h1 id="personal-projects">Personal Projects</h1>
<p>I lightly maintain a small Telegram bot named <a href="https://github.com/msfjarvis/linkleaner">linkleaner</a> which rewrites URLs sent by users to have better link previews and optionally redirect to privacy preserving frontends, which saw a <a href="https://github.com/msfjarvis/linkleaner/issues/70">little more</a> <a href="https://github.com/msfjarvis/linkleaner/issues/72">external attention</a> than I’m used to so I took some time to work on addressing the reported issues and fighting the bitrotted deployment pipeline.</p>
<p>Work on <a href="https://git.msfjarvis.dev/msfjarvis/my-pi">my-pi</a> continued this week, I’ve continued adding features to the harness. First up was a robust permissions system for the Bash tool so I can control what commands LLMs can run. I took care to ensure I was learning from the ways OpenCode falls short here, so the extension takes care of handling pipelines, “proxy” commands like `sudo` and `exec`, and much more. I also ported <a href="https://github.com/obra/superpowers">superpowers</a>, and a very rough implementation of the <a href="https://github.com/anthropics/claude-code/tree/main/plugins/ralph-wiggum">Ralph Loop technique</a> that has so far been rather terrible.</p>
<h1 id="people">People</h1>
<p>On Friday I worked out of <a href="https://www.bangaloreeats.com/place/cafe-grey-by-dongle-and-co-indiranagar">Cafe Grey</a> with <a href="https://yashgarg.dev/">Yash</a> and <a href="https://github.com/anunaym14">Anunay</a>, which was pretty nice and let us beat the heat for a few hours. The food was decent, coffees were great and the pricing was not as eye watering as I’ve come to expect from Bengaluru cafes.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/nachos-at-cafe-grey.ChHVbYkf_mpFJf.avif" type="image/avif"><source srcset="/_astro/nachos-at-cafe-grey.ChHVbYkf_Z1XoKJT.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/nachos-at-cafe-grey.ChHVbYkf_Z1XoKJT.webp" alt="A white ceramic plate with a pile of nachos in the middle. There's melty white cheese all over the top." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">These nachos were tasty but also obviously fresh out of a bag.</figcaption> </figure>  </div>  
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/chicken-popcorn-at-cafe-grey.BKv3hfpH_1hqi93.avif" type="image/avif"><source srcset="/_astro/chicken-popcorn-at-cafe-grey.BKv3hfpH_Z24G0PB.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/chicken-popcorn-at-cafe-grey.BKv3hfpH_Z24G0PB.webp" alt="A plate of fried chicken popcorn with small bowls of mayonnaise and ketchup. There's a lemon half sitting on top of the chicken, which itself is on thin bed of lettuce." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Can't really complain about fried chicken.</figcaption> </figure>  </div>  
<p>On Saturday I went to watch <a href="https://www.imdb.com/title/tt11378946/">Michael</a>, with Yash, Anunay, <a href="https://github.com/mayankofficial999">Mayank</a> and Anunay’s friend Harshit. The movie was great, and I only learned after the movie that Michael Jackson was portrayed by his nephew Jaafar who is also making his debut with this film. One hell of a start to an acting career!</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/michael.3Um9NKhl_2wU8Ba.avif" type="image/avif"><source srcset="/_astro/michael.3Um9NKhl_6AE6X.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/michael.3Um9NKhl_6AE6X.webp" alt="A scene from the movie Michael showing him singing on stage in a black jacket." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The picture's a little lopsided because of letterboxing, my positioning and most importantly my ineptitude.</figcaption> </figure>  </div>  
<p>When the movie finished we noticed some ruckus erupting in the front of the theater and apparently it was knock-off mascots just dancing away?!?? We still don’t know what the hell was happening there.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/knock-off-mascots-at-michael.fmzRP3Vz_I5M4R.avif" type="image/avif"><source srcset="/_astro/knock-off-mascots-at-michael.fmzRP3Vz_2raBT9.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/knock-off-mascots-at-michael.fmzRP3Vz_2raBT9.webp" alt="A slightly blurry zoomed in photo of the front of a movie theater, showing mascots in costumes: Hello Kitty, Mickey Mouse and a nondescript mouse I cannot identify immediately." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">For how random this was, the mascots were having fun with it.</figcaption> </figure>  </div>  
<h1 id="gaming">Gaming</h1>
<p>Big week of games news for me, Hades 2 got a couple of updates adding post end-game content that I checked out and loved being terrible at.</p>
<p>Marvel Rivals got another limited time zombies event which I will be sinking a lot of hours into, like last time. It will also probably be a solo affair, again like last time. Had a chance to play some Rivals ranked with <a href="https://www.sasikanth.dev/">Sasikanth</a> which validated our theory that the correct time to win games are the weeknights when the proverbial electricians and plumbers are playing (people who are too employed to be great at the game, to put it more directly).</p>
<h1 id="food">Food</h1>
<p>We didn’t do anything particularly new this week with food. We had an early mishap with a veggie pulav that we both put too much spice into and also failed to cook that spice out so it was borderline inedible. Yash and I found ourselves quietly eating mangoes right after to soothe our burning palates.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/dangerous-pulav.C7iqMW9y_RaHMg.avif" type="image/avif"><source srcset="/_astro/dangerous-pulav.C7iqMW9y_1LfTfn.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/dangerous-pulav.C7iqMW9y_1LfTfn.webp" alt="A bowl containing pulav, an Indian rice dish that's typically pressure cooked rice with misc veggies and spices. Ours doesn't look particularly different from what you'd expect, but the flavor was abysmal. We fucked up big time here." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">This thing was genuinely close to being radioactive.</figcaption> </figure>  </div>  
<p>On Friday I only had myself to make dinner for so I went for a classic nacho dip/salad thing which was incredibly easy to put together and tasted delicious, validated by resident protein maxxer Yash.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/nacho-dip-at-home.C8w95JLZ_Z1WEoXc.avif" type="image/avif"><source srcset="/_astro/nacho-dip-at-home.C8w95JLZ_1mdyMe.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/nacho-dip-at-home.C8w95JLZ_1mdyMe.webp" alt="A bean nacho dip with red beans, cucumbers, onions, tomatoes, and corn; all wrapped up in a dressing made with a some jarred salsa, olive oil, apple cider vinegar, onion powder, and a bit of hot sauce." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I could eat this every day and not tire of it, veggies in a bowl is a pretty versatile canvas.</figcaption> </figure>  </div>  ]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #16 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-16-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-16-2026/</guid>
      <description>Big week for watching movies and being outdoors!</description>
      <pubDate>Sun, 19 Apr 2026 13:42:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This week was a bit of a roller coaster, lots of fun and not-so-fun things happened but I’ve come out of the other end feeling good and that’s all that really matters.</p>
<h1 id="absolute-cinema">Absolute Cinema</h1>
<p><a href="https://yashgarg.dev/">Yash</a> and I finally managed to grab tickets for an IMAX screening of <a href="https://www.imdb.com/title/tt12042730/">Project Hail Mary</a> for Monday morning. Taking the half day off work felt worthwhile for how long we’ve had to wait for this, and by sheer coincidence I ran into <a href="https://www.rahulgonsalves.com/">Rahul Gonsalves</a> at the theatre who also agreed that Monday mornings are the best show times :P</p>
<p>On Thursday I watched <a href="https://www.imdb.com/title/tt39139925/">Dhurandhar 2</a> with Yash and <a href="https://github.com/swapnilmadavi">Swapnil</a> which was honestly quite a letdown. The first movie was <em>obviously</em> military and BJP propaganda, but the second one in so in-your-face about it that it really dampened my enjoyment of it. Since they were shot together, my complaints about the <a href="https://msfjarvis.dev/posts/weeknotes-week-51-2025/#:~:text=I%20watched%20Dhurandhar">first movie’s CGI</a> also apply to this one. The shock factor of the incredibly gory movie constantly getting interrupted by god awful CGI that fully breaks your immersion makes for a very lame viewing experience.</p>
<h1 id="personal-stuff">Personal stuff</h1>
<p>Not a lot of things happened since I was so busy hopping from one thing to the next.</p>
<p>Anunay and Yash had bought me a mouse pad for my birthday that arrived this week, it’s super cute and pretty high quality.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/mouse-mat.BIZBXVAF_Z6cq1g.avif" type="image/avif"><source srcset="/_astro/mouse-mat.BIZBXVAF_2sqoNw.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/mouse-mat.BIZBXVAF_2sqoNw.webp" alt="A cyberpunk style image of a girl with white hair and a can of paint in her hand. She has a lot of temporary tattoos all over her visible arm and a cute black cat reaching for the paint can. My mouse is resting next to her face." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I fear the day I spill anything on this beauty.</figcaption> </figure>  </div>  
<p>Getting increasingly frustrated at <a href="https://opencode.ai">OpenCode</a> shipping showstopper bugs that go unfixed for weeks and months I decided to switch to using <a href="https://pi.dev">pi</a> as my coding agent of choice. For the uninitiated, it’s a very robust but minimal coding agent that is also at the heart of OpenClaw. It is designed to be extended by the very LLMs that the harness manages, and users are encouraged to build their own custom UIs, extensions, commands and what not on top of this. I have started my <a href="https://git.msfjarvis.dev/msfjarvis/my-pi">custom distribution of pi</a> where I’m mostly focusing to first implement features that I’ve come to rely on in OpenCode, then go from there as my imagination takes me.</p>
<h2 id="people">People</h2>
<p>The big highlight this week was RustIndia Conference on Saturday, which sadly overlapped with IndieWebClub so I had to ditch this week’s session :(</p>
<p>RustIndia was great, met a lot of people I’ve talked to online for years and generally had a pleasant time. Niko Matsakis’ <a href="https://nikomatsakis.github.io/rusts-big-moment-2026/">keynote</a> was very illuminating, he talked at length about Rust’s current limitations and the work they’re doing to address things not just with the language but at the ecosystem level itself. I’m excited to see how these things shake out over the next year or two!</p>
<p>There were a lot of quizzes and prizes going around at the stalls so I tried my hand at them but massively fumbled on the e6data booth manned by my friend <a href="https://github.com/feniljain">Fenil</a> by knowing enough Rust but being horrible at mathematics.</p>
<h1 id="work">Work</h1>
<p>As I mentioned <a href="https://msfjarvis.dev/posts/weeknotes-week-15-2026/">last week</a>, my manager <a href="https://rohanarthur.com/">Rohan Arthur</a> is leaving the company which has, to put it mildly, caused some problems. I’ve spent the week figuring things out with him, planning next steps, and talking to our director <a href="https://www.linkedin.com/in/willigeiger">Willi</a> about the future of my team. Obviously can’t divulge much here, but things are looking as good as they can be in the circumstances. Rohan prepared us well in making himself redundant in our day to day work but it’s still quite a sad transition to navigate.</p>
<h1 id="games">Games</h1>
<p>With how much I was out gaming ended up being rather sparse, but I managed to eke out a Platinum rank up in Marvel Rivals with <a href="https://github.com/akhilnarang">Akhil</a> right before the season ended. I also tried <a href="https://www.graveyardkeeper.com/">Graveyard Keeper</a> since it was free for a while on Steam but it doesn’t seem like something I’ll go back to. Genuinely just feels like a morbid Stardew Valley and I didn’t quite gel with that game either.</p>
<h1 id="food">Food</h1>
<h2 id="at-home">At home</h2>
<p><a href="https://github.com/anunaym14">Anunay</a> is still in Bengaluru which means we’ve been away more nights than not doing things (what can I say he’s just a fun loving guy), so we’ve only had one night where we really got to cook anything. <a href="https://yashgarg.dev/">Yash</a> pitched that we make a chickpea salad and so that’s what we did. During cooking this somehow became an exercise in how much protein one can fit into a single salad, and that’s how we have this abomination of chicken, chickpeas, cottage cheese, and broccoli.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/how-much-protein.CIGVpRmq_LiPll.avif" type="image/avif"><source srcset="/_astro/how-much-protein.CIGVpRmq_Z1JfcgH.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/how-much-protein.CIGVpRmq_Z1JfcgH.webp" alt="A chickpea salad bowl containing strips of air fried chicken, some impeccably blanched broccoli, cubes of cottage cheese, corn, onions, and tomatoes. All of this is coated in a greek yogurt based sauce which had some random spices added to it." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">How much protein could this bowl possibly contain?</figcaption> </figure>  </div>  
<h2 id="away-from-home">Away from home</h2>
<p>Got to try lots of fun stuff at great restaurants during the week! On Tuesday we were at Burma Burma:</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/burma-burma-lotus-biscoff-boba-tea.D8E5Z0op_Z1hxoQ5.avif" type="image/avif"><source srcset="/_astro/burma-burma-lotus-biscoff-boba-tea.D8E5Z0op_1OpXKh.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/burma-burma-lotus-biscoff-boba-tea.D8E5Z0op_1OpXKh.webp" alt="A glass of boba tea with a thick straw in it. The black boba is visible near the bottom, and the drink is a very faded cream color with some biscoff biscuit crumbs on top as a garnish." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Lotus Biscoff Boba Tea, still as good as ever.</figcaption> </figure>  </div>  
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/burma-burma-hummus-thing.a6n8e9TE_Z1jK93v.avif" type="image/avif"><source srcset="/_astro/burma-burma-hummus-thing.a6n8e9TE_Z2n80pl.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/burma-burma-hummus-thing.a6n8e9TE_Z2n80pl.webp" alt="A basket of pita bread next to a plate of a bowl of hummus that has some big chillies and a red colored seasoning for garnish." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Some hummus thing that I fully forgot the name of, all I know is that it was a banger.</figcaption> </figure>  </div>  
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/burma-burma-papaya-thing.LrRdIT52_1W8Pa4.avif" type="image/avif"><source srcset="/_astro/burma-burma-papaya-thing.LrRdIT52_SKXNe.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/burma-burma-papaya-thing.LrRdIT52_SKXNe.webp" alt="A plate of small poppers that have a pile of cured papaya shreds on top." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Another papaya thing that I can't remember the name of.</figcaption> </figure>  </div>  
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/burma-burma-khowsuey.BA6kiyXX_lrWvr.avif" type="image/avif"><source srcset="/_astro/burma-burma-khowsuey.BA6kiyXX_Z12nYl3.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/burma-burma-khowsuey.BA6kiyXX_Z12nYl3.webp" alt="A bowl of khowsuey next to a condiments platter. The gravy is a bright yellowish orange in color and has regular flour noodles in it." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">A bowl of khowsuey which again I have forgotten the exact name of.</figcaption> </figure>  </div>  
<p>On Wednesday we had an impromptu company dinner at Amicii, where we mostly chatted around and picked at some starters.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/amicii-pepperoni-pizza-thing.B33oFTTs_ZAb84d.avif" type="image/avif"><source srcset="/_astro/amicii-pepperoni-pizza-thing.B33oFTTs_17SGK4.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/amicii-pepperoni-pizza-thing.B33oFTTs_17SGK4.webp" alt="A regular pepperoni pizza with a ball of burrata cheese in the middle." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">This pizza was delicious.</figcaption> </figure>  </div>  ]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #15 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-15-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-15-2026/</guid>
      <description>A shockingly eventful week that I was not quite prepared for</description>
      <pubDate>Sun, 12 Apr 2026 15:59:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>It was my birthday this week! I was expecting the weekend to be a little chaotic but my whole week ended up being crazy.</p>
<h1 id="life-and-people">Life and People</h1>
<p><a href="https://github.com/feniljain">Fenil</a> and <a href="https://github.com/anunaym14">Anunay</a> dropped by the office one day to meet with their ex-colleagues (they were both at Dyte for a while). Had a small group hanging out, was a fun time catching up with everyone.</p>
<p><a href="https://yashgarg.dev/">Yash</a> and I met with Anunay a few more times throughout the week, had a work-from-cafe day on Friday. We were at <a href="https://www.concu.in/location/849622919">Concu</a>, which was pleasant other than an obnoxiously loud table that was annoying everyone for half an hour or so.</p>
<p>On Saturday (my birthday) we made the rather painful trip to Meco Kartopia for go karting. I wasn’t originally a big fan but managed to get the hang of it. The worst part of it is definitely the 1 to 1.5 hour long car trip to get to the place, and the lack of a cafeteria. I don’t think I’ll ever go back there, but am open to trying it again somewhere that is a more reasonable distance from me. We tried doing dinner at some of the upscale establishments like Burma Burma, but it being a weekend they were all fully occupied. I ended up just pushing through Yash’s suggestion of <a href="https://www.zomato.com/bangalore/element3-indiranagar-bangalore">Element 3</a>, a pure vegetarian place Yash and I have been to many times and everyone loved the food there. A lot of food related escapades were had, which as usual you can head on over to <a href="https://yashgarg.dev/weekly-notes-15-2026/">Yash’s weeknotes</a> to take a peek at.</p>
<h1 id="work">Work</h1>
<p>My manager <a href="https://rohanarthur.com/">Rohan Arthur</a> informed us that he’s leaving at the end of this month which was a bit of a shock. It’s a massive bummer to have such little time with one of the few leaders who have measurably improved me as an engineer and a person but I’ll focus on making the best of the time we have left. Still feels a bit unreal.</p>
<h1 id="games">Games</h1>
<p>I continued playing through bits and pieces of <a href="https://store.steampowered.com/app/3081840/Chrono_Gear_Warden_of_Time/">Chrono Gear</a> when I could find the time. <a href="https://github.com/akhilnarang">Akhil</a> pulled me back into the dungeons of <a href="https://www.marvelrivals.com/">Marvel Rivals</a> ranked queue where we managed to make a good run at our placement games. As of writing we’ve climbed up from Gold 3 to Gold 1, in touching distance of Platinum 3.</p>
<details><summary>Somewhat frustrating health stuff that you probably don’t care for</summary><h1 id="health">Health</h1><p>The doctor I was seeing for my flu had asked me to come in for another consultation to help me with my weight and sleeping, and I decided to go. Historically doctors have pinned that as a personal failing instead of actually giving any helpful advice so I was willing to give someone earnest a shot. After I got blood tests and a sleep study (<a href="https://www.sleepapnea.org/diagnosis/sleep-studies/">polysomnography</a>) done, the doctor told me to get a C-PAP machine that was gonna set me back by 80K INR and to start on the equivalent of Ozempic next month. Wasn’t really a fan of either options there so I took that prescription to another doctor who had a significantly better take on the issue, and gave me helpful tips on lifestyle changes I could make. I’ve started working on meal plans and such, and hope to start on their suggested plan in a week or so.</p><p>I also got diagnosed with an extremely severe Vitamin D deficiency and had to get a shot for it, that one hurt. Intramuscular injections are no joke!</p></details>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #14 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-14-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-14-2026/</guid>
      <description>Finally over my sickness!</description>
      <pubDate>Sun, 05 Apr 2026 18:06:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Barely managing to squeeze this in thanks to a super packed Sunday.</p>
<h1 id="sickness-update">Sickness Update</h1>
<p>I finally got through the rough week of dealing with the Flu, and used that down time to catch up with TV shows I was behind on. Finished watching <a href="https://www.imdb.com/title/tt27444205">Paradise</a>, and caught up with <a href="https://www.imdb.com/title/tt18923754">Daredevil: Born Again</a> and <a href="https://www.imdb.com/title/tt6741278/">Invincible</a>. I also started reading <a href="https://www.couldshouldmightdont.com/">Could Should Might Don’t</a>, my first non-fiction read in a long time. So far I’m not completely sold on the whole premise, but the topics the author chooses to break down are well presented.</p>
<h1 id="games">Games!</h1>
<p>I’ve also picked up some of my half-completed games while I was sick. I’m almost done beating <a href="https://store.steampowered.com/app/3081840/Chrono_Gear_Warden_of_Time/">Chrono Gear: Warden of Time</a>. I also resumed playing <a href="https://store.steampowered.com/app/2420110/Horizon_Forbidden_West_Complete_Edition/">Horizon Forbidden West</a> but the clunky combat pissed me off again. I love the story too much to let this detract from my enjoyment of it, so I opted to cheese my way using a trainer mod for a boss that was genuinely anti-fun. I’m not no-clipping around the place, just using invulnerability to get past a fight that feels more annoying than skill based. I’ve only used it once so far, maybe in the future I’ll get too lazy and start using it for all bosses, who’s to say. Either way I’d rather enjoy the game this way than not play it at all.</p>
<h1 id="writing">Writing</h1>
<p>In my Flu-stricken stupor I completely forgot about <a href="https://aprilcools.club">April Cools</a> this year, and had to scramble at the last minute to put a <a href="https://msfjarvis.dev/posts/i-guess-i-cook-now/">post together</a>. Admittedly, I tried to cheat my way out of the spirit of the thing by having an LLM convert my rough outline to a post but I found the writing to be so genuinely sickening to read that I threw it all away and just tanked the effort of writing through a fever to get this out.</p>
<h1 id="work-and-career">Work and Career</h1>
<p>I got the increment I was expecting at work, which was nice. I took most days off while recovering from the Flu so not a whole lot got done, but I helped grease things along wherever I could manage to spare some time to review the documentation changes we were working on.</p>
<h1 id="miscellaneous-tech-things">Miscellaneous Tech Things</h1>
<p>I’m planning to redo my room layout to open up some space in the center following Yash’s example, so I had to get a multi-plug extension for my extension so I could neatly tuck away all the power cables that go into my — extension-ception. I didn’t know <a href="https://www.amazon.in/Eveready-Everprotect-Multiplug-i4/dp/B0CQPDT6RT?nsdOptOutParam=true">this</a> was a thing!</p>
<p>I also worked a bit on <a href="https://github.com/msfjarvis/compose-lobsters">Claw</a>, my Android app for <a href="https://lobste.rs">Lobsters</a>. I removed a data migration path that had done its job in the past two releases, and added the very initial bits of login support in the app. Eventually the goal is for the app to be able to let you upvote and save posts directly to Lobsters instead of just the local store, and to maybe let you reply to comments/posts if I take on the daunting task of handling WYSIWYG Markdown input.</p>
<h1 id="people">People!</h1>
<p>The weekend was packed with a lot of socializing. <a href="https://blr.indiewebclub.org">IndieWebClub</a> on Saturday was a discussion focused session and we traded a lot of hot takes about editors, LLMs, writing tools and everything in between. It was a great time! Sadly I was not able to finish my writing draft within the allotted time, but I hope to have it out by next week.</p>
<p>On Sunday Yash and I met up with <a href="https://github.com/anunaym14">Anunay</a> and a friend of his for lunch at <a href="https://www.instagram.com/23rdstreet_pizza/">23rd Street Pizza</a> which was rather underwhelming. We meandered around a fair bit after that for some dessert and much needed steps, then got back to our place to chill for a bit. Aforementioned friend introduced us to <a href="https://boardgamegeek.com/boardgame/420087/flip-7">Flip 7</a>, which was a hilarious time with just 4 players and we fully lost track of time until it had been 3 hours. This game made me fully understand how gambling addictions come to be. The temptation to got for just one more flip is too damn high!</p>
<h1 id="the-food-section">The food section</h1>
<p>For this week I will choose to be lazy and delegate the responsibility for capturing our week’s food to my roommate <a href="https://yashgarg.dev/">Yash</a>, who is far more diligent about this than I can ever hope to be. Check out his <a href="https://yashgarg.dev/weekly-notes-14-2026/">week note</a>!</p>]]></content:encoded>
    </item>
    <item>
      <title>I guess I cook now</title>
      <link>https://msfjarvis.dev/posts/i-guess-i-cook-now/</link>
      <guid>https://msfjarvis.dev/posts/i-guess-i-cook-now/</guid>
      <description>Learning to cook for myself went from necessity to hobby almost overnight</description>
      <pubDate>Wed, 01 Apr 2026 09:26:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>This is a post for <a href="https://www.aprilcools.club/">April Cools</a>, encouraging people to break out of their standard fare and write something new.</p>
</blockquote>
<p>For the longest time I’ve not had a particularly great inclination for cooking. I had lived at or very close to my home for practically all my life, I was pampered (spoiled) and was never really encouraged to pick it up. That had to change when I moved to Bengaluru as part of being acqui-hired by <a href="https://cloudflare.com">Cloudflare</a> in early 2025.</p>
<p>I moved into a 2 bedroom apartment with old friend and current colleague <a href="https://yashgarg.dev/">Yash</a>, and we decided to get a home cook. Having been in a hotel for a month or so while house-hunting we had landed pretty squarely on not wanting to be dependent on food delivery. Through some neighbourly drama that is not worth unpacking, we had to fire our home cook pretty quickly and we found ourselves in the kitchen with a strong desire to not starve. We started with the basics: simple curries with chapattis. Chickpeas, black beans, paneer (cottage cheese), and potatoes were pretty common.</p>
<p>The progress was honestly pretty slow. We started with using precooked chapattis sold in the supermarket for a few weeks before shame really set in. We bought a food processor (neither of us had the temperament for kneading dough by hand), and started making our own chapattis at home. We made a very early experimental foray into a <a href="https://en.wikipedia.org/wiki/Taro">Taro root</a> (called “Arbi” in Hindi) based curry, a favorite of Yash’, which was an absolute nightmare to prepare and really killed the vibe on gastronomical experimentation for a while. Eventually we started doing pastas, fried rice, <a href="https://en.wikipedia.org/wiki/Pilaf">pulao</a>, even a chicken curry where we learned the hard way why you’re supposed to thaw chicken 6 hours in advance (it was edible).</p>
<p>My mother, who had kept me out of the kitchen all this time, found herself quite concerned for the well being of her pampered buffoon. For maybe 6 months I had to send a picture of my dinner every night so she could rest assured that I had a home cooked meal and wasn’t pounding McChickens three times a day. My photo gallery in the past year is probably 40% dinner plates by count. I’m not gonna pretend I took all of those to satisfy her and not to gloat to myself, but she definitely is the reason it started :)</p>
<p>I was already part of the air fryer cult back at home, so when my friend <a href="https://subhrajyoti.me/">Subhrajyoti</a> was moving out to the Netherlands I happily snapped up his air fryer. This unlocked a whole new dimension in the kitchen, and since then we haven’t gone a week without some type of frozen food in our freezer. The convenience is simply unbeatable, though Yash has not been a very big user of it so far. Some day he’ll see the light.</p>
<p>Yash and I had been big fans of a hummus bowl sold by a food delivery service that was our staple breakfast at office for a while, and when they discontinued it I decided to finally look into how much effort it was to just make hummus myself. Turns out, not much! Hummus is just cooked chickpeas blended into a paste with maybe 5 ingredients added to it. I used the Christmas &lt;&gt; New Year’s week to try it out and ended up with a genuinely delicious batch.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/day-1-hummus.D1OZBhss_2sluI4.avif" type="image/avif"><source srcset="/_astro/day-1-hummus.D1OZBhss_Z21sT6l.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/day-1-hummus.D1OZBhss_Z21sT6l.webp" alt="A food processor with blended chickpeas inside it, and a spatula sitting a little to the left that has clearly been inside the food processor. You can still see a bunch of solid chickpeas that are yet to be grounded down, floating in a slightly off white paste of ground chickpeas." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">This was a bit of an arm workout not gonna lie</figcaption> </figure>  </div>  
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/day-1-hummus-result.BspyosDK_ZvJgvB.avif" type="image/avif"><source srcset="/_astro/day-1-hummus-result.BspyosDK_2dCpNb.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/day-1-hummus-result.BspyosDK_2dCpNb.webp" alt="A small metal plate with some hummus that I've done a poor job of garnishing with some olive oil, red chilli powder and cumin powder." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">4/10 plating but 8/10 flavour, I'll take it.</figcaption> </figure>  </div>  
<p>The success of this experiment made me try this 3 more times to increasing levels of success, and also make my own falafels a couple of times. Still haven’t nailed that one, but got very close the last time!</p>
<p>Yash also got us into making burritos ourselves, a repeat of the story with hummus but more driven by California Burrito being expensive rather than it disappearing altogether. It makes for something we can prep easily, lasts a couple of days, is quite healthy and very filling. Some highlights from the first burrito misadventures, before I resigned to just doing burrito bowls and skipping the embarrassment of not being able to wrap a tortilla very well.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/the-birth-of-a-burrito.D3510n2H_1QBM7M.avif" type="image/avif"><source srcset="/_astro/the-birth-of-a-burrito.D3510n2H_2vzgrU.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/the-birth-of-a-burrito.D3510n2H_2vzgrU.webp" alt="Two tortillas on a metal plate, overlapping around 30% with a big pile of fillings on top. The internet told me this is the right way to make a burrito with two wraps." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The internet told me this is how you make a burrito with two wraps.</figcaption> </figure>  </div>  
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/burrito-pan.Ck-DSyDc_1cbGWc.avif" type="image/avif"><source srcset="/_astro/burrito-pan.Ck-DSyDc_2wpp2c.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/burrito-pan.Ck-DSyDc_2wpp2c.webp" alt="A burrito being lightly grilled on a pan. Shocking it has not split open its guts all over the place." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">One of the rare few burritos that stayed in one piece when we grilled them.</figcaption> </figure>  </div>  
<p>I bought a veggie chopper that lets me just make big piles of nutritious salads, topped with some air fried chicken sausages or whatever and just have an exceptionally filling dinner in under 20 minutes. Completely game changer for the days when I’m feeling lazy, has helped immensely with my struggle to lose weight.</p>
<hr>
<p>When I started going to the <a href="https://blr.indiewebclub.org/">IndieWebClub</a> events late last year, I picked up the habit of publishing <a href="https://msfjarvis.dev/categories/weeknotes/">weeknotes</a>. These have gradually started featuring food more prominently as I’ve gotten comfortable with simply trying things in the kitchen. Being able to navigate the kitchen with confidence has given me so much freedom that I previously thought to be out of my reach. Excited to see what other cuisines I’ll pick up this year!</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #13 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-13-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-13-2026/</guid>
      <description>A Flu is no joke...</description>
      <pubDate>Sun, 29 Mar 2026 14:42:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This week has been a bit of a roller coaster, was having a pretty normal week then got hit with a <em>nasty</em> Flu to cap it off in the worst way possible.</p>
<h3 id="boring-tech-stuff">Boring tech stuff</h3>
<p>I’ve been trying out <a href="https://github.com/obra/superpowers">superpowers</a>, which takes gaslighting-via-markdown (otherwise known as Agent Skills) to a logical extreme, forcing LLMs to behave like a very disciplined senior engineer.</p>
<p>I used this to change a longstanding misfeature in Claw that caused it to mark comments as unread when you first view a post, which is quite redundant. superpowers spent a solid hour on this, during which I also manually reviewed a spec and an implementation plan, but the <a href="https://github.com/msfjarvis/compose-lobsters/commit/83db9bb8a8f7f1ae7dc3df305ab599f15fa7d512">result</a> is quite exhaustive and has robust test coverage which I definitely wouldn’t have bothered with.</p>
<p>I also let it loose on our work codebase by feeding it a list of prior issues we had faced in a particularly brittle component and had it refactor the code to follow the popular <a href="https://testing.googleblog.com/2025/10/simplify-your-code-functional-core.html">“functional core, imperative shell”</a> pattern and then unit test the core to prevent regressions along the bug categories we’ve been chasing for quite a while. That refactor ran even longer, but ultimately the result was again rather impressive. LLMs are (for the most part) really good at fixing tests, and superpowers forces the LLM to follow a red-green-refactor cycle for all changes which means it’s always trying to fix tests.</p>
<p>I finally finished running the full gauntlet of <a href="https://webmention.rocks">webmention.rocks</a> tests against my <a href="https://git.msfjarvis.dev/msfjarvis/acceptable-vibes/src/branch/main/webmentions-server">WebMentions server</a>. It exposed a few gaps in the implementation but I’m at 100% compliance now, the only remaining issue is now with my site not returning HTTP 410 for deleted posts because I don’t really know how to make it happen.</p>
<h3 id="boring-not-tech-stuff">Boring not-tech stuff</h3>
<p>I finished reading <a href="https://bookwyrm.social/book/102060/s/project-hail-mary">Project Hail Mary</a>, super good read. I hadn’t realized Andy Weir also wrote The Martian! The book was great, the movie’s promised to be even better but good lord has it been hard to find a way to watch it with the lack of IMAX screens.</p>
<p>The keyboard I had ordered last week arrived, completing the new set :)</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/keyboard-and-mouse.DOzaS--P_1MeuC6.avif" type="image/avif"><source srcset="/_astro/keyboard-and-mouse.DOzaS--P_Z1Sv9Xj.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/keyboard-and-mouse.DOzaS--P_Z1Sv9Xj.webp" alt="The new keyboard and mouse on top of a wooden particle board desk. The keyboard has an aluminum frame coated in blue, with a mix of blue, purple and white keys with white being the most common one. The mouse rests on a cheap RedGear mousepad." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">The white mouse honestly works better than the lavender</figcaption> </figure>  </div>  
<p>I hadn’t spotted our Metro station cat in a while, so it was nice to come across it again.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/orange-loaf.CSe5CmjY_ctdw5.avif" type="image/avif"><source srcset="/_astro/orange-loaf.CSe5CmjY_1wGUB5.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/orange-loaf.CSe5CmjY_1wGUB5.webp" alt="A ginger cat loafing in the middle of two bar stools." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Angy loaf</figcaption> </figure>  </div>  
<h3 id="health-woes">Health woes</h3>
<p>I caught a fever on Thursday at the office which was starting to feel concerning right away, and by night had gotten bad enough that I had to arrange to see a doctor in the morning. Got diagnosed with a Flu and got a big dose of medicines to work through the weekend and see the doctor again on Monday. Was not expecting the throat problems, even my damn uvula had swollen and I could barely drink or breath. Super rough 3 days so far, the fever’s settled down now but I still have a very sore mouth and have trouble eating and swallowing. <em>Pain</em></p>
<h3 id="and-finally-the-food">And finally, the food</h3>
<p>Before I got absolutely mauled by the Flu I did do some cooking, which mostly was burrito prep that lasted us two days.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/burrito-bowl-v1.DmUyeqDC_1qUNda.avif" type="image/avif"><source srcset="/_astro/burrito-bowl-v1.DmUyeqDC_2l0YFh.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/burrito-bowl-v1.DmUyeqDC_2l0YFh.webp" alt="A typical burrito bowl with rice, corn, lettuce, beans, diced jalapenos and some condiments." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">I feel burrito bowls are kind of underrated</figcaption> </figure>  </div>  
<p>I had some chicken breasts sitting in the freezer that I wanted to finish off, so I used some of the rice from the large batch we made for the burrito bowl meal prep and made a basic tomato and cream curry for the chicken which came out pretty delicious for being basically 20 minutes of work</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/chicken-curry.D1dPL1k4_2ivlh.avif" type="image/avif"><source srcset="/_astro/chicken-curry.D1dPL1k4_Z1NVcac.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/chicken-curry.D1dPL1k4_Z1NVcac.webp" alt="A plate of two chicken breasts and some rice with gravy over it. The gravy is reddish-orange in color due to the tomatoes, turmeric and heavy cream coming together. There are also bits of onion visible in the gravy." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Scrumptious and filling. This cooking shit is easy :P</figcaption> </figure>  </div>  
<hr>
<p>Hopefully the next weeknote will find me healthier :(</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #12 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-12-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-12-2026/</guid>
      <description>Movies, food, rain, and an early birthday present to myself!</description>
      <pubDate>Sun, 22 Mar 2026 10:32:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2 id="life">Life</h2>
<p>As I mentioned <a href="https://msfjarvis.dev/posts/weeknotes-week-11-2026/">last week</a>, I watched <a href="https://www.imdb.com/title/tt27564844/">Iron Lung</a> on Monday. Only the premium cinemas were running shows so I had to buy an expensive ticket at PVR Director’s Cut, and got to watch it in a reclining chair that was basically a bed. Loved the movie, Markiplier did great work as both director and actor. Hopefully we can get a couple more of these authentic video game recreations from individual creators before the suits at Hollywood figure out how to ruin them even more.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/iron-lung.CbQn8qrl_2bxh15.avif" type="image/avif"><source srcset="/_astro/iron-lung.CbQn8qrl_Zk11X4.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/iron-lung.CbQn8qrl_Zk11X4.webp" alt="A scene from the movie Iron Lung, showing Markiplier inside a submarine. The subtitle reads &quot;I'm seeing some voltage irregularities&quot;." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Iron Lung was a great watch</figcaption> </figure>  </div>  
<p>I was back in the office this week, work was mostly CI and suffering. On Wednesday I got caught out in the hailstorm on my way back from the office. Thankfully I didn’t catch anything from that, but I did have to figure out how to wash my shoes which was surprisingly easy — thanks Skechers.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/rain-and-hail.dscc5DRz_ZI9pIG.avif" type="image/avif"><source srcset="/_astro/rain-and-hail.dscc5DRz_2uN0yL.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/rain-and-hail.dscc5DRz_2uN0yL.webp" alt="A short video of rain and hail falling on a slightly waterlogged asphalt road. A red Maruti Suzuki Zen car is visible in the top left corner, with a partial number plate." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">It was 30 degree Celsius at noon, and by 6 PM I was getting pelted by hail!</figcaption> </figure>  </div>  
<h2 id="toys">Toys</h2>
<p>I had been saving up to gift myself a new keyboard and mouse for my birthday, and ended up making it an early gift by placing the orders this week. I went with the <a href="https://www.genesispc.in/products/wobkey-rainy75-mechanical-keyboard?variant=43852369068085">Wobkey Rainey75</a> keyboard and a <a href="https://meckeys.com/shop/mouse/vxe-r1-series/?attribute_pa_variations=r1-pro-max&amp;attribute_pa_colour-style=white">VXE R1 Pro Max</a> mouse. The mouse arrived today, surprisingly in white rather than the purple/lavender I thought I had chosen. Clearly I made a mistake during the order, but I’m quite happy with it regardless. It’s been a long time since I had a wireless mouse and it’s such a massive difference from my wired <a href="https://www.razer.com/gaming-mice/razer-viper-mini">Razer Viper Mini</a>. Also nice to have a working scroll wheel again :’)</p>
<p>I also bought myself a microphone arm so I could get a bit more desk space, and struggled through a few bouts of stupidity before realizing that I needed an additional screw to plug my Blue Yeti into the standard 3/8 inch mounts that feature in the microphone arms. I ordered said screw from Amazon, set it all up today and when I went to put the old desk stand in the box I realized that the screw actually came with the microphone and I am just a one-of-a-kind dumbass.</p>
<p>I had been bracing for pricing changes for my Netcup VPS, and it <a href="https://www.netcup.com/en/priceadjustment">finally arrived</a> this week. I run a pretty small ARM VPS at Netcup so my costs are only going from 5.84 EUR to 6.92 EUR, a hit I can take for the time being. Really unfortunate that the unprofitable companies propping up the AI bubble have so much access to free capital that they’re destabilizing the whole industry.</p>
<h2 id="books">Books</h2>
<p>I installed <a href="https://github.com/janeczku/calibre-web/">Calibre-Web</a> last week, and spent most of this week doing manual housekeeping on the 40 or so books I own. Cleaning up metadata, syncing read states, setting up shelves — a lot of frankly pointless work that will hopefully make life easier in the long run. At IndieWebClub I tried to give <a href="https://tanvibhakta.in">Tanvi</a> her own account on my instance, but hadn’t realized that we’d have to share the library. I’ve set up a shelf for my books so we can hopefully rely on that to work around the problem.</p>
<p>With my reading goal quite lagging, I finally finished off <a href="https://bookwyrm.social/book/235804/s/the-wizard-hunters-the-fall-of-ile-rien-book-1">The Wizard Hunters</a> that I started <a href="https://bookwyrm.social/user/msfjarvis/generatednote/9824676">reading on February 6</a>. I usually get through a book in 2 to 3 weeks, but this was slow both because it was technically two books in one and hence around 700 pages in length, and because I was just not reading as often the past few weeks. I want to read <a href="https://bookwyrm.social/book/102060/s/project-hail-mary">Project Hail Mary</a> before I watch the movie, so hopefully reading this in the next week will put me back on track for my goal of reading 24 books a year.</p>
<h2 id="writing">Writing</h2>
<p>At IndieWebClub this week I finished off and published the post about securing <a href="https://msfjarvis.dev/posts/setting-up-forward-auth-with-caddy-and-pocket-id/">Calibre-Web behind Caddy and Pocket ID</a>.</p>
<h2 id="and-finally-the-food">And finally, the food</h2>
<p>As I promised last week, with the missing Cloudflare R2 integration in my CMS no longer being an excuse for not showing off the food I keep talking about, here is some of the food I made this week.</p>
<p>I had some chicken breast lying around in the freezer and an overwhelming desire to consume it, so I threw together a pretty simple pasta sauce using tomato and cream, cooked the chicken in it and then tossed it with pasta and some spices. I used the air fryer to quickly toast some sourdough bread I had and that was dinner and the next day’s breakfast.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/chicken-pasta.Cw6z6eI6_1RLSYX.avif" type="image/avif"><source srcset="/_astro/chicken-pasta.Cw6z6eI6_1xbtu.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/chicken-pasta.Cw6z6eI6_1xbtu.webp" alt="A bowl of Fusilli pasta in a creamy and tomato rich sauce with some chicken pieces and a couple of slices of sourdough bread poking out from it." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="4080" height="3072"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Behold, the pasta.</figcaption> </figure>  </div>  
<p>Another night I ordered some nachos with my groceries so I put together a simple salad to go with the nachos. Apparently this is called <a href="https://www.spendwithpennies.com/cowboy-caviar/">“cowboy caviar”</a> in the US of A. I made this on a whim so I didn’t have kidney beans, but I did get some feta cheese and it came out very nice. Bright flavors, super satiating, lots of fiber. What’s not to like?</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/cowboy-caviar.DfEIbnCT_Z1tPeyR.avif" type="image/avif"><source srcset="/_astro/cowboy-caviar.DfEIbnCT_1J7bIA.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/cowboy-caviar.DfEIbnCT_1J7bIA.webp" alt="The cowboy caviar salad containing diced onions, cucumbers, cherry tomatoes, corn, feta cheese and a simple sauce made with olive oil, apple cider vinegar, lemon juice and onion powder." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Breakfast of champions or something I don't know.</figcaption> </figure>  </div>  
<p><a href="https://yashgarg.dev/">Yash</a> was back home on Saturday so we went back to our classic rajma (kidney beans) gravy which I supplemented with a side of raita.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/rajma-and-raita.20X7Zp9P_1o6E1Q.avif" type="image/avif"><source srcset="/_astro/rajma-and-raita.20X7Zp9P_2ibPtX.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/rajma-and-raita.20X7Zp9P_2ibPtX.webp" alt="A plate with a bowl of raita, a bowl of rajma gravy and two chapattis." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Feels like home.</figcaption> </figure>  </div>  ]]></content:encoded>
    </item>
    <item>
      <title>Setting up forward auth with Caddy and Pocket ID</title>
      <link>https://msfjarvis.dev/posts/setting-up-forward-auth-with-caddy-and-pocket-id/</link>
      <guid>https://msfjarvis.dev/posts/setting-up-forward-auth-with-caddy-and-pocket-id/</guid>
      <description>Using Pocket ID to secure services being proxied by Caddy</description>
      <pubDate>Sat, 21 Mar 2026 10:03:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>As I mentioned in my <a href="https://msfjarvis.dev/posts/weeknotes-week-11-2026/">last weeknote</a>, I set up <a href="https://github.com/janeczku/calibre-web">Calibre-Web</a> last week which necessitated the use of a forward authentication setup to work with my existing SSO provider. It was rather non-trivial to get it all to work, so I’m documenting it here in hopes of helping others.</p>
<blockquote>
<p>WARNING (added 05/05/2026): Since writing this post I’ve come to realise that caddy-security very frequently makes subtle breaking changes, and you only figure out the migration by asking yourself or reading the GitHub issue someone else posted. That’s far too much active involvement than I want to have with upgrading software that I require to be dependable. I will personally be moving to Authelia and LLDAP in the near future to leverage its first party support for forward and reverse auth, and a more stable release cycle.</p>
</blockquote>
<h2 id="requirements">Requirements</h2>
<ul>
<li><a href="https://caddyserver.com/">Caddy</a> with the <a href="https://github.com/greenpau/caddy-security/">caddy-security</a> plugin</li>
<li><a href="https://pocket-id.org/">Pocket ID</a></li>
<li>Patience.</li>
</ul>
<h2 id="pocket-id-and-caddy-security-setup">Pocket ID and caddy-security setup</h2>
<p>Follow the Caddy guide <a href="https://pocket-id.org/docs/guides/proxy-services">here</a> to set up an OIDC client and the caddy-security configuration in your Caddyfile. This gets you 90% of the way, but due to recent regressions in caddy-security you’ll need to make some tweaks.</p>
<p>First of all, in the <code>oauth identity provider</code> block, add this line:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>trust login redirect uri domain exact ${app.domain} path prefix /</span></span></code></pre>
<p>Replace <code>${app.domain}</code> with the domain to the service you are securing.</p>
<p>The guide also assumes you will re-use the same caddy-security authentication portal for all your services which is <em>fine</em>, but I prefer to have each OIDC client be isolated on a service level instead of just having a generic caddy-security one. I’ll explain the basic changes first then dive into the NixOS-specific stuff I did for my own deployment.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>security {</span></span>
<span class="line"><span>  # Rename the provider from generic to the service name</span></span>
<span class="line"><span>  oauth identity provider calibreweb {</span></span>
<span class="line"><span>    delay_start 3</span></span>
<span class="line"><span>    # Give this its own realm</span></span>
<span class="line"><span>    realm calibreweb</span></span>
<span class="line"><span>    # This is the OIDC provider type, make sure this stays `generic`</span></span>
<span class="line"><span>    driver generic</span></span>
<span class="line"><span>    # Service-scoped secrets from the Pocket ID OIDC client setup</span></span>
<span class="line"><span>    client_id {$CALIBRE_WEB_POCKET_ID_CLIENT_ID}</span></span>
<span class="line"><span>    client_secret {$CALIBRE_WEB_POCKET_ID_CLIENT_SECRET}</span></span>
<span class="line"><span>    scopes openid email profile</span></span>
<span class="line"><span>    base_auth_url https://auth.msfjarvis.dev</span></span>
<span class="line"><span>    metadata_url https://auth.msfjarvis.dev/.well-known/openid-configuration</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span></span></span>
<span class="line"><span>   # Rename the authentication portal as well</span></span>
<span class="line"><span>  authentication portal calibreweb_portal {</span></span>
<span class="line"><span>    crypto default token lifetime 3600</span></span>
<span class="line"><span>    # Use the new name for the identity provider defined above</span></span>
<span class="line"><span>    enable identity provider calibreweb</span></span>
<span class="line"><span>    trust login redirect uri domain exact books.msfjarvis.dev path prefix /</span></span>
<span class="line"><span>    cookie insecure off</span></span>
<span class="line"><span>    cookie domain books.msfjarvis.dev</span></span>
<span class="line"><span>    transform user {</span></span>
<span class="line"><span>      # The new realm name</span></span>
<span class="line"><span>      match realm calibreweb</span></span>
<span class="line"><span>      action add role user</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span></span></span>
<span class="line"><span>  # Per-service configuration</span></span>
<span class="line"><span>  authorization policy calibreweb_policy {</span></span>
<span class="line"><span>    set auth url /caddy-security/oauth2/calibreweb</span></span>
<span class="line"><span>    allow roles user</span></span>
<span class="line"><span>    inject headers with claims</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<p>With this setup you should be able to then copy-paste the code block above for a new service and just replace the name and URLs. Do note that the <code>security</code> block is globally unique, and only the contents inside it are meant to be duplicated.</p>
<h3 id="sidebar-nixos-version">Sidebar: NixOS version</h3>
<blockquote>
<p>People not using NixOS can ignore this section</p>
</blockquote>
<p>On NixOS I already have a <a href="https://github.com/msfjarvis/dotfiles/blob/d495c2afcc564b126a0ed1e89bbd8a2f2e3ff224/modules/nixos/caddy/default.nix">Caddy module</a> that applies a set of defaults, so I retrofitted generating the security block into it. I added an option to let individual services configure the inputs via module options:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="nix"><code><span class="line"><span style="color:#FFAB70">pocketIdApplications</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#FFAB70"> mkOption</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">  type</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> types</span><span style="color:#F97583">.</span><span style="color:#FFAB70">attrsOf</span><span style="color:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#FFAB70">    types</span><span style="color:#F97583">.</span><span style="color:#FFAB70">submodule</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">      options</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">        domain</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> mkOption</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">          type</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> types</span><span style="color:#F97583">.</span><span style="color:#FFAB70">str</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">          description</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "Domain of the proxied service"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">        };</span></span>
<span class="line"><span style="color:#B392F0">        clientIdEnvVar</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> mkOption</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">          type</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> types</span><span style="color:#F97583">.</span><span style="color:#FFAB70">str</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">          description</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "Environment variable name containing the client ID"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">        };</span></span>
<span class="line"><span style="color:#B392F0">        clientSecretEnvVar</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> mkOption</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">          type</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> types</span><span style="color:#F97583">.</span><span style="color:#FFAB70">str</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">          description</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "Environment variable name containing the client secret"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">        };</span></span>
<span class="line"><span style="color:#E1E4E8">      };</span></span>
<span class="line"><span style="color:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#E1E4E8">  );</span></span>
<span class="line"><span style="color:#B392F0">  default</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> { };</span></span>
<span class="line"><span style="color:#B392F0">  description</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "Applications to protect with Pocket ID OIDC via caddy-security"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span></code></pre>
<p>Then in the <code>services.caddy.globalConfig</code> option, you loop over the value of this to generate the config we wrote above.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="nix"><code><span class="line"><span style="color:#E1E4E8">${</span><span style="color:#FFAB70">lib</span><span style="color:#F97583">.</span><span style="color:#FFAB70">optionalString</span><span style="color:#E1E4E8"> (</span><span style="color:#FFAB70">config</span><span style="color:#F97583">.</span><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">pocketIdApplications</span><span style="color:#F97583"> !=</span><span style="color:#E1E4E8"> { }) </span><span style="color:#9ECBFF">''</span></span>
<span class="line"><span style="color:#9ECBFF">  security {</span></span>
<span class="line"><span style="color:#9ECBFF">    ${</span><span style="color:#79B8FF">lib</span><span style="color:#F97583">.</span><span style="color:#79B8FF">concatStringsSep</span><span style="color:#9ECBFF"> "</span><span style="color:#79B8FF">\n</span><span style="color:#9ECBFF">"</span><span style="color:#9ECBFF"> (</span></span>
<span class="line"><span style="color:#79B8FF">      lib</span><span style="color:#F97583">.</span><span style="color:#79B8FF">mapAttrsToList</span><span style="color:#9ECBFF"> (</span><span style="color:#E1E4E8">name</span><span style="color:#9ECBFF">: </span><span style="color:#E1E4E8">app</span><span style="color:#9ECBFF">: </span><span style="color:#9ECBFF">''</span></span>
<span class="line"><span style="color:#9ECBFF">        oauth identity provider </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">name</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF"> {</span></span>
<span class="line"><span style="color:#9ECBFF">          delay_start 3</span></span>
<span class="line"><span style="color:#9ECBFF">          realm </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">name</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">          driver generic</span></span>
<span class="line"><span style="color:#9ECBFF">          client_id {</span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">app</span><span style="color:#F97583">.</span><span style="color:#79B8FF">clientIdEnvVar</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">          client_secret {</span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">app</span><span style="color:#F97583">.</span><span style="color:#79B8FF">clientSecretEnvVar</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">          scopes openid email profile</span></span>
<span class="line"><span style="color:#9ECBFF">          base_auth_url https://auth.msfjarvis.dev</span></span>
<span class="line"><span style="color:#9ECBFF">          metadata_url https://auth.msfjarvis.dev/.well-known/openid-configuration</span></span>
<span class="line"><span style="color:#9ECBFF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        authentication portal </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">name</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">_portal {</span></span>
<span class="line"><span style="color:#9ECBFF">          crypto default token lifetime 3600</span></span>
<span class="line"><span style="color:#9ECBFF">          enable identity provider </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">name</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">          trust login redirect uri domain exact </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">app</span><span style="color:#F97583">.</span><span style="color:#79B8FF">domain</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF"> path prefix /</span></span>
<span class="line"><span style="color:#9ECBFF">          cookie insecure off</span></span>
<span class="line"><span style="color:#9ECBFF">          cookie domain </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">app</span><span style="color:#F97583">.</span><span style="color:#79B8FF">domain</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">          transform user {</span></span>
<span class="line"><span style="color:#9ECBFF">            match realm </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">name</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">            action add role user</span></span>
<span class="line"><span style="color:#9ECBFF">          }</span></span>
<span class="line"><span style="color:#9ECBFF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        authorization policy </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">name</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">_policy {</span></span>
<span class="line"><span style="color:#9ECBFF">          set auth url /caddy-security/oauth2/</span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">name</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">          allow roles user</span></span>
<span class="line"><span style="color:#9ECBFF">          inject headers with claims</span></span>
<span class="line"><span style="color:#9ECBFF">        }</span></span>
<span class="line"><span style="color:#9ECBFF">      ''</span><span style="color:#9ECBFF">) </span><span style="color:#79B8FF">config</span><span style="color:#F97583">.</span><span style="color:#79B8FF">services</span><span style="color:#F97583">.</span><span style="color:#79B8FF">caddy</span><span style="color:#F97583">.</span><span style="color:#79B8FF">pocketIdApplications</span></span>
<span class="line"><span style="color:#9ECBFF">    )}</span></span>
<span class="line"><span style="color:#9ECBFF">  }</span></span>
<span class="line"><span style="color:#9ECBFF">''</span><span style="color:#E1E4E8">}</span></span></code></pre>
<p>With all this in place, you can then configure this alongside (in my case) Calibre-Web and have all the stuff above get generated without having to think too much about it:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="nix"><code><span class="line"><span style="color:#FFAB70">sops</span><span style="color:#F97583">.</span><span style="color:#FFAB70">secrets</span><span style="color:#F97583">.</span><span style="color:#FFAB70">calibre-web-caddy-env</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">  sopsFile</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> lib</span><span style="color:#F97583">.</span><span style="color:#FFAB70">snowfall</span><span style="color:#F97583">.</span><span style="color:#FFAB70">fs</span><span style="color:#F97583">.</span><span style="color:#FFAB70">get-file</span><span style="color:#9ECBFF"> "secrets/calibre-web.env"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  format</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "dotenv"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  owner</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> config</span><span style="color:#F97583">.</span><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">user</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  restartUnits</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> [ </span><span style="color:#9ECBFF">"caddy.service"</span><span style="color:#E1E4E8"> ];</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span>
<span class="line"><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">pocketIdApplications</span><span style="color:#F97583">.</span><span style="color:#9ECBFF">"calibreweb"</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">  domain</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> cfg</span><span style="color:#F97583">.</span><span style="color:#FFAB70">domain</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  clientIdEnvVar</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "$CALIBRE_WEB_POCKET_ID_CLIENT_ID"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  clientSecretEnvVar</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "$CALIBRE_WEB_POCKET_ID_CLIENT_SECRET"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span>
<span class="line"><span style="color:#FFAB70">systemd</span><span style="color:#F97583">.</span><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">serviceConfig</span><span style="color:#F97583">.</span><span style="color:#FFAB70">EnvironmentFile</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> [</span></span>
<span class="line"><span style="color:#FFAB70">  config</span><span style="color:#F97583">.</span><span style="color:#FFAB70">sops</span><span style="color:#F97583">.</span><span style="color:#FFAB70">secrets</span><span style="color:#F97583">.</span><span style="color:#FFAB70">calibre-web-caddy-env</span><span style="color:#F97583">.</span><span style="color:#FFAB70">path</span></span>
<span class="line"><span style="color:#E1E4E8">]</span><span style="color:#FDAEB7;font-style:italic">;</span></span></code></pre>
<h2 id="caddy-routing-setup">Caddy routing setup</h2>
<p>Usually just throwing a <code>reverse_proxy localhost:&lt;port&gt;</code> gets you all the way with Caddy, but Calibre-Web specifically requires some extra configuration that serves for a nice demonstration. We need to treat different paths in different ways, so we’ll use the Caddy <a href="https://caddyserver.com/docs/caddyfile/directives/handle"><code>handle</code> directive</a> to create mutually exclusive routing paths for them.</p>
<p><code>caddy-security</code> adds a <code>/caddy-security</code> path in your sites that should always require authentication, so we simply route it to our portal without any changes.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>handle /caddy-security/* {</span></span>
<span class="line"><span>  route {</span></span>
<span class="line"><span>    authenticate with calibreweb_portal</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<p>Caddy lets you label a set of paths or with the <code>@name</code> syntax, this is just here for convenience. Calibre Web exposes OPDS and Kobo-specific endpoints for use with your devices that likely can’t do OIDC, so we allow those to bypass the authentication requirements. The transport buffer size was taken from https://github.com/janeczku/calibre-web/issues/1891 after I faced the same sync issues.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>@integrations {</span></span>
<span class="line"><span>  path /opds /opds/* /kobo /kobo/*</span></span>
<span class="line"><span>}</span></span>
<span class="line"><span>handle @integrations {</span></span>
<span class="line"><span>  reverse_proxy localhost:8080 {</span></span>
<span class="line"><span>    header_up X-Scheme https</span></span>
<span class="line"><span>    transport http {</span></span>
<span class="line"><span>      read_buffer 1024k</span></span>
<span class="line"><span>      write_buffer 1024k</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<p>This is the catch-all route for everything else that doesn’t need special treatment. This only differs from the one above in requiring <em>authorization</em>.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>handle {</span></span>
<span class="line"><span>  route {</span></span>
<span class="line"><span>    authorize with calibreweb_policy</span></span>
<span class="line"><span>    reverse_proxy localhost:8080 {</span></span>
<span class="line"><span>      header_up X-Scheme https</span></span>
<span class="line"><span>      transport http {</span></span>
<span class="line"><span>        read_buffer 1024k</span></span>
<span class="line"><span>        write_buffer 1024k</span></span>
<span class="line"><span>      }</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<p>The full configuration then becomes this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>handle /caddy-security/* {</span></span>
<span class="line"><span>  route {</span></span>
<span class="line"><span>    authenticate with calibreweb_portal</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span>
<span class="line"><span>@integrations {</span></span>
<span class="line"><span>  path /opds /opds/* /kobo /kobo/*</span></span>
<span class="line"><span>}</span></span>
<span class="line"><span>handle @integrations {</span></span>
<span class="line"><span>  reverse_proxy localhost:8080 {</span></span>
<span class="line"><span>    header_up X-Scheme https</span></span>
<span class="line"><span>    transport http {</span></span>
<span class="line"><span>      read_buffer 1024k</span></span>
<span class="line"><span>      write_buffer 1024k</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span>
<span class="line"><span>handle {</span></span>
<span class="line"><span>  route {</span></span>
<span class="line"><span>    authorize with calibreweb_policy</span></span>
<span class="line"><span>    reverse_proxy localhost:8080 {</span></span>
<span class="line"><span>      header_up X-Scheme https</span></span>
<span class="line"><span>      transport http {</span></span>
<span class="line"><span>        read_buffer 1024k</span></span>
<span class="line"><span>        write_buffer 1024k</span></span>
<span class="line"><span>      }</span></span>
<span class="line"><span>    }</span></span>
<span class="line"><span>  }</span></span>
<span class="line"><span>}</span></span></code></pre>
<h3 id="sidebar-authentication-vs-authorization">Sidebar: Authentication vs Authorization</h3>
<p>This is something that took too long for me to hammer into my brain, so I’ll reproduce it here as well for others who may have had the same question but didn’t feel confident asking it.</p>
<ul>
<li>Authentication: Verifying your identity. This answers the question of who the user is.</li>
<li>Authorization: Verifying your access. This answers the question of whether the previously identified user should be allowed through to a specific service.</li>
</ul>
<p>We require <em>authentication</em> for the <code>caddy-security</code> portal because you should be able to at least see that you have an account. The rest of the service requires <em>authorization</em> so only people who should be allowed access to the Calibre-Web instance are able to log in.</p>
<hr>
<p>And that’s the long and short of it! There was a lot of one-time effort in getting this working and I had to dig through a lot of GitHub issues to find bits and pieces that had to be tweaked, but I’m quite happy with how this all came together. I now have Calibre-Web syncing to my Kobo eReader, and no longer have to struggle with a split desktop library back at home and here in Bengaluru.</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #11 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-11-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-11-2026/</guid>
      <description>Would you believe it, even more travel!</description>
      <pubDate>Sun, 15 Mar 2026 14:07:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I am starting to realize that every other week I talk about traveling somewhere, in the midst of which I also threw in the part about being somewhat lonely and depressed when I had to be by myself in one place for a couple of weeks. This is probably giving people the impression that I love to travel and despise being a homebody, which is quite literally the exact opposite of who I really am. Hopefully this week was my last trip for the next few months and I can reclaim my nerdy homebody persona.</p>
<p>As hinted above, I was back home this week! My friend Rohan was getting married and I couldn’t imagine not being there, especially when our whole friend group was getting together. It was a great time, he sounded genuinely excited and happy which is all I care for. I got to see a lot of friends I haven’t met in person for over a year now, and it was very nice catching up and being a little rowdy over some drinks :)</p>
<p>While at home I also took the opportunity to hack some more on the homelab server and try out <a href="https://microvm-nix.github.io/microvm.nix/">microvm.nix</a> to enable running more than one instance of things on the same machine which has historically been a problem on NixOS. I intended to use all that preparatory work to try out <a href="https://github.com/booklore-app/booklore">Booklore</a> in a microVM since it uses MariaDB which I had no plans to run on my host, but that fell through when my suspicions around the quality issues in the v2 release were <a href="https://www.reddit.com/r/selfhosted/comments/1rs275q/psa_think_hard_before_you_deploy_booklore/">validated</a> by the revelation that the developer has let Claude run rampant over what was honestly a pretty decent bit of software. The following <a href="https://www.reddit.com/r/selfhosted/comments/1rs4nx0/my_side_of_the_story_from_the_developer_of/">meltdown from the developer</a> cemented my decision to stay away from it entirely. Instead, I set myself up with <a href="https://github.com/janeczku/calibre-web">Calibre-Web</a>.</p>
<p>It was quite straightforward, and most of my time tinkering with it actually went into setting up forward authentication using Caddy and Pocket ID. The end result of it can be found <a href="https://github.com/msfjarvis/dotfiles/blob/d4d81b24cd37456194c2e1a7ca8dde90fa19a986/modules/nixos/caddy/default.nix#L72-L107">here</a>, and I intend to write a post about it next week so others have to suffer a little less than I had to :’)</p>
<p>At home, I do my gaming on Linux where I ran into a peculiar bug involving Discord and Tailscale, which didn’t let me join any voice calls. I investigated this a bit online, and apparently it was <a href="https://github.com/tailscale/tailscale/issues/10396">reported already</a> and the <a href="https://github.com/tailscale/tailscale/issues/10396#issuecomment-3871203280">fix</a> was to hide the <code>tailscale0</code> interface from the Discord client?? That was also how I found about <a href="https://github.com/netblue30/firejail">firejail</a>, and like most things there was already a NixOS module <a href="https://github.com/msfjarvis/dotfiles/commit/d2ea971bd1276e1f47510711a7c5e813c24a50f9">to make the setup easy</a>.</p>
<p>On Saturday I flew back home via a morning flight, which is weirdly the first time I realized that the flight between Bengaluru and Hindon is closer to 3 hours than 2. Always thought otherwise, don’t know why.</p>
<p>I found out about <a href="https://izzyondroid.org/docs/general/AppInclusionPolicy/#ai-policy">IzzyOnDroid’s AI policy</a> yesterday and since I’ve <a href="https://msfjarvis.dev/posts/coming-around-on-the-utility-of-llms/">written before</a> about using LLMs for the development of Claw, I filed <a href="https://codeberg.org/IzzyOnDroid/repodata/issues/60">an issue</a> to ask for its removal from the repository. I massively appreciate the work they do for the FOSS ecosystem for Android (having donated a not-insignificant amount to their OpenCollective through Android Password Store) and I would hate to take that for granted and knowingly ignore their policies. There hasn’t been a decision made yet on it, but I’ve provided all the necessary context to aid the process.</p>
<p><a href="https://github.com/swapnilmadavi">Swapnil</a> and I went to watch <a href="https://www.imdb.com/title/tt33035197">Boong</a> right before I sat down to write this weeknote. Very cute and heartwarming, really loved it. It also made me realize how much I’ve missed going to the movies this past month, so I’m remedying it by watching <a href="https://www.imdb.com/title/tt27564844/">Iron Lung</a> tomorrow.</p>
<p>More meta stuff: I’ve <a href="https://msfjarvis.dev/posts/weeknotes-week-8-2026/">previously talked</a> about how my CMS does not have an integration with Cloudflare R2 as an excuse to not attach pictures of my food, which obviously meant that <a href="https://github.com/sveltia/sveltia-cms/commit/ff7aa3d885b4733d87c64c3a4fad350e325c6c99">the feature got shipped</a> so I have no reason to be lazy anymore. Prepare for diabolical salads next week!</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #10 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-10-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-10-2026/</guid>
      <description>The most boring week I've had this year</description>
      <pubDate>Sun, 08 Mar 2026 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Barely anything of note happened this week as practically my whole team was out-of-office so I was both working <em>and</em> living alone which made for an incredibly boring time.</p>
<p>My phone woes continued into this week, with the Pixel 8 seemingly forgetting my home Wi-Fi network entirely and refusing to connect to it. For two days I had it connect to my spare phone’s hotspot for Wi-Fi. Just like last week, this problem also magically resolved itself in two days and things went back to normal. I hate software even more.</p>
<p>I finally had to go bug my landlord to get some maintenance work done in the flat when my ceiling fan broke down, not a fun time in 32 degree Celsius weather. That took entirely too many phone calls than one would expect, but I managed to take the opportunity and get other stuff fixed around the apartment as well. Hopefully that’ll be the end of this.</p>
<p>At work I kept fighting with CI related things, which were made slower by the fact that I had to reach out to a different team to help with reviews since nobody in mine was working. Eventually I think I’ve got it all working but what a colossal waste of time it has been throughout this week. We had a <a href="https://www.cloudflarestatus.com/incidents/cs9vyjjj4trx">minor scare</a> with the Android SDK from a user report about crashes when attempting to join a meeting but thankfully we were able to debug it and identify that the problem wasn’t as widespread as we initially suspected and required a very specific setup to trigger. I fixed the root cause and cut a release, I’ll probably write a blog post about it once I’ve completed the internal incident report.</p>
<p>I had to fly back home via an early flight on the 8th and hadn’t packed for it yet so I was debating if I should go to IndieWebClub but thankfully I did end up going, the <a href="https://underline.center/t/indiewebclub-21/723">topics on the agenda</a> were great and a lot of thoughtful discussion happened that I enjoyed participating in.</p>
<p>I packed for the trip at a definitely-not-too-late 8 PM, stayed up till 2 AM playing games with friends while waiting for my taxi and managed to reach the airport in an astonishing 45 minutes. All the airport staff was still there but the foot traffic was maybe a fifth of what I usually see in Kempegowda airport, so I basically speedran through check-in and security. Time from taxi to departure gate, maybe 30 minutes.</p>
<p>And that was kind of it! I’m back home in Ghaziabad for a week to attend a friend’s wedding and will be back in Bengaluru next weekend.</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #9 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-9-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-9-2026/</guid>
      <description>Getting a little honest about my mental health</description>
      <pubDate>Sun, 01 Mar 2026 14:07:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>Warning: Feelings</p>
</blockquote>
<p>My roommate <a href="https://yashgarg.dev/">Yash</a> is back with his parents for a couple of weeks, which has left me alone in the house and quite severely exposed my delusion of being a loner. I’ll probably write a separate post about this at some point, but I’ve been quite overwhelmed with feelings of loneliness which aren’t helped at all by me being a general homebody so this week has been quite rough, and forced me to start training myself to address the things that feed into it. I ended up finally utilizing the free therapy options offered at Cloudflare, and make sure to have at least one cleaning related task on my daily to-do to ensure I do not spiral into my room becoming a depression nest (shoutout <a href="https://www.youtube.com/watch?v=WjsX_BfhRSo">Geega</a> my goat). This has helped so far, and will likely be plenty to tide me over until I myself go back to my family at the end of next week. I also reached out to my colleagues at work and have scheduled a day of working together out of a cafe since our teams agreed to work from home for the next week due to most people taking off for Holi.</p>
<p>I promise I’ll be okay, it’s a bit of a sad boy funk, I’ll get over it.</p>
<hr>
<p>Onward to the rest of the weeknote!</p>
<ul>
<li>I had to email the customer support for one my credit cards when I realized I needed to use my registered email, and had <em>helpfully</em> used one of DuckDuckGo’s disposable aliases for the job 😅. Figuring that one out was thankfully pretty quick, and has been <a href="https://msfjarvis.dev/notes/til-sending-emails-from-my-duckduckgo-email-alias/">put down in a note</a> for future Harsh.</li>
<li><a href="https://kat.bio/">Amogh</a> helped me figure out how to be able to pay for stuff on Steam (the aforementioned credit card being at fault) again, so any irresponsible gaming purchases I talk about in the coming weeks are his fault.</li>
<li><a href="https://msfjarvis.dev/posts/weeknotes-week-8-2026/">Last week</a> I talked about adding WebMentions support to this site, this week I kept iterating on it and it now properly extracts comment text when available and correctly attributes to the real user instead of the brid.gy relay.</li>
<li>I shipped a new release of <a href="https://github.com/msfjarvis/compose-lobsters/commit/a3d8d5b4d3dfd115b85dba7010393354543b14ee">Claw</a> this week, incorporating some UI feedback from <a href="https://abhinavsarkar.net/">Abhinav</a> and my first attempt at a (mostly) LLM generated feature: <a href="https://github.com/msfjarvis/compose-lobsters/commit/a3d8d5b4d3dfd115b85dba7010393354543b14ee">Temporary tag blocks</a>. I had to get my hands dirty at the end anyway because the LLM just completely forgot about data migration and integrity, but having it do 80% of the work while I was at the gym is pretty cool. I also have to admit that the UI flow that Claude came up with is miles better than anything that had been floating around in my brain. I don’t know much it helped, but I used <a href="https://skills.sh/wshobson/agents/mobile-android-design">this “skill”</a> as part of the prompt.</li>
<li>Someone on the payroll team at Cloudflare probably discovered <a href="https://en.wikipedia.org/wiki/Idempotence">Idempotence</a> this week as everyone at our office got paid twice :P
<ul>
<li>Fret not, the extra money is already back with Cloudflare. Cheers to a brief taste of unearned wealth, the closest I will come to sharing an emotion with a billionaire.</li>
</ul>
</li>
<li>While trying to make that bank transfer I realized that my SIM had stopped working altogether, suspiciously after the Android 17 Beta 2 upgrade. Mild panicking ensued, and I had to add an additional side quest for a duplicate or a new SIM the next morning when I was meeting <a href="https://github.com/swapnilmadavi">Swapnil</a> and <a href="https://github.com/mayankofficial999">Mayank</a> for breakfast.
<ul>
<li>The boys helpfully accompanied me to the Jio store where I got the unfortunate news that the SIM owner has to be present in-person for a duplicate (which happened to be my mother), so I went to get a new Airtel SIM instead which was a relatively quick affair.</li>
<li>Of course right after that when I get home, the Jio SIM starts working again so all of the struggle was for naught. I fucking hate software sometimes.</li>
</ul>
</li>
<li>My weekly NixOS upgrades were basically impossible this week, there was a <a href="https://github.com/NixOS/nixpkgs/pull/494816">bad Linux regression</a>, and home-manager <a href="https://github.com/nix-community/home-manager/issues/8786">also has a severe bug</a> so I just reverted everything and will deal with it next week.</li>
</ul>
<hr>
<p>In case anyone is missing the food talk, this week was rather light on the experimentation front due to the aforementioned sad boy hours. I mostly ate salads and air fried a chicken breast or two a couple of times. I did try air fried potato wedges one night and they came out pretty nice, albeit slightly dry since I got the temperatures wrong. Lesson learned!</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #8 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-8-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-8-2026/</guid>
      <description>The kitchen is my new comfort space</description>
      <pubDate>Sun, 22 Feb 2026 15:26:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I’ll apologize upfront: I’m going to talk about food a lot and not share any pictures because I have the excuse of <a href="https://github.com/sveltia/sveltia-cms/issues/586">my CMS</a> not supporting the asset pipeline I want to establish. Soon™️</p>
<p>Now, onward to the real content. A lot of time was spent in the kitchen this week, with <a href="https://yashgarg.dev/">Yash</a> and me continuing our burrito escapades. On Monday, I decided to try air-frying some tortilla chips to see how it would go, and despite the obvious problems caused by overcrowding the rather tiny fryer basket, I’d consider it a success overall. Definitely a good option for dinner in a pinch.</p>
<p>A couple of days later, we went back in for even more burritos, building on our previous experience. The beans got a more generous round of seasoning, the rice was cooked and cooled ahead of time, and we were more mindful about how much we tried to shove into each burrito. This was remarkably successful, with both of us managing to create fully wrapped burritos by the end of the ordeal. I opted for a veggie-heavy burrito bowl with my remaining portions instead of wrestling with the tortilla again, which felt like the smarter choice. Throwing in some air-fried chicken sausages elevated the whole situation, but there was a definite lack of carbs, which I’ll have to keep in mind for the future.</p>
<p>On Friday, I had zero energy to cook, so I just chopped up the remaining lettuce along with some onions, tomatoes, and cucumbers, tossed it all in some sauces we had lying around, and pretended it was a gourmet salad. 10/10, would do again.</p>
<p>Yash is back home for a couple of weeks, which means I’m left to fend for myself in terms of sustenance. So I’m making a U-turn back to the Mediterranean phase of my life and already have big batches of falafel and hummus sitting in my fridge. This time I did remember to use the appropriate amount of greens and—shocked Pikachu face—it’s actually come out way nicer than my last mishap. Who knew following the recipe was the way?</p>
<p>With all the food talk out of the way, the boys and I have been getting back into <a href="https://www.playstation.com/games/helldivers-2">Helldivers 2</a>, and things have been <a href="https://androiddev.social/@msfjarvis/116087678608961557">going (not) great</a>. Super fun, though!</p>
<p>Relatedly, my laptop is back to its fortnightly cycle of throttling at the most inopportune moments, which has made gaming frustrating every so often. Since Windows is fairly out of my wheelhouse, I decided to commit the sin of letting an LLM replace the usual experience of stumbling through outdated forum articles. I had it run a gamut of debugging scripts to try and diagnose the problem. Everything that came out of this multi-day rigmarole is archived in <a href="https://git.msfjarvis.dev/msfjarvis/nvidia-struggles">this Git repository</a>. I’m not yet 100% confident that what Claude Sonnet 4.5 identified as the problem has truly resolved the issue, but it’s been fine for the past day or so.</p>
<p>Continuing the theme of tying things into the next entry, I have more LLM nonsense to share! I use <a href="https://jtx.techbee.at/">jtx Board</a> to manage my TODOs via CalDAV and wanted a simple way to show them in my <a href="https://github.com/glanceapp/glance">Glance</a> dashboard, which supports <a href="https://github.com/glanceapp/glance/blob/6c5b7a3f4cc409e31739b2914bb6636d08299126/docs/custom-api.md">custom APIs</a>—but unsurprisingly, only JSON. I had Claude try to write a simple proxy that could massage the CalDAV response into a suitable JSON format that I could plug into Glance. On its first pass, it took the credentials I had explicitly provided in a separate gitignored file for integration tests and put them into config.example.yml. When I asked it to remove them, it also included them in the LLM response text itself, causing them to be leaked twice instead. It also tried to claim that integration tests were passing when they obviously weren’t. Somewhere along the way, it lost the ability to issue write tool calls, which resulted in hilarious scenarios where it would attempt a write that never went through, run a test that still obviously failed, and then crash out about it. Fun for the whole family.</p>
<p><a href="https://underline.center/t/indiewebclub-20/720">IndieWebClub #20</a> was this weekend! I had missed the last two installments because I was out of Bengaluru, so I was super excited to catch up with people again. I met a bunch of new folks, had some great conversations—just a really good time. I would wholeheartedly recommend it to anyone who wants to have their own website, regardless of their technical expertise. You can get any level of help from the nicest, most patient people and become part of a small but growing community. It’s really special to me! The next one’s on the 7th of March—mark your calendars 🙂</p>
<p>And finally, this site now supports <a href="https://www.w3.org/TR/webmention/">WebMentions</a>! The server-side of this is built on top of the Cloudflare Developer Platform, leveraging Workers, the D1 SQLite database and Workers Queues. It was a nice way to finally dive back into Cloudflare’s compute offerings since I had last used them in 2022 and I was quite pleased with how much better everything is now. A lot more things are now possible to run on Workers than before, and the local development experience is <strong>really</strong> good now. I’ll probably build some more things on Workers in the future, and hopefully write about them.</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #7 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-7-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-7-2026/</guid>
      <description>Back to work and getting really into burritos</description>
      <pubDate>Sun, 15 Feb 2026 14:52:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This was my first week back in the office after an absence of 3 weeks which was a nice return to my usual routine. Catching up with 10 days of emails and messages wasn’t as herculean of a task as I had expected :D</p>
<p>I was finally able to put in a replacement request for the defective Pixel Watch 4 I had purchased <a href="https://msfjarvis.dev/posts/weeknotes-week-4-2026/">a couple of weeks ago</a> now that I was in Bengaluru and delivery was a more straightforward affair. Finding the chat option for this was an absolute nightmare trying to navigate Google’s endless mazes but once I was in there it was a pretty easy process. Blue Dart’s speed was quite the savior, and I got the replacement device back in just 3 days.</p>
<p>Now that I have a more capable fitness tracker I’ve been trying to be more mindful about staying active, which has been going less than stellar because I had been away from the gym for nearly a month so building back my endurance and stamina is gonna take a few more days. Definitely going to try and push for 10K steps a day again.</p>
<p>On Wednesday I fell on my way back from the gym by getting my foot stuck in an uneven footpath, which left me with some light but painful bruises. Thankfully they seem to have buffed out over the week, and the only permanent damage is going to be to my phone case :’)</p>
<p>On Friday, my roommate Yash and I decided to try out making burritos which were quite scuffed but ultimately rather delicious. The 8 inch burrito wraps that we got ended up being rather small, and the absence of any burrito wrapping skill between the two of us did not help the situation. Would like to try it again sometime if we can find bigger wraps, or maybe make our own.</p>
<p>Valentine’s day was spent enjoying my own company and working on some homelab stuff I had been putting off. My Git server is constantly being spammed by Chinese and Vietnamese spammers, which I <a href="https://androiddev.social/@msfjarvis/116069080640569258">tried to mitigate unsuccessfully</a>. Will take another stab at it soon.</p>
<p>I also put out a <a href="https://github.com/msfjarvis/compose-lobsters/releases/tag/v1.60.0">new release of Claw</a> since it had been a month since the previous one, and it seems to have not exploded right away so that’s a relief.</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #6 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-6-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-6-2026/</guid>
      <description>Finally back in Bengaluru!</description>
      <pubDate>Sun, 08 Feb 2026 14:09:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>After 3 weeks of being away, I am finally back in Bengaluru this weekend. I was also off work for the past ~10 days, so I am looking forward to spending half of my Monday sorting through emails and unread Google Chat messages. I may never hit Inbox Zero ever again…</p>
<p>To start off the week, I finally got my first <a href="https://www.playbalatro.com/">Balatro</a> win off an extremely lucky draw of jokers – one which gave “mult” equal to the sell value of all jokers and another that gained sell value each turn. I also hit an extremely lucky spectral card that replaced a whole hand with jesters. The seed for that run was <code>A7IBENU9</code>, in case anyone wants to show me up :P</p>
<p>I also introduced Balatro to my sister who got the hang of the Poker aspect of it, but doesn’t quite get the Roguelike part yet.</p>
<p>Outside Balatro and the occasional ceremony for the wedding I was attending, I occupied my time in trying to finish reading the minor behemoth that was <a href="https://bookwyrm.social/book/1584147/s/book-of-ile-rien">Book of Ile-Rien</a>. When I finally got done with it, I realized I had basically every Martha Wells book on the Kobo <em>except</em> the sequels to the Ile-Rien series which I wanted to read next :‘D</p>
<p>The eBook edition was not available on the Kobo India store so I had to get it from Barnes &amp; Noble, then figure out how to transfer it to the Kobo which also became a quick note <a href="https://msfjarvis.dev/notes/til-easily-sending-ebooks-to-a-kobo/">on the blog</a>.</p>
<p>The flight back to Bengaluru was a Boeing which didn’t quite inspire confidence, and the rather rough turbulence in the last 15 minutes of the journey had me genuinely fearing for my life but I made it down in one piece. I had to wait a while for the 3:30 PM bus back to Indiranagar but it was made pleasant by the AC lounge in the basement bus pickup area of the Kempegowda Airport.</p>
<p>Earlier in the week I had discovered (via <a href="https://indiewebify.me/">IndieWebify</a>) about the existence of the <a href="https://microformats.org/wiki/h-entry">h-entry</a> standard, and the site implements it now.</p>
<p>I was planning to be at <a href="https://underline.center/t/indiewebclub-19/708/1">IndieWebClub #19</a> today, having been away for the last 2 iterations of it but I woke up with the sniffles and didn’t have the energy for it. Hopefully next time 🤞</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #5 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-5-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-5-2026/</guid>
      <description>I swear all this travel is against my will</description>
      <pubDate>Sun, 01 Feb 2026 15:09:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This week was mostly spent back in my ancestral village with effectively zero internet, so this weeknote is being typed up on my phone at 8:40 PM because I completely forgot about it.</p>
<p>The most interesting part of my week was me getting involved in some Discourse™ over a Cloudflare blog post that made dubious claims about a vibe coded Matrix implementation that was written to run on Workers. Needless to say I will not be exercising my “Disclaimer: I’m a CF employee but do not speak for the company” rights in the future.</p>
<p>On a positive note despite the biting cold and fog I’ve been able to get a solid 3-4 hours in the sun every day which were often translated into <em>heavenly</em> naps. To put it in perspective how rare it is for me to sleep out of turn, I’ve owned my Amazfit tracker for 2 years and this week is when I first discovered it tracks naps. During all the sun bathing I also managed to get through the first half of <a href="https://en.wikipedia.org/wiki/Ile-Rien#The_Element_of_Fire">The Book of Ile-Rien</a>.</p>
<p>And that’s kinda it! Having little to no access to the Internet has been kind of driving me crazy but I preloaded the Kobo with enough material to have no excuse to not just be reading, so that’s what I’ve been using my time for. The entirety of next week is going to be wrapped up in the festivities for the wedding I’m here for so I expect to have another light weeknote which will thankfully be written from Bengaluru :)</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #4 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-4-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-4-2026/</guid>
      <description>A slow week back at home</description>
      <pubDate>Sun, 25 Jan 2026 10:16:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I’m still back at home so there’s not been a whole lot happening with my life other than being unable to deal with the cold.</p>
<ul>
<li>
<p>I started reading <a href="https://bookwyrm.social/book/1584147/s/book-of-ile-rien">The Book of Ile-Rien</a>, without realizing that the copy I bought is actually a combined version of <a href="https://en.wikipedia.org/wiki/Ile-Rien#The_Element_of_Fire">The Element of Fire</a> and <a href="https://en.wikipedia.org/wiki/Ile-Rien#Death_of_the_Necromancer">Death of the Necromancer</a> so I have a 900+ page behemoth on my hand that is a departure from the science fiction fare I usually consume. In six chapters I’m up to 17 unique words describing medieval things that I had to look up so overall this has been quite the experience.</p>
</li>
<li>
<p>The Pixel Watch I ordered last week came in on Tuesday and by Thursday its display had already developed horrid discoloration that forced me to put it in for an <a href="https://en.wikipedia.org/wiki/Return_merchandise_authorization">RMA</a> and get a replacement. It’s particularly bad timing since I will be traveling to my hometown soon for a marriage and then head back to Bengaluru, so I tried to have it be picked up from here and get it delivered in Bengaluru. Unfortunately the support executive I talked to got the wires crossed on that and scheduled the pickup from Bengaluru 🥲. Still trying to get that sorted.</p>
</li>
<li>
<p>We had a fair amount of thunder and rains on Friday which immediately knocked out both me and the power grid. The power came back 7-8 hours later but I continue to have the worst cold.</p>
</li>
</ul>
<p>Being away from work for a couple of weeks now hasn’t been as isolating as I would’ve expected which says good things about the team, but it has still been quite the drudgery being stuck inside what with the cold and the rain. I already miss the fabled Bengaluru weather…</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #3 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-3-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-3-2026/</guid>
      <description>A relatively quiet week mostly spent traveling and reading</description>
      <pubDate>Sun, 18 Jan 2026 06:50:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>As I mentioned <a href="https://msfjarvis.dev/posts/weeknotes-week-2-2026/">last week</a>, I’ve been traveling most of this week to attend the last rites of some immediate family that passed away at the start of this year. This has meant limited access to my computer and taking the week off work which might’ve been a blessing in some part.</p>
<ul>
<li>When I was leaving Bangalore, I tried taking a <a href="https://kia.bengawalk.com/">KIA BengaWalk</a> electric bus to the airport but it never showed up so I ended up in an expensive taxi instead. My experience with the bus network was pretty good when I used it last time to return from the airport so this was quite disappointing.</li>
<li><a href="http://obsidian.md/">Obsidian</a> updated this week to use rounded elements everywhere which I have mixed feelings about. It hasn’t felt too disruptive this week but I also didn’t use it all that much due to being mostly offline, so we’ll see in the next couple of weeks what side of the fence I end up on.</li>
<li>Finished reading <a href="https://bookwyrm.social/book/225118/s/shift">Shift</a> on the flight to Varanasi.</li>
<li>Immediately picked up <a href="https://bookwyrm.social/book/65228/s/dust">Dust</a> once I reached the village.</li>
<li>The generally poor mobile network at the village exposed a failure condition for the home screen widget in my app <a href="https://github.com/msfjarvis/compose-lobsters/">Claw</a>, so I took a crack at having <a href="https://github.com/copilot">Copilot</a> implement a caching feature to make the updates tolerant of transiently bad network. It took some prodding to make it all work, but in the end I was mostly satisfied with its ability to copy existing code <a href="https://github.com/msfjarvis/compose-lobsters/pull/1041">[PR]</a>.</li>
<li>The day before we were supposed to head back home, I got roped into assembling wedding invites with my Dad which ended up taking most of our time but it was nice to catch up with Dad for an extended period that I usually don’t get the opportunity for.</li>
<li>We ended up splitting our drive back home with a pit stop in Allahabad at a family friend, and I ashamedly used that time to catch up with work. I had completely forgotten about the Q1 kickoff meetings so there’s a couple of hours of recordings to watch when I’m back next week.</li>
<li>We left bright and early for the drive back which was rather rough with the heavy fog. I used the time in the car to finish off reading <a href="https://bookwyrm.social/book/65228/s/dust">Dust</a>, which might’ve been the fastest I’ve gotten through 300+ pages. The Silo series is written in quite a cliffhanger-heavy style where every chapter keeps you hooked, so I found it hard to put down. No complaints though, I loved all 3 books.</li>
<li>My Amazfit fitness tracker fell apart <a href="https://msfjarvis.dev/posts/weeknotes-week-1-2026/">again</a> so I finally ordered the Pixel Watch 4 I had been eyeing, with a <a href="https://www.amazon.in/dp/B0FWT8ND63">sturdy case</a> to protect it from my neglect.</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #2 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-2-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-2-2026/</guid>
      <description>Rounding up a rather depressing week</description>
      <pubDate>Sun, 11 Jan 2026 14:08:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This week has been quite difficult on the mental, with the death of my maternal grandmother and my childhood best friend’s father in quick succession. I’ll be going home for their last rites next week, and probably miss the weeknote posting schedule. Despite the horrible start, other things did happen during the rest of the days as listed out below.</p>
<ul>
<li>I had to shave my beard because of some skin irritation that I am medicating for, which apparently was enough to confuse my landlord into wondering who I was.</li>
<li>As I am in a quite Mediterranean time of my life, another batch of hummus was created, this time with a lot of more spices and it came out quite delicious indeed.</li>
<li>My phone has had the “pink line of dead pixels” syndrome for a while but it finally became enough of a problem for me to get it repaired, and in the process Android’s <a href="https://support.google.com/pixelphone/answer/14266732?hl=en">Repair Mode</a> bugged out and forced me to wipe my device to get it out of repair mode. Quite the annoyance to put onto a person already in the dumps, made worse by a Firefox bug that <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=2009251">caused passkeys to not work</a>.</li>
<li>The aforementioned data wipe also exposed a bug in <a href="https://github.com/msfjarvis/compose-lobsters/">Claw</a> which prevented auto backup from working, so at least <a href="https://github.com/msfjarvis/compose-lobsters/commit/e07bd441a8801bbb5b633636076dc36a154fe30d">something good</a> came out of me losing 3 months of saved posts since I had last backed things up manually.</li>
<li><a href="https://www.linkedin.com/in/willigeiger">Willi Geiger</a>, the head of the Cloudflare Media team was visiting the Bengaluru office this week. He took a session explaining the Media business as well as 1:1 meetings with every one of us who works in the Media team. Was nice having him around, shadowing how we do things in the Bengaluru office.</li>
<li>Watched <a href="https://en.wikipedia.org/wiki/Avatar%3A_Fire_and_Ash">Avatar Fire and Ash</a> with a couple of friends, as usual it’s a great spectacle with a flimsy story. Hollywood, keep giving this guy money to justify the existence of IMAX.</li>
<li>Last week I had ordered some accessories for the <a href="https://www.ikea.com/in/en/cat/skadis-series-37813/">IKEA Pegboard</a> which arrived on Saturday, and I was finally able to fill out the thing and put up some of the stuff that was cluttering my desk.</li>
<li><a href="https://yashgarg.dev">Yash</a> and I have been experimenting on the cooking front to start making more things than our usual 4 dishes and made some <a href="https://www.vegrecipesofindia.com/baingan-bharta-recipe-punjabi-baingan-bharta-recipe/">baingan bharta</a> on Saturday. Definitely burnt some of my fingertips off while roasting and peeling the eggplants but completely worth it.</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Migrating my website's analytics from Plausible to Umami</title>
      <link>https://msfjarvis.dev/posts/migrating-my-website-s-analytics-from-plausible-to-umami/</link>
      <guid>https://msfjarvis.dev/posts/migrating-my-website-s-analytics-from-plausible-to-umami/</guid>
      <description>Frustrated by ClickHouse's high disk usage, I ditched Plausible for Umami</description>
      <pubDate>Fri, 09 Jan 2026 19:10:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>On January 5th I had another minor outage on one of my servers due to a full disk, caused in part by misconfigured Forgejo data dumps. In the process of fixing that I discovered that most of my server disk was actually being used by <a href="https://clickhouse.com">ClickHouse</a>, which is holding data for my <a href="https://plausible.io">Plausible</a> instance that records page views for this website.</p>
<p>Why does it need 108 gigabytes to store 102K page views? I wish I could tell you. One of the developers of Plausible <a href="https://hachyderm.io/@ifthenelse/115843953290917862">replied to my grumbling</a> explaining that it was likely ClickHouse collecting a ton of logs which was the real storage hog rather than the data itself. By that point I had already finished migrating off to <a href="https://umami.is/">Umami</a> and deleted the offending ClickHouse data so I couldn’t verify the hypothesis.</p>
<p>The migration itself was quite simple so I’ll reproduce the steps below for any interested people looking for migration steps.</p>
<ul>
<li>I <a href="https://git.msfjarvis.dev/msfjarvis/dotfiles/commit/323fccb9b16e805f665337284b8a4ae8313c0b6a">set up the NixOS module for Umami</a>, and <a href="https://git.msfjarvis.dev/msfjarvis/dotfiles/commit/94419eb680752f573916b133d8ca6bd3162b5969">enabled it</a> on a fresh subdomain to run it alongside Plausible during the migration.
<ul>
<li>Umami has this weird default of creating an admin account with the `admin:umami` credentials so make sure the first thing you do is change this password. I wish they would add OIDC support already so I could stop relying on this.</li>
</ul>
</li>
<li>I logged into Plausible and following <a href="https://plausible.io/docs/export-stats#export-all-stats-to-date">their docs</a>, obtained a ZIP file of all my data till date.</li>
<li>Umami does not have any first-party support for importing data, so I had to use a <a href="https://github.com/JeongJuhyeon/plausible-to-umami">third-party Python script</a> that converted my ZIP file of CSVs from Plausible into a 5 megabyte .sql file that I could import into the Umami database table. The conversion worked for me in the first try, which was quite the relief.</li>
<li>With the SQL file in hand, I was able to easily import it into the running Umami instance by running <code>sudo -u umami psql -U umami -d umami &lt; plausible_migration.sql</code>. This worked flawlessly as well, and I was able to confirm on the Umami web interface that my data had been successfully imported.</li>
<li>In my website, I <a href="https://git.msfjarvis.dev/msfjarvis/msfjarvis.dev/commit/b665eada40250af746d7f5212f6fba5d35086f3e">switched the JS script</a> to the one I got from Umami, and updated the <a href="https://git.msfjarvis.dev/msfjarvis/msfjarvis.dev/commit/e5e4406b3863793eae4a8027b0ed64e92abc2018">Content Security Policy headers</a> to match.</li>
<li>Once this was deployed, I quickly clicked through the site and was able to see my session in Umami and confirm it was working.</li>
</ul>
<p>And that’s kind of it! After this I turned off Plausible and was able to delete the ClickHouse directory and reclaim the nearly half of my 256 GB disk it had been keeping hostage. Umami’s lack of OIDC and easy import options is definitely a sore point, but with how easy the whole thing ended up being for me I can live with it for the time being. Interestingly, <a href="https://msfjarvis.dev/posts/migrating-from-simple-analytics-to-self-hosted-plausible/">Plausible only lasted 8 months</a> before I moved out of it. I should’ve been more wary of ClickHouse than I was of MySQL when I chose Plausible over Matomo :)</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #1 (2026)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-1-2026/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-1-2026/</guid>
      <description>Starting off the year being VERY lazy</description>
      <pubDate>Sat, 03 Jan 2026 10:50:58 GMT</pubDate>
      <content:encoded><![CDATA[<ul>
<li>
<p>I put out a new post this week <a href="https://msfjarvis.dev/posts/coming-around-on-the-utility-of-llms/">on my changing my stance on LLMs as coding assistants</a>. I talked myself out of it a few times because the discourse around generative AI is so polarized but I ended up deciding that I shouldn’t have to censor myself for fear of some angry mentions on Mastodon.</p>
</li>
<li>
<p>The Steam Winter Sale is ending early next week, so I finally pulled the trigger and bought the stuff in my cart, likely to sit in my backlog forever. I ended up cutting out ARC Raiders because the extraction shooter genre isn’t very appealing to me. The games I did buy are listed below:</p>
<ol>
<li>Balatro</li>
<li>PEAK</li>
<li>Slay The Spire</li>
<li>The Witcher trilogy</li>
<li>No Mans Sky</li>
</ol>
</li>
<li>
<p>Of the games I bought, Balatro ended up being the obvious first pick and has replaced Marvel Snap as my casual 15 minute game. I’m pretty sad that the mobile versions don’t sync with my Steam copy of the game so I don’t play it on mobile anymore.</p>
</li>
<li>
<p>My sister called me out of the blue asking why her laptop is not as easily repairable anymore, and we had quite a nice conversation about <a href="https://en.wikipedia.org/wiki/Enshittification">enshittification</a> and <a href="https://en.wikipedia.org/wiki/Planned_obsolescence">planned obsolescence</a>. Pretty proud of her for being so cognizant about the state of consumerism in everyday life.</p>
<ul>
<li>We ended up having another conversation later in the week around copyright law, because she wanted to find a book on <a href="https://en.wikipedia.org/wiki/Anna%27s_Archive">Anna’s Archive</a> and was distraught to discover that the only domain she was aware of had been blocked. Having to explain why domains people paid for can just be taken away was, to say the least, <em>interesting</em>.</li>
</ul>
</li>
<li>
<p>My cheap and reliable fitness tracker, the <a href="https://www.amazon.in/dp/B094NDV47Z">Amazfit GTS 2e</a>, <em>almost</em> succumbed to its 2 years of abuse. Thankfully I was able to seal it back shut with some superglue, but the vibration motor no longer works which means I don’t get reminded to get off my ass every hour and the alarm functionality is useless. Maybe this is the universe’s way of reinforcing my financially irresponsible desire of buying a Pixel Watch and adding <a href="https://wearos.google.com/">WearOS</a> to my skill set.</p>
</li>
</ul>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/my-broken-watch.C603NOsx_ZWASnE.avif" type="image/avif"><source srcset="/_astro/my-broken-watch.C603NOsx_Z3vGUx.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/my-broken-watch.C603NOsx_Z3vGUx.webp" alt="A picture of the aforementioned watch on my arm, with its display precariously detached from the rest of the body and only attached on the left side. The watch is kind of scuffed and filthy." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture>  </figure>  </div>  
<ul>
<li>
<p>I <a href="https://bookwyrm.social/user/msfjarvis/generatednote/9326045">started reading</a> the next part of the <a href="https://en.wikipedia.org/wiki/Silo_(series)">Silo series</a>: <a href="https://en.wikipedia.org/wiki/Silo_(series)#Shift">Shift</a>. With the constant power cuts happening here in Indiranagar, this book on my Kobo has been the only thing I can do reliably without the threat of an arbitrary interruption.</p>
</li>
<li>
<p><a href="https://yashgarg.dev/">Roomie</a> and I met up with <a href="https://github.com/feniljain">Fenil</a> this week for some tea and snacks, was lovely catching up and yapping about stuff. Hopefully we can make it a regular thing!</p>
</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Coming around on the utility of LLMs</title>
      <link>https://msfjarvis.dev/posts/coming-around-on-the-utility-of-llms/</link>
      <guid>https://msfjarvis.dev/posts/coming-around-on-the-utility-of-llms/</guid>
      <description>Having unsuccessfully tried to use Windsurf and GitHub Copilot over the years, OpenCode finally made LLMs useful to me</description>
      <pubDate>Fri, 02 Jan 2026 16:53:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>LLMs are an extremely divisive topic so I hesitated for a while before writing this post but reading <a href="https://www.joanwestenberg.com/the-case-for-blogging-in-the-ruins/">this blog</a> from Joan Westenberg convinced me to not worry too much about what I put on my own blog.</p>
</blockquote>
<p>For the longest time I’ve found LLMs to be quite underwhelming. I tried all the new and fancy models on and off, first via <a href="https://github.com/copilot">GitHub Copilot</a> which I get for free for being an active open source maintainer, and then with Windsurf at <a href="https://cloudflare.com">work</a>. The sycophancy was annoying, the training data was always outdated, and the models were yet to learn how to accept they had no fucking idea about the thing I asked.</p>
<p>That only came to change very recently, with <a href="https://github.com/sst/opencode">OpenCode</a>. We have access to it at work via the <a href="https://www.cloudflare.com/developer-platform/products/ai-gateway/">Cloudflare AI Gateway</a> and the latest Anthropic models.</p>
<p>OpenCode is quite capable by default, with sensible defaults and a functional UI to go with it. What really unlocked its capabilities for me was <a href="https://github.com/code-yeongyu/oh-my-opencode">oh-my-opencode</a>, a plugin that supercharges OpenCode by giving it the capabilities to launch background tasks via “agents” that enhance its capabilities across specific dimensions such as online research, UI design and system design. It adds integrations with the <a href="https://microsoft.github.io/language-server-protocol/">Language Server Protocol</a> to ensure edits don’t introduce diagnostic errors. It includes a comment checker to prevent the typical annoyance of over-commenting code and giving it the distinct LLM smell. It bundles a set of curated <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP servers</a> to enhance its ability to do research: <a href="https://docs.exa.ai/reference/exa-mcp">Exa</a> to do web searches, <a href="https://context7.com/">Context7</a> to find up-to-date documentation, and <a href="https://grep.app">Grep</a> to efficiently search through code on GitHub. All of these complicated bells and whistles come together to become a cohesive tool that can tackle both easy and difficult programming tasks with a very high degree of reliability.</p>
<p>I’ve only started using OpenCode outside work in the past week or two, but the value add is undeniable. Here’s a non-exhaustive list of things I used OpenCode for in <a href="https://github.com/msfjarvis/compose-lobsters">Claw</a>, with varying degrees of complexity and impact.</p>
<ul>
<li>It fixed <a href="https://github.com/msfjarvis/compose-lobsters/commit/9c05e9b2e2364fd7a43ecc3d1dd896ae71ad09e0">a crash</a> that has hit almost every user at some point in the past two years.</li>
<li>It helped me add <a href="https://github.com/msfjarvis/compose-lobsters/commit/7f5dbf79e370e586068c3d2a5031a8d199d398f2">Sentry instrumentation for the app’s database</a> which was previously missing and I kept struggling to understand the right APIs for it.</li>
<li>It helped write an <a href="https://github.com/msfjarvis/compose-lobsters/commit/6d0953742b9036620d10b5fcc221050290d3efb0">end-to-end UI testing</a> suite that ensured common flows didn’t break during my rowdy refactoring.</li>
<li>It fixed <a href="https://github.com/msfjarvis/compose-lobsters/commit/b3373473a79679fb1d260cbd1219fd0208f82bf3">another crash</a> that hit users of the app’s home screen widget</li>
<li>It fixed <a href="https://github.com/msfjarvis/compose-lobsters/commit/7f0acae2da776c3c0c598cf282d5a74220f264a5">yet another crash</a> caused by not handling process death correctly (I can already hear the groans of Android devs worldwide)</li>
<li>It fixed <a href="https://github.com/msfjarvis/compose-lobsters/commit/ab872b6eaa3c8aa8e5e751f15373f4f5ae1023a5">my poorly written deep linking implementation</a></li>
<li>It spent an hour taking apart the internals of the <a href="https://developer.android.com/topic/libraries/architecture/paging/v3-overview">AndroidX Paging3</a> library to fix <a href="https://github.com/msfjarvis/compose-lobsters/commit/7f608fd536a6f58ab1cbf47f5bc634e09f713c04">a performance regression</a> caused by the first fix.</li>
</ul>
<p>None of these are things that would be impossible for me to figure out, but they sure as hell wouldn’t have been done in the span of two weeks. I don’t really subscribe to the idea of fully phoning it in, the so called “vibecoding” aspect of using an LLM as a coding assistant. In every case where the LLM has solved a bug, I’ve learned something new from the fix and even went beyond in many cases because of the leg up I got by using the LLM.</p>
<p>With the <a href="https://docs.sentry.io/product/sentry-mcp/">Sentry MCP</a> server, I can spin up an OpenCode session, say “Investigate the root cause for Sentry issue &lt;id&gt; and include references in the summary” and go off to do <a href="https://bookwyrm.social/user/msfjarvis">something else</a>. When I get back, I have a better understanding of the issue in question, I can use the references to do my own research and tell the LLM to implement one of potentially multiple ways to fix the bug. In many situations, writing the actual code is just not the more fun part of the problem.</p>
<p>That said, I do want to clarify that I still find writing code to be enjoyable or fulfilling in most cases even with an LLM in the picture. <a href="https://github.com/msfjarvis/compose-lobsters/pull/1005">This pull request</a> was mostly me doing a bulk find -&gt; replace across the codebase, something an LLM would excel at. However, I believe I am more capable of learning about <a href="https://github.com/ZacSweers/metro/">Metro</a>’s capabilities ahead of time and spotting opportunities to replace existing constructs with Metro-native implementations, so I did it by hand and manually reviewed each file.</p>
<p>Just a few months ago, I would not have believed that I could use LLMs to write any code I intended to actually maintain. Similar to Simon Willison’s <a href="https://tools.simonwillison.net/">tools.simonwillison.net</a>, I had also set myself up with a <a href="https://git.msfjarvis.dev/msfjarvis/acceptable-vibes/">small repo</a> and a <a href="https://vibes.msfjarvis.dev/">public page</a> that hosted those tools. These things still have the default LLM sheen to them, and I gave up on even <a href="https://git.msfjarvis.dev/msfjarvis/acceptable-vibes/commit/ab038673622fea5e0a1ee0132caa9386735c796b">having them follow a personalized code style</a>. For all definitions of the phrase, it is vibe coding. They solve simple problems for me, and will likely never break or need an update.</p>
<p>Despite my changed opinions about LLMs as programming assistants, I am vehemently against the anthropomorphizing of LLM-based chatbots. They do very <a href="https://www.bbc.com/news/articles/cgerwp7rdlvo">real harm</a>, and should be very aggressively regulated. I hold an even stronger belief that image and video generation models should not be available to the general public at all. The fact that humans can no longer trust even their eyes is deeply disturbing to me. It has been proven that these technologies will actively <a href="https://www.pcgamer.com/software/ai/apparently-the-most-popular-clip-on-openais-new-ai-video-app-sora-depicts-sam-altman-stealing-graphics-cards/">be misused for deceit</a> and in the case of Elon Musk’s xAI, <a href="https://www.seangoedecke.com/grok-deepfakes/">sexual harassment of minors</a>.</p>
<p>This is close to devolving into rambling, so I’ll end it here. I don’t intend to form a crippling reliance on LLMs anytime soon, nor start paying for them. As long as GitHub is willing to continue wasting money on giving me access to LLMs for using their website, I’ll use them for my personal projects. When they stop, I’ll just go back to the good old elbow grease that has got me this far. If I can write Java over SSH into <a href="https://nano-editor.org/">nano</a>, I think I can still use a search engine by hand.</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #52 (2025)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-52-2025/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-52-2025/</guid>
      <description>The final edition of the 2025 weeknotes</description>
      <pubDate>Sun, 28 Dec 2025 14:35:00 GMT</pubDate>
      <content:encoded><![CDATA[<ul>
<li>With some help from my roommate, I’ve started doing some proper weight training at the gym to speed up my weight loss journey and help build up some muscle definition. Way too early for any results, but the burn of a good workout feels nice until the soreness sets in :P</li>
<li>The Periodic Notes plug in I use for Obsidian has a bug with how it handles week alignments (<a href="https://github.com/liamcain/obsidian-periodic-notes/issues/238">here</a>) which prevented the “Open weekly note” command from working. Thankfully I realized that Obsidian plugins are just regular files in my vault so I was able to go in and fix the bug and have it fully work again!</li>
<li>My Telegram account was suspended last week under dubious circumstances, and I was finally able to get it back on Thursday by appealing the suspension again. Apparently this time round my appeal got accepted in 1 minute flat, but I didn’t notice until way later in the day because I had no expectations from them.</li>
<li>I’ve been having a bit of a monster week working on <a href="https://github.com/msfjarvis/compose-lobsters/">Claw</a>, my Android client for <a href="https://lobste.rs">Lobsters</a>. I fixed every single crash logged on Sentry, did a long-pending migration to drop a deprecated dependency injection library, and added a chunky suite of end-to-end tests using <a href="https://maestro.dev">Maestro</a> to paper over the lack of unit testing. Soon™</li>
<li>Making hummus at home has been a longstanding goal of mine which I finally acted on this week, and it came out quite nice! Originally I planned to make my own <a href="https://en.wikipedia.org/wiki/Tahini">Tahini</a> as well but I realized quite quickly that I don’t have the patience to roast sesame seeds for an hour :)</li>
</ul>
<p>I’m writing this at 9 PM on a Sunday which for a weeknote feels like turning in an assignment at 11:59 PM on the due date, going forward I’ll try to block a chunk of time earlier in the day to work on this. The <a href="https://msfjarvis.dev/notes/collating-entries-in-my-obsidian-journal-for-week-notes/">Obsidian setup I created</a> to help with these weeknotes is still coming in quite handy, makes me quite happy that I chose to <a href="https://msfjarvis.dev/posts/migrating-from-logseq-to-obsidian/">migrate out of Logseq</a> earlier this year.</p>]]></content:encoded>
    </item>
    <item>
      <title>Weeknotes: Week #51 (2025)</title>
      <link>https://msfjarvis.dev/posts/weeknotes-week-51-2025/</link>
      <guid>https://msfjarvis.dev/posts/weeknotes-week-51-2025/</guid>
      <description>My very first weeknotes, ironically for the second last week of the year :D</description>
      <pubDate>Sat, 20 Dec 2025 10:55:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I got inspired by <a href="https://abhinavsarkar.net/notes/tags/weeknotes/">Abhinav</a> and <a href="https://ankursethi.com/">Ankur</a> last week to start writing weeknotes, so I spent that weekend setting up my existing journaling system to expose weeknote-relevant entries in a nice single page view (you can read about that <a href="https://msfjarvis.dev/notes/collating-entries-in-my-obsidian-journal-for-week-notes/">here</a>). This is the first weeknote as a direct result of that, being written at <a href="https://underline.center/t/indiewebclub-16-with-ankur-and-tanvi/622">IndieWebClub #16</a>!</p>
<ul>
<li>I finished my personal reading goal of 24 books this year, being tracked <a href="https://bookwyrm.social/user/msfjarvis/goal/2025">here</a>. Next year I wanna blend in a bit more non-fiction into this, and hopefully raise the count by a few more.</li>
<li>Continuing on the book front, I finished the last two short stories from the Murderbot series: <a href="https://torpublishinggroup.com/home-habitat-range-niche-territory/?isbn=9781250838865&amp;format=ebook">Home: Habitat, Range, Niche, Territory</a> and <a href="https://torpublishinggroup.com/rapport-friendship-solidarity-communion-empathy/?isbn=9781250425362&amp;format=ebook">Rapport: Friendship, Solidarity, Communion, Empathy</a>, and started reading <a href="https://en.wikipedia.org/wiki/Silo_(series)#Wool">Wool</a>.</li>
<li>There was an alumni dinner for <a href="https://www.obvious.in/">Obvious</a> on Wednesday where we ended up with 20+ people at <a href="https://www.idylllindia.com/">Idylll</a>. Met a lot of people from before my time at Obvious, and caught up with the ones I did work with. Super fun experience, was very glad that I could make it.</li>
<li>My Telegram account got banned for no particular reason, likely by mass reporting by someone I pissed off and forgot about? I’ve reconvened on Discord for now but it’s clearly a problem that my primary communication platform can just evaporate overnight. I can set something up for myself but moving over friends is gonna be the real pain.</li>
<li>I watched <a href="https://en.wikipedia.org/wiki/Dhurandhar">Dhurandhar</a> on Thursday with a couple of friends and it was great! Bit more gory than I expected from Bollywood and the CGI was a little iffy but the story and acting more than make up for it.</li>
<li>I tried getting back into <a href="https://www.hollowknight.com/">Hollow Knight</a> but the combat is not really working for me, so I’ll let the roguelike fever die down for a bit and pick up <a href="https://en.wikipedia.org/wiki/Control_(video_game)">Control</a> or <a href="https://en.wikipedia.org/wiki/Horizon_Forbidden_West">Horizon Forbidden West</a> which I’ve been sitting on for a while.</li>
<li>At work I’ve mostly been busy with the <a href="https://kotlinlang.org/docs/whatsnew23.html">Kotlin 2.3.0</a> upgrade which has a lot performance improvements and additions relevant to our needs. I was able to get everything to work, but unfortunately can’t ship it without a compatible <a href="https://github.com/touchlab/SKIE/issues/173">SKIE</a> release. Hopefully soon!</li>
<li>I finally added search to this blog! It’s something I had wanted for a long while and being at IndieWebClub finally motivated me to execute on it. I’m pretty happy with the result, but I intend to customize the UI and search result ranking a bit since I saw some instances of tag pages being prioritized over the actual blog post using the tag.</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>My experience at droidcon India 2025</title>
      <link>https://msfjarvis.dev/posts/my-experience-at-droidcon-india-2025/</link>
      <guid>https://msfjarvis.dev/posts/my-experience-at-droidcon-india-2025/</guid>
      <description>I finally stepped out of my house! And met people! So cool!</description>
      <pubDate>Sun, 14 Dec 2025 06:47:00 GMT</pubDate>
      <content:encoded><![CDATA[<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/droidcon-2025-badge.CKOz7EVw_ZuYiel.avif" type="image/avif"><source srcset="/_astro/droidcon-2025-badge.CKOz7EVw_2eno5r.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/droidcon-2025-badge.CKOz7EVw_2eno5r.webp" alt="My droidcon 2025 badge with my name, company and role." data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="3072" height="4080"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">My droidcon 2025 badge</figcaption> </figure>  </div>  
<p>I was at droidcon India <a href="https://india.droidcon.com/agenda">this Saturday</a> and got the chance to meet a bunch of people I hadn’t seen in a while and attend some really great talks. It was a great experience and definitely motivated me to come out for meetups and events a bit more and be less of a loner :P</p>
<p>Before I start walking through my day, I would like to extend an apology to my friends who accompanied me to the conference and the Android developer community at large. I might be the singular dumbass who would be at an event where both <a href="https://x.com/aditlal">Adit Lal</a> and <a href="https://jitinsharma.com/">Jitin Sharma</a> were speaking and plan an itinerary that included neither of their talks. I am extremely sorry and will never live this down.</p>
<p>We arrived at the <a href="https://www.hilton.com/en/hotels/blrkrci-conrad-bengaluru/">Conrad Bengaluru</a> a little before 9 AM to an already bustling space and settled into the Fireside Chat with Google’s DevRel Team. Lots of talk about AI as expected, but also the topic of passkeys and <a href="https://developer.android.com/identity/credential-manager">Credential Manager</a> potentially doing away with the ridiculous OTP authentication flows used by banks here in India (🤞 one can hope).</p>
<p>After a short break we attended our first talk, <a href="https://www.linkedin.com/in/suresh-maidaragi/">Suresh Maidaragi</a> speaking about Building Mobile Apps at Scale with Kotlin Multiplatform. It was a case study on their experience at Physics Wallah of unifying a lagging iOS app with their higher development velocity Android app via KMP. I was interested in learning some new things here as my work has involved a <a href="https://realtime.cloudflare.com/">Kotlin Multiplatform powered SDK</a> for ~2 years now, but was left a little disappointed as the talk was rather shallow on technical details. I was rather ~~surprised~~ horrified to hear that their app takes <em>2 hours</em> to build in their release CI with their current setup!</p>
<p>Another short break and then one of my highlights for the day: <a href="https://www.linkedin.com/in/rahulrav/">Rahul Ravikumar</a>’s “A Busy Android App Developer’s Guide to Perfetto”. Inspired by <a href="https://lalitm.com/">Lalit Maganti</a>’s recent writing I had been teaching myself how to use Perfetto to debug some performance issues at work, so this was perfect for me and the colleagues I had dragged along to the conference (I joke, they came willingly. I think). This talk was very information-dense, I learned a lot about navigating Perfetto and making sense of its seemingly endless UI components. We got a sneak peek at the in-development <a href="https://github.com/androidx/androidx/tree/c347e7ecf3ea6dcdb302ee0f641b6409a44c4f33/tracing/tracing">Tracing 2.0</a> library and all the problems it’s solving from Tracing 1. Especially exciting was <a href="https://github.com/androidx/androidx/tree/c347e7ecf3ea6dcdb302ee0f641b6409a44c4f33/tracing/tracing">context propagation</a>, which can connect parent coroutines to their children in the Perfetto UI allowing you to effectively trace multistep async operations.</p>
<p>The next talk I attended was <a href="https://www.linkedin.com/in/himanshoe/">Himanshu Singh</a> speaking about “Building Android Open Source Libraries: Managing Public APIs with Intention”. Another talk I found to be rather underwhelming, as the synopsis in the agenda made me assume this would carry a little more technical content than it ended up with. Himanshu mostly spoke about the social aspects of starting and managing open source projects in general before mentions of <a href="https://github.com/kotlin/binary-compatibility-validator">Kotlin BCV</a> and release automation towards the end.</p>
<p>After the talk we had a lunch break, and I got to catch up with <a href="https://pratul.com/">Pratul Kalia</a> who used to be my CTO at <a href="https://www.obvious.in/">Obvious</a>. He’s now building <a href="https://tramline.app">Tramline</a> to make mobile releases as streamlined as deploying to the web. I’m a happy solo user of the product, but am otherwise not paid to shill it here :P</p>
<p>We skipped the 1:30 PM to 2:00 PM time slot since and just hung out since none of the talks particularly appealed to our small group of 4.</p>
<p>At 2:00 PM we headed to <a href="https://www.linkedin.com/in/patilsid/">Sid Patil</a> and <a href="https://www.linkedin.com/in/rutvikbhatt/">Rutvik Bhatt</a>’s talk: “The Art of Re-architecture: Lessons from the Trenches at Foodpanda &amp; Delivery Hero”. As someone currently in the weeds of planning a re-architecture myself, their deep insights on how to evaluate the desired outcomes and engaging stakeholders of an engineering investment like this were very helpful. The mantra of “Refactor for today, Re-architect for tomorrow” was particularly inspiring as the resident refactorman of my team 👌</p>
<p>Next up was <a href="https://linkedin.com/in/hadiyarajesh">Rajesh Hadiya</a> with “Build Kotlin Compiler Plugins for Production level Android Apps”. Despite the extremely dense topic he did a great job at making it fun and approachable. I’ve tried to solve a logging-related problem earlier last year with a <a href="https://github.com/msfjarvis/tracelog">Kotlin compiler plugin</a> so I was pretty familiar with the pain involved in dealing with the Kotlin IR and the various compiler backends. Rajesh was able to explain the compiler fundamentals that enable the various frontend/backend compiler plugins work the way they do in a succinct manner.</p>
<p>We skipped the 3:45 PM to 04:05 PM slot as well due to nothing particularly appealing to the group.</p>
<p><a href="https://www.linkedin.com/in/omkartenkale/">Omkar Tenkale</a>’s talk titled “Building the Coroutine Framework from Scratch” fully delivered on the premise. We started by talking about the distinctions between <code>kotlinx.coroutines</code> and <code>kotlin.coroutines</code>, then fully throwing away the former and just relying on the standard library + compiler intrinsics to rebuild the primitives we’ve come to rely on from <code>kotlinx.coroutines</code> such as <code>runBlocking</code>, <code>launch</code>, <code>Dispatchers</code> , <code>GlobalScope</code> and such. The talk was packed to the gills with low level details yet was very approachable and I left the room a lot more knowledgeable and appreciative of <code>kotlinx.coroutines</code>! Omkar’s was easily my second favorite talk here after Rahul’s Perfetto deep dive.</p>
<p>The last talk we attended was <a href="https://www.linkedin.com/in/nikheel-savant/">Nikheel Vishwas Savant</a> from Meta talking about their experience building up a “Cross-Platform Bluetooth Architecture for Android, iOS, and Embedded Devices” for the Meta RayBan glasses. It was expected yet disappointing that we didn’t see any code in this talk, but there were a lot of great nuggets of information that I expect to take back to my work on RealtimeKit.</p>
<p>And that was it for droidcon! By 5:30 PM we were all pretty exhausted so I bid my goodbyes to the people staying back and explored the nearby Church Street area with a couple of friends before heading home.</p>
<p>This was my first proper con-style event in a <em>long</em> time (the last time I was at one was <a href="https://developers.google.com/events/gdd-india/schedule/day1">Google Developer Days</a> back in 2017!) so I was somewhat nervous but it ended up being a great time. I missed a few people who were also there which was sad, but now that I’m in Bengaluru full time I hope to take more opportunities to show up at meetups and see people more than once every 3 years :)</p>]]></content:encoded>
    </item>
    <item>
      <title>Creating private services on NixOS using Tailscale and Caddy</title>
      <link>https://msfjarvis.dev/posts/creating-private-services-on-nixos-using-tailscale-and-caddy/</link>
      <guid>https://msfjarvis.dev/posts/creating-private-services-on-nixos-using-tailscale-and-caddy/</guid>
      <description>A simple guide to setting up private services on NixOS using Tailscale and Caddy with authentication.</description>
      <pubDate>Sat, 13 Sep 2025 15:48:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://tailscale.com">Tailscale</a> is a mesh VPN that makes it dead simple to connect almost any device together in a private network. <a href="https://caddyserver.com">Caddy</a> is a web server that focuses on ease of use and automatic HTTPS. I am a fan of both of these, and I was very excited to discover that Tailscale has an experimental <a href="https://github.com/tailscale/caddy-tailscale">integration with Caddy</a> that leverages their <a href="https://tailscale.com/kb/1244/tsnet"><code>tsnet</code></a> library to allow creating unique Tailscale addresses for individual virtual hosts in your Caddy configuration. Here’s a quick run down of how to set this up on NixOS.</p>
<h2 id="installing-caddy-tailscale-for-nixos">Installing caddy-tailscale for NixOS</h2>
<p>As of the latest NixOS release, the Caddy package in nixpkgs supports including plugins directly which greatly simplifies this step. As an example, here’s how I include the caddy-tailscale plugin with the excellent <a href="https://github.com/JasonLovesDoggo/caddy-defender">caddy-defender</a> that lets me drop traffic from AI scrapers more safely.</p>
<blockquote>
<p>You can also keep the package definition in a separate file <a href="https://github.com/msfjarvis/dotfiles/blob/94b443ce6748a1897b7b839e1564eca34bfcbe3e/packages/caddy-with-plugins/default.nix">like this</a>, and use <a href="https://github.com/msfjarvis/dotfiles/blob/94b443ce6748a1897b7b839e1564eca34bfcbe3e/dev/caddy/update_caddy_plugins.py">this script</a> to keep it up-to-date. DISCLAIMER: The script was vibe coded by Claude, all I’ve done is checked it for obviously silly shit.</p>
</blockquote>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="diff"><code><span class="line"><span style="color:#E1E4E8">   services.caddy = {</span></span>
<span class="line"><span style="color:#E1E4E8">     enable = true;</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>    package =</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>      pkgs.caddy.withPlugins {</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>        plugins = [</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>          "github.com/jasonlovesdoggo/caddy-defender@v0.8.5"</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>          "github.com/tailscale/caddy-tailscale@v0.0.0-20250207163903-69a970c84556"</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>        ];</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>        hash = "sha256-z+zj3rfXbyxldRjO1yoLD77ACRWEAofzMDiZe/bHAqw=";</span></span>
<span class="line"><span style="color:#85E89D"><span style="user-select:none">+</span>     }</span></span>
<span class="line"><span style="color:#E1E4E8">     globalConfig = ''</span></span>
<span class="line"><span style="color:#E1E4E8">       servers {</span></span></code></pre>
<p>This will swap out the <code>caddy</code> binary in your NixOS configuration with one that has the <code>caddy-tailscale</code> module patched in. You will also need to provide a Tailscale authkey to allow the <code>caddy-tailscale</code> module to interact with the Tailscale API and provision new nodes in your Tailnet. This can be done by setting the <code>TS_AUTHKEY</code> environment variable in your Caddy service configuration. Here’s an example of how to do so using <a href="https://github.com/Mic92/sops-nix"><code>sops-nix</code></a>:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="nix"><code><span class="line"><span style="color:#FFAB70">sops</span><span style="color:#F97583">.</span><span style="color:#FFAB70">secrets</span><span style="color:#F97583">.</span><span style="color:#FFAB70">services-tsauthkey-env</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">  sopsFile</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> ./secrets/tailscale.env</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  owner</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> config</span><span style="color:#F97583">.</span><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">user</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  format</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "dotenv"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span>
<span class="line"><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">environmentFile</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#FFAB70"> config</span><span style="color:#F97583">.</span><span style="color:#FFAB70">sops</span><span style="color:#F97583">.</span><span style="color:#FFAB70">secrets</span><span style="color:#F97583">.</span><span style="color:#FFAB70">services-tsauthkey-env</span><span style="color:#F97583">.</span><span style="color:#FFAB70">path</span><span style="color:#FDAEB7;font-style:italic">;</span></span></code></pre>
<p>Create the <code>secrets/tailscale.env</code> file with sops, the contents should look something like this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>TS_AUTHKEY=tskey-auth-something-something</span></span></code></pre>
<h2 id="setting-up-your-first-private-service">Setting up your first private service</h2>
<p>Now we’re ready to rumble. To showcase the full capabilities of <code>caddy-tailscale</code>, we’re going to set up <a href="https://grafana.com">Grafana</a> with <a href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/auth-proxy/">proxy authentication</a> and let Tailscale <a href="https://github.com/tailscale/caddy-tailscale?tab=readme-ov-file#authentication-provider">handle the authentication</a> for us. Here’s a simple example (N.B. <code>$TAILNET_NAME</code> should be replaced with your Tailscale network name.):</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="nix"><code><span class="line"><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">virtualHosts</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#9ECBFF">  "https://</span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">config</span><span style="color:#F97583">.</span><span style="color:#79B8FF">services</span><span style="color:#F97583">.</span><span style="color:#79B8FF">grafana</span><span style="color:#F97583">.</span><span style="color:#79B8FF">settings</span><span style="color:#F97583">.</span><span style="color:#79B8FF">server</span><span style="color:#F97583">.</span><span style="color:#79B8FF">domain</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">"</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">    extraConfig</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> ''</span></span>
<span class="line"><span style="color:#9ECBFF">      # This will create a new node at grafana.$TAILNET_NAME.ts.net</span></span>
<span class="line"><span style="color:#9ECBFF">      bind tailscale/grafana</span></span>
<span class="line"><span style="color:#9ECBFF">      # Enables the Tailscale authentication provider</span></span>
<span class="line"><span style="color:#9ECBFF">      tailscale_auth</span></span>
<span class="line"><span style="color:#9ECBFF">      # Forwards your Tailscale user ID to Grafana. Usually your email address.</span></span>
<span class="line"><span style="color:#9ECBFF">      reverse_proxy </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">config</span><span style="color:#F97583">.</span><span style="color:#79B8FF">services</span><span style="color:#F97583">.</span><span style="color:#79B8FF">grafana</span><span style="color:#F97583">.</span><span style="color:#79B8FF">settings</span><span style="color:#F97583">.</span><span style="color:#79B8FF">server</span><span style="color:#F97583">.</span><span style="color:#79B8FF">http_addr</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">:</span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">toString</span><span style="color:#79B8FF"> config</span><span style="color:#F97583">.</span><span style="color:#79B8FF">services</span><span style="color:#F97583">.</span><span style="color:#79B8FF">grafana</span><span style="color:#F97583">.</span><span style="color:#79B8FF">settings</span><span style="color:#F97583">.</span><span style="color:#79B8FF">server</span><span style="color:#F97583">.</span><span style="color:#79B8FF">http_port</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF"> {</span></span>
<span class="line"><span style="color:#9ECBFF">        header_up X-Webauth-User {http.auth.user.tailscale_user}</span></span>
<span class="line"><span style="color:#9ECBFF">      }</span></span>
<span class="line"><span style="color:#9ECBFF">    ''</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">  };</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">grafana</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">  enable</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  settings</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#9ECBFF">    "auth.proxy"</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6A737D">      # Enable proxy-based authentication</span></span>
<span class="line"><span style="color:#B392F0">      enabled</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#6A737D">      # Automatically create new accounts for people who access the service</span></span>
<span class="line"><span style="color:#B392F0">      auto_sign_up</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#6A737D">      # Do not store login cookies, instead always relying on proxy authentication</span></span>
<span class="line"><span style="color:#B392F0">      enable_login_token</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> false</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">    };</span></span>
<span class="line"><span style="color:#B392F0">    server</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">      domain</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "grafana.$TAILNET_NAME.ts.net"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">      http_addr</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "127.0.0.1"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">      http_port</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> 2342</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">    };</span></span>
<span class="line"><span style="color:#E1E4E8">  };</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span></code></pre>
<p>This configuration will set up a new Grafana instance at <code>https://grafana.$TAILNET_NAME.ts.net</code> that is only accessible to members of your Tailscale network. When you visit the URL, your Tailscale identity will be used to log you into your existing Grafana account, or sign you up if this is your first time visiting the service.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/grafana-profile.C-d3OEw7_ZDKVBF.avif" type="image/avif"><source srcset="/_astro/grafana-profile.C-d3OEw7_Z2v0E89.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/grafana-profile.C-d3OEw7_Z2v0E89.webp" alt="The profile page on Grafana, showing the name, email and username fields with an additional 'synced via auth proxy' label next to them. The email and username have been blurred out to redact them for privacy reasons" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="1959" height="780"> </picture> <figcaption class="image-modal__caption" data-astro-cid-qj24g3rp="">Grafana profile showing that user identification is being done via the Tailscale proxy</figcaption> </figure>  </div>  
<h2 id="caveats">Caveats</h2>
<p>While this approach works great for most needs, there are a few caveats to be aware of:</p>
<ul>
<li>Each service created this way registers as a new device in your Tailnet. Make sure to remain aware of the devices limit on your account.</li>
<li>Since each “virtual” device being created via this is technically backed by your physical machine, the “Services” tab in your Tailscale dashboard will end up with duplicate entries of services that are running on the same machine. For example, I run 5 <code>caddy-tailscale</code> services on my server, which results in 6 entries of the SSH service being listed. This may be a deal breaker to you, depending on how you use the Tailscale dashboard.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Despite its experimental status, <code>caddy-tailscale</code> is pretty powerful and exposes enough functionality to cover most basic use cases. I’ve been pretty happy running it in my homelab for the past couple months and I’m excited to see how it evolves in the future. If you have any questions or corrections, please let me know either in the comments below or on <a href="https://androiddev.social/@msfjarvis">Mastodon</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Migrating from Gitea to Forgejo the long way</title>
      <link>https://msfjarvis.dev/posts/migrating-from-gitea-to-forgejo-the-long-way/</link>
      <guid>https://msfjarvis.dev/posts/migrating-from-gitea-to-forgejo-the-long-way/</guid>
      <description>With the renewed interest in Forgejo I decided to finally pull the plug on moving out of Gitea, and this is how it went.</description>
      <pubDate>Mon, 01 Sep 2025 14:02:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I’ve had a Git server at <a href="https://git.msfjarvis.dev/">https://git.msfjarvis.dev</a> for a while now, running <a href="https://about.gitea.com/">Gitea</a> but my faith in the project’s open core model has steadily been going down. When Codeberg announced they were forking Gitea as <a href="https://forgejo.org">Forgejo</a> I quietly put down a line item to switch over in my overflowing TODO list and promptly forgot about it.</p>
<p>GitHub has been getting increasingly unpleasant for the past few years, but them officially announcing its demise as an independent entity and joining Microsoft’s “CoreAI” division was the straw that broke the camel’s back and made me want to use my own Git forge more actively. Thus the Forgejo migration shot up in my TODO list, and ended up being a rather involved process.</p>
<blockquote>
<p>This post is gonna have some rambling and going around in circles, but the TL;DR is that I ended up having to migrate individual accounts instead of the whole instance data by using a tool that I ironically generated using GitHub Copilot (the code is available <a href="https://git.msfjarvis.dev/msfjarvis/acceptable-vibes/src/branch/main/gitea-forgejo-migrator">on my Git server</a>).</p>
</blockquote>
<h1 id="attempt-1-the-naive-way">Attempt 1: The naive way</h1>
<p>Forgejo initially used to support migrating data from Gitea pretty easily, but the increased friction of having to maintain a full fork against a moving target forced them to <a href="https://forgejo.org/2024-12-gitea-compatibility/">drop support for these migration paths</a>. I of course am stupid and was not aware that I had missed my window, so I had to make the unpleasant discovery that simply swapping the latest Gitea for latest Forgejo will not work anymore. The <a href="https://nixos.org/manual/nixos/stable/#module-forgejo-migration-gitea">NixOS manual’s migration section</a> suggested that this was possible, but it’s actually missing crucial information about version requirements that was <a href="https://github.com/NixOS/nixpkgs/commit/91947bb68e8184eba4c14476a6a14873f15e9ed4">added later</a>.</p>
<p>My naive attempt <em>almost</em> caused me to irreversibly corrupt my instance data, so I was more careful with future attempts and used a backup each time.</p>
<h1 id="attempt-2-the-gitea-dump-command">Attempt 2: The <code>gitea dump</code> command</h1>
<p>This looked like it had some potential until I realized the lack of a companion <code>gitea restore</code> functionality. The generated dump is a big ZIP file of your entire state directory, along with the SQL commands to recreate your database with whatever database engine you were using. The recommended way to use this dump is to unzip it and then replay the SQL commands into <code>sqlite</code> (my current database) to build up the state directory. This approach kind of felt like a dead-end already but I gave it a good try anyway.</p>
<p>I had also chosen to run Forgejo using PostgreSQL instead of SQLite, since I was already using it with a bunch of other software running on this machine and had proper backup strategies in place. This meant I had to reach out for <a href="https://pgloader.readthedocs.io/en/latest/">pgloader</a> to import the SQLite-compatible dump into Postgres which worked out pretty well. Running <code>pgloader sqlite:///var/lib/gitea/data/gitea.db 'postgresql://forgejo:@/forgejo?host=/run/postgresql'</code> as root was able to successfully migrate the database to Postgres.</p>
<p>Unfortunately this didn’t work due to the previously discussed version incompatibilities, and Forgejo rejected this database I had created for it. At this point I had lost a couple hours on this and constant electricity problems at my place had additionally caused my patience to run thin. I decided to cut my losses and re-deployed Forgejo with the same settings as my Gitea server, deciding that I would figure out a way to just copy my account data over since the instance configuration was managed by NixOS anyway (for the most part).</p>
<h1 id="attempt-3-vibe-coding-deployed-somewhat-effectively">Attempt 3: Vibe coding deployed somewhat effectively</h1>
<blockquote>
<p>I’m not generally a big believer in the AI hype. I have yet to pay for any of these tools, between GitHub and my employer I get plenty of access to top of the line models that are supposedly reinventing my field of work every 3 months. LLMs have yet to meaningfully help me at work, and the only times I’ve gotten real value out of them is by getting them to write one-off things like this that I am glad to have but would likely never invest the time to upskill into and build myself.</p>
</blockquote>
<p>I hooked up the free license to GitHub Copilot that I get for <a href="https://docs.github.com/en/copilot/get-started/plans">satisfying some criteria of “popular open source maintainer”</a> into <a href="https://zed.dev">Zed</a> and wrote out a simple README file describing what I wanted out of the tool and had it go to town. The end result of this was an unnecessarily abstracted Go project (I doubt anybody would use that directory structure for a project this small) that looked like it would do the job. You can find the source code for this <a href="https://git.msfjarvis.dev/msfjarvis/acceptable-vibes/src/branch/main/gitea-forgejo-migrator">here</a>.</p>
<blockquote>
<p>According to <a href="https://github.com/msfjarvis/msfjarvis.dev/issues/80#issuecomment-3446970120">a comment</a> on this post, organizations may not be migrated correctly by this script. I do not wish to change the code to an untested version so be prepared to do some manual fixes to address that deficiency. I would also accept a patch from someone who does end up fixing the script for themselves.</p>
</blockquote>
<p>Now onto actually running this tool. For this to work, it would require both my new Forgejo server and my old Gitea server to be up at the same time. First hurdle: conflicting ports. This was solved <a href="https://git.msfjarvis.dev/msfjarvis/dotfiles/commit/9a8cdd36cdf3f0b93834c86112fd113634985587">pretty easily</a>.</p>
<p>I screwed up here by running Forgejo on the primary domain and deploying the old Gitea server into a new Tailscale-based service. This caused multiple failures:</p>
<h3 id="inability-to-log-into-the-gitea-instance">Inability to log into the Gitea instance.</h3>
<p>I could no longer sign into Gitea since I had 2FA enabled using Passkeys which require the domains to match up.</p>
<p>To make up for the inability to log into my Gitea server to create an access token, I just used the gitea CLI instead.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>sudo -i su - gitea</span></span>
<span class="line"><span># To get the actual path to the Gitea binary, which weirdly isn't installed for the gitea user?</span></span>
<span class="line"><span>systemctl cat gitea.service | grep ExecStart=</span></span>
<span class="line"><span>/nix/store/foo-bar-baz-gitea-1.23.1/bin/gitea admin -w $(pwd) user generate-access-token --token-name forgejo-migration --scopes "read:repository,read:user" --raw</span></span></code></pre>
<p>On the forgejo side, a similar dance ensued but this time to actually create my account since this was a fresh start.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plain"><code><span class="line"><span>sudo -i su - forgejo</span></span>
<span class="line"><span>systemctl cat forgejo.service | grep ExecStart=</span></span>
<span class="line"><span># yes the actual CLI seems to still be available as gitea</span></span>
<span class="line"><span>/nix/store/foo-bar-baz-forgejo-10.0.0/bin/gitea admin user create -w $(pwd) --username msfjarvis --email me@msfjarvis.dev --admin --access-token --access-token-name forgejo-migration --access-token-scopes "read:user,write:repository"</span></span></code></pre>
<p>With access tokens in hand, I ran the tool and hit my second problem.</p>
<h3 id="tailscale-acls-bite-me-in-the-ass">Tailscale ACLs bite me in the ass</h3>
<p>The way Tailscale’s networking shebang works meant that the server running this isolated Gitea service couldn’t connect to it via the network without some tweaks to my ACL policies. I was not feeling like I had this extra debugging in me, so I opted to just temporarily hijack another subdomain pointing to this server for the purpose and resumed from there.</p>
<h3 id="mirrors-of-github-private-repos-did-not-work">Mirrors of GitHub private repos did not work</h3>
<p>Back when I first created mirrors of all my stuff I had also done so for my private repos and just forgot about it. When the tool tried to migrate them they obviously failed to mirror since I wasn’t providing any access tokens for GitHub. I went back to Zed and had Claude add a retry for this and pull an access token from the <code>$GITHUB_TOKEN</code> environment variable.</p>
<p>When I ran the tool again it was able to successfully recreate all the repositories from my Gitea server.</p>
<h1 id="conclusion">Conclusion</h1>
<p>Overall this migration was worthwhile for me, both for documenting it for others in my position as well as for finally being free of Gitea. I trust the people in charge significantly more, and they continue to deliver <a href="https://forgejo.org/2025-07-release-v12-0/">great work</a> driven in part by them actually using their own software at scale.</p>]]></content:encoded>
    </item>
    <item>
      <title>Mildly overengineering my Glance configuration</title>
      <link>https://msfjarvis.dev/posts/mildly-overengineering-my-glance-configuration/</link>
      <guid>https://msfjarvis.dev/posts/mildly-overengineering-my-glance-configuration/</guid>
      <description>The story of setting up a live environment for configuring my Glance dashboard</description>
      <pubDate>Wed, 07 May 2025 17:35:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>May 10th update: Powered by a lack of sleep and extreme fatigue I cooked up a far simpler solution that is mentioned at the end of the post.</p>
</blockquote>
<h1 id="setting-the-scene">Setting the scene</h1>
<p>I’m a very happy user of the <a href="https://github.com/glanceapp/glance">Glance</a> dashboard, and make use of it multiple times a day.</p>
<p>As a NixOS user, I have a very popular problem of wanting to iterate on my Glance configuration without having to rebuild my whole system every time to see the change.</p>
<p>One way I’ve seen this addressed (please message me on <code>@msfjarvis@androiddev.social</code> if you can find the actual blog post) is to have some sort of “dev” switch that generates a writable version of your configuration using some <a href="https://github.com/nix-community/home-manager">home-manager</a> tricks that you can poke at before encoding it into Nix. Turning off the aforementioned “dev” mode will make the configuration go through the more usual Nix ways and give you your declarative system back.</p>
<p>I think that idea is cool! I also think that idea kinda sucks when the configuration is in a “language” you despise, like YAML. Which is where I am!</p>
<h1 id="coming-up-with-a-solution">Coming up with a solution</h1>
<p>At the end of the day, the problem I have is that there are too many intermediate steps between me writing Nix and it being converted into YAML. So let’s break it down!</p>
<p>Phase 1: Pull out the Nix bits I need to a standalone file that I can muck around with: <a href="https://msfjarvis.dev/g/dotfiles/39c90cb831c6">done</a></p>
<p>Now, we need a way to convert this Nix code into a YAML file that Glance can ingest. I came across a relatively new project that solves exactly this: <a href="https://github.com/theobori/nix-converter">nix-converter</a>.</p>
<p>Running <code>nix-converter --from-nix -f path/to/file.nix -l yaml</code> outputs YAML to stdout, ready to be shoved wherever you desire. This worked almost flawlessly, except an easily worked-around issue:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="nix"><code><span class="line"><span style="color:#E1E4E8">{</span></span>
<span class="line"><span style="color:#6A737D">  # This generates the following (incorrect) YAML</span></span>
<span class="line"><span style="color:#6A737D">  # foo: "baz"</span></span>
<span class="line"><span style="color:#B392F0">  foo</span><span style="color:#E1E4E8">.</span><span style="color:#B392F0">bar</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "baz"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#6A737D">  # This way of declaraing the attrsets works around the</span></span>
<span class="line"><span style="color:#6A737D">  # issue and generates the following:</span></span>
<span class="line"><span style="color:#6A737D">  # foo:</span></span>
<span class="line"><span style="color:#6A737D">  #   bar: baz</span></span>
<span class="line"><span style="color:#B392F0">  foo</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">    bar</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "baz"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">  };</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>Glance will automatically reload itself if the config file changes, which
greatly simplifies the setup. The only remaining bit we have is to automatically
generate this YAML every time we make a change to the Nix file, so that we can freely
switch between editor and web browser to see changes without taking a trip to the
terminal.</p>
<p>I solved this using <a href="https://github.com/watchexec/watchexec">watchexec</a> and a tiny Bash script that I stuffed
into a <a href="https://github.com/numtide/devshell">devshell</a> command:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#E1E4E8">out_conf</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">$(</span><span style="color:#B392F0">mktemp</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">${lib.getExe pkgs.watchexec} -r -w </span><span style="color:#9ECBFF">"${</span><span style="color:#E1E4E8">glanceConf</span><span style="color:#9ECBFF">}"</span><span style="color:#E1E4E8"> -- </span><span style="color:#9ECBFF">"${</span><span style="color:#E1E4E8">lib</span><span style="color:#9ECBFF">.</span><span style="color:#E1E4E8">getExe</span><span style="color:#E1E4E8"> pkgs</span><span style="color:#9ECBFF">.</span><span style="color:#E1E4E8">nix-converter</span><span style="color:#9ECBFF">} --from-nix -f ${</span><span style="color:#E1E4E8">glanceConf</span><span style="color:#9ECBFF">} -l yaml &gt; </span><span style="color:#E1E4E8">$out_conf</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8"> &amp;</span></span>
<span class="line"><span style="color:#E1E4E8">${lib.getExe pkgs.glance} </span><span style="color:#79B8FF">-config</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$out_conf</span><span style="color:#9ECBFF">"</span></span></code></pre>
<p>This is a mix of regular Bash and Nix which will make sense to Nix users, but
for everyone else: <code>${lib.getExe pkgs.watchexec}</code> gives you a path of the form
<code>/nix/store/store-hash-watchexec-version/bin/watchexec</code> which is the binary you
can execute. <code>$glanceConf</code> is a Nix variable containing a string path to the
<code>glance.nix</code> file we pulled out earlier.</p>
<p>All put together it looks like
<a href="https://msfjarvis.dev/g/dotfiles/e1bffa7e9d97">this</a>. If I need to edit my
Glance config, I will go into the <code>glance</code> devShell, boot up
<a href="https://zed.dev">Zed</a>, run the <code>dev</code> command in my terminal and enjoy a fully
automated iteration experience.</p>
<h2 id="caveats">Caveats</h2>
<p>This setup is not even remotely perfect, <code>nix-generator</code> has bugs and discrepancies compared to the Nix to YAML implementation inside Nixpkgs. Not all constructs that work fine with Nixpkgs work that well with <code>nix-generator</code>, complicating development.</p>
<h2 id="may-10th-update">May 10th update</h2>
<p>Out of the blue I had a minor crisis looking at the kludge I had previously put together and realized there was actually a simpler way to be generating this YAML code without having to forego the niceties of Nixpkgs. The end result of that is <a href="https://msfjarvis.dev/g/dotfiles/6d3eb0c849be">this commit</a>, which I will explain below.</p>
<p>The bulk of the work is now handled by a bash script, which I have annotated as comments below to explain what’s going on.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A737D">#!/usr/bin/env bash</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># EXPR is a Nix expression that we will be evaluating</span></span>
<span class="line"><span style="color:#6A737D"># and building to obtain our final YAML</span></span>
<span class="line"><span style="color:#E1E4E8">EXPR</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span></span>
<span class="line"><span style="color:#9ECBFF">let</span></span>
<span class="line"><span style="color:#9ECBFF">  # Load nixpkgs from the search path because it's the easiest way to do it.</span></span>
<span class="line"><span style="color:#9ECBFF">  pkgs = import &lt;nixpkgs&gt; { };</span></span>
<span class="line"><span style="color:#9ECBFF">  # This is the same code we use in our Glance module to generate the YAML</span></span>
<span class="line"><span style="color:#9ECBFF">  settingsFormat = pkgs.formats.yaml { };</span></span>
<span class="line"><span style="color:#9ECBFF">  settingsFile = settingsFormat.generate </span><span style="color:#79B8FF">\"</span><span style="color:#9ECBFF">glance.yaml</span><span style="color:#79B8FF">\"</span><span style="color:#9ECBFF"> (import ./</span><span style="color:#79B8FF">${1</span><span style="color:#F97583">:?</span><span style="color:#79B8FF">}</span><span style="color:#9ECBFF">);</span></span>
<span class="line"><span style="color:#9ECBFF">in</span></span>
<span class="line"><span style="color:#9ECBFF"># settingsFile returns a derivation that the `</span><span style="color:#B392F0">nix</span><span style="color:#9ECBFF"> build` below will, well, build.</span></span>
<span class="line"><span style="color:#79B8FF">\"\$</span><span style="color:#9ECBFF">{settingsFile}</span><span style="color:#79B8FF">\"</span></span>
<span class="line"><span style="color:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0">nix</span><span style="color:#9ECBFF"> build</span><span style="color:#79B8FF"> \</span></span>
<span class="line"><span style="color:#6A737D">  # Prevent Nix from trying to check binary caches each time we build</span></span>
<span class="line"><span style="color:#B392F0">  --option</span><span style="color:#9ECBFF"> substitute</span><span style="color:#79B8FF"> false</span><span style="color:#79B8FF"> \</span></span>
<span class="line"><span style="color:#6A737D">  # Feed the nixpkgs input from your flakes registry to the search path</span></span>
<span class="line"><span style="color:#B392F0">  --impure</span><span style="color:#79B8FF"> -I</span><span style="color:#9ECBFF"> nixpkgs=flake:nixpkgs</span><span style="color:#79B8FF"> \</span></span>
<span class="line"><span style="color:#6A737D">  # Pass in our expression from above that will emit the derivation to be built</span></span>
<span class="line"><span style="color:#B392F0">  --expr</span><span style="color:#9ECBFF"> "${</span><span style="color:#E1E4E8">EXPR</span><span style="color:#9ECBFF">}"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># ./result is the default nix3-build output path</span></span>
<span class="line"><span style="color:#B392F0">glance</span><span style="color:#79B8FF"> -config</span><span style="color:#9ECBFF"> ./result</span></span></code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Migrating from Simple Analytics to self-hosted Plausible</title>
      <link>https://msfjarvis.dev/posts/migrating-from-simple-analytics-to-self-hosted-plausible/</link>
      <guid>https://msfjarvis.dev/posts/migrating-from-simple-analytics-to-self-hosted-plausible/</guid>
      <description>Documenting the misadventures of self-hosting my site analytics</description>
      <pubDate>Tue, 22 Apr 2025 11:13:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>For the longest time (nearly 6 years according to the data export), I have been a user of <a href="https://www.simpleanalytics.com/">Simple Analytics</a> and their dead simple web analytics offering. I believe I got access to it via GitHub’s big bundle of freemium services provided to students and they just didn’t bother checking that I had gone to and dropped out of college twice since they first offered me free access until this month.</p>
<p>So while I was rather satisfied with the free ride I had gotten from Simple Analytics till now, having to pay a rather eye-watering 15 USD every month for it was stretching the otherwise $0 USD budget for my blog. The site itself is hosted by Netlify, but I have a small server from <a href="https://netcup.de">netcup</a> which was suitable to take on the load of running a self-hosted analytics server that I could switch to.</p>
<h1 id="choosing-the-analytics-service">Choosing the analytics service</h1>
<p>Like the absolute chump I am, my first visit was to <a href="https://awesome-selfhosted.net/tags/analytics.html">awesome-selfhosted’s analytics page</a> and from there, the only options that stuck out were <a href="https://plausible.io">Plausible</a> and <a href="https://matomo.org/">Matomo</a>.</p>
<p>I will be honest here, the reason to not choose Matomo was very superficial - I didn’t want to be running PHP and MySQL. Plausible is an Elixir app and thus not much better as far as me understanding the language goes, but I was reasonably more confident about its choice of Clickhouse and Postgres as the backing data stores.</p>
<h1 id="getting-set-up">Getting set up</h1>
<p>My server runs NixOS, and thankfully there is a module for Plausible already so setup was very simple and all I had to add was this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="nix"><code><span class="line"><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">caddy</span><span style="color:#F97583">.</span><span style="color:#FFAB70">virtualHosts</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#9ECBFF">  "https://stats.msfjarvis.dev"</span><span style="color:#F97583"> =</span><span style="color:#F97583"> let</span></span>
<span class="line"><span style="color:#B392F0">    p</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> config</span><span style="color:#F97583">.</span><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">plausible</span><span style="color:#F97583">.</span><span style="color:#FFAB70">server</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583">  in</span></span>
<span class="line"><span style="color:#E1E4E8">  {</span></span>
<span class="line"><span style="color:#B392F0">    extraConfig</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> ''</span></span>
<span class="line"><span style="color:#9ECBFF">      reverse_proxy </span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">p</span><span style="color:#F97583">.</span><span style="color:#79B8FF">listenAddress</span><span style="color:#9ECBFF">}</span><span style="color:#9ECBFF">:</span><span style="color:#9ECBFF">${</span><span style="color:#79B8FF">toString</span><span style="color:#79B8FF"> p</span><span style="color:#F97583">.</span><span style="color:#79B8FF">port</span><span style="color:#9ECBFF">}</span></span>
<span class="line"><span style="color:#9ECBFF">    ''</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">  };</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#FFAB70">sops</span><span style="color:#F97583">.</span><span style="color:#FFAB70">secrets</span><span style="color:#F97583">.</span><span style="color:#FFAB70">plausible-secret</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">  sopsFile</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> lib</span><span style="color:#F97583">.</span><span style="color:#FFAB70">snowfall</span><span style="color:#F97583">.</span><span style="color:#FFAB70">fs</span><span style="color:#F97583">.</span><span style="color:#FFAB70">get-file</span><span style="color:#9ECBFF"> "secrets/plausible.yaml"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span>
<span class="line"><span style="color:#FFAB70">services</span><span style="color:#F97583">.</span><span style="color:#FFAB70">plausible</span><span style="color:#FDAEB7;font-style:italic"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">  enable</span><span style="color:#F97583"> =</span><span style="color:#79B8FF"> true</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  server</span><span style="color:#F97583"> =</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#B392F0">    baseUrl</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "https://stats.msfjarvis.dev"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">    secretKeybaseFile</span><span style="color:#F97583"> =</span><span style="color:#FFAB70"> config</span><span style="color:#F97583">.</span><span style="color:#FFAB70">sops</span><span style="color:#F97583">.</span><span style="color:#FFAB70">secrets</span><span style="color:#F97583">.</span><span style="color:#FFAB70">plausible-secret</span><span style="color:#F97583">.</span><span style="color:#FFAB70">path</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">  };</span></span>
<span class="line"><span style="color:#E1E4E8">}</span><span style="color:#FDAEB7;font-style:italic">;</span></span></code></pre>
<p>The only hiccup here was me apparently not knowing what the hell is 64 bytes supposed to mean and failing to fill in a long enough string into the <code>secretKeybaseFile</code>, which is how it came to end with <code>brother-man-please-work-what-the-fuck-man</code> during testing after which I swapped it out for a 20 word passphrase generated by Bitwarden.</p>
<p>Once everything was running, I set up an admin account and replaced the SimpleAnalytics JS snippet on my website with the one Plausible gave me. Notable difference here was a lack of tracking for users who disable JS, so that’s a net improvement to user privacy that I was too naive to think about in 2020.</p>
<h1 id="migrating-existing-data-from-simple-analytics">Migrating existing data from Simple Analytics</h1>
<p>To their credit, Simple Analytics made this very easy and the export option is at the top of their settings page. My only complaint is about their range selection feature, I found the calendar widget very un-intuitive and the input field didn’t let me just fill in the date manually.</p>
<p>Once I had the 45 mB CSV in hand, I looked around for someone else’s homework to copy so I didn’t have to write the migration tool myself. I came across <a href="https://github.com/magiobus/simpleanalytics-to-plausible-converter">this</a> repo which looked like it would do the job but I had no plans to install NodeJS, so I had gpt-4o (via my free, GitHub-provided Copilot license) rewrite it as a single-file Python script that I could hack on more comfortably. That version is <a href="https://git.msfjarvis.dev/msfjarvis/simpleanalytics-to-plausible">available here</a>, with some additional changes described below.</p>
<p>I ran the script and got a handful of CSVs that Plausible was happy to import, after which I realized that the location data was completely missing. Indeed, my hopes of not having to write any code were going to remain so.</p>
<p>I pulled up the <a href="https://plausible.io/docs/csv-import#csv-format-guidelines">Plausible CSV import docs</a>, got the headers it was expecting for the location CSV and was able to piece together the data relatively easily. One big omission was Simple Analytics not recording cities for page visits, which Plausible had a field for. After figuring out what it was expecting, I filled in zeroes for that column.</p>
<blockquote>
<p>I have a big gripe with Plausible’s non-existent error reporting here. When my newly generated CSV failed to import, Plausible simply put up an ⚠️ sign in the list of imports and offered no logs. After digging through their GitHub issues I discovered that the errors had to be <strong>pulled out from a Postgres table</strong> which I was not very impressed by. For others who come by here with the same problem: it’s in the <code>errors</code> column in the <code>oban_jobs</code> table of the <code>plausible</code> database.</p>
</blockquote>
<p>The final step was making the data public like it was on Simple Analytics, so my humble blog’s popularity or lack thereof can continue to be <a href="https://stats.msfjarvis.dev/msfjarvis.dev">scrutinized by anyone</a>.</p>
<h1 id="wrapping-up">Wrapping up</h1>
<p>All things considered this took me approximately an hour to get done, most of which was spent digging through Plausible’s GitHub issues and re-learning how to use the <code>psql</code> CLI. I’m quite pleased with how it turned out in the end!</p>
<p>Thanks to <a href="https://mastodon.social/@tanvibhakta">Tanvi</a> and <a href="https://fantastic.earth/@abnv">Abhinav</a> for answering some questions regarding Plausible.</p>]]></content:encoded>
    </item>
    <item>
      <title>Overengineering an Obsidian dashboard to get better at Marvel Snap</title>
      <link>https://msfjarvis.dev/posts/overengineering-an-obsidian-dashboard-to-get-better-at-marvel-snap/</link>
      <guid>https://msfjarvis.dev/posts/overengineering-an-obsidian-dashboard-to-get-better-at-marvel-snap/</guid>
      <description>Using data to answer the ultimate gamer question - why am I so bad?</description>
      <pubDate>Tue, 01 Apr 2025 05:16:52 GMT</pubDate>
      <content:encoded><![CDATA[<p><a href="https://www.marvelsnap.com/">Marvel Snap</a> at its core is a card battler set in the Marvel Universe, where you play a deck of 12 cards and are trying to win
at least 2 of 3 randomly generated locations on the board in 6 turns.</p>
<p>You start with 3 cards in your hand, and draw one each turn. Each card has an energy that decides what it costs to play, and a power that adds to the total
to determine which player wins that location. You start at 1 energy and gain +1 energy every turn.</p>
<p>The game has a competitive mode, and I am horrendous at it. Over the course of the 2 years or so I’ve played this game I’ve made multiple excuses as to why I am so unlucky on the ranked ladder but ultimately I just play the game wrong and then blame my opponents. Like a true gamer.</p>
<p>Last month I <a href="https://msfjarvis.dev/posts/migrating-from-logseq-to-obsidian/">migrated my personal journal and knowledge base</a> to <a href="https://obsidian.md">Obsidian</a>. On a whim, I decided that it might be a fun project to build a system to track and visualize statistics about my Snap games to try and objectively answer the question - why am I so bad?</p>
<h1 id="laying-down-the-foundation">Laying down the foundation</h1>
<p>To get started with analysis, I first need to figure out what data to collect and how.</p>
<p>For a typical game, the stuff you want to record is the following:</p>
<ul>
<li>Your deck: The game has many archetypes, some more reliably effective than others</li>
<li>Opponent’s deck: If they are playing a deck that hard counters or just tends to always beat yours</li>
<li>Locations: Some locations benefit certain decks and can heavily influence the result</li>
<li>Outcome: Whether you won, lost or retreated</li>
<li>Cubes: You lose or gain cubes depending on the outcome and the number of players who “snapped”</li>
</ul>
<p>Taking all this into account I settled on the following template that I would use for each individual game I recorded.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="markdown"><code><span class="line"><span style="color:#E1E4E8">---</span></span>
<span class="line"><span style="color:#85E89D">date</span><span style="color:#E1E4E8">: { { </span><span style="color:#9ECBFF">date</span><span style="color:#E1E4E8"> } }</span></span>
<span class="line"><span style="color:#85E89D">time</span><span style="color:#E1E4E8">: { { </span><span style="color:#9ECBFF">time</span><span style="color:#E1E4E8"> } }</span></span>
<span class="line"><span style="color:#85E89D">type</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">snap-game</span></span>
<span class="line"><span style="color:#85E89D">result</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">win/loss/retreat</span></span>
<span class="line"><span style="color:#85E89D">opponent_name</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">opponent_deck</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">my_deck</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">locations</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">  - </span><span style="color:#9ECBFF">location1</span></span>
<span class="line"><span style="color:#E1E4E8">  - </span><span style="color:#9ECBFF">location2</span></span>
<span class="line"><span style="color:#E1E4E8">  - </span><span style="color:#9ECBFF">location3</span></span>
<span class="line"><span style="color:#85E89D">cubes</span><span style="color:#E1E4E8">: </span><span style="color:#6A737D"># Use positive for gains (e.g., +4), negative for losses (e.g., -2)</span></span>
<span class="line"><span style="color:#85E89D">notes</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">---</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;font-weight:bold">## Game Details</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;font-weight:bold">### Key Moments</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;font-weight:bold">### Lessons Learned</span></span></code></pre>
<p>The YAML frontmatter is what I use to power my dashboards, and the Markdown underneath is unstructured data to record how the game actually progressed so I can also have a play-by-play if I fumbled a good play or managed to cook something clever.</p>
<p>Obsidian provides a very serviceable GUI to edit frontmatter fields which makes the job of filling it in pretty easy.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/template.C6GyHpIt_ZwuQCb.avif" type="image/avif"><source srcset="/_astro/template.C6GyHpIt_ZTHiSX.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/template.C6GyHpIt_ZTHiSX.webp" alt="Obsidian's GUI for the frontmatter given above, of note is the date picker and the 'chips' pattern for entering list values for the locations field" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="426" height="486"> </picture>  </figure>  </div>  
<p>I fed this page into the Obsidian <a href="https://help.obsidian.md/plugins/templates">Templates</a> plugin which takes care of filling in the date and time automatically when I create a new page based on this template.</p>
<h1 id="building-the-visualization">Building the visualization</h1>
<p>For this purpose I leveraged the excellent <a href="https://github.com/blacksmithgu/obsidian-dataview">Obsidian DataView</a> plugin which provides an SQL-like query language that can introspect the Markdown documents in a standard Obsidian Vault and surface structured data from it.</p>
<p>As an example, here’s the code for the dashboard that is in my daily note template to see the current day’s games.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#F97583">TABLE</span><span style="color:#F97583"> WITHOUT</span><span style="color:#E1E4E8"> ID</span></span>
<span class="line"><span style="color:#F97583">	length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "Total Games"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#F97583">	length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">filter</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#79B8FF"> x</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">result</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "win"</span><span style="color:#E1E4E8">)) </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> Wins,</span></span>
<span class="line"><span style="color:#F97583">	length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">filter</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#79B8FF"> x</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">result</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "loss"</span><span style="color:#E1E4E8">)) </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> Losses,</span></span>
<span class="line"><span style="color:#F97583">	length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">filter</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#79B8FF"> x</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">result</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "retreat"</span><span style="color:#E1E4E8">)) </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> Retreats,</span></span>
<span class="line"><span style="color:#79B8FF">	sum</span><span style="color:#E1E4E8">(map(</span><span style="color:#79B8FF">rows</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">cubes</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#F97583"> number</span><span style="color:#E1E4E8">(x))) </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "Net Cubes"</span></span>
<span class="line"><span style="color:#F97583">FROM</span><span style="color:#9ECBFF"> "snap-games"</span></span>
<span class="line"><span style="color:#F97583">WHERE</span><span style="color:#F97583"> type</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "snap-game"</span></span>
<span class="line"><span style="color:#F97583">	AND</span><span style="color:#F97583"> date</span><span style="color:#F97583"> =</span><span style="color:#F97583"> date</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"{{date}}"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">FLATTEN </span><span style="color:#79B8FF">file</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">link</span><span style="color:#F97583"> as</span><span style="color:#E1E4E8"> FileLink</span></span>
<span class="line"><span style="color:#F97583">GROUP BY</span><span style="color:#F97583"> null</span></span></code></pre>
<p>It is pretty self-explanatory, but essentially this walks through every page in my Vault that has its <code>type</code> field set to <code>snap-game</code> and today’s date, then collates the win/loss record from the other YAML frontmatter fields and makes a nice and simple table out of it.</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/daily-dashboard.BEHc8Dqk_1urAtI.avif" type="image/avif"><source srcset="/_astro/daily-dashboard.BEHc8Dqk_Z116Huq.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/daily-dashboard.BEHc8Dqk_Z116Huq.webp" alt="A table with a single row showing the total games, the wins, the losses, the retreats and the net cube change for the day" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="721" height="78"> </picture>  </figure>  </div>  
<p>This can also be extended to cover the past week</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/weekly-dashboard.BM4a7o7V_19lxsu.avif" type="image/avif"><source srcset="/_astro/weekly-dashboard.BM4a7o7V_1JIhs1.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/weekly-dashboard.BM4a7o7V_1JIhs1.webp" alt="A table with a single row showing the total games, the wins, the losses, the retreats and the net cube change for the week" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="721" height="153"> </picture>  </figure>  </div>  
<details><summary>DataView code</summary><pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#F97583">TABLE</span></span>
<span class="line"><span style="color:#F97583">	date</span><span style="color:#F97583"> as</span><span style="color:#F97583"> Date</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#F97583">	time</span><span style="color:#F97583"> as</span><span style="color:#F97583"> Time</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">	result </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> Result,</span></span>
<span class="line"><span style="color:#E1E4E8">	my_deck </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "My Deck"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">	opponent_deck </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "Opponent's Deck"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#E1E4E8">	cubes </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "Cubes Δ"</span></span>
<span class="line"><span style="color:#F97583">FROM</span><span style="color:#9ECBFF"> "snap-games"</span></span>
<span class="line"><span style="color:#F97583">WHERE</span><span style="color:#F97583"> type</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "snap-game"</span></span>
<span class="line"><span style="color:#F97583">	AND</span><span style="color:#F97583"> date</span><span style="color:#F97583"> &gt;=</span><span style="color:#F97583"> date</span><span style="color:#E1E4E8">(today) </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> dur(</span><span style="color:#79B8FF">7</span><span style="color:#F97583"> days</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">SORT </span><span style="color:#F97583">date</span><span style="color:#F97583"> desc</span><span style="color:#E1E4E8">, </span><span style="color:#F97583">time</span><span style="color:#F97583"> desc</span></span></code></pre></details>
<hr>
<p>I can see what decks I played and how they performed relative to each other</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/deck-performance.DaVMbHIJ_ZxyAAz.avif" type="image/avif"><source srcset="/_astro/deck-performance.DaVMbHIJ_2N8nW.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/deck-performance.DaVMbHIJ_2N8nW.webp" alt="A table showing the decks I played in each row and their respective game counts, win rate and net cubes statistics" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="721" height="188"> </picture>  </figure>  </div>  
<details><summary>DataView code</summary><pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#F97583">TABLE</span></span>
<span class="line"><span style="color:#F97583">	length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">as</span><span style="color:#E1E4E8"> Games,</span></span>
<span class="line"><span style="color:#79B8FF">	round</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">filter</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#79B8FF"> x</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">result</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "win"</span><span style="color:#E1E4E8">)) </span><span style="color:#F97583">/</span><span style="color:#F97583"> length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">*</span><span style="color:#79B8FF"> 100</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">+</span><span style="color:#9ECBFF"> "%"</span><span style="color:#F97583"> as</span><span style="color:#9ECBFF"> "Win Rate"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF">	sum</span><span style="color:#E1E4E8">(map(</span><span style="color:#79B8FF">rows</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">cubes</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#F97583"> number</span><span style="color:#E1E4E8">(x))) </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "Net Cubes"</span></span>
<span class="line"><span style="color:#F97583">FROM</span><span style="color:#9ECBFF"> "snap-games"</span></span>
<span class="line"><span style="color:#F97583">WHERE</span><span style="color:#F97583"> type</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "snap-game"</span></span>
<span class="line"><span style="color:#F97583">	AND</span><span style="color:#F97583"> date</span><span style="color:#F97583"> &gt;=</span><span style="color:#F97583"> date</span><span style="color:#E1E4E8">(today) </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> dur(</span><span style="color:#79B8FF">7</span><span style="color:#F97583"> days</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583">GROUP BY</span><span style="color:#E1E4E8"> my_deck</span></span>
<span class="line"><span style="color:#E1E4E8">SORT </span><span style="color:#F97583">length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">desc</span></span></code></pre></details>
<hr>
<p>I can also see which locations favor me the most</p>
<div data-image-modal-root="" data-astro-cid-qj24g3rp=""> <figure data-image-lightbox="" data-astro-cid-qj24g3rp=""> <picture data-astro-cid-qj24g3rp="true"> <source srcset="/_astro/locations.DcrRl9TP_lDAQX.avif" type="image/avif"><source srcset="/_astro/locations.DcrRl9TP_Z1ggX1y.webp" type="image/webp">  <img src="https://msfjarvis.dev/_astro/locations.DcrRl9TP_Z1ggX1y.webp" alt="A table of every location with its name, number of times I played there, my win rate and the net cubes" data-astro-cid-qj24g3rp="true" loading="lazy" decoding="async" width="721" height="888"> </picture>  </figure>  </div>  
<details><summary>DataView code</summary><pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="sql"><code><span class="line"><span style="color:#F97583">TABLE</span></span>
<span class="line"><span style="color:#F97583">	length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "Times Encountered"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF">	round</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">filter</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#79B8FF"> x</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">result</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "win"</span><span style="color:#E1E4E8">)) </span><span style="color:#F97583">/</span><span style="color:#F97583"> length</span><span style="color:#E1E4E8">(</span><span style="color:#F97583">rows</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">*</span><span style="color:#79B8FF"> 100</span><span style="color:#E1E4E8">, </span><span style="color:#79B8FF">1</span><span style="color:#E1E4E8">) </span><span style="color:#F97583">+</span><span style="color:#9ECBFF"> "%"</span><span style="color:#F97583"> as</span><span style="color:#9ECBFF"> "Win Rate"</span><span style="color:#E1E4E8">,</span></span>
<span class="line"><span style="color:#79B8FF">	sum</span><span style="color:#E1E4E8">(map(</span><span style="color:#79B8FF">rows</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">cubes</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#F97583"> number</span><span style="color:#E1E4E8">(x))) </span><span style="color:#F97583">as</span><span style="color:#9ECBFF"> "Net Cubes"</span></span>
<span class="line"><span style="color:#F97583">FROM</span><span style="color:#9ECBFF"> "snap-games"</span></span>
<span class="line"><span style="color:#F97583">WHERE</span><span style="color:#F97583"> type</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "snap-game"</span></span>
<span class="line"><span style="color:#F97583">	AND</span><span style="color:#F97583"> date</span><span style="color:#F97583"> &gt;=</span><span style="color:#F97583"> date</span><span style="color:#E1E4E8">(today) </span><span style="color:#F97583">-</span><span style="color:#E1E4E8"> dur(</span><span style="color:#79B8FF">7</span><span style="color:#F97583"> days</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">FLATTEN locations</span></span>
<span class="line"><span style="color:#F97583">GROUP BY</span><span style="color:#E1E4E8"> locations</span></span>
<span class="line"><span style="color:#E1E4E8">SORT </span><span style="color:#79B8FF">sum</span><span style="color:#E1E4E8">(map(</span><span style="color:#79B8FF">rows</span><span style="color:#E1E4E8">.</span><span style="color:#79B8FF">cubes</span><span style="color:#E1E4E8">, (x) </span><span style="color:#F97583">=&gt;</span><span style="color:#F97583"> number</span><span style="color:#E1E4E8">(x))) </span><span style="color:#F97583">desc</span></span></code></pre></details>
<h1 id="did-it-help">Did it help?</h1>
<p>I don’t know!</p>
<p>Work getting hectic meant I had little time to actually play and the re-launch of the <a href="https://www.marvelsnap.com/newsdetail?id=7415247892945885957">High Voltage</a> mode took up most of that.</p>
<p>I did play a tiny amount of games when I first built this out, and did pick up on some of my tendencies that make me lose games but ultimately it’s a very small sample size. The new season of Snap will start tomorrow so hopefully I’ll be able to get back into it and actually figure out if this whole shebang was worth it.</p>]]></content:encoded>
    </item>
    <item>
      <title>The Obsidian Migration - One Week Later</title>
      <link>https://msfjarvis.dev/posts/the-obsidian-migration--one-week-later/</link>
      <guid>https://msfjarvis.dev/posts/the-obsidian-migration--one-week-later/</guid>
      <description>An experience report of using Obsidian for a week, coming from a year of Logseq</description>
      <pubDate>Mon, 17 Mar 2025 05:38:37 GMT</pubDate>
      <content:encoded><![CDATA[<p>In my <a href="https://msfjarvis.dev/posts/migrating-from-logseq-to-obsidian">last post</a> I had a list of to-dos to fully finish up the migration, which I’ll reproduce below:</p>
<blockquote>
<ul>
<li>I used internal links of <code>[[this format]]</code> pretty liberally in my Logseq graph as a tag system, which will need migration to Obsidian’s tags mechanism.</li>
<li>Some of my pages refer to daily journal pages using a humanized date variant which Logseq automatically converted to proper links, which does not work in Obsidian.</li>
<li>There seems to be no automatic tagger for Obsidian similar to how <a href="https://github.com/sawhney17/logseq-automatic-linker">logseq-automatic-linker</a> works.</li>
</ul>
</blockquote>
<h2 id="migrating-dates-and-tags-to-obsidian">Migrating dates and tags to Obsidian</h2>
<p>I haven’t found a solution for automated tagging yet, but inspired by Simon Willison’s <a href="https://simonwillison.net/2025/Mar/11/using-llms-for-code/">post about how he uses LLMs for code</a> I decided to have GitHub Copilot write out a Python script to perform the migration of Logseq-isms to the appropriate Obsidian-native patterns.</p>
<p>The choice of Python was for multiple reasons: it’s fast enough for the job, I understand it well, and it can get much farther without needing third-party dependencies.</p>
<p>There wasn’t admittedly a whole lot to this process, since all I was doing is reviewing and running some 100 odd lines of Python then hard resetting my repo after it did the wrong thing. My initial prompt was this:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="plaintext"><code><span class="line"><span>Write a Python script using only the standard library to process an Obsidian</span></span>
<span class="line"><span>vault and migrate Logseq patterns to Obsidian's systems in the markdown files.</span></span>
<span class="line"><span>Below is a list of changes to make:</span></span>
<span class="line"><span></span></span>
<span class="line"><span>1. Convert any `[[Thing]]` style links to use Obsidian-native tags by adding a</span></span>
<span class="line"><span>`#` prefix, resulting in `#Thing`. If there are spaces in the link text,</span></span>
<span class="line"><span>convert them to underscores.</span></span>
<span class="line"><span></span></span>
<span class="line"><span>2. If a link's text matches the date format `May 8th, 2024`, replace its text</span></span>
<span class="line"><span>by changing the date format to YYYY-MM-DD. In the given example, the output</span></span>
<span class="line"><span>should be `[[2024-05-08]]`.</span></span></code></pre>
<p>The first output from this was unsurprisingly buggy, the script converted <code>[[May 13th, 2024]]</code> to <code>#2024-05-13</code> instead of <code>[[2024-05-13]]</code>. The second iteration did not resolve the bug either, but in the third one progress was made.</p>
<p>This was where Copilot impressed me a bit. After running the third iteration and finding a bug in the results, I told the chatbot that the date <code>Jun 8th, 2024</code> was not being converted and it was able to quickly clock that this was caused by my usage of <code>May</code> in the initial prompt which made it look only for full month names, while <code>Jun</code> is obviously abbreviated. I’m not sure if I would’ve figured that one out right away, but it was a good way to also learn more about the Python standard library’s date formatting facilities.</p>
<p>The final script is available <a href="https://gist.github.com/msfjarvis/1892c898df746cfa1d24932a02a1da82">here</a>, with individual revisions so you can step through the changes made by Copilot as I prompted it.</p>
<h2 id="getting-set-up-on-mobile">Getting set up on mobile</h2>
<p>This was the bit I was more excited for, because one major advantage of using Obsidian on mobile that I didn’t realize earlier was that the same plugins Just Work™️. As far as I could tell, Logseq did not have support for plugins on mobile which mas a little annoying because how I interacted with the app changed across platforms even though there was no technical reason for it to. In Obsidian everything works out-of-the-box since plugins get vendored into your vault so you can actually modify and review the plugin code you’re running.</p>
<p>My existing workflow on Android involved manually committing and pushing changes to my graph/vault via <a href="https://github.com/maks/MGit">MGit</a> which was too many clicks in the MGit app and was getting a little frustrating over time. I <a href="https://androiddev.social/@msfjarvis/114136599118536735">asked on the Fediverse</a> if someone was aware of a newer Git client for Android and very quickly discovered <a href="https://github.com/catpuppyapp/PuppyGit">PuppyGit</a>.</p>
<p>Once I had removed Logseq and imported my existing vault into Obsidian (I’ve opted to re-use my existing Git repository), I followed PuppyGit’s guide to <a href="https://www.patreon.com/posts/puppygit-auto-122757321">setting up automatic syncing with Obsidian</a> which worked flawlessly. It works by mapping a list of apps to a list of repositories that they interact with, and using an accessibility service monitors when apps are launched or closed, upon which it would pull remote changes to the repos or commit and push respectively.</p>
<p>The Git plugin I was using for my desktop didn’t work on Android since it required a <code>git</code> binary, but they helpfully include an option to disable it only for the current device which let me delegate to PuppyGit on mobile with no issues.</p>
<hr>
<p>A week into it I find myself much happier using Obsidian than I ever was with Logseq, and I’ve even started using it for more things beyond journaling such as taking meeting notes and organizing my tasks which I found too unintuitive to achieve with Logseq. Logseq excels at a daily journaling tool but once my needs started expanding it was pretty clear that Obsidian really is the all-rounder I needed.</p>]]></content:encoded>
    </item>
    <item>
      <title>Migrating from Logseq to Obsidian</title>
      <link>https://msfjarvis.dev/posts/migrating-from-logseq-to-obsidian/</link>
      <guid>https://msfjarvis.dev/posts/migrating-from-logseq-to-obsidian/</guid>
      <description>Logseq's clunky apps and glacial development pace finally motivated me to migrate my journaling to Obsidian, here's how I did it.</description>
      <pubDate>Sun, 09 Mar 2025 10:29:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I have been using <a href="https://logseq.com">Logseq</a> for just over <a href="https://androiddev.social/@msfjarvis/112378523734491769">a year</a> to maintain a daily journal, but I’ve been eyeing Obsidian for the last couple months as my note-keeping needs have expanded past daily journaling.</p>
<p>Logseq has had a long-standing problem with <a href="https://github.com/logseq/logseq/issues/11644">the version of Electron used by it</a> being EOL, which finally prompted its removal from <a href="https://github.com/NixOS/nixpkgs">Nixpkgs</a> on security grounds this week and accelerating my migration to Obsidian. The below is a short list of things I did to make Obsidian more comfortable as a daily journaling system.</p>
<h2 id="moving-over-content">Moving over content</h2>
<p>I made the following changes to the files in my Logseq graph when moving to the new Obsidian Vault</p>
<ul>
<li><code>assets</code> and <code>pages</code> got copied over as-is</li>
<li>The <code>journals</code> folder got renamed to <code>Daily</code> since I wanna do notes for longer durations as well, and all the files inside renamed to replace underscores with hyphens since it looked like that was more common in Obsidian.</li>
</ul>
<h2 id="plugins">Plugins</h2>
<p>I enabled the following core plugins:</p>
<ul>
<li><strong>Daily Notes</strong>: Automates creation of daily notes</li>
<li><strong>Templates</strong>: Allows you to create reusable templates</li>
<li><strong>Backlinks</strong>: Shows references to the current note</li>
</ul>
<p>Looking around other people’s experience with this migration path I also found the following community plugins:</p>
<ul>
<li><strong>Periodic Notes</strong>: Enhanced daily/weekly/monthly notes</li>
<li><strong>Natural Language Dates</strong>: Type dates like “tomorrow” or “next Monday”</li>
<li><strong>Tasks</strong>: Advanced task management</li>
<li><strong>Dataview</strong>: Query and display info from your notes</li>
<li><strong>Templater</strong>: More powerful templating than the core plugin</li>
<li><strong>Git</strong>: Self-managed sync for vaults</li>
</ul>
<blockquote>
<p>Side note: I greatly appreciate the fact that Obsidian defaults to vendoring plugins in the Vault itself so if you’re tracking the <code>.obsidian</code> folder in Git, you get a fully reproducible Vault regardless of what machine you open it on.</p>
</blockquote>
<h2 id="configuration-changes">Configuration changes</h2>
<p>In Settings → Daily Notes:</p>
<ul>
<li><strong>Date format</strong>: <code>YYYY-MM-DD</code></li>
<li><strong>New file location</strong>: <code>Daily/</code></li>
<li><strong>Open daily note on startup</strong>: <code>Enabled</code></li>
</ul>
<p>In Settings → Hotkeys:</p>
<ul>
<li>Open today’s daily note: <code>Alt+D</code></li>
<li>Create new note: <code>Ctrl+N</code></li>
<li>Toggle edit/preview mode: <code>Ctrl+E</code></li>
<li>Search in all notes: <code>Ctrl+Shift+F</code></li>
</ul>
<h1 id="pending-changes">Pending changes</h1>
<p>What I’ve done so far is just the result of 30 minutes of looking around, and there is still a bunch of currently broken stuff that needs resolving.</p>
<ul>
<li>
<p>I used internal links of <code>[[this format]]</code> pretty liberally in my Logseq graph as a tag system, which will need migration to Obsidian’s tags mechanism.</p>
</li>
<li>
<p>Some of my pages refer to daily journal pages using a humanized date variant which Logseq automatically converted to proper links, which does not work in Obsidian.</p>
</li>
<li>
<p>There seems to be no automatic tagger for Obsidian similar to how <a href="https://github.com/sawhney17/logseq-automatic-linker">logseq-automatic-linker</a> works.</p>
</li>
</ul>
<p>Despite these issues, I think the move to Obsidian will end up being a net positive to my note taking process. Please let me know on <a href="https://androiddev.social/@msfjarvis/114132263177269990">Mastodon</a> or the comments here if you have any tips to improve things.</p>]]></content:encoded>
    </item>
  </channel>
</rss>