<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on sbgrl.me</title><link>https://sbgrl.me/posts/</link><description>Recent content in Posts on sbgrl.me</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>sylvain.bougerel@gmail.com (Sylvain Bougerel)</managingEditor><webMaster>sylvain.bougerel@gmail.com (Sylvain Bougerel)</webMaster><copyright>© 2026 Sylvain Bougerel</copyright><lastBuildDate>Thu, 20 Aug 2026 16:31:00 +0800</lastBuildDate><atom:link href="https://sbgrl.me/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Documentation performance, humans and LLMs</title><link>https://sbgrl.me/posts/documentation-performance-humans-llms/</link><pubDate>Thu, 20 Aug 2026 16:31:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/documentation-performance-humans-llms/</guid><description>&lt;p&gt;All coding harnesses I&amp;rsquo;ve used (Claude Code, OpenCode or Pi) give you a way to read your current session&amp;rsquo;s token spend.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Session compacted 13 times
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Session Info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; File: /home/me/.pi/agent/sessions/path/file.jsonl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ID: 01a013cf-f306-7b76-994a-028aefc7a999
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Messages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Total: 593
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; User: 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assistant: 285
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Tools: 304 calls, 304 results
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Tokens
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Input: 12,105,934
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Cached: 11,131,164 (91.9%)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Uncached: 974,770
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Output: 290,380
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Total: 12,396,314
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Cost
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Total: $0.000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; llama.cpp/Qwen3.8-27B-Q4_K_M Text-only: $0.000 (12M tokens)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Tools/summaries: $0.000 (375k tokens)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Cache Re-billed: 2,262 tokens, 1 miss&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Pi&amp;rsquo;s &lt;code&gt;/session&lt;/code&gt; statistics for a recent session.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Of all the capabilities that LLMs enable, I find this ability to quantify thinking remarkable and not really tapped into yet. You — as a human — have always been able to tell whether doing any thinking was hard or easy. Usually, we proxy this quantity with the &lt;em&gt;amount of time spent thinking&lt;/em&gt;. However, many everyday cognitive activities don&amp;rsquo;t reduce themselves to simple problems, and it becomes hard to otherwise measure them. Reading and understanding documentation is one of these activities.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The documentation performance metric
 &lt;div id="the-documentation-performance-metric" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-documentation-performance-metric" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;In the age of LLMs, you can effectively measure how efficient your documentation is at answering a given set of questions, regardless of the question&amp;rsquo;s complexity; let&amp;rsquo;s call this &lt;em&gt;documentation performance&lt;/em&gt; (I don&amp;rsquo;t know of any other term for this).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take 2 (or more) versions of your documentation,&lt;/li&gt;
&lt;li&gt;Define a set of questions that you want to optimize against,&lt;/li&gt;
&lt;li&gt;Run your models to answer test questions,&lt;/li&gt;
&lt;li&gt;Sum input and output tokens used to generate all answers,&lt;/li&gt;
&lt;li&gt;Run your models to grade the answers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To account for the difference in answers generated or variation in grades across repeated calls you can even take multiple samples. Dividing the sum of I/O tokens used to generate the answer by the grade of the answer gives you the documentation performance score. Lower is better:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Doc versions&lt;/th&gt;
 &lt;th&gt;I/O tokens for all answers&lt;/th&gt;
 &lt;th&gt;Answer grade&lt;/th&gt;
 &lt;th&gt;Final score&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;10,000&lt;/td&gt;
 &lt;td&gt;100%&lt;/td&gt;
 &lt;td&gt;10,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;B&lt;/td&gt;
 &lt;td&gt;5,000&lt;/td&gt;
 &lt;td&gt;100%&lt;/td&gt;
 &lt;td&gt;5,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;C&lt;/td&gt;
 &lt;td&gt;3,000&lt;/td&gt;
 &lt;td&gt;50%&lt;/td&gt;
 &lt;td&gt;6,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;D&lt;/td&gt;
 &lt;td&gt;1,000&lt;/td&gt;
 &lt;td&gt;10%&lt;/td&gt;
 &lt;td&gt;10,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In the example above, version B is better than version A because the model gets equally good answers with half the effort. Version C isn&amp;rsquo;t necessarily better merely because it is short.&lt;/p&gt;
&lt;p&gt;Running this pipeline may be costly today, but many software companies would likely agree that a well-written documentation is a competitive edge. I don&amp;rsquo;t know of any organization making use of this yet, but I would not be surprised if some already do or have attempted to do so.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Beware of Goodhart&amp;rsquo;s Law
 &lt;div id="beware-of-goodhart-s-law" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#beware-of-goodhart-s-law" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Tuning for documentation performance could be a good thing as long as documentation is written primarily by and for humans. If you build a system where LLMs are both authors and reviewers with the goal of optimizing for documentation performance alone — disregarding any other metrics and without any human in the loop — the output will likely end up resembling this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- [done] Path-scheme Feature references (COMMANDS.md *Feature Reference Resolution*): `file://` / relative-path entries resolve to a Feature folder already on disk (trusted, no network) — 205 tests green. Design: any reference that is neither a bare name nor a registry-qualified OCI reference is a path reference; a `file://` prefix is stripped and the remainder is used literally (no percent-decoding, v0.1); relative paths resolve against the workspace root, `.`/empty = the workspace root itself; backslashes normalize to `/`; the result is lexically normalized (a `..` escaping the root is kept); the folder must exist and hold a parseable `devcontainer-feature.json`, but the folder name need not match the manifest `id` (the path names the folder directly; the build uses the manifest `id`); plans as `FeaturePlan::Local` with a new `FeatureSource::Path`, no registry gate, no `--allow-remote-features`; manifest loading shared with the search path via `load_feature_manifest`. Review passed: spec table rows (`file://`, relative) match, trust/no-network verified by a non-interactive no-allowlist test, `..`-escape kept not dropped, id≠folder-name rule tested, empty last component on an OCI ref is a hard error; one doc nit fixed (`is_bare_feature_name` comment mislabeled `.` as `..`-containing)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;A billboard note written by the agent during that session.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That output is a single line of my agents&amp;rsquo; &lt;em&gt;billboard&lt;/em&gt; and at 1300 characters, it&amp;rsquo;s one of the shortest. The agents use the &lt;em&gt;billboard&lt;/em&gt; to communicate with each other. This is what documentation looks like when agents talk to agents. There is definitely structure, but a complete absence of formatting, description or justification. This is clearly information dense and would likely do great on documentation performance, but I don&amp;rsquo;t think it&amp;rsquo;ll be a read that any human would enjoy.&lt;/p&gt;</description></item><item><title> "In Australia, a Home Battery Boom Has Helped Cut Wholesale Power Prices in Half"</title><link>https://sbgrl.me/posts/quoting-in-australia-a-home-battery-boom-helped-cut-power-price-in-half/</link><pubDate>Mon, 17 Aug 2026 10:41:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/quoting-in-australia-a-home-battery-boom-helped-cut-power-price-in-half/</guid><description>&lt;blockquote&gt;&lt;p&gt;The subsidy program has been “the major factor in the wholesale price of energy falling in Australia by 47 percent in the last 12 months,” Bowen said. “Australia is one of the very, very few countries in the world which is reducing wholesale prices in the midst of a global energy crisis.”&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://e360.yale.edu/digest/australia-home-batteries" target="_blank" rel="noreferrer"&gt;In Australia, a Home Battery Boom Has Helped Cut Wholesale Power Prices in Half&lt;/a&gt;; on Australia&amp;rsquo;s program to subsidize home batteries to use the excess power from home solar installations that previously went to waste.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m very excited about developments in PV and the improvements that this can brings to countries that make the necessary investment into this transition. Australia is arguably a leader in that domain and demonstrates time and again the viability of such approach. 47% reduction is no joke! Every government in Europe should pay attention.&lt;/p&gt;</description></item><item><title>Open-source takes on AI</title><link>https://sbgrl.me/posts/open-source-takes-on-ai/</link><pubDate>Sat, 01 Aug 2026 23:15:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/open-source-takes-on-ai/</guid><description>&lt;p&gt;In the last 2 weeks, it started to feel like the dust is settling around AI in the open-source community. Major open-source projects released their policies or guidelines around AI usage. LLMs are having an existential impact on open-source: plundering its commons, putting a stress on its resources, threatening the trust that is the fabric of this community. In this post, I cover the spectrum of AI policies adopted across popular OSS projects.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Human Emacs
 &lt;div id="human-emacs" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#human-emacs" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The GNU project has not yet shared a statement on AI but the GNU Compiler Collection and the GUIX project did. Similarly, several members of the Emacs project decided to take a stance on AI and published it on &lt;a href="https://human-emacs.org/" target="_blank" rel="noreferrer"&gt;Human Emacs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Therefore we are here to declare our intent to use and develop a version of Emacs that is free from LLM-generated contributions regardless of whatever policy GNU decides upon.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Human Emacs stands firmly against &lt;strong&gt;any&lt;/strong&gt; AI usage. It is also the only document on that list that included moral and ethical ground in the policy itself. It garnered a fair bit of interest (and lots of praise) on &lt;a href="https://www.reddit.com/r/emacs/s/9PVFrEgT6G" target="_blank" rel="noreferrer"&gt;r/emacs&lt;/a&gt; and &lt;a href="https://lobste.rs/s/t0aqzy/human_emacs" target="_blank" rel="noreferrer"&gt;Lobster&lt;/a&gt;. This one hit close to my heart. Emacs has been gaining in popularity since Covid, its community has been thriving since. I would hate for the momentum to die because of AI and I hope the community leaders will arrive at a consensus soon.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Codeberg
 &lt;div id="codeberg" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#codeberg" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The maintainers of Codeberg have put up a &lt;a href="https://blog.codeberg.org/protecting-our-floss-commons-from-llms.html" target="_blank" rel="noreferrer"&gt;blog post&lt;/a&gt; explaining &lt;a href="https://codeberg.org/Codeberg/org/commit/71149c7fc95ccfeae36109b5cddca339e4aa1473" target="_blank" rel="noreferrer"&gt;the changes in their terms of use&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;ol&gt;
&lt;li&gt;You must not share projects that mostly consist of code written by &amp;ldquo;generative AI&amp;rdquo;-tools (including services such as &lt;strong&gt;Claude&lt;/strong&gt;, &lt;strong&gt;OpenAI Codex&lt;/strong&gt;). Such projects having an unclear copyright status [&amp;hellip;] and furthermore have little safeguards to ensure that they do not include harmful code [&amp;hellip;].&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;&lt;p&gt;After reading their blog post, I expected Codeberg to ban any AI usage, but the updated terms of use remain accepting of LLMs so long as LLMs do not generate the code. The original blog post feels lot more combative against AI and is worth reading because it covers the impact of LLM on the FLOSS community in more detail than the other entries on this list. The maintainers also pledge that Codeberg won&amp;rsquo;t allow LLMs to train on their hosted data.&lt;/p&gt;

&lt;h2 class="relative group"&gt;GNU Compiler Collection
 &lt;div id="gnu-compiler-collection" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#gnu-compiler-collection" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The GCC project also recently published their &lt;a href="https://gcc.gnu.org/ai-policy.html" target="_blank" rel="noreferrer"&gt;AI policy&lt;/a&gt;. It&amp;rsquo;s a clear policy that declines only &lt;a href="https://www.gnu.org/prep/maintain/maintain.html#Legally-Significant" target="_blank" rel="noreferrer"&gt;legally-significant&lt;/a&gt; contributions except for test cases:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;For the time being, the GNU Compiler Collection (GCC) policy is to decline any legally significant contributions which include LLM-generated content or are derived from LLM-generated content.
[&amp;hellip;]
As an exception, the GCC maintainers are free to accept &lt;em&gt;legally significant&lt;/em&gt; contributions to &lt;strong&gt;test cases&lt;/strong&gt;, generated in whole or in part by an LLM.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;This policy is an acknowledgement that the project is benefiting from LLM use. It is worth noting their clarity on what constitutes a &lt;em&gt;legally significant&lt;/em&gt; change:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A change of just a few lines (less than 15 or so) is not legally significant for copyright.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Their rather accepting stance on AI should not be surprising given that GCC&amp;rsquo;s development is supported by companies selling LLM products. However, the project requires that commit messages include an &amp;ldquo;&lt;code&gt;Assisted-by:&lt;/code&gt;&amp;rdquo; tag for any LLM-generated contributions.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Kubernetes
 &lt;div id="kubernetes" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#kubernetes" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s by far the most AI-friendly project on that list. The 2 most important contributing organizations to the project also compete to produce frontier models. Kubernetes has an &lt;a href="https://github.com/kubernetes-sigs/ai-conformance" target="_blank" rel="noreferrer"&gt;AI Conformance SIG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recently, I listened to the &lt;a href="https://kubernetespodcast.com/episode/269-ai-usage/" target="_blank" rel="noreferrer"&gt;Kubernetes podcast, episode 269 on AI usage&lt;/a&gt; featuring &lt;a href="https://bsky.app/profile/kat.lol" target="_blank" rel="noreferrer"&gt;Kat Cosgrove&lt;/a&gt; who is a K8s Steering Committee member and Tech Lead in 2 SIGs (And who obviously likes Evangelion). The whole episode was a fun listen. She mentioned that they just updated the project&amp;rsquo;s &lt;a href="https://www.kubernetes.dev/docs/guide/pull-requests/#ai-guidance" target="_blank" rel="noreferrer"&gt;Pull Request Process with their AI guidance&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Using AI tools to help write your PR is acceptable, but as the author, you are responsible for understanding every change. If you used AI tools in preparing your PR, you must disclose this in the description of your PR. For example, including “This PR was written in part with the assistance of generative AI,” in the PR description is sufficient.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;As expected, there are very few restrictions around AI usage, besides &amp;ldquo;understand every change&amp;rdquo; which is definitely the minimum bar. During the podcast, Kat did emphasize however that the project members really wanted to talk to humans, not AI: indeed, the guidelines ban LLM use for commits, PR descriptions and comments.&lt;/p&gt;
&lt;p&gt;What caught my attention is that the guidelines also &lt;em&gt;ban&lt;/em&gt; the use of &amp;ldquo;&lt;code&gt;Assisted-by:&lt;/code&gt;&amp;rdquo; statements in the commit messages while enforcing that authors disclose LLM use — presumably, manually and without vendor reference, though it is not explicitly stated. Kat commented on the podcast that AI vendors have used the commits of merged PRs in K8s to legitimize their tools. A neutral choice, for a project that is backed by the fiercest competitors in the AI landscape. I believe the concern is genuine though, and GCC could probably take a page from their book.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Linux
 &lt;div id="linux" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#linux" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;I think the projects above embody the full spectrum of reactions in the open-source community. But I couldn&amp;rsquo;t finish this post without &lt;a href="https://lore.kernel.org/linux-media/CAHk-=wi4zC&amp;#43;Ze8e&amp;#43;p3tMv8TtG_80KzsZ1syL9anBtmEh5Z40vg@mail.gmail.com/" target="_blank" rel="noreferrer"&gt;the take from the most important person in the largest of all open-source projects&lt;/a&gt; (which you have likely already read):&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Linux is not one of those anti-AI projects, and if somebody has issues with that, they can do the open-source thing and fork it.&lt;/p&gt;
&lt;p&gt;Or just walk away.&lt;/p&gt;
&lt;p&gt;AI is a tool, just like other tools we use. And it&amp;rsquo;s clearly a useful one.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;What&amp;rsquo;s great about this take is that you instantly know it was not LLM-generated. Linus, please don&amp;rsquo;t ever change.&lt;/p&gt;
&lt;p&gt;The Kernel does have &lt;a href="https://www.kernel.org/doc/html/latest/process/coding-assistants.html" target="_blank" rel="noreferrer"&gt;a contributing policy with regards to AI coding assistants&lt;/a&gt;. I&amp;rsquo;m not going to requote it — it&amp;rsquo;s very similar to the GCC policy, which makes sense since both projects share so much in common.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Where do I stand?
 &lt;div id="where-do-i-stand" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#where-do-i-stand" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;My opinions on AI have not shifted much, but my usage varied a lot. It peaked around May 2026; that month, I spent well over US$1,200 in API-token pricing on Claude alone. That was not something sustainable — we don&amp;rsquo;t have so many credits — but it did produce useful results. Since then, I&amp;rsquo;ve been spending much less on LLMs and cut some of the lesser used subscriptions.&lt;/p&gt;
&lt;p&gt;On the one hand, I can&amp;rsquo;t stand the terrible impact AI is having on the open-source community, the environment and our culture. On the other hand, I still want to experience this incredible technology. So, let&amp;rsquo;s be honest. I&amp;rsquo;m torn. &lt;a href="https://www.theocharis.dev/blog/llm-critics-are-right-i-use-llms-anyway" target="_blank" rel="noreferrer"&gt;And, I&amp;rsquo;m not alone.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I deal with my indecision by categorizing the issues in 2 parts: those that I think will resolve themselves, and those that I can act on now. For example: I think the impact on the open-source community will resolve itself — I believe the community will eventually build the tools and culture to thrive in the age of LLMs. However, I don&amp;rsquo;t think the pollution generated by the wave of newly built AI data centers will resolve itself. So, as a small step in the right direction, I aim to stop using cloud-based inference. I&amp;rsquo;ve started to use &lt;a href="https://pi.dev/" target="_blank" rel="noreferrer"&gt;Pi&lt;/a&gt; with a &lt;a href="https://huggingface.co/unsloth/Qwen3.5-27B-GGUF" target="_blank" rel="noreferrer"&gt;distilled Qwen3.5-27B-Q4_K_M&lt;/a&gt; model on my modest Radeon RX 7900 XTX (24GB VRAM) and the results are not what I anticipated: it&amp;rsquo;s much slower, but significantly more capable than I expected. It is already accomplishing tasks that I thought only a full-fledged product like Claude Code could accomplish. I&amp;rsquo;ll be writing about it soon.&lt;/p&gt;</description></item><item><title>The end of tech-debt?</title><link>https://sbgrl.me/posts/the-end-of-tech-debt/</link><pubDate>Sat, 16 May 2026 18:03:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/the-end-of-tech-debt/</guid><description>&lt;p&gt;After &lt;a href="https://claude.com/product/claude-code" target="_blank" rel="noreferrer"&gt;Claude Code&lt;/a&gt; performed a refactor across our codebase in less than 24 hours and mostly autonomously, I started to wonder if we could end our tech-debt, and put a price on it?&lt;/p&gt;

&lt;h2 class="relative group"&gt;Setting the stage
 &lt;div id="setting-the-stage" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#setting-the-stage" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The codebase I&amp;rsquo;m referring to is that of our main &lt;a href="https://nextjs.org/" target="_blank" rel="noreferrer"&gt;Next.js&lt;/a&gt; application. It&amp;rsquo;s not a small codebase by any measure: 200KLOC, 1500 files, Typescript, Terraform and other languages.&lt;/p&gt;
&lt;p&gt;Within this codebase, we use both &lt;a href="https://nextjs.org/docs/app/api-reference/file-conventions/route" target="_blank" rel="noreferrer"&gt;API route handlers&lt;/a&gt; and &lt;a href="https://react.dev/reference/rsc/server-functions" target="_blank" rel="noreferrer"&gt;server functions&lt;/a&gt; to fetch or mutate data. Each mechanism fits a specific purpose: an application should make its public API available via route handlers, while it should use server functions for internal mutations only.&lt;/p&gt;
&lt;p&gt;In our application, however, we had leftover server functions used only to fetch data — without mutating it. This often resulted in the server function returning stale information, because it was doing something it was not designed for. We needed to migrate them to route handlers.&lt;/p&gt;
&lt;p&gt;I had already fixed 4 of them, but I found 18 more that needed addressing.&lt;/p&gt;
&lt;p&gt;This is not hard work, but not a dumb task either. Each case requires a new API route, a new contract, properly bounded inputs, validated outputs, updating how the client-side sends requests and handles responses, all while making sure it lints correctly and passes the ~2700 unit tests of our application — a perfect task for an agent.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Ralph to the rescue!
 &lt;div id="ralph-to-the-rescue" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#ralph-to-the-rescue" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;To make fixing the first 4 easier, I had already refined and tested a 150-line Claude skill. I knew it was time for a &lt;a href="https://ghuntley.com/ralph/" target="_blank" rel="noreferrer"&gt;Ralph loop&lt;/a&gt;. So I set up the skill as the spec file, provided additional instructions around merge request (MR) submission and handling, and got it going in auto mode.&lt;/p&gt;
&lt;p&gt;And it did. After 8 hours of work, it created 18 MRs for me to review, and continued to autonomously address the comments and fix any failures reported by the CI pipeline.&lt;/p&gt;
&lt;p&gt;At the point where all 18 MRs were submitted and ready to merge, this task had cost roughly USD$180. I estimate that Claude went roughly 5-10 times faster than I would.&lt;/p&gt;
&lt;p&gt;$180 is not a large amount of money for a specific, targeted refactor. We have dozens of those we can do to migrate and improve our codebase. Can we end tech-debt in our codebase?&lt;/p&gt;

&lt;h2 class="relative group"&gt;Then reality caught up
 &lt;div id="then-reality-caught-up" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#then-reality-caught-up" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;After MR 6/18 merged into the &lt;code&gt;main&lt;/code&gt; branch, our testing environment went down. It was not hard to notice: even a local test server would not run after rebasing. MR 6/18 contained a route that was not set up properly, and that could only be caught at runtime. It was an easy fix, and it reminded me that I had forgotten a step in my Ralph loop: I quickly instructed it to build comprehensive test cases for us to exhaustively verify the changes.&lt;/p&gt;
&lt;p&gt;Testing is going to need improvement, but we knew this; we had already started to experiment with end-to-end tests to tackle this tech-debt. Here again, with great help from Claude, we managed to automate some end-to-end testing. However, in contrast to the current migration, none of the results so far have been satisfactory. This time, it seems our issues have more to do with the design of our application — and the solution isn&amp;rsquo;t yet clear.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The trouble with the process
 &lt;div id="the-trouble-with-the-process" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-trouble-with-the-process" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;But the testing environment going down was not actually the biggest friction — it was fixed quickly. The problem was that it took Claude 4 to 5 hours &lt;em&gt;just&lt;/em&gt; to merge the remaining 12 MRs after MR 6. 8 hours of productive work, 4 hours of watching paint dry.&lt;/p&gt;
&lt;p&gt;We use short-lived development branches against the &lt;code&gt;origin/main&lt;/code&gt; trunk, and maintain a semi-linear history on merges (trunk-based development). This means that after each merge, the next merge request needs to be rebased, and the CI pipeline must run again. A rebase + CI cycle takes around 8 to 10 minutes today; and if the pipeline fails or a conflict occurs during rebase, additional CI cycles may need to run.&lt;/p&gt;
&lt;p&gt;Every day, we race each other to merge our work. That day, we also raced the 18 MRs the agent had to merge. And our CI pipeline is not going to get faster — if anything it&amp;rsquo;s going to take &lt;em&gt;more&lt;/em&gt; time once we add end-to-end tests.&lt;/p&gt;
&lt;p&gt;So, can we end tech-debt?&lt;/p&gt;
&lt;p&gt;In short, not all tech-debt scales equally well for agents, especially when the issue may have more to do with application design or the solution is not yet clear — as in our case with end-to-end testing.&lt;/p&gt;
&lt;p&gt;And while we can certainly price some migration work, or even execute it concurrently with our feature work, it still needs to clear CI. Agents didn&amp;rsquo;t eliminate the implementation bottleneck — they moved it to the CI process.&lt;/p&gt;
&lt;p&gt;I now find myself thinking about &lt;a href="https://docs.gitlab.com/ci/pipelines/merge_trains/" target="_blank" rel="noreferrer"&gt;merge trains&lt;/a&gt; and other merging strategies to eliminate the CI bottleneck. I&amp;rsquo;m also wondering whether larger organisations are now experiencing the same pressure on their CI processes, and how this will shape the future of CI and trunk-based development.&lt;/p&gt;</description></item><item><title>Announcing autosync-git to replace autosync-magit</title><link>https://sbgrl.me/posts/autosync-git-to-replace-autosync-magit/</link><pubDate>Fri, 15 May 2026 11:36:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/autosync-git-to-replace-autosync-magit/</guid><description>&lt;p&gt;I&amp;rsquo;m deprecating &lt;a href="https://github.com/sbougerel/autosync-magit" target="_blank" rel="noreferrer"&gt;Autosync-Magit&lt;/a&gt; in favour of &lt;a href="https://github.com/sbougerel/autosync-git" target="_blank" rel="noreferrer"&gt;Autosync-git&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Migrating
 &lt;div id="migrating" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#migrating" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;I kept aliases to the previous variables and hooks such as &lt;code&gt;autosync-magit-after-pull-hook&lt;/code&gt; to make switching to the new tool easier. &lt;code&gt;autosync-git&lt;/code&gt; conflicts with &lt;code&gt;autosync-magit&lt;/code&gt; due to aliases, so you can&amp;rsquo;t load both concurrently. I will eventually deprecate these aliases, so you should port your &lt;code&gt;.dir-locals.el&lt;/code&gt; to look like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;autosync-git-commit-message&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;My commit message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;autosync-git-pull-timer&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;autosync-git&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;What changed
 &lt;div id="what-changed" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#what-changed" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The new tool sheds a dependency on &lt;a href="https://magit.vc/" target="_blank" rel="noreferrer"&gt;Magit&lt;/a&gt;, to just use &lt;code&gt;git&lt;/code&gt; and Emacs&amp;rsquo; async &lt;code&gt;make-process&lt;/code&gt; directly. This allows it to provide its own execution buffer, instead of sharing it with Magit.&lt;/p&gt;
&lt;p&gt;But the more exciting update is the better pull strategy, which reduces the risk of leaving conflicts in your current workspace, and does better at merging what does not conflict.&lt;/p&gt;
&lt;p&gt;When the remote and your local branch have diverged, the pull operation starts with a &amp;ldquo;probe&amp;rdquo; stage, where &lt;code&gt;autosync-git&lt;/code&gt; performs an in-memory 3-way merge with &lt;a href="https://git-scm.com/docs/git-merge-tree" target="_blank" rel="noreferrer"&gt;&lt;code&gt;git merge-tree&lt;/code&gt;&lt;/a&gt;. If this 3-way merge succeeds, &lt;code&gt;autosync-git&lt;/code&gt; does a &lt;code&gt;rebase&lt;/code&gt; (default) or &lt;code&gt;merge&lt;/code&gt;. &lt;code&gt;rebase&lt;/code&gt; is now the default to keep the history as linear as possible. But you can change it to &lt;code&gt;merge&lt;/code&gt; if you prefer.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;autosync-git-pull-style&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;autosync-git&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;When keeping the default &lt;code&gt;rebase&lt;/code&gt; strategy, if a conflict occurs, it immediately aborts the rebase and informs you in &lt;code&gt;*Messages*&lt;/code&gt;. You can also force the pull operation to leave any conflicts in the repository instead of aborting. More to discover at &lt;a href="https://github.com/sbougerel/autosync-git" target="_blank" rel="noreferrer"&gt;Autosync-git&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Why I continue to review code</title><link>https://sbgrl.me/posts/why-i-continue-to-review-code/</link><pubDate>Fri, 01 May 2026 10:01:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/why-i-continue-to-review-code/</guid><description>&lt;p&gt;Given the increase in velocity from coding assistants such as Claude Code and Copilot, automated review had become a necessity. Since we rolled out &lt;a href="https://coderabbit.ai" target="_blank" rel="noreferrer"&gt;Coderabbit&lt;/a&gt; on our project, almost everybody stopped reviewing code. It didn&amp;rsquo;t happen immediately, but organically.&lt;/p&gt;
&lt;p&gt;As it turns out, Coderabbit is a formidable reviewer: with only a bit of context in the commit messages and a well-written &lt;code&gt;AGENTS.md&lt;/code&gt;, Coderabbit will often be more thorough in reviews than most of my colleagues. And if that wasn&amp;rsquo;t enough, Coderabbit is almost always available—&lt;a href="https://status.coderabbit.ai/" target="_blank" rel="noreferrer"&gt;98.3% of the time in the last 30 days&lt;/a&gt;, not good for typical SLA targets but much better than colleagues—and a fraction of their cost.&lt;/p&gt;
&lt;p&gt;Eventually everybody stopped reviewing code&amp;hellip; Save for me.&lt;/p&gt;
&lt;p&gt;For one thing, coding assistants can hallucinate blunders that neither humans—regrettably—nor Coderabbit will catch:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NON_EDITABLE_BOOKING_STATUSES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;cancelled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;canceled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;declined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;withdrawn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I took that snippet from a merge request submitted yesterday. We do not have any &lt;code&gt;declined&lt;/code&gt; or &lt;code&gt;canceled&lt;/code&gt; statuses in our entire domain model—they simply don&amp;rsquo;t exist. I chose this example because it&amp;rsquo;s one of the simplest. The day before, a merge request had a complete re-implementation of a 200+ lines utility we already had. This happens all the time.&lt;/p&gt;
&lt;p&gt;But most importantly, if the phone rings because of an issue on the system, &lt;strong&gt;I&lt;/strong&gt; need to get on the call and fix the issue. When my work has potential to impact my personal life and my weekends with family, there&amp;rsquo;s no AI that I will trust enough. And every alert, whether it fires on the job or not, still costs the whole team velocity when we scramble for unplanned work.&lt;/p&gt;
&lt;p&gt;Ultimately, our customers don&amp;rsquo;t care who wrote the code: &amp;ldquo;We&amp;rsquo;re sorry, Claude wrote that line and Coderabbit didn&amp;rsquo;t catch the problem&amp;rdquo; is not a good look. No matter how powerful our tools have become, responsibility is still mine. So I&amp;rsquo;ll keep on reviewing code.&lt;/p&gt;</description></item><item><title>AI exacerbated the divide between engineers</title><link>https://sbgrl.me/posts/ai-exacerbated-the-divide-between-engineers/</link><pubDate>Sat, 25 Apr 2026 22:12:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/ai-exacerbated-the-divide-between-engineers/</guid><description>&lt;p&gt;Any complex product requires deep context about the product&amp;rsquo;s domains, architecture, implementation. And if you want to make effective use of AI to solve a problem, you need to inject the right subset of that entire context into the prompt, or that smart auto-complete will spit out costly nonsense.&lt;/p&gt;
&lt;p&gt;Naturally, engineers with the best understanding of the context, the capacity to articulate the problem and the skills to implement it are even stronger with AI: they can provide better input to the model and quickly validate its output. The once derided 10x engineer is starting to become a reality for anyone who can use AI to implement a correct solution at a fraction of the time it would have taken them.&lt;/p&gt;
&lt;p&gt;On the other end of the spectrum, weaker engineers are now compelled to keep up with the pace set by their stronger peers, only they can neither prompt the LLM effectively nor validate what it produces. So they just let the garbage out—knowingly or not.&lt;/p&gt;
&lt;p&gt;If you feel like you&amp;rsquo;ve become a &amp;ldquo;man-in-the-middle&amp;rdquo;—a proxy between somebody else&amp;rsquo;s request and an LLM—you will be phased out. The great engineers of today learned the ropes at a time AI didn&amp;rsquo;t exist, when they could invest in their fundamentals. Give yourself the same gift: carve out time to use less AI, and start learning again so you can close the divide.&lt;/p&gt;</description></item><item><title>Claude Code more than doubled my productivity</title><link>https://sbgrl.me/posts/claude-code-made-me-twice-more-productive/</link><pubDate>Sat, 14 Mar 2026 15:28:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/claude-code-made-me-twice-more-productive/</guid><description>&lt;p&gt;I wrote twice as much code last quarter, and it was better. I use Claude Code daily via &lt;a href="https://github.com/stevemolitor/claude-code.el" target="_blank" rel="noreferrer"&gt;Steve Molitor&amp;rsquo;s claude-code.el and monet.el&lt;/a&gt; integration for &lt;a href="https://emacs.org" target="_blank" rel="noreferrer"&gt;Emacs&lt;/a&gt;. Once a review is ready, &lt;a href="https://coderabbit.ai" target="_blank" rel="noreferrer"&gt;CodeRabbit&lt;/a&gt; handles it. My daily AI usage really kicked into high gear back in October 2025, when we got a large amount of Claude Code credit grants and I could use it as much as I wanted.&lt;/p&gt;
&lt;p&gt;The graph below is the number of lines of code changed (added and deleted) in my merge requests (MR), summed monthly, on our main application repository; a Next.js application with 200KLoC — not something an agent can easily get into. I authored 773 merge requests on this project alone which runs in production to serve our clients — just to emphasize the value of the sample-size:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/Lines%20changed%20vs.%20month.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;You can see a clear 2x increase when I started to use Claude Code daily around October 2025. Like most folks, I found Claude Code to be amazing when working as a pair programmer to write the code or review my changes in short feedback loops, to explore greenfield projects or to strengthen tests on brownfield projects.&lt;/p&gt;
&lt;p&gt;Around that time, I felt the quality of my code improving too. Below is the number of &lt;em&gt;normalized comments&lt;/em&gt; \(N\) in each of the MRs: or the number of comments \(C\) divided by the sum of lines of code added \(A\) and deleted \(D\):&lt;/p&gt;
&lt;p&gt;\begin{equation}
N = \frac{C}{(A + D)}
\end{equation}&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/Normalized%20comments%20vs.%20month.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Back in April 2025, we rolled out CodeRabbit which picked up over 70% of the reviewing work for everyone. Most team members do not perform code reviews anymore, except for myself — I&amp;rsquo;ll probably explain why in another post. Thus, on the changes I author, over 95% of the comments are just done by CodeRabbit from that time onwards.&lt;/p&gt;
&lt;p&gt;Normalized comments is an interesting metric since it reflects the quality of my work with Claude Code, prior to review by others and CodeRabbit. If the value &lt;em&gt;decreased&lt;/em&gt; there, it could be that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We got lazy about reviewing, but CodeRabbit is pretty much the only reviewer of my work;&lt;/li&gt;
&lt;li&gt;We output a lot more code or the quality of the code increased;&lt;/li&gt;
&lt;li&gt;The work we do is a lot easier, so there&amp;rsquo;s just fewer issues with it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI certainly had an impact on point 2, given the top graph. You can also see a dip soon after October 2025 when I started to use Claude Code, but then a bump appears again in January 2026. That bump is due to the fact that I had to tackle a challenging issue that spanned several MRs. It&amp;rsquo;s quite visible when looking at the histogram of independent values:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/Normalized%20comments%20per%20merge%20requests.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;There are other common proxies for code quality: such as unit tests. This is the raw number of unit tests on the &lt;code&gt;main&lt;/code&gt; project branch, over time and it went up nearly 5x in the last 4 months:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/Unit%20tests.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Additionally, we have not had production rollbacks or outages in the last 4 months, consistent with the past year (We only ever had to perform production rollbacks twice). So agent usage is not called into &lt;a href="https://www.msn.com/en-us/news/technology/amazon-tightens-guardrails-after-ai-coding-tools-contributed-to-outages/ar-AA1Yttje" target="_blank" rel="noreferrer"&gt;question in our team&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The stats in this post were extracted &lt;a href="https://gitlab.com/sylvain.bougerel/code-review-stats" target="_blank" rel="noreferrer"&gt;using a tool written by Claude Code&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Should you use React Strict Mode in Next.js?</title><link>https://sbgrl.me/posts/should-you-use-react-strictmode/</link><pubDate>Tue, 03 Mar 2026 17:34:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/should-you-use-react-strictmode/</guid><description>&lt;p&gt;If you&amp;rsquo;re not familiar with &lt;a href="https://react.dev/reference/react/StrictMode" target="_blank" rel="noreferrer"&gt;React StrictMode&lt;/a&gt;, you&amp;rsquo;re probably not using a framework like Next.js that enables it by default. Strict Mode aims to help you find bugs during development. But it feels more like a band-aid that can reveal bugs or hide them, especially when mixing with server-side rendering (SSR).&lt;/p&gt;
&lt;p&gt;In this post, I will assume you&amp;rsquo;re using SSR and Strict Mode too as they are usually enabled by default with Next.js.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Enter the rabbit hole
 &lt;div id="enter-the-rabbit-hole" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#enter-the-rabbit-hole" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Now let&amp;rsquo;s suppose you write the following component, that renders a simple &lt;code&gt;&amp;lt;time /&amp;gt;&lt;/code&gt; markup for a given &lt;code&gt;date&lt;/code&gt;. Nothing fancy:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&amp;gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s say you also have a simple home page, in your typical React application:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./local-time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Home() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;It&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;LocalTime&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;02/03/2026, 10:00 AM&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If your application uses SSR, what you will see &lt;strong&gt;always&lt;/strong&gt; depends on your locale and on the time zone of the server; in this case, with an &lt;code&gt;en-US&lt;/code&gt; locale and a UTC timezone, the server will render the following page, even in development:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It is 2/3/2026, 10:00:00 AM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;However, that&amp;rsquo;s usually &lt;strong&gt;not&lt;/strong&gt; what you want for your clients. Let&amp;rsquo;s say your client lives in Singapore, they should expect to see a time in a different timezone (GMT+8 in the case of Singapore). If you&amp;rsquo;re a bit experienced, you know your component must run on the client, and there&amp;rsquo;s a &lt;code&gt;&amp;quot;use client;&amp;quot;&lt;/code&gt; directive in React that ensures a component can be an entry point to client rendering. You update the &lt;code&gt;LocalTime&lt;/code&gt; component:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;use client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// new directive
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&amp;gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Your development server hot-reloads and this time you see:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It is 2/3/2026, 6:00:00 PM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Success! However, when you refresh the page, you notice there&amp;rsquo;s a flicker: the UTC time shows first, then the local time appears. You pull up the console, and you see a pretty large error message:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Uncaught Error: Hydration failed because the server rendered text didn&amp;#39;t match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- A server/client branch `if (typeof window !== &amp;#39;undefined&amp;#39;)`.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Variable input such as `Date.now()` or `Math.random()` which changes each time it&amp;#39;s called.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Date formatting in a user&amp;#39;s locale which doesn&amp;#39;t match the server.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- External changing data without sending a snapshot of it along with the HTML.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Invalid HTML tag nesting.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We&amp;rsquo;ve hit a classic issue with React SSR/CSR: hydration mismatch due to a time zone difference between the server and the client. &lt;strong&gt;And more importantly&lt;/strong&gt;, you might not have noticed that error at all. The flicker might not have caught your attention, you might not have looked at the console.&lt;/p&gt;
&lt;p&gt;You might just have deployed it to production. And to your surprise, once in production the page would show the UTC time only. It seems the component did not run on the client. Not a great user experience:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;It is 2/3/2026, 10:00:00 AM&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In reality, there was no reason for your component to re-render on the client again after hydration. This happened in development only due to React Strict Mode, so we&amp;rsquo;ve been led down the wrong path; thinking the application worked where it didn&amp;rsquo;t. You think this example is contrived? &lt;a href="http://canterly.com" target="_blank" rel="noreferrer"&gt;At Canterly&lt;/a&gt;, we have already faced 3 scenarios similar to this one; each time React Strict Mode was hiding an issue.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The reasoning behind Strict Mode
 &lt;div id="the-reasoning-behind-strict-mode" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-reasoning-behind-strict-mode" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;React brings a flavor of functional programming (FP) to UI. In FP, when the output of a function is defined only by its input, and it has no side effects, we call it a &lt;strong&gt;pure&lt;/strong&gt; function. The React equivalent means that the rendering of a component should be &lt;a href="https://react.dev/learn/keeping-components-pure#purity-components-as-formulas" target="_blank" rel="noreferrer"&gt;idempotent&lt;/a&gt;: it depends only on the component properties, its state and its context. These 3 sources of information are the rendering &lt;strong&gt;inputs&lt;/strong&gt; in React; you should not use any other inputs.&lt;/p&gt;
&lt;p&gt;That is not to say React does not handle side effects. However, you should confine side effects to events handlers, and in case you can&amp;rsquo;t find a suitable event handler for your side effect, React dedicates a whole phase of its update to side effects &lt;a href="https://react.dev/reference/react/hooks#effect-hooks" target="_blank" rel="noreferrer"&gt;which you declare in the &lt;code&gt;use*Effect()&lt;/code&gt; hooks&lt;/a&gt;. The steps below are an over-simplification of an update with React (&lt;a href="https://react.dev/learn/render-and-commit" target="_blank" rel="noreferrer"&gt;more about it here&lt;/a&gt;):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there are no updates to component inputs, go to the last step;&lt;/li&gt;
&lt;li&gt;Render updated components;&lt;/li&gt;
&lt;li&gt;Run side effects;&lt;/li&gt;
&lt;li&gt;Return to the top;&lt;/li&gt;
&lt;li&gt;Commit changes to the DOM.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Idempotency allows React to track component changes by looking only at their inputs. However, it&amp;rsquo;s easy to break idempotency if you unknowingly use an external input, modify properties incorrectly or rely on a variable during rendering that is not provided through its properties, state or context.&lt;/p&gt;
&lt;p&gt;To help uncover these issues, React introduced Strict Mode. Wrapping your component in &lt;code&gt;&amp;lt;StrictMode&amp;gt;&lt;/code&gt; adds an extra render on mount and after the side effects run, to check that rendering was not impacted:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there are no updates to component inputs, go to the last step;&lt;/li&gt;
&lt;li&gt;Render updated components;&lt;/li&gt;
&lt;li&gt;Run declared side effects;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strict mode: render all components once more and check there are no differences with the output in 2.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Return to the top;&lt;/li&gt;
&lt;li&gt;Commit changes to the DOM.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If your React component is pure, then that extra render should be idempotent and similar to the first.&lt;/p&gt;

&lt;h2 class="relative group"&gt;False positives with Strict Mode
 &lt;div id="false-positives-with-strict-mode" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#false-positives-with-strict-mode" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Returning to our example: the first server-side render produces an incorrect UTC time, which could have been caught if it was not immediately redrawn by the extra Strict Mode render that occurred when React mounted the component. Instead, the developer saw the local time.&lt;/p&gt;
&lt;p&gt;This little example illustrates that this approach is just as likely to catch a bug as to hide one, since Strict Mode can lead to false positives. Especially if you&amp;rsquo;re not careful with checking hydration issues.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that a more careful developer could also catch the hydration mismatch. And they might be tempted to address the locale &amp;amp; time zone hydration mismatch by simply silencing it, since this component does not have any children:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;use client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="na"&gt;suppressHydrationWarning&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Perhaps unexpectedly, once the component uses the property &lt;code&gt;suppressHydrationWarning&lt;/code&gt;, the UTC time rendered server side re-appears and is not replaced by the local time in development. Even in the presence of Strict Mode, the component is not regenerated again. This could have revealed the error again; but requires employing a property that is usually discouraged.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Beyond Strict Mode
 &lt;div id="beyond-strict-mode" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#beyond-strict-mode" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Since React 18, the React Compiler &lt;a href="https://react.dev/blog/2024/10/21/react-compiler-beta-release#we-recommend-everyone-use-the-react-compiler-linter-today" target="_blank" rel="noreferrer"&gt;has grown a new linter&lt;/a&gt;. I highly recommend the React Compiler ESLint plugin. While it does not address all the objectives of Strict Mode, there&amp;rsquo;s a significant amount of overlap between the two tools. In my own usage, I have found that the linter helps me better with identifying violations of &lt;a href="https://react.dev/reference/rules" target="_blank" rel="noreferrer"&gt;the Rules of React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, when it comes to our little example, the linter does not identify that &lt;code&gt;date.toLocaleString()&lt;/code&gt; requires a time zone and a locale that are not declared as input to the component. The linter is still a work in progress; I hope that these limitations will eventually be addressed.&lt;/p&gt;
&lt;p&gt;I will leave you with a version of the component that follows the Rules of React (note that this version will not scale well if you have a lot of components to render):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;use client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffectEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;react&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;: &lt;span class="kt"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;formattedDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFormattedDate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Fix a locale and a time zone for idempotency
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;en-US&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;timeZone&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;UTC&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isoString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useEffectEvent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setFormattedDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt; &lt;span class="na"&gt;dateTime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isoString&lt;/span&gt;&lt;span class="p"&gt;}&amp;gt;{&lt;/span&gt;&lt;span class="nx"&gt;formattedDate&lt;/span&gt;&lt;span class="p"&gt;}&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;time&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This may seem a little complicated for a component that just renders a date without any interactions; but remember that there&amp;rsquo;s a hidden side effect in the original rendering. And unfortunately, in this specific scenario, Strict Mode put us on the wrong path.&lt;/p&gt;
&lt;p&gt;One of the selling points of Strict Mode is that it will help reveal the issues with components that violate the Rules of React. But I would argue that if the issues can&amp;rsquo;t be found without Strict Mode, then you might as well just ignore them: your component is probably too simple to care about them. Otherwise, you should always pay attention to hydration issues and ensure your project uses the React Compiler ESLint plugin.&lt;/p&gt;</description></item><item><title>Interop makes my day joyful</title><link>https://sbgrl.me/posts/interop-makes-my-day-joyful/</link><pubDate>Tue, 17 Feb 2026 20:06:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/interop-makes-my-day-joyful/</guid><description>&lt;p&gt;Check out &lt;a href="https://hacks.mozilla.org/2026/02/launching-interop-2026" target="_blank" rel="noreferrer"&gt;Interop 2026&lt;/a&gt;. It has been one of the driving force behind aligning browser&amp;rsquo;s compatibility across the market and towards the standards.&lt;/p&gt;
&lt;p&gt;I used to hate UI development, and by far I hated web development the most. The incompatibility across browsers has been a plague on web development. I think my feelings are widely echoed across the community, as something you would just have to put up with. I generally stayed away from doing any web development.&lt;/p&gt;
&lt;p&gt;For me, things started to really turn around circa 2017. When I joined Grab around that time and discovered &lt;a href="https://caniuse.com/" target="_blank" rel="noreferrer"&gt;caniuse.com&lt;/a&gt; or &lt;a href="https://github.com/facebook/react/blob/main/CHANGELOG.md#1600-september-26-2017" target="_blank" rel="noreferrer"&gt;React 16&lt;/a&gt;, web development started to feel less like a pile of hacks. Structure and organized community efforts started to emerge.&lt;/p&gt;
&lt;p&gt;The launch of the &lt;a href="https://web-platform-tests.org/" target="_blank" rel="noreferrer"&gt;web-platform-tests&lt;/a&gt; has undoubtedly been a major step forward. Thanks to this project and others, browser compatibility is generally not a concern in my daily web development routine. I can safely say there&amp;rsquo;s real joy to web development nowadays, and LLMs have made it easier than ever to get into.&lt;/p&gt;</description></item><item><title>Introducing @sbougerel/eslint-plugin-next-use-client-boundary</title><link>https://sbgrl.me/posts/introducing-sbougerel-eslint-plugin-next-use-client-boundary/</link><pubDate>Sat, 14 Feb 2026 17:46:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/introducing-sbougerel-eslint-plugin-next-use-client-boundary/</guid><description>&lt;p&gt;In &lt;a href="https://sbgrl.me/posts/dont-abuse-use-client/" target="_blank" rel="noreferrer"&gt;my last post&lt;/a&gt; I spoke about releasing an ESLint plugin to lint the serialization of &lt;code&gt;'use client'&lt;/code&gt; module entries. I&amp;rsquo;ve spend a few hours with Claude code to &lt;a href="https://www.npmjs.com/package/@sbougerel/eslint-plugin-next-use-client-boundary" target="_blank" rel="noreferrer"&gt;get this done and published on npmjs.org&lt;/a&gt;. I&amp;rsquo;ll probably come back in another post on the experience there (Some gains, some losses). I&amp;rsquo;ve just started a branch on my product to use the plugin, looking forward to see if it&amp;rsquo;s useful to others.&lt;/p&gt;</description></item><item><title>Don't abuse 'use client'</title><link>https://sbgrl.me/posts/dont-abuse-use-client/</link><pubDate>Sat, 31 Jan 2026 17:56:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/dont-abuse-use-client/</guid><description>&lt;p&gt;I see &lt;code&gt;'use client'&lt;/code&gt; used too often in our own code base, and it seems to be &lt;a href="https://github.com/vercel/next.js/discussions/46795" target="_blank" rel="noreferrer"&gt;a matter of confusion elsewhere too&lt;/a&gt;: devs don&amp;rsquo;t understand &lt;code&gt;'use client'&lt;/code&gt; or don&amp;rsquo;t want to get into it. Let me give a quick explanation of why you should not use it everywhere and hopefully, by the end of this post, you&amp;rsquo;ll have at least understood what it is &lt;strong&gt;not&lt;/strong&gt; for.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;'use client'&lt;/code&gt; indicates that a client module can be called within server modules. No more no less. That means &lt;code&gt;'use client'&lt;/code&gt; modules have some restrictions, because they can sit at the server-client boundary:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;When a server evaluated module imports values from a &lt;code&gt;'use client'&lt;/code&gt; module, the values must either be a React component or supported serializable prop values to be passed to a Client Component. Any other use case will throw an exception.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;(Source: &lt;a href="https://react.dev/reference/rsc/use-client#caveats" target="_blank" rel="noreferrer"&gt;React reference&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The thing is: most types are serializable, so you don&amp;rsquo;t generally see an issue with putting &lt;code&gt;'use client'&lt;/code&gt; everywhere. But one commonly used type is not: &lt;strong&gt;plain simple functions&lt;/strong&gt;. It should make intuitive sense: a reference to a function is meaningless at the server-client boundary (unless it is a server action).&lt;/p&gt;
&lt;p&gt;In practice: if your reactive client component exposes events to its parents, like &lt;code&gt;onSubmit&lt;/code&gt; and &lt;code&gt;onCancel&lt;/code&gt; in the component below, that means we expect the parents to be client component themselves.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LineItemForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onCancel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;account&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;LineItemData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onCancel&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;How could it be otherwise? The function &lt;code&gt;onSubmit&lt;/code&gt; or &lt;code&gt;onCancel&lt;/code&gt; could never cross the client-server boundary. Thus, you can remember now:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;When an exported client component has properties that are not serializable, like functions, its module should not start with &lt;code&gt;'use client'&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Now, you may be thinking: &amp;ldquo;But adding &lt;code&gt;'use client'&lt;/code&gt; does not hurt anything, right?&amp;rdquo; Ever taken painkillers when you don&amp;rsquo;t need it? It&amp;rsquo;s the same reasoning: it does not harm, but at the critical moment where you need to feel pain, you&amp;rsquo;re suddenly numb to it.&lt;/p&gt;
&lt;p&gt;Without &lt;code&gt;'use client'&lt;/code&gt; at the top of that component, the compiler will not let you call that client component from a server component. But with an incorrect &lt;code&gt;'use client'&lt;/code&gt; the compiler will just obey and assume you&amp;rsquo;ve made sure the properties are serializable &lt;strong&gt;without checking&lt;/strong&gt;, and misusing that component in server components could result in runtime errors.&lt;/p&gt;
&lt;p&gt;Unfortunately, I cannot find any linter that lints this. There is however a dynamic warning provided by the &lt;code&gt;typescript-language-server&lt;/code&gt;: &lt;code&gt;Props must be serializable for components in the &amp;quot;use client&amp;quot; entry file&lt;/code&gt;. Adding a linter for this would be an interesting project.&lt;/p&gt;</description></item><item><title>3 rules for simple tenancy in DynamoDB</title><link>https://sbgrl.me/posts/3-rules-for-simple-tenancy-in-dynamodb/</link><pubDate>Mon, 12 Jan 2026 12:39:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/3-rules-for-simple-tenancy-in-dynamodb/</guid><description>
&lt;h2 class="relative group"&gt;Access control in DynamoDB
 &lt;div id="access-control-in-dynamodb" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#access-control-in-dynamodb" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://aws.amazon.com/dynamodb/" target="_blank" rel="noreferrer"&gt;DynamoDB&lt;/a&gt; does not come with its own account management &amp;amp; fine-grained access control. Instead, it integrates with IAM, just like other AWS services.&lt;/p&gt;
&lt;p&gt;This is great when your users authenticate with IAM, but it&amp;rsquo;s not the case of the majority of serverless web applications that authenticate users via &lt;a href="https://oauth.net/2" target="_blank" rel="noreferrer"&gt;OAuth&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Serverless web applications on AWS are &lt;em&gt;usually&lt;/em&gt; structured this way: API Gateway receives a request, proxies this request to a Lambda, that constructs the response, optionally querying DynamoDB. In this architecture, the Lambda runs with a static IAM role. OAuth authentication is performed in the Lambda runtime using this static role; i.e. &lt;em&gt;no&lt;/em&gt; fine-grained IAM access control.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/3-rules-for-simple-tenancy-in-dynamodb-1.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This approach is usually preferred because it is simple, responsive, and yet scales. It leaves the responsibility of access control to the Lambda&amp;rsquo;s runtime, however&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re writing a SaaS application in particular, you need to make sure that your tenants are &lt;a href="https://www.saas-architecture.com/capabilities/tenant-isolation.html" target="_blank" rel="noreferrer"&gt;isolated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, by following 3 rules, DynamoDB can provide tenant-based access control.&lt;/p&gt;

&lt;h2 class="relative group"&gt;All partition keys have &lt;code&gt;tenant&lt;/code&gt;
 &lt;div id="all-partition-keys-have-tenant" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#all-partition-keys-have-tenant" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Make the &lt;code&gt;tenant&lt;/code&gt; identifier a constituent of all your items&amp;rsquo; partition key (a.k.a. hash key). For a &lt;code&gt;String&lt;/code&gt; partition key, with a &lt;a href="https://aws.amazon.com/blogs/database/amazon-dynamodb-single-table-design-using-dynamodbmapper-and-spring-boot/" target="_blank" rel="noreferrer"&gt;single-table design&lt;/a&gt;, the value of the partition key be:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;${tenant}#${entity_kind}#${item_partition_key}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;No &lt;code&gt;SCAN&lt;/code&gt;
 &lt;div id="no-scan" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#no-scan" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;All other &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html" target="_blank" rel="noreferrer"&gt;CRUD operations&lt;/a&gt; are allowed, however &lt;em&gt;you shall not use &lt;code&gt;scan&lt;/code&gt;&lt;/em&gt;. Scanning in DynamoDB reads any of your partitions &lt;em&gt;at random&lt;/em&gt;. Thus, it bypasses the first rule. Enforce this in your Lambda role permission: deny &lt;code&gt;dynamodb:Scan&lt;/code&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Get &lt;code&gt;tenant&lt;/code&gt; from the request only
 &lt;div id="get-tenant-from-the-request-only" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#get-tenant-from-the-request-only" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The final piece: the tenant identifier is always provided by the request. It shall not be a value you can retrieve from a query in the same table. If you do need to provide a list of tenants somewhere, this should be done separately, in a different table or preferably in a different cell.&lt;/p&gt;

&lt;h2 class="relative group"&gt;How does it work?
 &lt;div id="how-does-it-work" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-does-it-work" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;All CRUD operations in DynamoDB require a partition key. All except &lt;code&gt;scan&lt;/code&gt; which is why we disable it in rule 2.&lt;/p&gt;
&lt;p&gt;Rule 3 ensures that a client&amp;rsquo;s access to a tenant partition is handled separately; the &lt;code&gt;tenant&lt;/code&gt; identifier acts as a message passed between this step and the current requests; isolating each request to a single tenant. As a corollary, you should treat any request that require access to multiple tenant with higher restrictions, ideally running on a separate Lambda with the least privileges.&lt;/p&gt;
&lt;p&gt;Finally, rule 1 leverages rule 2 and 3 to ensure that your CRUD operations can only retrieve information from that &lt;code&gt;tenant&lt;/code&gt;&amp;rsquo;s partition, since it becomes impossible to access other tenants&amp;rsquo; data without their identifiers.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Limitations
 &lt;div id="limitations" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#limitations" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;While this works well to isolate requests to a single tenant, this remains a &amp;ldquo;coarse-grained&amp;rdquo; approach; you do not have access to the attribute based access control provided by IAM and will need to implement the equivalent permissions in your application.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html" target="_blank" rel="noreferrer"&gt;API Gateway&amp;rsquo;s Lambda authorizers&lt;/a&gt; allow you to provide context variables that can be used in dynamic fine-grained access control roles. This approach is more complex and not common for Next.js applications, in particular.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>Yodarabbit</title><link>https://sbgrl.me/posts/tune-coderabbit-s-tone/</link><pubDate>Sun, 11 Jan 2026 13:06:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/tune-coderabbit-s-tone/</guid><description>&lt;p&gt;&lt;a href="https://www.coderabbit.ai/" target="_blank" rel="noreferrer"&gt;Coderabbit&lt;/a&gt; has this new feature &lt;a href="https://docs.coderabbit.ai/changelog/new-features-and-improvements-2#tone-settings" target="_blank" rel="noreferrer"&gt;that allows you to change its tone&lt;/a&gt;. I can&amp;rsquo;t wait to ask it to speak like Yoda.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# .coderabbit.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tone_instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Speak like Yoda, you must.&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</description></item><item><title>Pino log instrumentation with Next.js 16</title><link>https://sbgrl.me/posts/pino-log-instrumentation-in-next-js-16/</link><pubDate>Sun, 04 Jan 2026 11:39:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/pino-log-instrumentation-in-next-js-16/</guid><description>&lt;p&gt;Recently at &lt;a href="https://canterly.com" target="_blank" rel="noreferrer"&gt;Canterly&lt;/a&gt;, we migrated from Next.js 14 to Next.js 16. In that process, we also switched from &lt;a href="https://webpack.js.org/" target="_blank" rel="noreferrer"&gt;webpack&lt;/a&gt; to &lt;a href="https://nextjs.org/docs/app/api-reference/turbopack" target="_blank" rel="noreferrer"&gt;turbopack&lt;/a&gt;. This was a great speed improvement that came with some surprises. In particular, log instrumentation via &lt;a href="https://github.com/sainsburys-tech/next-logger" target="_blank" rel="noreferrer"&gt;next-logger&lt;/a&gt; now triggered a failure during compilation. I had been wanting to replace it anyway, since all it does is to patch &lt;code&gt;console&lt;/code&gt; to use &lt;a href="https://getpino.io" target="_blank" rel="noreferrer"&gt;Pino&lt;/a&gt; instead (It also patches log functions in Next.js &lt;code&gt;next/dist/build/output/log&lt;/code&gt;, which is redundant since these functions use &lt;code&gt;console&lt;/code&gt; too). So instead, I patched &lt;code&gt;console&lt;/code&gt; on my own. This is simpler and allows us to keep control over the instrumentation, rather than obfuscating the process with an additional dependency.&lt;/p&gt;
&lt;p&gt;This is what we have now in &lt;code&gt;instrumentation.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;register() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_RUNTIME&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;nodejs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instrumentation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./instrumentation-nodejs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;instrumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We&amp;rsquo;ve replaced &lt;code&gt;next-logger.config.cjs&lt;/code&gt; with our own &lt;code&gt;logger.ts&lt;/code&gt; which is a straight forward port:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoggerOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pino&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pino&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pinoOptions&lt;/span&gt;: &lt;span class="kt"&gt;LoggerOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;warn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;production&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pinoOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;info&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pino-pretty&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pino&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pinoOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And in &lt;code&gt;instrumentation-nodejs.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LogFn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pino&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;instrumentLogging() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// NextJS also patches `console` functions during development for better
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// debugging; so we normally bypass instrumentation unless the environment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// variable CANTERLY_INSTRUMENTATION is &amp;#34;true&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;development&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CANTERLY_INSTRUMENTATION&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loggerModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;./logger&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;: &lt;span class="kt"&gt;LogFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;: &lt;span class="kt"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Converts usual Console usage to Pino&amp;#39;s meta patterns. Console
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// calls usually start with the message, while Pino meta patterns take the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// message in the second argument. And when we can&amp;#39;t tell if we have a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// message, we use a `consoleArgs` property instead, which is clearer.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;string&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;string&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Next.js&amp;#39;s prefixedLog calls console.error(ansiPrefix, errorObject[, ...]).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Errors in consoleArgs would serialise to `{}` (no enumerable properties),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// so extract one and let Pino&amp;#39;s built-in err serializer handle it instead.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errArg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errArg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;errArg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;: &lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;: &lt;span class="kt"&gt;errArg&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consoleArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;consoleArgs&lt;/span&gt;: &lt;span class="kt"&gt;rest&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;consoleArgs&lt;/span&gt;: &lt;span class="kt"&gt;args&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;consoleLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loggerModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;console&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;warn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;debug&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;info&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;log&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;trace&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* eslint-disable no-console */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;log&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;: &lt;span class="kt"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;wrapLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;: &lt;span class="kt"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;wrapLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consoleLogger&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="cm"&gt;/* eslint-enable no-console */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;register() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;instrumentLogging&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And that&amp;rsquo;s it. This instrumentation does not just replace next-logger, it improves on it. With next-logger, the following &lt;code&gt;console&lt;/code&gt; call&amp;hellip;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;console.log(&amp;#34;Log without interpolation&amp;#34;, valueA, valueB, valueC)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;hellip;would be patched to the Pino call&amp;hellip;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;logger.log(&amp;#34;Log without interpolation&amp;#34;, valueA, valueB, valueC)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&amp;hellip;but that would lose logging data: when the first argument is a &lt;code&gt;string&lt;/code&gt; message, Pino will only keep other arguments &lt;a href="https://getpino.io/#/docs/api?id=interpolationvalues-any" target="_blank" rel="noreferrer"&gt;when they get interpolated&lt;/a&gt;. This new instrumentation preserves the additional arguments, by treating them as a &lt;a href="https://getpino.io/#/docs/api?id=mergingobject-object" target="_blank" rel="noreferrer"&gt;mergingObject&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, when our application starts, we can immediately see the additional arguments in the logs, as soon as instrumentation kicks in (We use &lt;code&gt;pino-pretty&lt;/code&gt; as the default transport during development):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm run dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; env TZ=UTC next dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;▲ Next.js 16.1.1 (Turbopack)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Local: http://localhost:3000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Network: http://...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Environments: .env.local
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✓ Starting...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[03:23:14.265] INFO (console/28071): ✓ Ready in 1439ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[03:23:14.447] WARN (console/28071): [auth][warn][debug-enabled]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; consoleArgs: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;Read more: https://warnings.authjs.dev&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Update&lt;span class="timestamp-wrapper"&gt;&lt;span class="timestamp"&gt;&amp;lt;2026-09-01 Tue&amp;gt;&lt;/span&gt;&lt;/span&gt;:
I&amp;rsquo;ve updated the logger code above, there was a bug lurking in the original code that would cause the logger to print an empty message. I fixed it and the post has the new version.&lt;/p&gt;</description></item><item><title>NextJS revalidateTag won't work if it's not called in an awaited promise</title><link>https://sbgrl.me/posts/nextjs-revalidate-tag-wont-work-unless-awaited/</link><pubDate>Thu, 18 Dec 2025 18:37:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/nextjs-revalidate-tag-wont-work-unless-awaited/</guid><description>&lt;p&gt;Using &lt;code&gt;unstable_cache&lt;/code&gt; extensions or &lt;code&gt;&amp;quot;use cache&amp;quot;&lt;/code&gt; if you&amp;rsquo;re on NextJS16, you can leverage &lt;a href="https://nextjs.org/docs/app/api-reference/functions/revalidateTag" target="_blank" rel="noreferrer"&gt;&lt;code&gt;revalidateTag&lt;/code&gt;&lt;/a&gt; to invalidate cache across cached calls. However, I noticed today that these won&amp;rsquo;t work in a route handler that is not &lt;code&gt;async&lt;/code&gt;. And I&amp;rsquo;m not yet sure why.&lt;/p&gt;
&lt;p&gt;This was my original (simplified) route handler:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;: &lt;span class="kt"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;someTask&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Internal error`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;: &lt;span class="kt"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that &lt;code&gt;someTask&lt;/code&gt; returns a &lt;code&gt;Promise&lt;/code&gt; which I didn&amp;rsquo;t &lt;code&gt;await&lt;/code&gt; initially: the caller only needed to know that &lt;code&gt;someTask&lt;/code&gt; was called, not that it succeeded. However, it became evident &lt;code&gt;someTask&lt;/code&gt; didn&amp;rsquo;t behave properly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;someTask&lt;/code&gt; gets a list of subscriptions and updates them progressively. After each update, it re-validates cache entries associated with the &lt;code&gt;Subscription&lt;/code&gt; tag:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachedFindOutdatedSubscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unstable_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;findOutdatedSubscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Unused (For additional keys)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Subscription&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;someTask() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cachedFindOutdatedSubscriptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;updateSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Subscription&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The function &lt;code&gt;cachedFindOutdatedSubscriptions&lt;/code&gt; returns subscriptions from a cache entry also tagged with &lt;code&gt;Subscription&lt;/code&gt;. You would therefore expect that across repeated calls to this route, the &lt;code&gt;subscriptions&lt;/code&gt; array becomes empty when no new subscriptions are created.&lt;/p&gt;
&lt;p&gt;This is not what happened; the same &lt;code&gt;subscriptions&lt;/code&gt; got reprocessed every single time, served from the cache. As if &lt;code&gt;revalidateTag&lt;/code&gt; was not called.&lt;/p&gt;
&lt;p&gt;Interestingly, the fix is simply to await the call to &lt;code&gt;someTask&lt;/code&gt; in the route handler:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; export async function POST(req: Request) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; try {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- void someTask();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ await someTask();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return NextResponse.json({ done: true });
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; } catch (err) {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return new NextResponse(`Internal error`, {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; status: 500,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After reading the code for &lt;code&gt;unstable_cache&lt;/code&gt;, I got the intuition for the fix. I&amp;rsquo;m not sure of what is exactly happening under the hood yet, but I think it has to do with the lifetime of the request&amp;rsquo;s context. I&amp;rsquo;ll return to this once I understand it properly.&lt;/p&gt;</description></item><item><title>Private Language Tools for browser spell checks</title><link>https://sbgrl.me/posts/private-language-tools-for-browser-spell-checks/</link><pubDate>Mon, 15 Dec 2025 09:00:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/private-language-tools-for-browser-spell-checks/</guid><description>&lt;p&gt;With &lt;a href="https://languagetool.org" target="_blank" rel="noreferrer"&gt;LanguageTool&lt;/a&gt; (LT), it&amp;rsquo;s possible to run a local language tool server and have your browser extension connect to it. That way, your personal prose remains private (if you trust the browser extension, &lt;a href="https://www.reddit.com/r/privacy/comments/1n1v6yi/any_good_grammar_and_spellchecker_software/" target="_blank" rel="noreferrer"&gt;that is&lt;/a&gt;). &lt;a href="https://dev.languagetool.org/http-server" target="_blank" rel="noreferrer"&gt;It&amp;rsquo;s all documented here&lt;/a&gt; and it&amp;rsquo;s pretty simple to do. In this post, I cover how you can do this on Arch Linux (but it&amp;rsquo;s likely easy to port to other distro) with &lt;a href="https://systemd.io/" target="_blank" rel="noreferrer"&gt;systemd&lt;/a&gt; to keep that server running.&lt;/p&gt;
&lt;p&gt;Start by getting LT.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;yay -S languagetool&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You&amp;rsquo;ll also need &lt;a href="https://fasttext.cc/" target="_blank" rel="noreferrer"&gt;fastText&lt;/a&gt; and a language identification model. Fortunately, there&amp;rsquo;s already 2 good packages for it in &lt;a href="https://aur.archlinux.org/" target="_blank" rel="noreferrer"&gt;AUR&lt;/a&gt; (Check the &lt;code&gt;PKGBUILD&lt;/code&gt; — one can never be &lt;a href="https://dailysecurityreview.com/podcasts/arch-linux-website-forums-and-aur-targeted-in-sustained-cyber-assault/" target="_blank" rel="noreferrer"&gt;too&lt;/a&gt; &lt;a href="https://www.bleepingcomputer.com/news/security/arch-linux-pulls-aur-packages-that-installed-chaos-rat-malware/" target="_blank" rel="noreferrer"&gt;sure&lt;/a&gt; — but it looks correct at the time of writing).&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;yay -S fasttext fasttext-langid-models&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s it for the dependencies. Now for your local setup. Create the file &lt;code&gt;~/.config/systemd/user/my-languagetool.service&lt;/code&gt; with the content:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;[Unit]
Description=LanguageTool Grammar Checker Server
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/languagetool --http --port 8081 --allow-origin &amp;#34;*&amp;#34; --config %h/.config/LanguageTool/server.properties
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As well as the file &lt;code&gt;~/.config/LanguageTool/server.properties&lt;/code&gt; with the content:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;fasttextModel=/usr/share/fasttext/lid.176.bin
fasttextBinary=/usr/bin/fasttext&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now you&amp;rsquo;re ready to start your local server and keep it up and running for your browser extension:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;systemctl --user daemon-reload
systemctl --user enable my-languagetool.service
systemctl --user start my-languagetool.service&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You just need to configure the settings in your browser extension to ensure it connects to your local LT server.&lt;/p&gt;</description></item><item><title>Tips for technical interviews</title><link>https://sbgrl.me/posts/tips-for-technical-interviews/</link><pubDate>Fri, 08 Aug 2025 23:35:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/tips-for-technical-interviews/</guid><description>&lt;p&gt;Since I am currently on the lookout for a position, I thought I&amp;rsquo;d share some tips that apply to system design or technical interviews — except the coding interview — that could help you prepare as well.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Growth mindset
 &lt;div id="growth-mindset" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#growth-mindset" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s the interviewer&amp;rsquo;s job to throw curveballs. Your role is not to dodge them. Always work with new constraints; often, when doing so, you find new ways to look at your problem. It&amp;rsquo;s okay to not know something, so long as you demonstrate how you&amp;rsquo;d move forward:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;I don&amp;rsquo;t know this part, but here is how I can find out or tackle it&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;Focus on the technical
 &lt;div id="focus-on-the-technical" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#focus-on-the-technical" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;It may sound obvious, but if asked a question about your experience or if asked to provide more details about a line on your resume, focus on the technical. Mentioning your stint as a manager or the fact that you took a sabbatical break to travel the world may be okay if kept to an absolute minimum (as mentioned here), but don&amp;rsquo;t expend your energy on this; this is not the signal the interviewer is usually looking for.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Balance breadth and depth
 &lt;div id="balance-breadth-and-depth" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#balance-breadth-and-depth" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The interviewer wants to find if you have the breadth and depth of knowledge that fit the role. You must play both angles: explore in breadth whenever you need to outline your solution, but don&amp;rsquo;t miss an opportunity to explore in depth when answering a question. If you answer a specific question with hand-wavy statements, you may not get that opportunity back: you rarely come back twice on the same question in an interview.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Research!
 &lt;div id="research" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#research" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;You know what never happened in 20 years of interviewing: company working in domain A asking a question about domain B. Every single technical interview will center around something the company &lt;em&gt;already does or will do very soon&lt;/em&gt;. Therefore, those who do extensive research about the company&amp;rsquo;s domain stand to have an edge: you understand hints and questions better, it becomes easier to speak the same language as the interviewer.&lt;/p&gt;
&lt;p&gt;Things you should research before a technical interview:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blogs or engineering articles by the company or its competitors,&lt;/li&gt;
&lt;li&gt;Discover as much about the company&amp;rsquo;s systems and technologies in use as you do about its process, regulations, and other domain constraints,&lt;/li&gt;
&lt;li&gt;Map the organization, how it relates to its customer base,&lt;/li&gt;
&lt;li&gt;Understand its engineering culture,&lt;/li&gt;
&lt;li&gt;Harness AI to be a mock interviewer to train on related topics from the information you gathered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Quantitative over qualitative
 &lt;div id="quantitative-over-qualitative" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#quantitative-over-qualitative" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;If you can quantify something, it&amp;rsquo;s better than qualifying it. But if you can&amp;rsquo;t quantify it, then qualifying it is better than nothing. For example, prefer:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;DynamoDB can handle 1M QPS (read and write) compared to MySQL 10K QPS (reads and writes)&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Over:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;DynamoDB can handle much larger QPS (read and write) than MySQL.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Numbers are king, and help you justify your design decisions. I included a data capacity ladder at the end of the article.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Divide and conquer generalities
 &lt;div id="divide-and-conquer-generalities" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#divide-and-conquer-generalities" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;If the interviewer asks any question, you should evaluate whether to break it down first before narrowing and going in depth on some topics. An example of a vague question could be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;I see that you have experience with AWS EC2, could you describe what it is?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;EC2 is one of the most complex AWS services, one that could take the entire interview to answer. Typically, the answer calls for a breakdown after a short intro:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;EC2 is an AWS service that manages virtual machines running on the cloud. EC2 is made up of many subsystems required to run VMs: the nodes themselves (instances), the block storage (EBS), the images (AMIs), the network security groups and network interfaces (ENIs). It provides autoscaling capabilities through Auto Scaling Groups. Instances can be provisioned on-demand, spot, or reserved. Would you like me to zoom in on any particular subsystem?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;This is a top-level breakdown of EC2, only 1 layer deep. It&amp;rsquo;s not in-depth on any system but gives enough insights to the interviewer that you are comfortable with the topic. That last hook gives pause and allows the interviewer to decide how it wants to spend its time.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Structure of the system design interview
 &lt;div id="structure-of-the-system-design-interview" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#structure-of-the-system-design-interview" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;For system design interviews specifically, you want to follow a structured plan and a design methodology. I recommend using the &lt;a href="https://c4model.com" target="_blank" rel="noreferrer"&gt;C4 model&lt;/a&gt; as a design methodology because it is hierarchical and feels natural. Usually, the plan is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clarify: make sure you understand the focus of the exercise. You don&amp;rsquo;t want to dwell on sub-components that the interviewer does not care about. At this stage you should have an understanding of the domain and you should be able to draw your system &lt;a href="https://c4model.com/diagrams/system-context" target="_blank" rel="noreferrer"&gt;context&lt;/a&gt;. Ensure that you do not miss any &amp;ldquo;meaty&amp;rdquo; parts of your system.&lt;/li&gt;
&lt;li&gt;Dimension: pin down the spatial scope (e.g. world-wide vs city?), temporal scope, and the quantity of data exchanged and stored in each context. At this stage, you should have a rough understanding of the orders of magnitude for every dimension. This will drive the justifications of your design.&lt;/li&gt;
&lt;li&gt;Overview: break each system context into its constituent &lt;a href="https://c4model.com/diagrams/container" target="_blank" rel="noreferrer"&gt;containers&lt;/a&gt;. This is where you describe your approach to the interviewer. You should label the flow, the main functions and give a brief description of how each container satisfies the constraints of the problem, e.g. how the system scales to meet its demand.&lt;/li&gt;
&lt;li&gt;Deep-dive: give the interviewer the opportunity to ask specific questions and propose to zoom in on some particular &lt;a href="https://c4model.com/diagrams/component" target="_blank" rel="noreferrer"&gt;component&lt;/a&gt; of your design. It&amp;rsquo;s a good time to discuss protocols, define APIs and describe in detail how your design can solve a specific problem.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a 45-minute interview, try to keep the Clarify and Dimension sections under 15 minutes total. You want to spend most of your time in Overview and Deep-dive. Finally, if it helps (it does for me) restate the objective of what you are trying to demonstrate at each hierarchy.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Practice
 &lt;div id="practice" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#practice" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Nothing replaces practice, no matter how confident you are in your skills; being able to demonstrate that confidence in a short interview context requires practice.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with your CV items,&lt;/li&gt;
&lt;li&gt;Identify the technical questions based on the keywords or claims in your CV,&lt;/li&gt;
&lt;li&gt;Write down your narratives,&lt;/li&gt;
&lt;li&gt;Practice answering these questions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;Remember to smile
 &lt;div id="remember-to-smile" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#remember-to-smile" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;An interview is a stressful setting. But all things being equal, candidates who came across best get the gig.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Sometimes, it&amp;rsquo;s better to ask
 &lt;div id="sometimes-it-s-better-to-ask" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#sometimes-it-s-better-to-ask" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Most interviewers prefer to be engaged in an interview. I certainly do, when I interview. If you feel out of your depth on a particular task or question, don&amp;rsquo;t hesitate to ask for guidance. The interviewer will generally give you the clues needed so you can move forward. It&amp;rsquo;s better to show humility in such a situation than to speak incorrectly about something you do not know or understand well, or — worse — leave no doubt about your ignorance of the subject.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Get past your impostor syndrome
 &lt;div id="get-past-your-impostor-syndrome" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#get-past-your-impostor-syndrome" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;The interview is not the place to question your (lack of) knowledge constantly. The biggest disservice you can do to yourself is selling yourself short. Do not provide information about the limits of your expertise, as that could further be misinterpreted. It is already difficult enough to convince the interviewer that you know enough in a particular area, in such a short span of time. Give yourself a break and a bit of confidence.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Data transfer ladder
 &lt;div id="data-transfer-ladder" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#data-transfer-ladder" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Some illustrative operations or requests per second to memorize when discussing distributed systems:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/throughput-operations.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Typical data transfer rates of some servers, proxies and messaging technologies:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/throughput-network.svg" alt="" /&gt;
 
 
 &lt;/figure&gt;</description></item><item><title>Domino and tromino tiling sorcery</title><link>https://sbgrl.me/posts/domino-tromino-tiling-sorcery/</link><pubDate>Sat, 18 May 2024 23:24:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/domino-tromino-tiling-sorcery/</guid><description>&lt;p&gt;Since I&amp;rsquo;m currently looking for a job, I try to train daily on Leetcode. I started with a now classic study plan, originally authored by Yang Shun Tay and hosted on &lt;a href="https://www.techinterviewhandbook.org/grind75" target="_blank" rel="noreferrer"&gt;Grind75&lt;/a&gt;. Eventually though, Leetcode presented me with a tiling problem that I found immediately appealing: &lt;a href="https://leetcode.com/problems/domino-and-tromino-tiling/description/" target="_blank" rel="noreferrer"&gt;Domino and tromino tiling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spoiler alert: the following content reveals the solution to the problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t know the problem yet, take a look at the description in the link. If you are eager to solve it yourself, stop reading this post immediately. Try solving it and come back to this afterward.&lt;/p&gt;
&lt;p&gt;I was in the process of training on bottom-up dynamic programming, and this problem lends itself perfectly to it. So let me present my conceptual solution to the problem:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/domino-tromino1-1.png" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;For a 2x2 (&lt;code&gt;n = 2&lt;/code&gt;) area, only the first 2 arrangements above tile the area entirely.
But if we allow a &lt;strong&gt;single&lt;/strong&gt; gap to remain on the right, either at the top or the bottom, then all 4 arrangements on the first row tile the area.
With the same rules for a 3x2 (&lt;code&gt;n = 3&lt;/code&gt;) area, there are 9 different arrangements that (nearly) tile the area:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/domino-tromino1-2.png" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;We can write the equation for the number of arrangements, according to the variables \((n, h)\), with \(h\) a boolean value representing the presence of a hole:&lt;/p&gt;
&lt;p&gt;\begin{equation}
f(n, h)=
\left\{
\begin{array}{ l l }
1 &amp;amp; \quad \textrm{if } n = 1, h = 0 \\
0 &amp;amp; \quad \textrm{if } n = 1, h = 1 \\
2 &amp;amp; \quad \textrm{if } n = 2, h = 0 \\
2 &amp;amp; \quad \textrm{if } n = 2, h = 1 \\
f(n - 1, 0) + f(n - 2, 0) + f(n - 1, 1) &amp;amp; \quad \textrm{if } n &amp;gt; 2, h = 0 \\
2 f(n - 2, 0) + f(n - 1, 1) &amp;amp; \quad \textrm{if } n &amp;gt; 2, h = 1
\end{array}
\right.
\end{equation}&lt;/p&gt;
&lt;p&gt;The first 4 cases should be straight-forward given the prior explanation.
So let&amp;rsquo;s dig into the last 2 lines.&lt;/p&gt;
&lt;p&gt;Imagine having to tile the \(n\)x2 area by extending it to the right from a smaller value \(n - x\).
Considering only the case where there&amp;rsquo;s no hole left after, for now:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/domino-tromino2.png" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;If an \(n - 1\) arrangement has no hole, placing a vertical domino to the right extends it to \(n\).
If an \(n - 2\) arrangement has no hole, placing 2 horizontal dominoes to the right extends it to \(n\).
Also, if an \(n - 1\) arrangement has a hole (either top or bottom), placing a single tromino to the right extends it to \(n\).&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/domino-tromino3.png" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Following this same approach, in the case of a remaining hole to the right, an \(n - 1\) arrangement with a hole can be extended to \(n\) by placing a single horizontal domino and an \(n - 2\) arrangement without hole can be extended to \(n\) in 2 different ways by placing a tromino up or down.&lt;/p&gt;
&lt;p&gt;Any other action extending \(n - x\) to \(n\) can be constructed from the 6 actions above.
With this understanding, this is my initial bottom-up solution:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;numTilings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n2h0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n2h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n1h0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n1h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n2h0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n1h0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n2h0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2h1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1h0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n1h0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1h1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n2h0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n1h0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n1h1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;n2h0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n1h1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n1h0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Neat, hey?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at the top answer, as one should normally do.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;numTilings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;WTF!? What is this sorcery? I spent hours arriving at my neat solution!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As one would normally find out when looking at the solutions, there&amp;rsquo;s always someone smarter than oneself.
Aside from the fact that the top solution consumes more memory (due to the array and the growing integers), it does much better with a more compact equation, that does not need to consider any holes.&lt;/p&gt;
&lt;p&gt;And, yes, there is a bit of sorcery with the negative array subscripts going on in the case \(n = 2\):&lt;/p&gt;
&lt;p&gt;\begin{equation}
f(n)=
\left\{
\begin{array}{ l l }
1 &amp;amp; \quad \textrm{if } n = 0\\
1 &amp;amp; \quad \textrm{if } n = 1\\
2 = 2 f(1) + 0 &amp;amp; \quad \textrm{if } n = 2\\
2 f(n - 1) + f(n - 3) &amp;amp; \quad \textrm{otherwise}
\end{array}
\right.
\end{equation}&lt;/p&gt;
&lt;p&gt;This equation is neat but not evident, in my opinion. Though you may have Mensa-level IQ, like this person in the comments 🫠:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt; Step 1: Run test cases for 1 to 8.
 Step 2: Observe the relation among A[i], a[i-1] and a[i-3]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Anyway, I figured my initial equation and the one above should reduce to the same form, so I was probably lazy. And indeed, when only considering cases where \(n &amp;gt; 2\):&lt;/p&gt;
&lt;p&gt;\begin{equation}
\begin{array}{rcl}
f(n, 0) &amp;amp; = &amp;amp; f(n - 1, 0) + f(n - 2, 0) + f(n - 1, 1) &amp;amp; \\
\textrm{since } f(n, 1) &amp;amp; = &amp;amp; 2 f(n - 2, 0) + f(n - 1, 1) \\
f(n, 0) &amp;amp; = &amp;amp; f(n - 1, 0) + f(n - 2, 0) + 2f(n - 3, 0) + f(n - 2, 1) \\
\textrm{rearranging } f(n, 0) &amp;amp; = &amp;amp; f(n - 1, 0) + f(n - 3, 0) + \, \\
&amp;amp; &amp;amp; f(n - 2, 0) + f(n - 3, 0) + f(n - 2, 1) \\
\textrm{since } f(n - 1, 0) &amp;amp; = &amp;amp; f(n - 2, 0) + f(n - 3, 0) + f(n - 2, 1) \\
f(n, 0) &amp;amp; = &amp;amp; f(n - 1, 0) + f(n - 3, 0) + f(n - 1, 0) \\
f(n, 0) &amp;amp; = &amp;amp; 2f(n - 1, 0) + f(n - 3, 0)
\end{array}
\end{equation}&lt;/p&gt;
&lt;p&gt;Damn. I hate how beautiful this is: notice how we can do away with the holes now. &lt;a href="https://leetcode.com/problems/domino-and-tromino-tiling/solutions/116581/detail-and-explanation-of-o-n-solution-why-dp-n-2-d-n-1-dp-n-3/" target="_blank" rel="noreferrer"&gt;Here is another original explanation&lt;/a&gt; of the same results through a different route.&lt;/p&gt;
&lt;p&gt;Time to rewrite the program.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;numTilings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It happens to be similar to the 4th best solution. This should be the fastest algorithm for sufficiently large values of \(n\) though.&lt;/p&gt;</description></item><item><title>Logseq as Org-roam on the go (Part III)</title><link>https://sbgrl.me/posts/logseq-org-roam-3/</link><pubDate>Sat, 27 Apr 2024 14:02:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/logseq-org-roam-3/</guid><description>&lt;p&gt;This is the last part of a 3 part series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://sbgrl.me/posts/logseq-org-roam-1/" target="_blank" rel="noreferrer"&gt;Introduction, Logseq and Termux on mobile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sbgrl.me/posts/logseq-org-roam-2/" target="_blank" rel="noreferrer"&gt;Improving Logseq and Org-roam interoperability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Automating synchronisation (this part)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post will cover automating synchronisation between the 2 mediums, to the point where you will almost forget it is there.
It&amp;rsquo;s not perfect (merge conflicts sometime arise) but for the most part, it works flawlessly.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Automatic synchronisation in Emacs
 &lt;div id="automatic-synchronisation-in-emacs" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#automatic-synchronisation-in-emacs" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s many ways to automatically synchronise Git repositories on your desktop.
I wrote an Emacs package for it which has the added benefit to let me execute Emacs hooks post merge.
Skip this part if you already have a solution that you are satisfied with.&lt;/p&gt;
&lt;p&gt;Pros of my package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Self-contained in Emacs, no need to setup external daemons&lt;/li&gt;
&lt;li&gt;Simple configuration with &lt;code&gt;.dir-locals.el&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Works with &lt;code&gt;magit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Provides a hook to run tasks after each merge&lt;/li&gt;
&lt;li&gt;Works asynchronously, except when merging to avoid Emacs stepping on its own toes&lt;/li&gt;
&lt;li&gt;Commits and pushes as necessary (when files are saved)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons (unsurprisingly):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only works within Emacs and while it is running&lt;/li&gt;
&lt;li&gt;Starts synchronisation only when Emacs visits a file within the repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can grab it at &lt;a href="https://github.com/sbougerel/autosync-magit" target="_blank" rel="noreferrer"&gt;https://github.com/sbougerel/autosync-magit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;straight.el&lt;/code&gt; and &lt;code&gt;use-package.el&lt;/code&gt;, your configuration could look like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;use-package&lt;/span&gt; &lt;span class="nv"&gt;autosync-magit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:straight&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;:host&lt;/span&gt; &lt;span class="nv"&gt;github&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:repo&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;sbougerel/autosync-magit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;*.el&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Once you have this package installed, head to your &lt;code&gt;Notes/&lt;/code&gt; directory, and create a &lt;code&gt;.dir-locals.el&lt;/code&gt; file with the following content:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;((nil . ((autosync-magit-commit-message . &amp;#34;Update from desktop&amp;#34;)
 (autosync-magit-pull-timer . 600)
 (eval add-hook &amp;#39;autosync-magit-after-merge-hook #&amp;#39;logseq-org-roam nil t)
 (mode . autosync-magit)
 (mode . auto-revert))))&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The first line specifies the commit message used every time a commit is created.&lt;/li&gt;
&lt;li&gt;The second line specifies the background pull timer: the interval between pulls done in the background, here set to 10 minutes.
In addition to the background pull timer, by default &lt;code&gt;autosync-magit&lt;/code&gt; pulls files when they are visited.
The package is smart enough to throttle pulls when Emacs visits files in the repository in quick succession (e.g. during &lt;code&gt;logseq-org-roam&lt;/code&gt; calls).
You can change the throttle value but you should never set it to &lt;code&gt;0&lt;/code&gt;: since &lt;code&gt;logseq-org-roam&lt;/code&gt; causes files to be visited in quick succession, it calls &lt;code&gt;autosync-magit&lt;/code&gt; often with each calls potentially calling &lt;code&gt;logseq-org-roam&lt;/code&gt; again due to a merge.&lt;/li&gt;
&lt;li&gt;The third line tells Emacs to run &lt;code&gt;logseq-org-roam&lt;/code&gt; once merge is complete.
This ensures that all files edited with Logseq are now converted to Org-roam automatically.&lt;/li&gt;
&lt;li&gt;The fourth line activates the minor mode for files in this directory.
None of the above works without this.&lt;/li&gt;
&lt;li&gt;The last line sets up &lt;code&gt;auto-revert&lt;/code&gt; on files that have been modified, either due to merge or post-processing with &lt;code&gt;logseq-org-roam&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first time you visit a file after the creation of the &lt;code&gt;.dir-locals.el&lt;/code&gt;, Emacs will ask you whether you should trust these settings? Never trust anything you read or copy from Internet! With the warning out of the way, proceed to accept the settings permanently 😇.&lt;/p&gt;
&lt;p&gt;With this done, the workflow on your desktop changes from:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Invoke &lt;code&gt;logseq-org-roam&lt;/code&gt; in Emacs&lt;/li&gt;
&lt;li&gt;Edit notes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -a -m &amp;quot;Update from desktop&amp;quot;; git push&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Edit notes (and Save)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Didn&amp;rsquo;t I say it&amp;rsquo;s almost magic? I&amp;rsquo;ve tested it for a while now, but if you face any problem don&amp;rsquo;t hesitate to raise an issue or a PR.
Now let&amp;rsquo;s keep the magic going with the mobile setup.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Automatic synchronisation on mobile with Tasker
 &lt;div id="automatic-synchronisation-on-mobile-with-tasker" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#automatic-synchronisation-on-mobile-with-tasker" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;This section assumes you&amp;rsquo;ve already gone through &lt;a href="https://sbgrl.me/posts/logseq-org-roam-1" target="_blank" rel="noreferrer"&gt;part 1&lt;/a&gt;, meaning you already have Logseq and Termux on your mobile.
Similar to part 1, this section only applies to Android; there could be iOS alternatives but you&amp;rsquo;ll have to adapt the following.&lt;/p&gt;
&lt;p&gt;The first thing is to add a couple of scripts on your Termux home.
There&amp;rsquo;s nothing Termux-specific about these scripts, so you can test them on your desktop first.&lt;/p&gt;
&lt;p&gt;Copy the content below in file named &lt;code&gt;~/pull-notes&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git rev-parse @&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git rev-parse @&lt;span class="o"&gt;{&lt;/span&gt;u&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nb"&gt;echo&lt;/span&gt; Nothing to &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;elif&lt;/span&gt; git merge-base --is-ancestor @&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt; @&lt;span class="o"&gt;{&lt;/span&gt;u&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	git merge &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nb"&gt;echo&lt;/span&gt; Pulled
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;elif&lt;/span&gt; git merge-base --is-ancestor @&lt;span class="o"&gt;{&lt;/span&gt;u&lt;span class="o"&gt;}&lt;/span&gt; @&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nb"&gt;echo&lt;/span&gt; Nothing to &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	git merge &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nb"&gt;echo&lt;/span&gt; Conflict
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Similarly, copy the content below in a file &lt;code&gt;~/push-notes&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add -A &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Update from Android&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git rev-parse @&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; !&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git rev-parse @&lt;span class="o"&gt;{&lt;/span&gt;u&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	git push &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nb"&gt;echo&lt;/span&gt; Pushed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nb"&gt;echo&lt;/span&gt; Nothing to &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can test these 2 files after you&amp;rsquo;ve made them executable:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;chmod a+x pull_notes
chmod a+x push_notes&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The roles of these 2 files is to print a simple one-line statement about the results of git synchronisation operations.
The application Tasker (installed next) calls these 2 scripts to perform the git operations.
First move the scripts to a specific location so that Tasker can call them with a path relative to &lt;code&gt;HOME&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;mkdir -p ~/.termux/tasker
mv push-notes pull-notes ~/.termux/tasker&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now grab &lt;a href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm&amp;amp;pcampaignid=web_share" target="_blank" rel="noreferrer"&gt;Tasker&lt;/a&gt; from the Play Store and install the companion termux-tasker app from &lt;a href="https://github.com/termux/termux-tasker" target="_blank" rel="noreferrer"&gt;Github&lt;/a&gt; or &lt;a href="https://f-droid.org/en/packages/com.termux.tasker/" target="_blank" rel="noreferrer"&gt;F-droid&lt;/a&gt;.
&lt;strong&gt;Tasker is not free&lt;/strong&gt; but at less that USD$3 it&amp;rsquo;s a small price to pay for all the time it will save you (If you know of a free alternative that is on par with the functionalities presented below, I&amp;rsquo;d love to hear about it).&lt;/p&gt;
&lt;p&gt;The first time you open Tasker, it will invite you to watch tutorials, etc.
Tasker has a ton of useful features outside of this project, which I&amp;rsquo;ll let you discover on your own.&lt;/p&gt;
&lt;p&gt;Before I describe this Tasker project, let&amp;rsquo;s go through some definitions of Tasker&amp;rsquo;s entities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Profiles&lt;/strong&gt; describe events or situations which (in general) are used as a trigger for tasks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tasks&lt;/strong&gt; describe operations to perform, and they are made up of a set of &lt;strong&gt;actions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scenes&lt;/strong&gt; represents UI elements that can displays or used for input during &lt;strong&gt;task&lt;/strong&gt; execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vars&lt;/strong&gt; allow &lt;strong&gt;tasks&lt;/strong&gt; to store information across execution or even share information across tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This Tasker project consists of setting up 2 tasks and 2 profiles.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Task &lt;strong&gt;LogseqPull&lt;/strong&gt; sets a throttle, calls the script &lt;code&gt;~/push-notes&lt;/code&gt; created above and displays its result in a toaster.&lt;/li&gt;
&lt;li&gt;Task &lt;strong&gt;LogseqPush&lt;/strong&gt; calls the script &lt;code&gt;~/pull-notes&lt;/code&gt; created above and displays its result in a toaster.&lt;/li&gt;
&lt;li&gt;Profile &lt;strong&gt;Logseq&lt;/strong&gt; triggers &lt;strong&gt;LogseqPull&lt;/strong&gt; when we launch Logseq on our phone and triggers &lt;strong&gt;LogseqPush&lt;/strong&gt; when we exit it.&lt;/li&gt;
&lt;li&gt;Finally, profile &lt;strong&gt;Every 30m&lt;/strong&gt; triggers &lt;strong&gt;LogseqPull&lt;/strong&gt; every 30 minutes.&lt;/li&gt;
&lt;li&gt;The task &lt;strong&gt;LogseqPull&lt;/strong&gt; will also create the variable &lt;strong&gt;Launch_logseq&lt;/strong&gt; to throttle calls to &lt;code&gt;~/pull-notes&lt;/code&gt;, you don&amp;rsquo;t need to do anything about it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Setting it all up is rather cumbersome, so &lt;a href="https://gist.github.com/sbougerel/0cb4630962159ded1e7a703352dc9718" target="_blank" rel="noreferrer"&gt;I provided the project as a gist&lt;/a&gt; which you can review, download, and import into Tasker.
The XML is rather opaque but hopefully the above information helps with the review before you trust me with another download.
You&amp;rsquo;re welcome to build or modify your own version, of course.&lt;/p&gt;
&lt;p&gt;To import it into Tasker, use &amp;ldquo;Import Project&amp;rdquo; from the 🏠 button. The result should look similar to:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/tasker_logseq.jpg" alt="" /&gt;
 
 &lt;figcaption&gt;&lt;span class="figure-number"&gt;Figure 1: &lt;/span&gt;Tasker project &amp;ldquo;Logseq&amp;rdquo;&lt;/figcaption&gt;
 &lt;/figure&gt;
&lt;p&gt;As soon as you enable the different profiles in the project, you should start seeing it in action.
Navigate to the task to verify that they execute properly.&lt;/p&gt;
&lt;p&gt;At this point, the workflow on your mobile changes from:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git pull&lt;/code&gt; on Termux&lt;/li&gt;
&lt;li&gt;Edit notes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -a -m &amp;quot;Update from phone&amp;quot;; git push&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Edit notes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;How it&amp;rsquo;s going
 &lt;div id="how-it-s-going" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#how-it-s-going" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been using and refining this setup for months, I&amp;rsquo;m really happy with it and I hope you&amp;rsquo;ll find it works great for you too.
It takes bit of time to setup, but it works almost flawlessly in my case.
And in case there&amp;rsquo;s an issue, I&amp;rsquo;ve got git to resolve it.&lt;/p&gt;
&lt;p&gt;There are some left over quirks which can be addressed through habits.
For example: don&amp;rsquo;t leave trailing &lt;code&gt;*&lt;/code&gt; at then of a page in Logseq.
Logseq does so when you press enter in edit mode, and sometimes it messes with Org-element parsing.
A good habit is to press the page title or journal date to exit the input mode instead of pressing &lt;code&gt;Enter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it for now.
I&amp;rsquo;ll continue to improve on &lt;a href="https://github.com/sbougerel/logseq-org-roam" target="_blank" rel="noreferrer"&gt;logseq-org-roam&lt;/a&gt; in the future.
It&amp;rsquo;s already pretty fast, but I know it still a bit rough under the edges, the code is a little messy, and there&amp;rsquo;s more features I&amp;rsquo;m planning to add for tagging and cards (spaced repetitions).
I hope you found this useful.
Feel free to contribute!&lt;/p&gt;</description></item><item><title>Logseq as Org-roam on the go (Part II)</title><link>https://sbgrl.me/posts/logseq-org-roam-2/</link><pubDate>Sat, 13 Apr 2024 16:00:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/logseq-org-roam-2/</guid><description>&lt;p&gt;This is the second part of a 3 part series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://sbgrl.me/posts/logseq-org-roam-1/" target="_blank" rel="noreferrer"&gt;Introduction, Logseq and Termux on mobile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Improving Logseq and Org-roam interoperability (this part)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sbgrl.me/posts/logseq-org-roam-3/" target="_blank" rel="noreferrer"&gt;Automating synchronisation&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This part covers my Emacs configuration and package to enable Org-roam and Logseq interoperability.&lt;/p&gt;
&lt;p&gt;I personally use Emacs on my desktop and Logseq on the go, but if you plan to use both on the same machine, this post should apply as well.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Org-roam configuration
 &lt;div id="org-roam-configuration" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#org-roam-configuration" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve introduced some of it in my previous post, to detail Logseq&amp;rsquo;s configuration.
This is the gist of it:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-lisp" data-lang="lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:preferred-format&lt;/span&gt; &lt;span class="ss"&gt;:org&lt;/span&gt; &lt;span class="c1"&gt;;; required!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:pages-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;pages&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:journals-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;journals&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; default, must match `org-roam-dailies-directory`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:journal/page-title-format&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;yyyy-MM-dd&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; match with `org-roam-dailies-capture-templates`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:journal/file-name-format&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;yyyy-MM-dd&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; match with `org-roam-dailies-capture-templates`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:preferred-workflow&lt;/span&gt; &lt;span class="ss"&gt;:todo&lt;/span&gt; &lt;span class="c1"&gt;;; recommended&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:property-pages/enabled?&lt;/span&gt; &lt;span class="nv"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;;; recommended, disable property pages&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here we&amp;rsquo;ll focus on Emacs &amp;amp; Org-roam configuration.
You can also refer to &lt;a href="https://github.com/sbougerel/doom-config/blob/main/config.el" target="_blank" rel="noreferrer"&gt;my current setup&lt;/a&gt;, if you need to see the full configuration.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Directory layout
 &lt;div id="directory-layout" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#directory-layout" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;This is how my directory organisation currently looks like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;~/
└── Notes/
 ├── ... # Org stuff in here
 └── roam/ # Org-roam stuff in here
 ├── .git/ # Use Git for synchronisation
 ├── assets/ # Use for attachments too
 ├── journals/ # All dailies go here
 ├── logseq/ # Logseq&amp;#39;s own space, Org-roam must ignore it
 └── pages/ # All notes go here&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I nested my Org-roam directory under my Org directory for historical reason.
If you are starting fresh with Org-roam &amp;amp; Logseq directly, this distinction is not necessary.&lt;/p&gt;
&lt;p&gt;Here is how you can configure Emacs to achieve the above:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;org-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;~/Notes/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-directory&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file-truename&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file-name-concat&lt;/span&gt; &lt;span class="nv"&gt;org-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;roam/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-attach-id-dir&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;expand-file-name&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;assets&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;org-roam-directory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-dailies-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;journals/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-file-exclude-regexp&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;\\.git/.*\\|logseq/.*$&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The last line is essential to ensure that Org-roam ignores the &lt;code&gt;logseq&lt;/code&gt; directory.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Org-roam capture
 &lt;div id="org-roam-capture" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#org-roam-capture" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Capture is responsible for determining notes location when you create them.
Therefore you should update your capture template(s) to match Logseq&amp;rsquo;s directories.
Your Emacs configuration should minimally look like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;org-roam-capture-templates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;d&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;plain&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;%?&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;;; Accomodates for the fact that Logseq uses the &amp;#34;pages&amp;#34; directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:target&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file+head&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;pages/${slug}.org&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#+title: ${title}\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:unnarrowed&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-dailies-capture-templates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;d&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;entry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;* %?&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:target&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file+head&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;%&amp;lt;%Y-%m-%d&amp;gt;.org&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;#+title: %&amp;lt;%Y-%m-%d&amp;gt;\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Pay attention to &lt;code&gt;page/${slug}.org&lt;/code&gt; in particular.
If you have configured Logseq differently for some reason, adjust accordingly.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Task management
 &lt;div id="task-management" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#task-management" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Logseq task management is pretty basic, but Org is &lt;em&gt;very&lt;/em&gt; flexible and can be easily extended beyond what Logseq does.
If you want complete interoperability, your Emacs configuration should contain:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;org-todo-keywords&lt;/span&gt; &lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;sequence&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;TODO(t)&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;DOING(s)&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;|&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;DONE(d!)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sequence&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;[ ](T)&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;[-](S)&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;[?](W)&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;|&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;[X](D)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Logseq&amp;rsquo;s equivalent is:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;:preferred-workflow :todo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can keep the &lt;code&gt;:now&lt;/code&gt; style and configure Org accordingly, &lt;code&gt;TODO&lt;/code&gt; simply felt more natural when using Emacs.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Link format
 &lt;div id="link-format" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#link-format" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Logseq provides the user with 2 choices of link format when using the Org format.
Neither choice is perfect, so it depends on your use case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fuzzy links&lt;/strong&gt; (or internal links):
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Style:&lt;/strong&gt; &lt;code&gt;This is a link to a [[Note]]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Easy to capture, edit, especially with the limited mobile screen estate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Ambiguous (hence the name) in certain cases, e.g. when the title of a heading matches with the link (Org internal link).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When to use:&lt;/strong&gt; You create many small notes (Zettelkasten style) and notes are shorter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File links&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Style&lt;/strong&gt;: &lt;code&gt;This is a link to a [[file://../pages/note.org][Note]]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Unambiguous&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Because of a bug in Logseq, when the target note does not yet exists, the path is rendered incorrectly. It must be fixed manually every time, or it will remain incorrect even when the notes is eventually created.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When to use:&lt;/strong&gt; you create fewer notes and you already use internal links in your notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Logseq defaults to fuzzy links and that&amp;rsquo;s the one I recommend if you can&amp;rsquo;t choose.
If you prefer to go with file links, update your &lt;code&gt;config.edn&lt;/code&gt; in Logseq with:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;:org-mode/insert-file-link? true&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Converting Logseq captures to Org-roam
 &lt;div id="converting-logseq-captures-to-org-roam" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#converting-logseq-captures-to-org-roam" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;In the last post, I&amp;rsquo;ve detailed some of the problems with using Logseq out-of-the-box with Org-roam.
As a recap, the biggest issue is how Logseq and Org-roam differ on links: Logseq uses internal links or file links as external links, while Org-roam relies on ID links.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve created an Emacs package named &lt;code&gt;logseq-org-roam&lt;/code&gt; (&lt;a href="https://github.com/sbougerel/logseq-org-roam" target="_blank" rel="noreferrer"&gt;Github&lt;/a&gt;) to address this problem and some others.
Assuming you have &lt;code&gt;straight.el&lt;/code&gt; &amp;amp; &lt;code&gt;use-package.el&lt;/code&gt;, you can install it with:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;use-package&lt;/span&gt; &lt;span class="nv"&gt;logseq-org-roam&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:straight&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;:host&lt;/span&gt; &lt;span class="nv"&gt;github&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:repo&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;sbougerel/logseq-org-roam&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:files&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;*.el&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Invoking &lt;code&gt;logseq-org-roam&lt;/code&gt; crawls your entire Org-roam directory, to migrate Org files edited with Logseq as Org-roam files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adds missing ID for each notes, so that Org-roam can index them&lt;/li&gt;
&lt;li&gt;Adds missing titles for each notes&lt;/li&gt;
&lt;li&gt;Converts all links to ID links, when they match an existing note; works with both fuzzy and file links&lt;/li&gt;
&lt;li&gt;Converts Logseq aliases to Org-roam aliases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These 4 actions fix much of the friction between Org-roam and Logseq, and make both application interoperable.
&lt;code&gt;logseq-org-roam&lt;/code&gt; is capable of more: it can use cache information from Org-roam in order to speed up its operations, it can even capture new notes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;logseq-org-roam&lt;/code&gt; &lt;em&gt;will&lt;/em&gt; however break some things for Logseq as I mentioned in Part I (backlinks and graph).
Here, I assume that Org-roam is your main note-taking tool, and Logseq is used to take Org-roam on the go.&lt;/p&gt;
&lt;p&gt;When installed and used for the first time, I recommend to invoke it with &lt;code&gt;C-u M-x logseq-org-roam&lt;/code&gt;: it will parse all Org-roam files and update the ones created or modified with Logseq.
Afterwards, you can use &lt;code&gt;M-x logseq-org-roam&lt;/code&gt; which works incrementally by skipping cached Org-roam files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recommend that you use git or another source version control before using &lt;code&gt;logseg-org-roam&lt;/code&gt; in case you are not happy with the changes.&lt;/strong&gt;
When you invoke &lt;code&gt;logseq-org-roam&lt;/code&gt;, it creates the buffer &lt;code&gt;*Logseq-org-roam &amp;lt;org-roam-directory&amp;gt;*&lt;/code&gt; which you can consult to see the modifications made.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;logseq-org-roam&lt;/code&gt; comes with a hook which you can customize to automatically sync the Org-roam cache after files are updated:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;logseq-org-roam-updated-hook&lt;/span&gt; &lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;org-roam-db-sync&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Workflow and remaining frictions
 &lt;div id="workflow-and-remaining-frictions" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#workflow-and-remaining-frictions" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;ve followed the guide so far, you&amp;rsquo;ve installed Logseq on your phone, you&amp;rsquo;ve setup your version control and you&amp;rsquo;ve tested the interoperability of Logseq with Org-roam, you should have a good idea if you want to use this regularly.&lt;/p&gt;
&lt;p&gt;Your workflow at this point, looks as such:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When editing notes on your phone:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git pull&lt;/code&gt; on Termux&lt;/li&gt;
&lt;li&gt;Edit notes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -a -m &amp;quot;Update from phone&amp;quot;; git push&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;When editing notes on your desktop
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Invoke &lt;code&gt;logseq-org-roam&lt;/code&gt; in Emacs&lt;/li&gt;
&lt;li&gt;Edit notes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -a -m &amp;quot;Update from desktop&amp;quot;; git push&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is works but it&amp;rsquo;s pretty tideous: I&amp;rsquo;m sure you prefer to focus on editing notes.
I do too.
In my next post I&amp;rsquo;ll cover automating synchronisation between your mobile and your desktop, on top of git.
With automated synchronisation, Org-roam note taking on the go with Logseq feels natural, almost magical.&lt;/p&gt;</description></item><item><title>Logseq as Org-roam on the go (Part I)</title><link>https://sbgrl.me/posts/logseq-org-roam-1/</link><pubDate>Mon, 08 Apr 2024 00:10:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/logseq-org-roam-1/</guid><description>&lt;p&gt;This is the first part of a 3 part series:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introduction, Logseq and Termux on mobile (this part)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sbgrl.me/posts/logseq-org-roam-2/" target="_blank" rel="noreferrer"&gt;Improving Logseq and Org-roam interoperability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sbgrl.me/posts/logseq-org-roam-3/" target="_blank" rel="noreferrer"&gt;Automating synchronisation&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I started to use &lt;a href="https://logseq.com/" target="_blank" rel="noreferrer"&gt;Logseq&lt;/a&gt; in the fall of 2023, and with almost 9 months of use, I&amp;rsquo;m confident it is a great mobile application for Org-roam.
Some applications do a better job at being compatible with the Org file format (e.g. &lt;a href="https://github.com/200ok-ch/organice" target="_blank" rel="noreferrer"&gt;organice&lt;/a&gt;) however they do not support Org-roam readily.
Logseq, while a bit lax with its support of the Org file format (more on that in a bit), is closer to Org-roam&amp;rsquo;s Zettelkasten philosophy: links between pages are a breeze to create, backlinks show up immediately and the journal entries quickly becomes your entry point for time management.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;ll go over my mobile setup and some of the quirks of using Logseq with Org-roam.
I&amp;rsquo;ll follow up on my Emacs setup and automating all this in future posts.
Let&amp;rsquo;s start by understanding their differences.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Logseq &amp;amp; org-roam interoperability
 &lt;div id="logseq-and-org-roam-interoperability" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#logseq-and-org-roam-interoperability" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;The good
 &lt;div id="the-good" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-good" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Logseq was deliberately designed for the minimal interface required for web/mobile applications.
Capture is both user-friendly and functional.&lt;/p&gt;
&lt;p&gt;As an Org editor and interface, it is impressive.
It supports heading highlights for tasks, checkboxes, tags.
It renders quote blocks, code blocks (with syntax highlighting), properties, keywords, tables, etc.
It also supports inline \(\LaTeX\) flawlessly, putting my Emacs setup to shame.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/logseq_eulers_identity.jpg" alt="" /&gt;
 
 &lt;figcaption&gt;&lt;span class="figure-number"&gt;Figure 1: &lt;/span&gt;Logseq&lt;/figcaption&gt;
 &lt;/figure&gt;
&lt;p&gt;Performance-wise, Logseq easily handles the thousands of notes that I have without noticeable penalty.
Its Org-agenda equivalents (queries) render their results almost immediately.&lt;/p&gt;
&lt;p&gt;Logseq also has templates. Granted, the format to create a template is different and your Yasnippets won&amp;rsquo;t work, but this feature is still invaluable to ease input on a phone.&lt;/p&gt;
&lt;p&gt;Logseq has even more features making you love it other ways, and it has definitely influenced my Org-roam setup, for the better.&lt;/p&gt;

&lt;h3 class="relative group"&gt;The bad
 &lt;div id="the-bad" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-bad" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;Using Logseq and Org-roam together imposes some constraints on how you organise your notes.
I&amp;rsquo;ve come to prefer Logseq&amp;rsquo;s approach over my own, inherited from Org, but it might not be your cup of tea.
Logseq imposes the following directory structure for your &amp;ldquo;graph&amp;rdquo; (i.e. &lt;code&gt;org-roam-directory&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;└── org-roam-directory/
 ├── assets/
 ├── journals/
 ├── logseq/
 └── pages/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;assets&lt;/code&gt; directory is where Logseq stores attachments, thus it is handy to map it to your &lt;code&gt;org-attach-id-dir&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;journals&lt;/code&gt; directory is where Logseq stores your dailies, and should map to your &lt;code&gt;org-roam-dailies-directory&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;logseq&lt;/code&gt; directory is Logseq&amp;rsquo;s own internal folder, and it must be hidden from Org-roam in &lt;code&gt;org-roam-file-exclude-regexp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;pages&lt;/code&gt; directory is where Logseq stores every single note.
The lack of hierarchy for pages may not suit everyone.
I find personally that Org-roam tagging provides more flexibility, so it&amp;rsquo;s not an issue for me.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Within a file, Logseq insists on hierarchical note-taking and does not handle well the top section of an Org file.
It will shove top section paragraphs, top node properties and keywords under a heading&amp;hellip;
Which you should not try to edit.&lt;/p&gt;
&lt;p&gt;Also, some pages have special meaning in Logseq.
For example, you will need to port your Yasnippets to the &amp;ldquo;Templates&amp;rdquo; page if you need them in Logseg.
If you want to see all your to-dos, you&amp;rsquo;ll need to visit the &amp;ldquo;TODO&amp;rdquo; page, where content gets generated on the fly.&lt;/p&gt;
&lt;p&gt;This is not the only thing Logseq&amp;rsquo;s generates on the fly: results of Logseq queries are generated on the fly too, and the result is not stored in the file, unlike what Babel does.
And since Logseq&amp;rsquo;s does not support Babel, queries are not interoperable between the 2 platforms.&lt;/p&gt;

&lt;h3 class="relative group"&gt;The ugly
 &lt;div id="the-ugly" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-ugly" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;The biggest problem however is that Logseq uses Org internal link syntax for external links.
This is an Org internal link:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;* Heading A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;* Heading B internal link to [[Heading A]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As the name suggests, internal links are looked up &lt;em&gt;only&lt;/em&gt; within the same file in Org.
A link to another Org file is an called external link, and they have a different syntax:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;* External file link to [[file:other.org][another note]]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;* External ID link to [[id:some-id][another note]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Logseq uses Org&amp;rsquo;s internal link syntax by default, which may conflict with Org.
There&amp;rsquo;s an option to force Logseq to use file paths instead.
Unfortunately, this is buggy (both version 0.9.x and 0.10.x) when a note does not exists yet.&lt;/p&gt;
&lt;p&gt;And ultimately, Org-roam uses ID links.&lt;/p&gt;
&lt;p&gt;Regrettably, when converting Org links to ID links (as Org-roam usually does) it breaks the link relationship for Logseq.
Logseq can &lt;em&gt;still&lt;/em&gt; follow the link, but the backlink will appear under &amp;ldquo;Unlinked references&amp;rdquo;.
This unfortunately breaks the graph in Logseq: all your notes suddenly look isolated.
If you really enjoy the graph visualisation in Logseq, this could be a deal-breaker.&lt;/p&gt;
&lt;p&gt;So while Logseq does look like a good companion for Org-roam, it does not care about Org-roam.
Logseq will not add property ID your notes, it won&amp;rsquo;t use Org-roam aliases, and it will not convert links to ID links.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Why use Logseq with Org-roam then?
 &lt;div id="why-use-logseq-with-org-roam-then" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-use-logseq-with-org-roam-then" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h3&gt;
&lt;p&gt;To address some of these issues, I (and others) have written Emacs packages that I&amp;rsquo;ll present in my next post.
With these packages, the ugly issues disappear and only some of the bad remains.
Thus Logseq shines with what it does well, and it is really good at what it does well.&lt;/p&gt;
&lt;p&gt;Even without these packages, the Zettelkasten philosophy, its usability and all of its functionalities really make up for the remaining quirks.
You can start using Logseq with Org-roam to try it, if only as a viewer.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re convinced, let&amp;rsquo;s move on to configuring Termux for git synchronisation on your phone.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Setting up Termux and Git
 &lt;div id="setting-up-termux-and-git" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#setting-up-termux-and-git" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;I synchronise my notes with git.
There&amp;rsquo;s easier ways to doing it, but I don&amp;rsquo;t think there&amp;rsquo;s much better ways than using an SCM.
All my notes are on a private repository.
This section only applies to Android, I am sure there are similar methods for iOS, though I don&amp;rsquo;t know them.&lt;/p&gt;
&lt;p&gt;Firstly, install the excellent &lt;a href="https://termux.dev/en/" target="_blank" rel="noreferrer"&gt;Termux app&lt;/a&gt; on your phone. Once done, you can open the app and simply:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt upgrade
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg install git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;During the upgrade process, you may sometimes have to answer the question: &lt;code&gt;Configuration file ... The default action is to keep your current version. (Y/I/N/O/D/Z) [default=N] ?&lt;/code&gt; Just type &lt;code&gt;I&lt;/code&gt; since it&amp;rsquo;s a fresh install.
I also recommend picking an editor of your choice if you do not like &lt;code&gt;nano&lt;/code&gt;.
I find &lt;code&gt;nano&lt;/code&gt; is a good fit on a phone.
This will come in handy when you have merge conflicts to resolve.&lt;/p&gt;
&lt;p&gt;Then type &lt;code&gt;termux-setup-storage&lt;/code&gt; and grant Termux access to your shared storage (i.e. user data storage, SD card).&lt;/p&gt;
&lt;p&gt;I placed my notes at the root of my phone&amp;rsquo;s shared storage, which you can access from your Termux user at &lt;code&gt;~/storage/shared/Notes&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir storage/shared/Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ln -s storage/shared/Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cd Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global --add safe.directory ~/storage/shared/Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global user.name ${name}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global user.email ${email}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Line 4 is required since the user performing the git commands (you) is different from the user owning the git repository. I believe this is due to the fuse abstraction on top of the shared storage&amp;rsquo;s file system but haven&amp;rsquo;t dug into it. Onto SSH now.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pkg install openssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The installation process should prompt you to create a default SSH key. If it does not, once the installation is complete, just:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-keygen&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I recommend you do not set a passphrase for this key.
Unlike your desktop or laptop, Android does not come with a standard keychain application, so you&amp;rsquo;ll have to enter your passphrase every time you need to use it or rely on third-party applications.
It&amp;rsquo;s a significant hassle in one case, or an issue of trust in the other.
For third-party applications, you could explore using &lt;a href="https://www.openkeychain.org/" target="_blank" rel="noreferrer"&gt;OpenKeychain&lt;/a&gt; together with &lt;a href="https://github.com/DDoSolitary/OkcAgent" target="_blank" rel="noreferrer"&gt;OkcAgent&lt;/a&gt;, though I can&amp;rsquo;t recommend it given that OkcAgent&amp;rsquo;s sole maintainer does not seem to have time to work on it.&lt;/p&gt;
&lt;p&gt;For security, you should &lt;em&gt;only&lt;/em&gt; use this key for the single purpose of synchronising your notes, and you should prefer to rotate it regularly; I do it every 6 months.
I personally use &lt;a href="https://gitlab.com/" target="_blank" rel="noreferrer"&gt;GitLab&lt;/a&gt; for the unlimited number of private repositories, which also allows me to set an expiration date on the key, forcing me to rotate the key. After adding your key to your SCM, try:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cd ~/Notes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone git@${remote-url}:${user-name}/${repository}.git .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config pull.rebase false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Since this is a repository for synchronisation of text content; you will save yourself a lot of hassle by letting git pick the best merge strategies for you.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to make our first change on the phone, by telling git to ignore Logseq&amp;rsquo;s backup data:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;echo &amp;#34;logseq/bak&amp;#34; &amp;gt;&amp;gt; .gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;echo &amp;#34;logseq/.recycle&amp;#34; &amp;gt;&amp;gt; .gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -a -m &amp;#34;Ignore Logseq cache&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Logseq installation &amp;amp; configuration
 &lt;div id="logseq-installation-and-configuration" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#logseq-installation-and-configuration" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Grab the latest version of Logseq here: &lt;a href="https://github.com/logseq/logseq/releases" target="_blank" rel="noreferrer"&gt;https://github.com/logseq/logseq/releases&lt;/a&gt;.
You can go with the Nightly if you feel adventurous.&lt;/p&gt;
&lt;p&gt;Once installed, Logseq will present you with a bare-bone graph. Load your Org-roam notes by finding the graph button (with the database icon) in the side-panel and select &amp;ldquo;Add new graph&amp;rdquo;.
Now pick the directory containing your recently cloned repository.&lt;/p&gt;
&lt;p&gt;Once loaded and indexed, go straight to the configuration of the graph: &amp;ldquo;\(\cdot\cdot\cdot\)&amp;rdquo; → &amp;ldquo;Settings&amp;rdquo; → &amp;ldquo;Edit &lt;code&gt;config.edn&lt;/code&gt;&amp;rdquo;.
You want to modify/set the following fields, or at least take note of them:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-lisp" data-lang="lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:preferred-format&lt;/span&gt; &lt;span class="ss"&gt;:org&lt;/span&gt; &lt;span class="c1"&gt;;; required!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:pages-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;pages&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:journals-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;journals&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; default, must match `org-roam-dailies-directory`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:journal/page-title-format&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;yyyy-MM-dd&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; match with `org-roam-dailies-capture-templates`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:journal/file-name-format&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;yyyy-MM-dd&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; match with `org-roam-dailies-capture-templates`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:preferred-workflow&lt;/span&gt; &lt;span class="ss"&gt;:todo&lt;/span&gt; &lt;span class="c1"&gt;;; recommended&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ss"&gt;:property-pages/enabled?&lt;/span&gt; &lt;span class="nv"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;;; recommended, disable property pages&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You will need to review some of your configuration in Emacs, and maybe move some files around, if you want Logseq to handle them.
A minimal corresponding Org-roam configuration to the above could look like:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-emacs-lisp" data-lang="emacs-lisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;org-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;~/Notes/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-directory&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file-truename&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file-name-concat&lt;/span&gt; &lt;span class="nv"&gt;org-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;roam/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-dailies-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;journals/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;org-roam-file-exclude-regexp&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;\\.git/.*\\|logseq/.*$&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-capture-templates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;d&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;plain&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;%?&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;;; Accomodates for the fact that Logseq uses the &amp;#34;pages&amp;#34; directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:target&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file+head&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;pages/${slug}.org&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;#+title: ${title}\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:unnarrowed&lt;/span&gt; &lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;org-roam-dailies-capture-templates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;d&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;entry&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;* %?&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:target&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;file+head&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;%&amp;lt;%Y-%m-%d&amp;gt;.org&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;;; format matches Logseq&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s"&gt;&amp;#34;#+title: %&amp;lt;%Y-%m-%d&amp;gt;\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Pay attention to &lt;code&gt;org-roam-file-exclude-regexp&lt;/code&gt; which should exclude the &lt;code&gt;logseq&lt;/code&gt; directory. Once you have updated your Logseq (and possibly Emacs) configuration, don&amp;rsquo;t forget to push the changes to your repository.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Start exploring!
 &lt;div id="start-exploring" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#start-exploring" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;re now minimally setup to use Logseq and Org-roam together and you can start playing with them and see how it feels to take your Org-roam notes on the go with Logseq.&lt;/p&gt;
&lt;p&gt;In subsequent posts, I&amp;rsquo;ll explain how I&amp;rsquo;ve configured Emacs in details, and present packages I&amp;rsquo;ve written to improve interoperability between Logseq and Org-roam.&lt;/p&gt;</description></item><item><title>My first post from org-mode</title><link>https://sbgrl.me/posts/my-first-post/</link><pubDate>Tue, 20 Feb 2024 15:14:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/my-first-post/</guid><description>&lt;p&gt;This post was exported from Emacs &lt;code&gt;org-mode&lt;/code&gt; (with &lt;code&gt;org-roam&lt;/code&gt;) using &lt;code&gt;ox-hugo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Yes, that&amp;rsquo;s all there is to that post. But I&amp;rsquo;m gonna leave it there for keepsake.&lt;/p&gt;</description></item><item><title>Programming a fast integral sine</title><link>https://sbgrl.me/posts/fast-integral-sine/</link><pubDate>Mon, 12 Nov 2018 18:28:00 +0800</pubDate><author>sylvain.bougerel@gmail.com (Sylvain Bougerel)</author><guid>https://sbgrl.me/posts/fast-integral-sine/</guid><description>&lt;p&gt;I started playing puzzles on &lt;a href="https://www.codingames.com" target="_blank" rel="noreferrer"&gt;codingames.com&lt;/a&gt; recently. Although many challenges can be solved by beginners, getting to the top spots on the ladder for the toughest challenges demands a lot of time investment. Which makes it a great game.&lt;/p&gt;
&lt;p&gt;The game I focus on is &lt;a href="https://www.codingame.com/multiplayer/bot-programming/mad-pod-racing" target="_blank" rel="noreferrer"&gt;Mad Pod Racing&lt;/a&gt;, it plays in an arena where your bot meets another bot. Your bot has a finite amount of time for each move. This became an excuse to look into approx fast integer operations. I don&amp;rsquo;t really need to, but when I started looking at integer computations for Sine, I was swallowed whole into the rabbit hole. What could be more pointless fun than that?&lt;/p&gt;
&lt;p&gt;I started to dig into GNU&amp;rsquo;s \(\sin x\) implementations before embarking on my journey here. The implementation is complex and is likely the product of long research. Towards the end of this article, I give a brief overview of the &lt;a href="https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/ieee754/dbl-64/s_sin.c;hb=HEAD#l200" target="_blank" rel="noreferrer"&gt;GNU libc IEEE754&amp;rsquo;s implementation of Sine&lt;/a&gt;, should you want to get into it. But that didn’t really stop me:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I’m gonna write a simple integer version of \(\sin x\) and it’s gonna be so much faster than the precise, floating point equivalent of the standard library.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;Me, in a moment of pure hubris&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Computing the result of such function efficiently crosses into 3 different domains: approximation, range reduction and a good understanding of what your architecture can or cannot do. Also, I was determined to waste my weekend (And most of the week — as it turned out) on this. Such an exercise is generally futile however, as the standard library implementation is already very quick.&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/ugly_face_of_the_rabbit.webp" alt="" /&gt;
 
 &lt;figcaption&gt;&lt;span class="figure-number"&gt;Figure 1: &lt;/span&gt;The ugly face of the rabbit hole. &lt;a href="https://www.sketchport.com/drawing/5209109242576896/dawww" target="_blank" rel="noreferrer"&gt;Drawing by Hababoon © 2014&lt;/a&gt;&lt;/figcaption&gt;
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Function Prototype
 &lt;div id="function-prototype" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#function-prototype" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Mad Pod Racing is a hard challenge about predictions of mechanics of motion and collision. Your bot interacts with the world through standard input/output with angles expressed in degrees, using whole integers (e.g. 0, 45, 90&amp;hellip;). Therefore, I will focus on this input domain only (whole integers expressed in degrees).&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/Trigono_sine_en2.svg" alt="" /&gt;
 
 &lt;figcaption&gt;&lt;span class="figure-number"&gt;Figure 2: &lt;/span&gt;For angle α, the sine function is the ratio between the length of the opposite side and the hypotenuse of the right triangle (&lt;a href="https://en.wikipedia.org/wiki/Sine" target="_blank" rel="noreferrer"&gt;source&lt;/a&gt;)&lt;/figcaption&gt;
 &lt;/figure&gt;
&lt;p&gt;My version of \(\sin x\) is going to provide arbitrary precision, set to match the length of the hypotenuse without overflow:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Also, I want my function to take a large input range for the degree of the angle, so I don’t need to worry about adding angles together and having to perform range reduction or modulo before using it. This is enough to meet the demands of the challenge, without compromising too much on precision. Precision is also bounded since the arena&amp;rsquo;s size for Mad Pod Racing is in the range [\(2 \cdot -10^4\), \(2 \cdot 10^4\)].&lt;/p&gt;

&lt;h2 class="relative group"&gt;Range reduction and sine&amp;rsquo;s symmetries
 &lt;div id="range-reduction-and-sine-s-symmetries" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#range-reduction-and-sine-s-symmetries" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Fortunately, the sine function has multiple symmetries and we don&amp;rsquo;t actually need to tackle the entire input range:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/sine_quadrants.webp" alt="" /&gt;
 
 &lt;figcaption&gt;&lt;span class="figure-number"&gt;Figure 3: &lt;/span&gt;The four quadrants of the Sine function&lt;/figcaption&gt;
 &lt;/figure&gt;
&lt;p&gt;First, sine has a symmetry around &lt;code&gt;x = 0&lt;/code&gt;. This allows us to focus on positive angles only. If the input angle was negative, we simply negate the result as well:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 31 for 32-bit int
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// sign mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// absolute angle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... rest of the computation for val
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// negate val, same as &amp;#34;val * s&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The first 3 lines need a bit of explanation. In C++, &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; translates to &lt;code&gt;SAR&lt;/code&gt; or the arithmetic bit shift to the right which carries the sign bit with it. This means that if an angle was positive then &lt;code&gt;s=0x00000000&lt;/code&gt;, but if it was negative then &lt;code&gt;s=0xFFFFFFFF&lt;/code&gt; or -1. This is the same principle as &lt;code&gt;abs()&lt;/code&gt;, which I needed to decompose to keep the mask &lt;code&gt;s&lt;/code&gt; to restore the sign to the result:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// simple abs() implementation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Sine also has 4 quadrants, 2 halves with a period 2π; all of which are powers of 2. Thus, if we divide the input angle by a quadrant, or 90 degrees in our case, we can suddenly take advantage of the bit representation of the quotient. The symmetry between the first and second quadrant requires that we subtract the angle by 90:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 31 for 32-bit ints
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// sign mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// absolute angle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// quotient quadrants
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// reminder quadrants
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nl"&gt;Qr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Qr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// first quadrant symmetry
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... rest of the computation for val
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// negate val, same as &amp;#34;val * s&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The last symmetry to exploit is that between the 2 halves. If we find ourselves in the top half of the trigonometric circle, the result is positive, otherwise it is negative. This is similar to the symmetry around &lt;code&gt;x = 0&lt;/code&gt;, we just need to XOR the test for both symmetries:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 31 for 32-bit ints
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// sign mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// absolute angle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// quotient quadrants
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// reminder quadrants
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nl"&gt;Qr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Qr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// first quadrant symmetry
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// semi-circle (half) mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... rest of the computation for val
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// XOR half and sign mask together
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// negate val, same as &amp;#34;val * s&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This is the naive skeleton of our function. We only need to compute the value of sine for the reduced interval [0, 90] now.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Approximating Sine with Taylor Series
 &lt;div id="approximating-sine-with-taylor-series" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#approximating-sine-with-taylor-series" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;At first I decided to attempt approximation with a third order Taylor series \(T_3\) for sine:&lt;/p&gt;
&lt;p&gt;\begin{equation}
\sin x \approx T_3(x) = x - \frac{x^3}{3!}
\end{equation}&lt;/p&gt;
&lt;p&gt;This approximation is very imprecise: up to 1% error in the range [-π/4, π/4] ([-45, 45] in degrees) and errors accumulate even quicker beyond π/4. It is possible to find extra curve-fitting factors by solving a system of equations to force the point π/2 through the value &lt;code&gt;1.0&lt;/code&gt;: &lt;a href="https://www.coranac.com/2009/07/sines/" target="_blank" rel="noreferrer"&gt;it&amp;rsquo;s already been done&lt;/a&gt; so I won&amp;rsquo;t go through the details. This is the resulting equation:&lt;/p&gt;
&lt;p&gt;\begin{equation}
\sin x \approx S_3(x) = \frac{4}{\pi} x - \frac{4}{\pi^3}x^3
\end{equation}&lt;/p&gt;
&lt;p&gt;Since we are working with degrees, we can find a related solution that forces the point &lt;code&gt;90&lt;/code&gt; through the value &lt;code&gt;1.0&lt;/code&gt; with the same method, let&amp;rsquo;s call it \(S_{d3}\):&lt;/p&gt;
&lt;p&gt;\begin{equation}
\sin x \approx S_3(x) = ax - bx^3
\end{equation}&lt;/p&gt;
&lt;p&gt;\begin{equation}
\begin{array}{ll}
S(90) &amp;amp;= 1 = 90 a - b90^3\\
S\prime(90) &amp;amp;= 0 = a - 3b90^2\\
\end{array}\rightarrow\begin{array}{ll}
a &amp;amp;= \frac{3}{2 \cdot 90}\\
b &amp;amp;= \frac{1}{2 \cdot 90^3}\\
\end{array}\\
\end{equation}&lt;/p&gt;
&lt;p&gt;\begin{equation}
S_{d3}(x) = \frac{3}{2 \cdot 90}x - \frac{1}{2 \cdot 90^3}x^3
\end{equation}&lt;/p&gt;
&lt;p&gt;To avoid sacrificing readability, I kept the form as is. This curve seems to fit sine closely when eyeballing it:&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/approxsd3.png" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;However, even this approximation is still imprecise across the range [-90, 90]. Near the 40 (or -40) degree value for the angle, the chosen approximation&amp;rsquo;s error is at its maximum (≈ 2%).&lt;/p&gt;

&lt;figure&gt;
 &lt;img class="my-0 rounded-md" src="https://sbgrl.me/ox-hugo/approxsd3-error.png" alt="" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;On top of that, we need to deal with overflows (and avoid underflow) in the calculation when factoring the scale. The naive implementation limits the actual range of inputs for the precision; in fact it overflows as soon as scale \(&amp;gt;2^{10}\), which does not cover the entire desired input domain. So we will compute the solution in the &lt;code&gt;int64_t&lt;/code&gt; domain. We can also rearrange the operations, to minimize the numbers of divisions. This time, with the scale (or hypotenuse) &lt;code&gt;H&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;\begin{equation}
S_{d3}(x, H) = H (\frac{3}{2 \cdot 90}x - \frac{1}{2 \cdot 90^3}x^3)
\end{equation}&lt;/p&gt;
&lt;p&gt;\begin{equation}
S_{d3}(x, H) = H \frac{1}{2 \cdot 90^3} x (3 \cdot 90^2 - x^2)
\end{equation}&lt;/p&gt;
&lt;p&gt;Now there is no issues with underflow or overflow, and no compromise on precision in the entire range of scale:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 31 for 32-bit ints
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// sign mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// absolute angle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// quotient quadrants
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Qr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// reminder quadrants
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nl"&gt;Qr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Qr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// first quadrant symmetry
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Qh&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// semi-circle (half) mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// XOR half and sign mask together
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// negate val, same as &amp;#34;val * s&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There is still the residual error of the approximation itself. My naive implementation is also easily outclassed by implementations that &lt;a href="https://www.coranac.com/2009/07/sines/" target="_blank" rel="noreferrer"&gt;consider power of 2 units for the circle&lt;/a&gt; instead of sticking with degrees or radians. I might re-write the above in the future, however the cost on precision should still be significant. Simply put, there&amp;rsquo;s no free lunch: we can&amp;rsquo;t have both precision and speed. Or can we?&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why not lookup everything?
 &lt;div id="why-not-lookup-everything" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-not-lookup-everything" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;Given how small the integer range is across the period (we&amp;rsquo;re talking only 180 values for the semi-circle afterall), could I not pre-compute everything? I could fix the precision since the input for the hypotenuse is bounded in the range [\(2 \cdot -10^4\), \(2 \cdot 10^4\)]. Fixing the precision to \(2^{16}\) allows me to do simple bit-shifting, after the multiplication by the hypotenuse.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;sin_lut&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;0.01745240643728351&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;0.03489949670250097&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;0.06975647374412552&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 176
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;0.05233595624294381&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 177
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;0.0348994967025007&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 178
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mf"&gt;0.01745240643728344&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 179
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The first step consists in creating a table of pre-computed sine values for each input degree. This table is not our final lookup table, but helps with the computation of the actual lookup table (LUT), which has the fixed precision, &lt;code&gt;SinTable180points&amp;lt;P&amp;gt;.values&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;SinTable180points&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="nf"&gt;SinTable180points&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sin_lut&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Thanks to C++&amp;rsquo;s &lt;code&gt;constexpr&lt;/code&gt;, we can ensure all this is done at compilation time. The new &lt;code&gt;sin_lut()&lt;/code&gt; fixed precision function removes the need for any calculations and is as precise as needed for the entire scale of the domain (and we can easily extend it):&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-C++" data-lang="C++"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;isin_lut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Static table with precalculated outputs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SinTable180points&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 31 for 32-bit int
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slide&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// sign mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// absolute angle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// quotient semi-circle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aa&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// reminder semi-circle
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// semi-circle mask: 0 or -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// XOR semi-circle and sign together
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ha&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Was it worth it?
 &lt;div id="was-it-worth-it" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#was-it-worth-it" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;When micro-benchmarking all 3 approaches &lt;code&gt;std::sin&lt;/code&gt;, &lt;code&gt;isin&lt;/code&gt; and &lt;code&gt;isin_lut&lt;/code&gt;, I get the following results on my machine, which should be easily reproducible. All benchmarking is done after warm-up by computing over 1440 fixed points multiple times and averaging the results:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;function&lt;/th&gt;
 &lt;th&gt;Num. cycles for 1440 points&lt;/th&gt;
 &lt;th&gt;Avg. cycles per calculation&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;std::sin()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;89604&lt;/td&gt;
 &lt;td&gt;62&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;isin()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;29906&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sin_lut()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;17252&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The tests attempts to make the compare each function as closely as possible by using the same precision when possible. However we should realise 2 things: &lt;code&gt;std::sin()&lt;/code&gt; and &lt;code&gt;sin_lut()&lt;/code&gt; do not compromise on precision, only &lt;code&gt;isin()&lt;/code&gt; does. It&amp;rsquo;s quite remarkable, therefore, that the lookup table version is so quick and yet uncompromising. Nevertheless, I was impressed by how fast &lt;code&gt;std::sin()&lt;/code&gt; is.&lt;/p&gt;
&lt;p&gt;This entire exercise was personally educational but ultimately unnecessary for the needs of the challenge. The standard library implementation is already fast enough, and I have a better appreciation for it now. There is evidently much more I can squeeze out of the approximation (I worked on this without much prior knowledge) and maybe I&amp;rsquo;ll return to it in a future post.&lt;/p&gt;
&lt;p&gt;The source code for this project &lt;a href="https://gitlab.com/sylvain.bougerel/fast-integral-sine" target="_blank" rel="noreferrer"&gt;can be found here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Further reading
 &lt;div id="further-reading" class="anchor"&gt;&lt;/div&gt;
 
 &lt;span
 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
 &lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#further-reading" aria-label="Anchor"&gt;#&lt;/a&gt;
 &lt;/span&gt;
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/ieee754/dbl-64/s_sin.c;hb=HEAD#l200" target="_blank" rel="noreferrer"&gt;The standard library implementation&lt;/a&gt; of sine combines multiple techniques, some of which are already explored above: range reduction, Taylor approximation, lookup tables. It also makes uses of additional techniques which are not covered here, but it is a hard read.&lt;/p&gt;
&lt;p&gt;While working on the approximation, I stumbled across another post that would have required much content to be rewritten, had I followed through with what I learned there. Notably, the approximation (S3) is nearly twice faster than &lt;code&gt;isin_lut&lt;/code&gt; in my own tests, even if its suffer from imprecision too (still ≈2%) when having to convert from degrees. It&amp;rsquo;s a great read in any case, and it&amp;rsquo;s already linked in other places above: &lt;a href="http://www.coranac.com/2009/07/sines/" target="_blank" rel="noreferrer"&gt;http://www.coranac.com/2009/07/sines/&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>