<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Wai Hon's Blog</title><link>https://whhone.com/</link><description>Recent content on Wai Hon's Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>whhone@gmail.com (Wai Hon Law)</managingEditor><webMaster>whhone@gmail.com (Wai Hon Law)</webMaster><lastBuildDate>Fri, 03 Jul 2026 20:00:00 -0700</lastBuildDate><atom:link href="https://whhone.com/index.xml" rel="self" type="application/rss+xml"/><item><title>One-Line-Per-Day Diary in Org-mode</title><link>https://whhone.com/posts/one-line-per-day-diary-in-org-mode/</link><pubDate>Fri, 03 Jul 2026 20:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/one-line-per-day-diary-in-org-mode/</guid><description>&lt;p&gt;Recently, I came across these plain text systems and concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://georgecoghill.wordpress.com/2018/01/01/the-one-line-per-day-one-page-plain-text-daily-journal/"&gt;The One-Line-Per-Day, One-Page Plain Text Daily Journal&lt;/a&gt;&lt;/strong&gt;: A single, reverse-chronological plain text file to record only the most significant event of each day in a single line.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.google.com/search?q=obtf+one+big+text+file"&gt;OBTF (One Big Text File)&lt;/a&gt;&lt;/strong&gt;: Putting everything in a big text file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They resonated me because they match very closely with how I keep my daily diary in Org-mode!&lt;/p&gt;
&lt;h2 id="the-one-line-per-day-format"&gt;The One-Line-Per-Day Format&lt;/h2&gt;
&lt;p&gt;I keep my diary in a one-line-per-day format. Under this system, each day is represented by a single line using Org-mode&amp;rsquo;s description list syntax:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Level-1 heading:&lt;/strong&gt; The Year (e.g., &lt;code&gt;* 2026&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Level-2 heading:&lt;/strong&gt; The Week, containing the week number, date range, and a brief weekly highlight (e.g., &lt;code&gt;** W25 (06-15 - 06-21) - Weekly highlight&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;List items:&lt;/strong&gt; One line per day, starting with an inactive timestamp and a description separator (&lt;code&gt;::&lt;/code&gt;) followed by the day&amp;rsquo;s events.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example illustrating a complete recent week:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-org" data-lang="org"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;*&lt;/span&gt;&lt;span class="gs"&gt; 2026&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="gu"&gt;**&lt;/span&gt; W26 (06-22 - 06-28) - Run 3 times. Mele X1! CPU PL1/PL2/Multpilers. TPM: LUKS + SSH.
&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;- &lt;/span&gt;[2026-06-22 Mon] :: Team dinner.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;- &lt;/span&gt;[2026-06-23 Tue] :: Running. Daycare fire false alarm. Primes day Mini PC research.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;- &lt;/span&gt;[2026-06-24 Wed] :: Buy Mele X1.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;- &lt;/span&gt;[2026-06-25 Thu] :: Running. Benchmark Mele X1. Good single core performance. Emacs startup time ~0.4s.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;- &lt;/span&gt;[2026-06-26 Fri] :: Bike to pick up. Dinner: YAYOI.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;- &lt;/span&gt;[2026-06-27 Sat] :: TIL: Performance Copilot (pcp). Arch: migrate to systemd &lt;span class="gd"&gt;+ sd-encrypt. TPM: LUKS unlock +&lt;/span&gt; SSH Agent.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;- &lt;/span&gt;[2026-06-28 Sun] :: Running. Order Square Paper. Termux workaround with non-local network.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="daily-entries"&gt;Daily Entries&lt;/h2&gt;
&lt;p&gt;At the end of every day, I go to the top of &lt;code&gt;diary.org&lt;/code&gt;, add a line for today with the things that worth remembering.&lt;/p&gt;
&lt;p&gt;To make writing daily entries frictionless, I use a simple Emacs Lisp helper. I use the following function to insert today&amp;rsquo;s date formatted as an inactive timestamp:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;my/insert-current-date&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="s"&gt;&amp;#34;Insert today&amp;#39;s date in Org inactive format.&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 class="nb"&gt;interactive&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="nf"&gt;insert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;format-time-string&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;[%Y-%m-%d %a]&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;p&gt;I bind this command to a key combination for quick access:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;keymap-global-set&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;C-c s d&amp;#34;&lt;/span&gt; &lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;my/insert-current-date&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;p&gt;With this helper, starting a daily log is as simple as typing &lt;code&gt;-&lt;/code&gt; (or using &lt;code&gt;M-RET&lt;/code&gt; to start a list item), calling the function, typing &lt;code&gt;::&lt;/code&gt;, and entering the daily highlight.&lt;/p&gt;
&lt;h2 id="weekly-highlight"&gt;Weekly Highlight&lt;/h2&gt;
&lt;p&gt;At the end of each week (Sunday), I pick the things worth highlighting from my daily entries and put them in the weekly heading. This allows me to look back at my past weeks, know what happened, and feel like my days count.&lt;/p&gt;
&lt;h2 id="why-this-works-well"&gt;Why This Works Well&lt;/h2&gt;
&lt;p&gt;This format has greatly improved both writing and searching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Self-Contained Search Results:&lt;/strong&gt; Since the date and the content of the entry reside on the same line, searching with &lt;code&gt;consult-line&lt;/code&gt; or &lt;code&gt;ripgrep&lt;/code&gt; returns the full context directly. A search for a keyword like &amp;ldquo;sourdough&amp;rdquo; immediately displays the date it happened without needing to check parent headings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compact File Structure:&lt;/strong&gt; The outline remains clean and fast to fold and navigate, with only weeks and years as headings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lower Friction:&lt;/strong&gt; Writing a single line per day lowers the cognitive barrier to journaling. Even on busy days, it is easy to jot down a quick summary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am also using a &lt;code&gt;diary-work.org&lt;/code&gt; to track my daily work similarly.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This layout is a lightweight and sustainable way to keep a daily log. By using Org-mode&amp;rsquo;s description lists and automating timestamp insertions, the diary remains clean, highly readable, and easily searchable for years to come.&lt;/p&gt;
&lt;h2 id="appendix-screenshot"&gt;Appendix: Screenshot&lt;/h2&gt;
&lt;p&gt;This is how the &lt;code&gt;diary.org&lt;/code&gt; looks like:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/org-diary.png"&gt;
&lt;/figure&gt;</description></item><item><title>Using Caddy with Extensions on Arch Linux</title><link>https://whhone.com/posts/using-caddy-with-extensions-on-archlinux/</link><pubDate>Sun, 14 Jun 2026 11:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/using-caddy-with-extensions-on-archlinux/</guid><description>&lt;p&gt;A recent &lt;a href="https://archlinux.org/news/active-aur-malicious-packages-incident/"&gt;security incident&lt;/a&gt; on the Arch Linux AUR (Arch User Repository) highlighted the potential risks of running community-maintained packages without thorough auditing. I don&amp;rsquo;t rely heavily on the AUR, but I do use a few AUR packages, and I carefully review PKGBUILD files whenever I update or install something new.&lt;/p&gt;
&lt;p&gt;Currently, my Arch Linux uses four AUR packages: &lt;code&gt;yay&lt;/code&gt;, &lt;code&gt;xcaddy&lt;/code&gt;, &lt;code&gt;bindfs&lt;/code&gt;, and &lt;code&gt;chrome-remote-desktop&lt;/code&gt;. To reduce this footprint further, I decided to see if I could get rid of &lt;code&gt;xcaddy&lt;/code&gt; while maintaining my custom Caddy setup.&lt;/p&gt;
&lt;h2 id="the-problem-with-the-previous-approach"&gt;The Problem with the Previous Approach&lt;/h2&gt;
&lt;p&gt;Previously, I used &lt;code&gt;xcaddy&lt;/code&gt; to build a custom Caddy binary with both the &lt;code&gt;webdav&lt;/code&gt; (which I use to sync my Org-mode files, as detailed in &lt;a href="https://whhone.com/posts/webdav-syncthing/"&gt;Use Syncthing with WebDAV to Sync Everywhere&lt;/a&gt;) and &lt;code&gt;cloudflare&lt;/code&gt; extensions (which I use to get an HTTPS certificate for a host without a public IP). I then manually replaced the official binary at &lt;code&gt;/usr/bin/caddy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While this did the job, I was never fully satisfied with it for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Binary Overwrites:&lt;/strong&gt; Every time the official &lt;code&gt;caddy&lt;/code&gt; package got updated, package manager transactions would overwrite my custom binary, stripping out the extensions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lacking Arch Patches:&lt;/strong&gt; Building Caddy directly via &lt;code&gt;xcaddy&lt;/code&gt; meant the binary did not benefit from Arch Linux-specific packaging adjustments, such as custom compilation flags or system-specific integrations.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-solution-arch-build-system"&gt;The Solution: Arch Build System&lt;/h2&gt;
&lt;p&gt;To address this, I switched to the &lt;a href="https://wiki.archlinux.org/title/Arch_build_system"&gt;Arch Build System (ABS)&lt;/a&gt;. By leveraging ABS along with &lt;code&gt;git subtree&lt;/code&gt;, we can import the official Arch packaging repository for Caddy as a local subdirectory in a configuration repository. This lets us edit the &lt;code&gt;PKGBUILD&lt;/code&gt; file to inject our custom plugins directly into the official compile step, maintaining a binary that benefits from official Arch patches while avoiding AUR helpers for Caddy.&lt;/p&gt;
&lt;h2 id="implementation-with-git-subtree"&gt;Implementation with `git subtree`&lt;/h2&gt;
&lt;h3 id="1-dot-initial-setup"&gt;1. Initial Setup&lt;/h3&gt;
&lt;p&gt;First, set up a dedicated directory to manage your custom package builds:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir abs-builds &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; abs-builds
&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 init
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit --allow-empty -m &lt;span class="s2"&gt;&amp;#34;Initial commit.&amp;#34;&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="c1"&gt;# Link your private remote backup repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# git remote add origin git@github.com:yourusername/abs-builds.git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-dot-clone-upstream-package-via-git-subtree"&gt;2. Clone Upstream Package via Git Subtree&lt;/h3&gt;
&lt;p&gt;Next, pull down the official Caddy package source as a local subdirectory using &lt;code&gt;git subtree&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Pull down official Caddy as a local subtree directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git subtree add --prefix&lt;span class="o"&gt;=&lt;/span&gt;caddy https://gitlab.archlinux.org/archlinux/packaging/packages/caddy.git main --squash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-dot-injecting-custom-extensions-and-committing-local-changes"&gt;3. Injecting Custom Extensions &amp;amp; Committing Local Changes&lt;/h3&gt;
&lt;p&gt;Edit &lt;code&gt;prepare()&lt;/code&gt; and &lt;code&gt;check()&lt;/code&gt; in &lt;code&gt;caddy/PKGBUILD&lt;/code&gt;. We can inject the custom plugins directly into the build tree by modifying &lt;code&gt;prepare()&lt;/code&gt; to generate the plugin registry file and download module dependencies, and &lt;code&gt;check()&lt;/code&gt; to clean the modcache.&lt;/p&gt;
&lt;p&gt;Here is the patch/diff to apply to &lt;code&gt;caddy/PKGBUILD&lt;/code&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/caddy-pkgbuild-patch.png"&gt;
&lt;/figure&gt;
&lt;p&gt;Compile, install, and verify that the custom modules are included:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Compile and install&lt;/span&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; caddy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;makepkg -si
&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="c1"&gt;# Ensure dns.providers.cloudflare and http.handlers.webdav are included.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;caddy list-modules
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Return to the root directory and commit the changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add caddy/PKGBUILD
&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;feat(caddy): inject cloudflare and webdav modules into build tree&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-dot-pacman-configuration"&gt;4. Pacman Configuration&lt;/h3&gt;
&lt;p&gt;To prevent &lt;code&gt;pacman&lt;/code&gt; from replacing our custom package with the official upstream binary during system updates, add &lt;code&gt;caddy&lt;/code&gt; to the &lt;code&gt;IgnorePkg&lt;/code&gt; list in &lt;code&gt;/etc/pacman.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cfg" data-lang="cfg"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;IgnorePkg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;caddy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="5-dot-updating-the-repo-from-upstream"&gt;5. Updating the Repo from Upstream&lt;/h3&gt;
&lt;p&gt;When the official &lt;code&gt;caddy&lt;/code&gt; package is updated, running a system upgrade (&lt;code&gt;pacman -Syu&lt;/code&gt;) will warn you that a newer version of Caddy is available. Since we added it to &lt;code&gt;IgnorePkg&lt;/code&gt;, pacman will not replace it automatically. We then manually pull down upstream updates and compile them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. Fetch, squash, and merge upstream updates into your subfolder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git subtree pull --prefix&lt;span class="o"&gt;=&lt;/span&gt;caddy https://gitlab.archlinux.org/archlinux/packaging/packages/caddy.git main --squash
&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="c1"&gt;# 2. Fix any merge conflicts in caddy/PKGBUILD (keep your plugin blocks right before go mod vendor)&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="c1"&gt;# 3. Finalize the merge commit if conflicts occurred&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add caddy/PKGBUILD
&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;chore(caddy): merge upstream changes and resolve build blocks&amp;#34;&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="c1"&gt;# 4. Compile and install again&lt;/span&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; caddy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;makepkg -si
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Adopting the Arch Build System allowed me to eliminate &lt;code&gt;xcaddy&lt;/code&gt; from my list of AUR packages, lowering my security footprint without sacrificing the custom capabilities of my Caddy installation. It integrates custom extensions with native Arch Linux package management. While it does require manual merges when updating, the process is straightforward and keeps the system clean.&lt;/p&gt;</description></item><item><title>Refining Org-mode Deadlines</title><link>https://whhone.com/posts/refining-org-mode-deadlines/</link><pubDate>Sun, 24 May 2026 22:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/refining-org-mode-deadlines/</guid><description>&lt;p&gt;For a long time, I didn&amp;rsquo;t use deadlines in Org-mode at all. However, over the past year (or perhaps even longer, though I forget exactly when), I have found myself relying on them more and more. Deadlines have quietly become the backbone of my day-to-day productivity.&lt;/p&gt;
&lt;h2 id="a-new-prioritization-hierarchy"&gt;A New Prioritization Hierarchy&lt;/h2&gt;
&lt;p&gt;In my earlier post on &lt;a href="https://whhone.com/posts/org-mode-task-management/"&gt;task management workflow&lt;/a&gt;, my day-to-day focus was heavily centered around scheduled dates and TODO states. Today, my prioritization hierarchy has fundamentally evolved to:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deadline + Priority&lt;/strong&gt; &amp;gt; &lt;strong&gt;Schedule&lt;/strong&gt; &amp;gt; &lt;strong&gt;TODO States&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deadlines + Priorities&lt;/strong&gt; represent my primary focus. Deadlines show what must be delivered and when, while priorities help determine which deadline to tackle first. Together, they serve as the main driver for my planning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scheduled&lt;/strong&gt; dates are reserved for habits, recurring chores, or temporary &amp;ldquo;snoozing&amp;rdquo; (hiding tasks until they need to be revisited).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TODO States&lt;/strong&gt; (like &lt;code&gt;NEXT&lt;/code&gt; or &lt;code&gt;PROG&lt;/code&gt;) act as a backlog of next actions. If my deadline and prioritized items are blocked or completed, I look here to see what to pick up next.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="default-org-agenda-view"&gt;Default Org Agenda View&lt;/h3&gt;
&lt;p&gt;However, the default deadline behavior in Org-mode didn&amp;rsquo;t really work for me. Out of the box, deadlines look identical to scheduled tasks, and the short warning window makes them hard to plan around.&lt;/p&gt;
&lt;p&gt;Here is the default Org agenda view:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/org-deadline-before.png"&gt;
&lt;/figure&gt;
&lt;h3 id="customized-org-agenda-view"&gt;Customized Org Agenda View&lt;/h3&gt;
&lt;p&gt;To support this deadline-first workflow, here are two tricks I used to turn Org-mode deadlines into a powerful planning tool.&lt;/p&gt;
&lt;p&gt;Here is the customized Org agenda view:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/org-deadline-after.png"&gt;
&lt;/figure&gt;
&lt;h2 id="trick-1-visual-deadlines-with-custom-leaders-and-sorting"&gt;Trick 1: Visual Deadlines with Custom Leaders and Sorting&lt;/h2&gt;
&lt;p&gt;In the Org agenda view, distinguishing scheduled tasks from deadlines at a glance is surprisingly difficult. Because they share a similar layout and color palette, a looming deadline can easily get lost in a sea of routine scheduled chores.&lt;/p&gt;
&lt;p&gt;To solve this, I customized the &amp;ldquo;leaders&amp;rdquo; (the prefix text shown in the agenda) by prepending a bright red circle emoji (🔴) to all deadline items. This makes them immediately pop out.&lt;/p&gt;
&lt;p&gt;Also, sort deadeline at the very top lines by customizing the &lt;code&gt;org-agenda-sorting-strategy&lt;/code&gt; to clearly separate &amp;ldquo;deadline&amp;rdquo; and &amp;ldquo;scheduled&amp;rdquo; items.&lt;/p&gt;
&lt;p&gt;Here is the Elisp configuration I use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; Shorten the leaders to reserve spaces for the repeater.&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-agenda-scheduled-leaders&lt;/span&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;Sched&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;S.%2dx&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-agenda-deadline-leaders&lt;/span&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;🔴 0d&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;🔴%2dd&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;🔴-%1dd&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="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;my/org-agenda-repeater&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="s"&gt;&amp;#34;The repeater shown in org-agenda-prefix for agenda.&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 class="nb"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;pom&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;org-get-at-bol&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;org-marker&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;org-get-scheduled-time&lt;/span&gt; &lt;span class="nv"&gt;pom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;org-get-deadline-time&lt;/span&gt; &lt;span class="nv"&gt;pom&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="nf"&gt;format&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;%5s: &amp;#34;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;org-get-repeat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&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="s"&gt;&amp;#34;------------&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="c1"&gt;;; Add repeater and effort to the agenda prefixes.&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-agenda-prefix-format&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="nv"&gt;agenda&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="s"&gt;&amp;#34; %i %-12:c%?-12t%s%(my/org-agenda-repeater)&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="c1"&gt;;; Add deadline-up to agenda to separate schedule and deadline.&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-agenda-sorting-strategy&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="nv"&gt;agenda&lt;/span&gt; &lt;span class="nv"&gt;deadline-up&lt;/span&gt; &lt;span class="nv"&gt;habit-down&lt;/span&gt; &lt;span class="nv"&gt;time-up&lt;/span&gt; &lt;span class="nv"&gt;priority-down&lt;/span&gt; &lt;span class="nv"&gt;category-keep&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;p&gt;This is an extension of my previous post on &lt;a href="https://whhone.com/posts/org-agenda-repeated-tasks/"&gt;distinguishing repeated tasks&lt;/a&gt;. By shortening the scheduled leaders, we free up horizontal space for the repeater interval. The addition of the 🔴 emoji ensures that any item with an active deadline immediately commands attention.&lt;/p&gt;
&lt;h2 id="trick-2-expanding-the-warning-window-to-a-month"&gt;Trick 2: Expanding the Warning Window to a Month&lt;/h2&gt;
&lt;p&gt;By default, Org-mode starts warning you about a deadline 7 days in advance. While a week sounds reasonable, in practice, it is often too short.&lt;/p&gt;
&lt;p&gt;When a deadline suddenly appears on your agenda with only 7 days to go, it can easily disrupt your cadence. Since 7 days technically translates to only 5 working days, you might already be fully committed to other urgent tasks. Instead of helping you plan, a short warning window simply induces stress.&lt;/p&gt;
&lt;p&gt;To fix this, I increased the default warning period from a week to a month (35 days, or 5 weeks):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&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-deadline-warning-days&lt;/span&gt; &lt;span class="mi"&gt;35&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;p&gt;With a 35-day warning window, upcoming major deadlines are visible on the agenda far in advance. This gives me ample time to adjust my weekly plan and budget resources before the deadline becomes urgent.&lt;/p&gt;
&lt;p&gt;Of course, not every task warrants a month-long warning. For minor tasks, you can override the default warning period on a per-item basis. For instance, you can use the &lt;code&gt;-3d&lt;/code&gt; syntax to only trigger a warning 3 days prior:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-org" data-lang="org"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;DEADLINE: &lt;/span&gt;&lt;span class="cs"&gt;&amp;lt;2026-06-19 Fri ++28d -3d&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I used to find schedules and deadlines somewhat overlapping in Org-mode. However, separating them into distinct roles, by using custom leaders for urgency and a longer warning window for planning, has made my agenda a lot easier to parse. It is a simple config tweak, but it has made my planning a bit smoother.&lt;/p&gt;</description></item><item><title>Introducing markdown-indent-mode</title><link>https://whhone.com/posts/markdown-indent-mode/</link><pubDate>Sun, 08 Mar 2026 16:30:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/markdown-indent-mode/</guid><description>&lt;h2 id="the-itch"&gt;The Itch&lt;/h2&gt;
&lt;p&gt;One thing I particularly love about &lt;code&gt;org-mode&lt;/code&gt; is &lt;code&gt;org-indent-mode&lt;/code&gt;, which
visually indents content under each heading based on its level. It makes long
org files much easier to read and navigate.&lt;/p&gt;
&lt;p&gt;Occasionally, I need to edit Markdown files — and every time I do, I miss that
clean visual hierarchy. So I vibe-coded a first version over a weekend.&lt;/p&gt;
&lt;h2 id="vibe-coding"&gt;Vibe-Coding&lt;/h2&gt;
&lt;p&gt;The idea translates naturally from &lt;code&gt;org-indent.el&lt;/code&gt;, which ships with Emacs.
Headings marked with &lt;code&gt;#&lt;/code&gt; instead of &lt;code&gt;*&lt;/code&gt;, same concept.&lt;/p&gt;
&lt;p&gt;The first version worked partially, but was full of edge
cases: code fences confusing the heading parser, list items indenting wrong,
wrapped lines losing alignment. I kept using it day-to-day, tweaking it when
something looked off, and simplifying whenever I found a cleaner approach.
Eventually it reached a state I was genuinely happy with.&lt;/p&gt;
&lt;p&gt;At that point I thought it might be useful to others too, so I decided to share it.&lt;/p&gt;
&lt;h2 id="first-attempt-merging-into-markdown-mode"&gt;First Attempt: Merging into markdown-mode&lt;/h2&gt;
&lt;p&gt;My first instinct was to contribute this directly to &lt;a href="https://github.com/jrblevin/markdown-mode"&gt;markdown-mode&lt;/a&gt;, the de-facto
Markdown package for Emacs, so all users could get it without installing
anything extra. It turns out this is a long-anticipated feature — there have
been open issues requesting it for years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jrblevin/markdown-mode/issues/330"&gt;Issue #330&lt;/a&gt; (opened in 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jrblevin/markdown-mode/issues/679"&gt;Issue #679&lt;/a&gt; (opened in 2021)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I opened &lt;a href="https://github.com/jrblevin/markdown-mode/pull/935"&gt;a pull request&lt;/a&gt; to add the feature directly into markdown-mode.&lt;/p&gt;
&lt;h2 id="going-standalone-package"&gt;Going Standalone Package&lt;/h2&gt;
&lt;p&gt;The markdown-mode maintainer reviewed the PR and suggested that this would be
better as a standalone package since it would be a burden for them to maintain
the new feature.&lt;/p&gt;
&lt;p&gt;I took the suggestion, refactored the code into its own package:
&lt;code&gt;markdown-indent-mode&lt;/code&gt;, and submitted it to &lt;a href="https://github.com/melpa/melpa"&gt;MELPA&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-melpa-journey"&gt;The MELPA Journey&lt;/h2&gt;
&lt;p&gt;Submitting to MELPA was a learning experience in itself. I picked up a few tools
along the way for checking Elisp package quality:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;checkdoc&lt;/code&gt; — checks that docstrings follow Emacs conventions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;package-lint&lt;/code&gt; — catches common packaging issues&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/riscy/melpazoid"&gt;&lt;code&gt;melpazoid&lt;/code&gt;&lt;/a&gt; — an automated checker specifically designed for MELPA submissions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The MELPA maintainers are volunteers who typically review PRs on weekends, so
the process took a few days.&lt;/p&gt;
&lt;h2 id="what-markdown-indent-mode-does"&gt;What markdown-indent-mode Does&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what it does — nothing fancy, just the basics done cleanly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;line-prefix&lt;/code&gt; and &lt;code&gt;wrap-prefix&lt;/code&gt; text properties for visual indentation&lt;/li&gt;
&lt;li&gt;Content under a heading is indented according to the heading level&lt;/li&gt;
&lt;li&gt;Leading &lt;code&gt;#&lt;/code&gt; symbols are hidden&lt;/li&gt;
&lt;li&gt;List items are indented to align with their content&lt;/li&gt;
&lt;li&gt;Everything is purely visual: no buffer content is ever modified&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation"&gt;Installation&lt;/h2&gt;
&lt;p&gt;This package is available on MELPA. You can use &lt;code&gt;use-package&lt;/code&gt; to install it and
add hook to enable it for &lt;code&gt;markdown-mode&lt;/code&gt;.&lt;/p&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;markdown-indent-mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:hook&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;markdown-mode&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;markdown-indent-mode&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;p&gt;Or toggle it on demand with &lt;code&gt;M-x markdown-indent-mode&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://whhone.com/ox-hugo/markdown-indent-mode-off.png" alt=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://whhone.com/ox-hugo/markdown-indent-mode-on.png" alt=""&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The source is available at &lt;a href="https://github.com/whhone/markdown-indent-mode"&gt;https://github.com/whhone/markdown-indent-mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you ever find yourself editing Markdown in Emacs, give it a try and let me
know what you think!&lt;/p&gt;</description></item><item><title>Distinguish Repeated Tasks in Org Agenda</title><link>https://whhone.com/posts/org-agenda-repeated-tasks/</link><pubDate>Fri, 03 Jan 2025 18:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/org-agenda-repeated-tasks/</guid><description>&lt;h2 id="problem-repeated-or-not"&gt;Problem: Repeated or Not?&lt;/h2&gt;
&lt;p&gt;In the Org agenda view, &lt;a href="https://orgmode.org/manual/Repeated-tasks.html"&gt;repeated&lt;/a&gt; and non-repeated tasks look identical. This similarity makes me hesitant to mark a task as DONE; if a task that should be recurring is missing its repeater (e.g., &lt;code&gt;++1w&lt;/code&gt;), it will disappear permanently from the agenda instead of being rescheduled.&lt;/p&gt;
&lt;p&gt;It would be helpful if repeated tasks were visually distinct in the Org agenda, for example, by displaying the repeater interval (&lt;code&gt;++1w&lt;/code&gt;) as part of the entry.&lt;/p&gt;
&lt;h2 id="solution-setting-the-prefix-format"&gt;Solution: Setting the Prefix Format&lt;/h2&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/org-agenda-repeat-prefix-compare.png"&gt;
&lt;/figure&gt;
&lt;p&gt;Thanks to a &lt;a href="https://fosstodon.org/@yantar92/113769144726128359"&gt;tip&lt;/a&gt; from yantar92 (the current Org maintainer), I was able to add the repeater to the Org agenda using &lt;code&gt;org-agenda-prefix-format&lt;/code&gt;. The process involves three steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shorten the scheduled leaders to make space for the repeater.&lt;/li&gt;
&lt;li&gt;Add a function to retrieve the formatted repeater.&lt;/li&gt;
&lt;li&gt;Include the new function in &lt;code&gt;org-agenda-prefix-format&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This requires only a few lines of Elisp:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; Shorten leaders to reserve space for the repeater.&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-agenda-scheduled-leaders&lt;/span&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;Sched.&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;S.%2dx&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-agenda-deadline-leaders&lt;/span&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;Deadl.&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;In%2dd&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;D.%2dx&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="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;my/org-agenda-repeater&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="s"&gt;&amp;#34;Return the repeater string for the current agenda entry.&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 class="nb"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;org-before-first-heading-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="s"&gt;&amp;#34;-------&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;; Align with the time grid&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="nf"&gt;format&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;%5s: &amp;#34;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;org-get-repeat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&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="c1"&gt;;; Add `my/org-agenda-repeater&amp;#39; to the agenda prefix format.&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="nf"&gt;setcdr&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;assoc&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;agenda&lt;/span&gt; &lt;span class="nv"&gt;org-agenda-prefix-format&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="s"&gt;&amp;#34; %i %-12:c%?-12t%s%(my/org-agenda-repeater)&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;h2 id="alternatives"&gt;Alternatives&lt;/h2&gt;
&lt;h3 id="using-the-agenda-finalize-hook--hacky"&gt;Using the Agenda Finalize Hook (Hacky)&lt;/h3&gt;
&lt;p&gt;Before learning about yantar92&amp;rsquo;s tip, I used a hackier solution (&lt;a href="https://gist.github.com/whhone/214fa34d57ec6eebc09f3c45bc153378"&gt;gist&lt;/a&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fetch the repeater using &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Text-Properties.html"&gt;text properties&lt;/a&gt; associated with the agenda entry.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;org-agenda-finalize-hook&lt;/code&gt;, which runs after the agenda view is built but before it is displayed, to insert the repeater string.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="using-column-view--cluttered"&gt;Using Column View (Cluttered)&lt;/h3&gt;
&lt;p&gt;Prior to that, I tried another solution using column view (&lt;code&gt;M-x org-agenda-columns&lt;/code&gt;). This method involves setting the &lt;code&gt;:COLUMNS:&lt;/code&gt; property to display the &lt;code&gt;SCHEDULED&lt;/code&gt; date as a separate column.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/org-agenda-columns-with-scheduled.png"&gt;
&lt;/figure&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&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-columns-default-format-for-agenda&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;%TODO %PRIORITY(Pri) %60ITEM(Task) %SCHEDULED&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;p&gt;While this approach works and is built-in, the resulting display is verbose and difficult to parse visually. This led me to seek a better solution with custom Elisp.&lt;/p&gt;</description></item><item><title>Using Emacs in a Terminal</title><link>https://whhone.com/posts/emacs-in-a-terminal/</link><pubDate>Fri, 23 Aug 2024 20:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/emacs-in-a-terminal/</guid><description>&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/24-bit-color.png"&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;em&gt;Fun fact: a coworker thought I was using a GUI application when they saw my terminal Emacs.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;At my workplace, most coding tasks must be performed on a remote workstation (or a browser-based IDE) due to policies prohibiting local storage of source code and the limited development tools available on company laptops.&lt;/p&gt;
&lt;p&gt;Coding with Emacs on a remote workstation requires access via SSH or a remote desktop connection. I have experimented with various solutions, including Tramp, Chrome Remote Desktop, &lt;a href="https://docs.gtk.org/gtk3/broadway.html"&gt;GTK Broadway&lt;/a&gt;, &lt;a href="https://github.com/Xpra-org/xpra"&gt;xpra&lt;/a&gt;, and &lt;a href="https://github.com/wayland-transpositor/wprs"&gt;wprs&lt;/a&gt;. However, these proved buggy, resource-intensive, or slow. Consequently, I settled on the reliable terminal-based approach using SSH and Tmux.&lt;/p&gt;
&lt;h2 id="issues"&gt;Issues&lt;/h2&gt;
&lt;p&gt;Initially, I encountered several issues using Emacs in the terminal, as things were not working out-of-the-box:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Emacs was no longer colorful.&lt;/li&gt;
&lt;li&gt;Some keybindings did not work.&lt;/li&gt;
&lt;li&gt;I could not copy text to the native clipboard.&lt;/li&gt;
&lt;li&gt;Version control diff highlighting was not visible.&lt;/li&gt;
&lt;li&gt;Opening links in a local browser was not easy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over time, I discovered solutions and workarounds for these issues and began to enjoy some extra benefits I had not expected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using the same Emacs instance across multiple computers.&lt;/li&gt;
&lt;li&gt;Eliminating Crostini on my Chromebook.&lt;/li&gt;
&lt;li&gt;Reducing the need to synchronize files and resolve conflicts.&lt;/li&gt;
&lt;li&gt;Remote file editing is faster than Tramp.&lt;/li&gt;
&lt;li&gt;Providing a more native terminal experience via Tmux multiplexing (compared to &lt;code&gt;vterm&lt;/code&gt;, &lt;code&gt;shell&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;No font size scaling issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="solutions"&gt;Solutions&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s how I configured Emacs for comfortable terminal use.&lt;/p&gt;
&lt;h3 id="pick-a-good-terminal-emulator"&gt;Pick a Good Terminal Emulator&lt;/h3&gt;
&lt;p&gt;First, pick a terminal emulator that supports at least:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;true color, and&lt;/li&gt;
&lt;li&gt;OSC 52 (for system clipboard integration).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ideally, it should have fewer default keybindings or allow customization to avoid conflicts with Emacs.&lt;/p&gt;
&lt;p&gt;The default Chrome OS terminal (&lt;a href="https://hterm.org/"&gt;hterm&lt;/a&gt;) is what I currently use, as it meets all three requirements. I do not use Linux, macOS, or Windows frequently enough nowadays to offer recommendations for those platforms.&lt;/p&gt;
&lt;p&gt;Some terminals can even display images! E.g., the &lt;a href="https://sw.kovidgoyal.net/kitty/graphics-protocol/"&gt;terminal graphics protocol&lt;/a&gt; by Kitty, the &lt;a href="https://iterm2.com/documentation-images.html"&gt;inline image protocol&lt;/a&gt; by iTerm2, and the &lt;a href="https://en.wikipedia.org/wiki/Sixel"&gt;Sixel protocol&lt;/a&gt;. It would be nice if there were Emacs integration with them!&lt;/p&gt;
&lt;h3 id="use-emacs-in-a-tmux-session"&gt;Use Emacs in a Tmux Session&lt;/h3&gt;
&lt;p&gt;I use Emacs inside Tmux because it provides a persistent session that can be reattached from any computer or after losing an SSH connection. I also automatically reattach to the Tmux session upon SSH connection with this SSH config:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-ssh-config" data-lang="ssh-config"&gt;Host myhost
RemoteCommand tmux -u new -A -D -s main
RequestTTY yes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternative ssh command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -t myhost tmux -u new -A -D -s main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alternative shell script approach:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .bashrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Attach to the main Tmux session if it is&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# - in SSH session&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# - not inside Tmux&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# - not inside Emacs&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; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$SSH_CLIENT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$TMUX&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$INSIDE_EMACS&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="c1"&gt;# &amp;#34;-A&amp;#34; : reattach if session-name already exists&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# &amp;#34;-D&amp;#34; : detach other clients (ensure $SSH_TTY is always correct)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tmux new -A -D -s main
&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;p&gt;Tmux also allows me to have multiple workspaces using Tmux windows (or sessions).&lt;/p&gt;
&lt;p&gt;If I need extra terminals, I split new panes or new windows. I find the Tmux terminal better than Emacs&amp;rsquo;s &lt;code&gt;term&lt;/code&gt;, &lt;code&gt;shell&lt;/code&gt;, &lt;code&gt;vterm&lt;/code&gt;, or &lt;code&gt;eat&lt;/code&gt;, because it is more native, faster, and easily distinguishable from an Emacs buffer.&lt;/p&gt;
&lt;p&gt;Also, I can add information like the system status (CPU, RAM, Disk, etc.) and the current time to the Tmux status bar.&lt;/p&gt;
&lt;p&gt;Optionally, change the default Tmux leader to reserve &lt;code&gt;C-b&lt;/code&gt; for moving the cursor.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-tmux" data-lang="tmux"&gt;set-option -g prefix M-\
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="make-emacs-colorful"&gt;Make Emacs Colorful&lt;/h3&gt;
&lt;p&gt;The screenshots below show the difference with and without true color (24-bit color) enabled.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without true color, low contrast&lt;/th&gt;
&lt;th&gt;With true color, high contrast&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://whhone.com/ox-hugo/emacs-without-truecolor.png" alt=""&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://whhone.com/ox-hugo/emacs-with-truecolor.png" alt=""&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are a few ways to get true color in a terminal Emacs session:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a &lt;code&gt;TERM&lt;/code&gt; that supports true color, e.g., &lt;code&gt;xterm-direct&lt;/code&gt;. (This won&amp;rsquo;t work inside Tmux, which changes the term to &lt;code&gt;tmux-256color&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;COLORTERM&lt;/code&gt; environment variable to &lt;code&gt;truecolor&lt;/code&gt;. (&lt;strong&gt;preferred&lt;/strong&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;!--listend--&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .bashrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;COLORTERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;truecolor
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are a few ways to get true color support in Tmux:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a &lt;code&gt;TERM&lt;/code&gt; that supports true color, e.g., &lt;code&gt;xterm-direct&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;Start tmux with the RGB feature, e.g., &lt;code&gt;tmux -T RGB&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;Override the features of your terminal in &lt;code&gt;.tmux.conf&lt;/code&gt;, e.g.:&lt;/li&gt;
&lt;/ol&gt;
&lt;!--listend--&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .tmux.conf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See https://github.com/tmux/tmux/wiki/FAQ#how-do-i-use-rgb-colour.&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; -as terminal-overrides &lt;span class="s2"&gt;&amp;#34;,xterm-256color:RGB&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tip: Use &lt;a href="https://gist.github.com/whhone/6852e485e8ccf51fd3631ba29ab7fad2"&gt;this script&lt;/a&gt; to test for true color support.&lt;/p&gt;
&lt;h3 id="copy-text-to-native-clipboard--osc-52"&gt;Copy Text to Native Clipboard (OSC 52)&lt;/h3&gt;
&lt;p&gt;OSC 52 works by printing an unreadable sequence, &lt;code&gt;\033]52;c;[base64 data]\a&lt;/code&gt;, which instructs the terminal to alter the system clipboard with the base64-encoded data. To verify that OSC 52 is working for your terminal setup, run &lt;code&gt;printf &amp;quot;\033]52;c;$(printf \&amp;quot;Hello, world\&amp;quot; | base64)\a&amp;quot;&lt;/code&gt; from the terminal. It should put &amp;ldquo;Hello, world&amp;rdquo; into the system clipboard.&lt;/p&gt;
&lt;p&gt;In the Emacs config, install the &lt;a href="https://github.com/spudlyo/clipetty"&gt;Clipetty&lt;/a&gt; package, which sends text that you kill in Emacs to the native clipboard.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; init.el&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;use-package&lt;/span&gt; &lt;span class="nv"&gt;clipetty&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:hook&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;after-init&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;global-clipetty-mode&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;p&gt;To enable clipboard support in Tmux, add these lines to &lt;code&gt;.tmux.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .tmux.conf&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; -g set-clipboard on
&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="c1"&gt;# Required to make Clipetty work better on re-attach by appending&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#34;SSH_TTY&amp;#34; to &amp;#34;update-environment&amp;#34;. See&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# https://github.com/spudlyo/clipetty?tab=readme-ov-file#dealing-with-a-stale-ssh_tty-environment-variable&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; -ag update-environment &lt;span class="s2"&gt;&amp;#34;SSH_TTY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If it still does not work, try running the &lt;code&gt;printf&lt;/code&gt; verification command above and check &lt;a href="https://github.com/tmux/tmux/wiki/Clipboard"&gt;the Tmux clipboard wiki&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="open-link-in-local-browser"&gt;Open Link in Local Browser&lt;/h3&gt;
&lt;p&gt;I have two workarounds for opening a web link from a remote Emacs session in a local browser:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a mouse click if the terminal emulator can recognize links and open them locally.&lt;/li&gt;
&lt;li&gt;Copy the link to the local clipboard and paste it into the browser.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For (2), I added an advice to &lt;code&gt;browse-url&lt;/code&gt; to place the URL into the native clipboard via OSC 52. I then paste it into a local browser. I also added key and mouse bindings for the function to make copying more seamless.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;define-advice&lt;/span&gt; &lt;span class="nv"&gt;browse-url&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;:around&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;orig-fun&lt;/span&gt; &lt;span class="kp"&gt;&amp;amp;rest&lt;/span&gt; &lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;copy-url-if-terminal&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;display-graphic-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 class="nf"&gt;apply&lt;/span&gt; &lt;span class="nv"&gt;orig-fun&lt;/span&gt; &lt;span class="nv"&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="nb"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nth&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&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="nf"&gt;message&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Clipetty link: %s&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;url&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;clipetty--emit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;clipetty--osc&lt;/span&gt; &lt;span class="nv"&gt;url&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;See &lt;a href="https://whhone.com/emacs-config/"&gt;my Emacs configuration&lt;/a&gt; for the full and updated version.&lt;/p&gt;
&lt;h3 id="tweak-for-xterm-paste"&gt;Tweak for xterm-paste&lt;/h3&gt;
&lt;p&gt;When pasting from the native clipboard, I would like to delete the region if one is active.&lt;/p&gt;
&lt;p&gt;This is like &lt;code&gt;(delete-selection-mode)&lt;/code&gt; for &lt;code&gt;xterm-paste&lt;/code&gt;.&lt;/p&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;define-advice&lt;/span&gt; &lt;span class="nv"&gt;xterm-paste&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;:before&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;delete-active-region&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="s"&gt;&amp;#34;Delete the selected text first before pasting from xterm.&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 class="nb"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;use-region-p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;delete-active-region&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;h3 id="update-key-maps-that-works-in-terminal"&gt;Update Key Maps that Works in Terminal&lt;/h3&gt;
&lt;p&gt;The idea is to have a set of keybindings that the terminal can respond to.&lt;/p&gt;
&lt;p&gt;This part varies from person to person. For example, I have remapped:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-x C-;&lt;/code&gt; to &lt;code&gt;C-x ;&lt;/code&gt; for &lt;code&gt;comment-line&lt;/code&gt; (&lt;code&gt;C-;&lt;/code&gt; is unsupported).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-c C-,&lt;/code&gt; to &lt;code&gt;C-c ,&lt;/code&gt; for &lt;code&gt;org-insert-structure-template&lt;/code&gt; (&lt;code&gt;C-,&lt;/code&gt; is unsupported).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can use &lt;code&gt;key-translation-map&lt;/code&gt; for the mapping.&lt;/p&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="c1"&gt;;; e.g., comment-line&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="nf"&gt;define-key&lt;/span&gt; &lt;span class="nv"&gt;key-translation-map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;kbd&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;C-x ;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;kbd&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;C-x C-;&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="c1"&gt;;; e.g., org-insert-structure-template&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="nf"&gt;define-key&lt;/span&gt; &lt;span class="nv"&gt;key-translation-map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;kbd&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;C-c ,&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;kbd&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;C-c C-,&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;p&gt;Alternatively, if your terminal supports the &lt;a href="https://sw.kovidgoyal.net/kitty/keyboard-protocol/"&gt;Kitty Keyboard Protocol&lt;/a&gt; (KKP), you can use &lt;a href="https://github.com/benotn/kkp"&gt;kkp.el&lt;/a&gt; to make terminal Emacs handle those &amp;ldquo;impossible&amp;rdquo; keybindings (thanks to sned@ for the tip).&lt;/p&gt;
&lt;h3 id="show-diff-highlighting-with-margin"&gt;Show Diff Highlighting with Margin&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;diff-hl&lt;/code&gt; does not work in the terminal by default, and this issue had annoyed me for a while until I searched for a solution. It turns out that &lt;code&gt;diff-hl&lt;/code&gt; already has &lt;a href="https://github.com/dgutov/diff-hl/issues/155"&gt;a solution&lt;/a&gt;: using the &amp;ldquo;margin&amp;rdquo; to show the diff.&lt;/p&gt;
&lt;p&gt;I added this Elisp config to turn on &lt;code&gt;diff-hl-margin-mode&lt;/code&gt; whenever I am inside a terminal.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; init.el&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;add-hook&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;diff-hl-mode-on-hook&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;lambda&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;unless&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;display-graphic-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 class="nv"&gt;diff-hl-margin-local-mode&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;figure&gt;&lt;img src="https://whhone.com/ox-hugo/diff-hl-margin-mode.png"&gt;
&lt;/figure&gt;
&lt;h3 id="enable-mouse-support"&gt;Enable Mouse Support&lt;/h3&gt;
&lt;p&gt;Don&amp;rsquo;t forget to enable mouse support.&lt;/p&gt;
&lt;p&gt;Note that there was a conflict where any mouse movement over Emacs would deactivate the Tmux prefix key (this is fixed in Tmux 3.5!). See &lt;a href="https://github.com/tmux/tmux/issues/4111"&gt;https://github.com/tmux/tmux/issues/4111&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; init.el&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;xterm-mouse-mode&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-tmux" data-lang="tmux"&gt;# .tmux.conf
# Enables mouse support in Tmux (switching windows and panes,
# resizing panes, etc.). Setting mouse on or off does not disable
# Emacs&amp;#39;s xterm-mouse-mode.
set -g mouse on
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;For most Emacsers, GUI Emacs is still a better choice because it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Has no network latency.&lt;/li&gt;
&lt;li&gt;Has better multimedia support (images, PDFs, etc.).&lt;/li&gt;
&lt;li&gt;Can handle all keybindings.&lt;/li&gt;
&lt;li&gt;Can interact with the clipboard natively.&lt;/li&gt;
&lt;li&gt;Can open links in a local browser easily.&lt;/li&gt;
&lt;li&gt;Is more customizable (font size per buffer, tooltips, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, if you, like me, need to work remotely or want to use the same Emacs instance across multiple computers via SSH and Tmux, I hope the tricks above can improve your setup, even at the expense of the aforementioned GUI features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update on 2025-09&lt;/strong&gt;: also check out this YouTube video &lt;a href="https://youtu.be/2_6OxZDoWvw"&gt;nobody knows that using Emacs in the terminal is so great&lt;/a&gt; by Jake B!&lt;/p&gt;</description></item><item><title>Text Replacement Tools for Large Codebases</title><link>https://whhone.com/posts/large-scale-replace-string/</link><pubDate>Wed, 25 Oct 2023 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/large-scale-replace-string/</guid><description>&lt;p&gt;10 years ago, when I became a software engineer, my mentor showed me how to replace all occurrences of a string (or a regex) under a large codebase effectively with a tool called &lt;a href="https://github.com/facebookarchive/codemod"&gt;codemod&lt;/a&gt;. It turned out to be one of the most useful text-editing techniques in my next 10 years, saving me many hours of &amp;ldquo;find and replace&amp;rdquo; manually.&lt;/p&gt;
&lt;p&gt;Over time, I discovered additional options for different use cases. Here they are.&lt;/p&gt;
&lt;h2 id="for-simple-replacement-sed"&gt;For Simple Replacement - &lt;code&gt;sed&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sed&lt;/code&gt; is my favorite for simple cases because it comes pre-installed in most operating systems.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# replace all instances of `foo` (regex) with `bar` (regex) in a file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i.bak &lt;span class="s2"&gt;&amp;#34;s/foo/bar/g&amp;#34;&lt;/span&gt; path/to/file
&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="c1"&gt;# replace all instances of `foo` (regex) with `bar` (regex) found by `find`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find . -name &lt;span class="s1"&gt;&amp;#39;*.cpp&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs sed -i.bak &lt;span class="s2"&gt;&amp;#34;s/foo/bar/g&amp;#34;&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="c1"&gt;# replace all instances of `foo` (regex) with `bar` (regex) in modified files under git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --name-only &lt;span class="p"&gt;|&lt;/span&gt; xargs sed -i.bak &lt;span class="s2"&gt;&amp;#34;s/foo/bar/g&amp;#34;&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="c1"&gt;# replace all instances of `foo` (regex) with `bar` (regex) in modified files under hg&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hg status --rev .^ -n &lt;span class="p"&gt;|&lt;/span&gt; xargs sed -i.bak &lt;span class="s2"&gt;&amp;#34;s/foo/bar/g&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If I encounter a performance issue, I will switch to &lt;code&gt;fastmod&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="for-very-large-code-base-fastmod"&gt;For Very Large Code Base - &lt;code&gt;fastmod&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/facebookincubator/fastmod"&gt;fastmod&lt;/a&gt; is a fast partial replacement for &lt;a href="https://github.com/facebookarchive/codemod"&gt;codemod&lt;/a&gt;. It is written in Rust and is designed for large-scale codebase refactoring.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# replace a regex pattern in specific files or directories:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fastmod &lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt; -- &lt;span class="o"&gt;{{&lt;/span&gt;path/to/file path/to/directory ...&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To install, run &lt;code&gt;cargo install fastmod&lt;/code&gt;, assume you have Rust&amp;rsquo;s Cargo installed.&lt;/p&gt;
&lt;p&gt;For more use cases, see &lt;a href="https://tldr.inbrowser.app/pages/common/fastmod"&gt;tldr:fastmod&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="for-simple-replacement-in-emacs-dired"&gt;For Simple Replacement in Emacs - &lt;code&gt;dired&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;As an Emacser, I must also provide a way to achieve this in Emacs.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;M-x dired&lt;/li&gt;
&lt;li&gt;Mark the required files and directories (&lt;code&gt;m&lt;/code&gt; to select, &lt;code&gt;t&lt;/code&gt; to toggle all)&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Q&lt;/code&gt; (&lt;code&gt;dired-do-find-regexp-and-replace&lt;/code&gt;) to replace string by regex
&lt;ol&gt;
&lt;li&gt;Press &lt;code&gt;y&lt;/code&gt; to accept.&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;!&lt;/code&gt; to accept all.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="for-interactive-replacement-in-emacs-wgrep"&gt;For Interactive Replacement in Emacs - &lt;code&gt;wgrep&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;wgrep&lt;/code&gt; stands for &amp;ldquo;Writable Grep Buffer&amp;rdquo;. It is a very flexible interactive replacement in Emacs. It gathers all occurrences in a buffer and then you can use whatever you want edit them.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Search with &lt;code&gt;counsel&lt;/code&gt;, e.g., &lt;code&gt;counsel-rg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;C-c C-o (&lt;code&gt;ivy-occur&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;C-x C-q (&lt;code&gt;ivy-wgrep-change-to-wgrep-mode&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Edit (e.g., &lt;code&gt;replace-regex&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;C-c C-c (commit the changes)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For more details on this method, see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sam217pa.github.io/2016/09/11/nuclear-power-editing-via-ivy-and-ag/"&gt;Nuclear weapon multi-editing via Ivy and Ag · Samuel Barreto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emacs-china.org/t/emacs/16537"&gt;Emacs 核弹级的功能与应用 - Emacs China&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="for-interactive-replacement-in-emacs-project-project-query-replace-regex"&gt;For Interactive Replacement in Emacs Project - &lt;code&gt;project-query-replace-regex&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;project.el&lt;/code&gt; provides a command called &lt;code&gt;project-query-replace-regex&lt;/code&gt; (default keybinding: &lt;code&gt;C-x p r&lt;/code&gt;) that allows you to interactively replace a regex pattern within a project.&lt;/p&gt;
&lt;h2 id="epilogue"&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;In most cases, I prefer &lt;code&gt;sed&lt;/code&gt; and &lt;code&gt;fastmod&lt;/code&gt; since they allow me to do it in a single command, which makes it easier to iterate the desired command (execute and revert for the right &amp;ldquo;foo&amp;rdquo; and &amp;ldquo;bar&amp;rdquo;).&lt;/p&gt;</description></item><item><title>Local Network DNS Rewrite</title><link>https://whhone.com/posts/local-dns-rewrite/</link><pubDate>Wed, 10 May 2023 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/local-dns-rewrite/</guid><description>&lt;h2 id="problem-slow-throughput-at-home-network"&gt;Problem: Slow Throughput at Home Network&lt;/h2&gt;
&lt;p&gt;I host some services at home by mapping public domain names to my home IP. It is a typical self-hosting setup that allows me to access those services anywhere, even outside of my home. For example, I use &lt;a href="https://whhone.com/posts/webdav-syncthing/"&gt;a WebDAV service to sychronize with mobile&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, I noticed the network throughput is very low (10~20 Mbps) when I am at home, connecting to the same local network. I would expect at least a few hundred Mbps.&lt;/p&gt;
&lt;h2 id="cause-requests-routed-outside"&gt;Cause: Requests Routed Outside&lt;/h2&gt;
&lt;p&gt;The requests to my home server are routed outside my home&amp;rsquo;s network and come back because my service&amp;rsquo;s domain is mapped to a public IP.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Public DNS Record&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;service.example.com&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Home IP (e.g., 123.123.123.123)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;While my ISP gives me 500M+ Mbits/sec of download, it is stint on the upload bandwidth! My local network throughout is throttled by the ISP upload speed.&lt;/p&gt;
&lt;p&gt;I benchmark the throughput with &lt;code&gt;iperf3&lt;/code&gt;. With a public IP, I get 21 Mbps. It matches the upload bandwidth from the ISP.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ iperf3 -c &amp;lt;PUBLIC IP&amp;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="o"&gt;[&lt;/span&gt; ID&lt;span class="o"&gt;]&lt;/span&gt; Interval Transfer Bitrate Retr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; 5&lt;span class="o"&gt;]&lt;/span&gt; 0.00-10.00 sec 25.6 MBytes 21.5 Mbits/sec &lt;span class="m"&gt;380&lt;/span&gt; sender
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; 5&lt;span class="o"&gt;]&lt;/span&gt; 0.00-10.01 sec 25.0 MBytes 21.0 Mbits/sec receiver
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With a private IP, I get 29 Gbps, a local throughout that looks right!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ iperf3 -c &amp;lt;PRIVATE IP&amp;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="o"&gt;[&lt;/span&gt; ID&lt;span class="o"&gt;]&lt;/span&gt; Interval Transfer Bitrate Retr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; 5&lt;span class="o"&gt;]&lt;/span&gt; 0.00-10.00 sec 33.5 GBytes 28.8 Gbits/sec &lt;span class="m"&gt;0&lt;/span&gt; sender
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt; 5&lt;span class="o"&gt;]&lt;/span&gt; 0.00-10.00 sec 33.5 GBytes 28.8 Gbits/sec receiver
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These results match my hypothesis! The question becomes &amp;ndash; &lt;strong&gt;Can I resolve the domain to a private IP when I am home?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="solution-local-dns-rewrite-with-adguard-home"&gt;Solution: Local DNS Rewrite with AdGuard Home&lt;/h2&gt;
&lt;p&gt;Yes! One easy solution is to rewrite the DNS record of my domain to a private IP with a local DNS (&lt;a href="https://hub.docker.com/r/adguard/adguardhome"&gt;AdGuard Home&lt;/a&gt;).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Local DNS Record&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;service.example.com&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Server Private IP (e.g., 192.168.1.2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It works! I can get a much higher local throughput at home using the public domain name! Also. it works seamlessly when leaving or arriving home because my phone flush its system&amp;rsquo;s DNS cache when switching network.&lt;/p&gt;
&lt;h2 id="solution-response-policy-zone--rpz--with-bind9"&gt;Solution: Response Policy Zone (RPZ) with Bind9&lt;/h2&gt;
&lt;p&gt;Another way is to set up response policy zone (RPZ) with bind9/named.&lt;/p&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="c1"&gt;// /etc/named.conf
&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="n"&gt;options&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="c1"&gt;// Add the subnet for recursion
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;recursion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mf"&gt;127.0.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mf"&gt;192.168.1.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;24&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Add external DNS to forward requests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;forwarders&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="mf"&gt;8.8.8.8&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="mf"&gt;8.8.4.4&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="c1"&gt;// Set up the response policy zone
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;zone&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;rpz&amp;#34;&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&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="n"&gt;zone&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;rpz&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="n"&gt;type&lt;/span&gt; &lt;span class="n"&gt;master&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;file&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;rpz.zone&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dns" data-lang="dns"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;;; /var/named/rpz.zone&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;$TTL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;1h&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;;; (Serial, Refresh, Retry, Expire, Negative Cache TTL)&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="nc"&gt;@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SOA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="py"&gt;localhost. root.localhost. &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;2024120462&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;8h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;30m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;1w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;1h&lt;/span&gt;&lt;span class="p"&gt;)&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="nc"&gt;@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="py"&gt;localhost.&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;;; List of overriding domains&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="nc"&gt;service.example.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192.168.1.2&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="nc"&gt;service2.example.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;192.168.1.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Test if it works. It should return 192.168.1.2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nslookup service.example.com localhost
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="alternative-hairpin-nat"&gt;Alternative: Hairpin NAT&lt;/h2&gt;
&lt;p&gt;Thanks to &lt;a href="https://www.reddit.com/r/selfhosted/comments/13dpiqw/comment/jjlis7k/"&gt;this reply&lt;/a&gt;, it turns out this problem could be solved by routers with &amp;ldquo;Hairpin NAT&amp;rdquo; support (a.k.a. &amp;ldquo;NAT Loopback&amp;rdquo;). Unfortunately, mine (Google Wifi) doesn&amp;rsquo;t seem to support Hairpin NAT and I rely on a local DNS rewrite.&lt;/p&gt;
&lt;h2 id="major-updates"&gt;Major Updates&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2024-12-05: &lt;a href="#solution-response-policy-zone--rpz--with-bind9"&gt;Add the lightweight solution using bind9/named response policy zone (RPZ).&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2023-05-10: Thanks to &lt;a href="https://emacs.ch/@RogerBW/110344738557535111"&gt;@RogerBW&lt;/a&gt; for telling me this problem is known as &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Split-horizon_DNS"&gt;Split-horizon DNS&lt;/a&gt;&amp;rdquo;. As a hindsight, I was solving a known problem from scratch. Here is how:/&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>From Linux to Chrome OS</title><link>https://whhone.com/posts/linux-to-chrome-os/</link><pubDate>Sat, 01 Apr 2023 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/linux-to-chrome-os/</guid><description>&lt;p&gt;&lt;em&gt;Disclaimer: The opinions stated here are my own, not those of my company.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I thought I would always be a GNU Linux user but I switched to Chrome OS early this year.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/chrome-os.png"&gt;
&lt;/figure&gt;
&lt;h2 id="why-switch-to-chrome-os"&gt;Why Switch to Chrome OS?&lt;/h2&gt;
&lt;p&gt;There were multiple reasons why I switched.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;My company builds Chrome OS and I was interested in trying our own dog food.&lt;/li&gt;
&lt;li&gt;Chrome OS can run Linux (via Crostini) which allows me to use Emacs and keeps my workflow.&lt;/li&gt;
&lt;li&gt;Every new year need some changes and I believe the variety is worthwhile.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As a result, I started to use my spare Chromebook more often and it has been 3 months already. I loved it. Light and thin. Fanless and silent. Long battery life. Bluetooth just works.&lt;/p&gt;
&lt;h2 id="what-changed-after-the-switch"&gt;What Changed After the Switch?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s talk about what has changed after switching from Linux to Chrome OS.&lt;/p&gt;
&lt;h3 id="less-bias-on-tiling-window-manager"&gt;Less Bias on Tiling Window Manager&lt;/h3&gt;
&lt;p&gt;I thought a tiling window manager was superior to a stacking window manager. After using Chrome OS, I think both are fine. I am not less productive with Chrome OS&amp;rsquo;s stacking window manager than my beloved i3wm. It works well for most daily scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Alt+=&lt;/code&gt; to maximize the window.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Alt+[&lt;/code&gt; and &lt;code&gt;Alt+]&lt;/code&gt; to tile the window on the left and right respectively.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Alt+N&lt;/code&gt; to focus to a specific application from the taskbar, like the i3&amp;rsquo;s scratchpad.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Alt+Tab&lt;/code&gt; to switch windows. It works most of the time, given the two main windows I have are Emacs and Chrome.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, I still complain about the overuse of the &lt;code&gt;Alt&lt;/code&gt; key in Chrome OS&amp;rsquo;s system keybindings which can conflict with the underlying application. Hope that it will be improved soon.&lt;/p&gt;
&lt;h3 id="less-time-tweaking-dotfiles"&gt;Less Time Tweaking Dotfiles&lt;/h3&gt;
&lt;p&gt;I am spending much less time tweaking my dotfiles like i3wm, rofi, randr, wayland, ibus and so on. I might still configure the Crostini like the sommelier daemon, but on average, much less often. As a result, there is more time for other things, the family, the kids, real work, and even just taking a rest.&lt;/p&gt;
&lt;h3 id="more-okay-to-the-google-eco-system"&gt;More Okay to the Google Eco-System&lt;/h3&gt;
&lt;p&gt;There are advocates to reduce the dependency on big tech for privacy and autonomy. I love this vision and also self-hosting some services to own my data. I tried to self-host Google Photo and/or Google Drive with an open-sourced alternative, I don&amp;rsquo;t find a good solution as these two services require backing up a large amount of data, and need a special setup for transcoding video.&lt;/p&gt;
&lt;p&gt;The idea of self-hosting Drive and Photo faded away after using Chrome OS more often. It makes sense to use Google Drive on Chrome OS because the integration is so good. The more I use Chrome OS, the more I am okay with using the Google ecosystem.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I am very happy with Chromebook and Chrome OS so far. I even switched my work laptop to Chromebook a few weeks ago so now I am using Chromebook for both personal and work. I don&amp;rsquo;t know how long I will stay. Likely until there is a decent Linux laptop with great hardware and driver support.&lt;/p&gt;</description></item><item><title>Implementing the PARA Method in Org-mode</title><link>https://whhone.com/posts/para-org-mode/</link><pubDate>Sat, 31 Dec 2022 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/para-org-mode/</guid><description>&lt;p&gt;This post first talks about the PARA method and then my Org-mode implementation for organizing tasks and notes.&lt;/p&gt;
&lt;h2 id="the-para-method"&gt;The PARA Method&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://fortelabs.com/blog/para/"&gt;The PARA Method&lt;/a&gt; is a modern system for organizing digital information. It stands for Projects-Areas-Resources-Archives, the four categories of all digital information.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project&lt;/strong&gt;: a series of tasks linked to a goal, with a deadline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Area&lt;/strong&gt;: a sphere of activity with a standard to be maintained over time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource&lt;/strong&gt;: a topic or theme of ongoing interest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Archive&lt;/strong&gt;: inactive items from the other 3 categories.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 id="project-vs-area-vs-resource"&gt;Project vs Area vs Resource&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;Projects&amp;rdquo; and &amp;ldquo;Areas&amp;rdquo; are inherited from GTD. &amp;ldquo;Distinguish Project and Area&amp;rdquo; is a common challenge for GTD and PARA adopters, including me. I once had many unimportant projects without deadlines that cluttered my agenda. There are many discussions and articles on it&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; and the model answer is to check (1) the nature of the task and (2) if there is a deadline.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A project has (1) a specific outcome/goal and (2) a deadline.&lt;/li&gt;
&lt;li&gt;An area has (1) a standard to be maintained that (2) is continuous over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;In addition to the model answer, I have my interpretation,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Projects contain the tasks I should focus on&lt;/strong&gt; (because of the importance, urgency and desirability). They have higher priority over the other categories. I work on them first, especially when there is a chunk of uninterrupted time or my energy level is high. If a &amp;ldquo;project&amp;rdquo; does not come with a deadline, I set one for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Areas contain the tasks I have to do.&lt;/strong&gt; I reserve some time for these tasks every day to keep areas maintained. I can also skip them for a while when the current projects are too demanding.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resources contain the tasks I am interested to do.&lt;/strong&gt; When there is no pressure from projects or areas, I can choose to work on these tasks or simply take a rest.&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Need my focus&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Have to do&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Maybe&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want to do&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Maybe&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Maybe&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priority&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These three questions help decide which PARA categories a task should go to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Does it require my focus within a timeframe?&lt;/strong&gt; If yes, make it a &lt;strong&gt;project&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is it something I have to do?&lt;/strong&gt; If yes, move it to an &lt;strong&gt;area&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is it something I want to do?&lt;/strong&gt; If yes, move it to a &lt;strong&gt;resource&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="para-is-pragmatic"&gt;PARA is Pragmatic&lt;/h3&gt;
&lt;p&gt;On one hand, I organize my tasks. On the other hand, I know it is not necessary to get it perfect.&lt;/p&gt;
&lt;p&gt;Firstly, it takes a lot of effort. I move forward and do the actual work when the tasks are by-and-large in the right places that help to prioritize. The end goal should be the actual outcomes instead of organizing.&lt;/p&gt;
&lt;p&gt;Secondly, it is impossible. PARA is a system of single classification. Everything is either a project, an area, or a resource. In practice, a task or note can fall into multiple projects, areas, or interests. For example, &amp;ldquo;planning a trip for the 10-th anniversary&amp;rdquo; could arguably belong to either area &amp;ldquo;spouse&amp;rdquo; or area &amp;ldquo;travel&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Organizing should be forgiving. It is okay to organize wrongly as long as it works. PARA is not a system for perfectionism but for pragmatism.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s talk about my implementation!&lt;/p&gt;
&lt;h2 id="org-mode-implementation"&gt;Org-mode Implementation&lt;/h2&gt;
&lt;p&gt;I organize my tasks with PARA using a single &lt;code&gt;todo.org&lt;/code&gt; file.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/para-todo-org.png"&gt;
&lt;/figure&gt;
&lt;h3 id="tags-and-categories"&gt;Tags and Categories&lt;/h3&gt;
&lt;p&gt;I use &lt;strong&gt;tags&lt;/strong&gt; for the actual areas and resources. I assign each of them a specific tag to the corresponding subheadings. This gives me a set of &lt;a href="https://en.wikipedia.org/wiki/Controlled_vocabulary"&gt;controlled vocabularies&lt;/a&gt; for tagging projects and notes. For example, I know I should use &lt;code&gt;writing&lt;/code&gt; instead of &lt;code&gt;write&lt;/code&gt; or &lt;code&gt;blogging&lt;/code&gt; for area &amp;ldquo;writing&amp;rdquo;. I don&amp;rsquo;t create tags for projects (which are short-term and grow over time) to keep my tag set small and useful&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I use &lt;strong&gt;categories&lt;/strong&gt; for the PARA top categories, Project-Area-Resource. When looking at my org agenda view, I immediately know which tasks are my top focuses (Project), which are things I need to do (Area) or interested to do (Resource). I also know which exact area or resource they belong to with the tags on the right.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/para-agenda.png"&gt;
&lt;/figure&gt;
&lt;p&gt;Tip: I can filter tasks by category and tag by pressing &lt;code&gt;/&lt;/code&gt; (&lt;code&gt;M-x org-agenda-filter&lt;/code&gt;) and clear filters with &lt;code&gt;|&lt;/code&gt; (&lt;code&gt;M-x org-agenda-filter-remove-all&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id="organizing-my-notes"&gt;Organizing my Notes&lt;/h3&gt;
&lt;p&gt;For project notes, I add or link them directly under the corresponding org subtree in &lt;code&gt;todo.org&lt;/code&gt; as I avoid adding project tags. When a project is completed or canceled, I convert the reusable part into the area or resource notes, and archive the project subtree to &lt;code&gt;todo.org_archive&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I place the areas and resources notes under &lt;code&gt;~/note&lt;/code&gt;. I tag them with the tags defined in &lt;code&gt;todo.org&lt;/code&gt; and reserve the directory hierarchy&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; for the note types. This gives me the flexibility to have multiple tags for a note.&lt;/p&gt;
&lt;p&gt;Here is a simplified version of my org directory:&lt;/p&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;org
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ todo.org
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─ note
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 20221125T211904--org-attach__emacs.org
&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; ├─ meta
&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; │ └─ 20200202T222222--using-org-mode__emacs.org
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ reference
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ article
&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; │ └─ 20221226T150449--creating-family-rules__parenting.org
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ book
&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; └─ 20220907T134123--building-a-second-brain-tiago-forte__pkm.org
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;note/20221125T211904--org-attach__emacs.org&lt;/code&gt;&lt;/strong&gt; is a regular note.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;note/meta/20200202T222222--using-org-mode__emacs.org&lt;/code&gt;&lt;/strong&gt; is a meta note that connects other notes, like the &lt;code&gt;org-attach__eamcs.org&lt;/code&gt; above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;note/reference/article/20221226T150449--creating-family-rules__parenting.org&lt;/code&gt;&lt;/strong&gt; is a reference note for the article &amp;ldquo;&lt;a href="https://www.cdc.gov/parents/essentials/structure/rules.html"&gt;Creating Family Rules&lt;/a&gt;&amp;rdquo;, tagged with my resource &amp;ldquo;parenting&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;note/reference/book/20220907T134123--building-a-second-brain-tiago-forte__pkm.org&lt;/code&gt;&lt;/strong&gt; is a new book note for the book &amp;ldquo;Building a Second Brain&amp;rdquo; by Tiago Forte.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So far, I don&amp;rsquo;t archive notes under &lt;code&gt;~/note&lt;/code&gt;. If I need to, I might use the tag &lt;code&gt;ARCHIVED&lt;/code&gt; and write some elisp to filter them.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Defining how I am going to organize my digital information reduces the cognitive load when working with tasks and notes. I can add or retrieve the information without thinking too much.&lt;/p&gt;
&lt;p&gt;I love to learn how other people works and I hope this post could be interesting and helpful to you, too. If you like it, you probably also check out these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tasshin.com/blog/implementing-a-second-brain-in-emacs-and-org-mode/"&gt;Tassin&amp;rsquo;s implementation&lt;/a&gt; which combines Org-mode and Evernote for tasks and notes respectively.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://renatgalimov.github.io/org-basb-code/#orgb77d8dc"&gt;Emacs CODE implementation&lt;/a&gt; which focuses on its note taking workflow with lots of details.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Search them yourself or read this great article: &lt;a href="https://fortelabs.com/blog/project-people-vs-area-people-are-you-running-a-sprint-or-a-marathon/"&gt;Project People vs. Area People - Forte Lab&lt;/a&gt;.&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;li id="fn:2"&gt;
&lt;p&gt;It is &lt;a href="https://karl-voit.at/2022/01/29/How-to-Use-Tags/"&gt;suggested&lt;/a&gt; to &lt;em&gt;use a self-defined set of tags&lt;/em&gt; and &lt;em&gt;keep them as few as possible&lt;/em&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;That is why &lt;a href="https://whhone.com/posts/denote-with-subdirectories/"&gt;using denote with subdirectories&lt;/a&gt; is important for me.&amp;#160;&lt;a href="#fnref:3" 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>Using Denote with Subdirectories</title><link>https://whhone.com/posts/denote-with-subdirectories/</link><pubDate>Thu, 08 Dec 2022 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/denote-with-subdirectories/</guid><description>&lt;p&gt;My &lt;a href="https://whhone.com/posts/denote-vs-org-roam/"&gt;last post&lt;/a&gt; mentioned the out-of-the-box experience of &lt;a href="https://protesilaos.com/emacs/denote"&gt;Denote&lt;/a&gt; is not so great, especially when having notes across many subdirectories. By default, Denote searches and creates notes from the root &lt;code&gt;denote-directory&lt;/code&gt;. I need to know the exact file location before searching for a note. I need to move note from the root directory to subdirectories after creating the note.&lt;/p&gt;
&lt;p&gt;The good news is that, these issues are now fixed and this post talks about how.&lt;/p&gt;
&lt;h2 id="1-search-by-filename-in-subdirectories"&gt;#1 Search by Filename in Subdirectories&lt;/h2&gt;
&lt;p&gt;By default, Denote searches only in the &lt;code&gt;denote-directory&lt;/code&gt;. To find a note hidden inside a subdirectory, the user needs to know the path to that file and navigate there. Needing to know the exact directory to be able to search for a file is counter-intuitive.&lt;/p&gt;
&lt;p&gt;However, thanks to &lt;a href="https://git.sr.ht/~protesilaos/denote/commit/6bb6da088211324cdd1ad35396c2988ccb2eccb7"&gt;a recent (2022-11-18) refactoring&lt;/a&gt; by nobiot@, it is no longer the case! The commit makes notes inside subdirectories searchable with common Denote commands like &lt;code&gt;denote-open-or-create&lt;/code&gt; and &lt;code&gt;denote-link-*&lt;/code&gt;. This greatly improves the out-of-the-box experience of Denote. Update Denote to a newer version including this new change!&lt;/p&gt;
&lt;h2 id="2-full-text-search-in-subdirectories"&gt;#2 Full-Text Search in Subdirectories&lt;/h2&gt;
&lt;p&gt;By default, Denote does not come with any command for full-text search.&lt;/p&gt;
&lt;p&gt;However, it could be done easily with &lt;code&gt;consult-ripgrep&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;consult-denote-ripgrep&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="s"&gt;&amp;#34;Search with ‘rg’ for files in denote-directory where the content matches a regexp.&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 class="nb"&gt;interactive&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;consult-ripgrep&lt;/span&gt; &lt;span class="nv"&gt;denote-directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&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;h2 id="3-create-notes-in-subdirectories"&gt;#3 Create Notes in Subdirectories&lt;/h2&gt;
&lt;p&gt;By default, Denote creates notes in the root &lt;code&gt;denote-directory&lt;/code&gt;. Every time after creating a note, I need to move them manually to the right subdirectory. This is cumbersome.&lt;/p&gt;
&lt;p&gt;However, it turned out to be my fault of not reading the official documentation, which has already &lt;a href="https://protesilaos.com/emacs/denote#h:887bdced-9686-4e80-906f-789e407f2e8f"&gt;suggested&lt;/a&gt; two ways to select a specific directory when creating a new note:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;includes &lt;code&gt;subdirectory&lt;/code&gt; to &lt;code&gt;denote-prompts&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;uses &lt;code&gt;M-x denote-subdirectory&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I go with (1) because it works with all Denote commands that could create a note, like &lt;code&gt;denote-open-or-create&lt;/code&gt; and &lt;code&gt;denote-link-or-create&lt;/code&gt;. It also allows other customizations to &lt;code&gt;denote-prompts&lt;/code&gt;. For example, I prefer setting &amp;ldquo;subdirectory&amp;rdquo; before &amp;ldquo;title&amp;rdquo; (reorder &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;subdirectory&lt;/code&gt;) and updating keyword later with &lt;code&gt;denote-keyword-*&lt;/code&gt; (remove &lt;code&gt;keyword&lt;/code&gt; from &lt;code&gt;denote-prompts&lt;/code&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&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;denote-prompts&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;subdirectory&lt;/span&gt; &lt;span class="nv"&gt;title&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;p&gt;If you have too much subdirectories, use &lt;code&gt;denote-excluded-directories-regexp&lt;/code&gt; to exclude some of them.&lt;/p&gt;
&lt;h2 id="comparing-with-org-roam-again"&gt;Comparing with Org-roam Again&lt;/h2&gt;
&lt;p&gt;With these tweaks, I can now use Denote as my primary note-taking package.&lt;/p&gt;
&lt;p&gt;I mentioned a concern with the link type &lt;code&gt;denote:&lt;/code&gt; in my &lt;a href="https://whhone.com/posts/denote-vs-org-roam/"&gt;last post&lt;/a&gt;. It turns out to be not concerning because it is easy to migrate to or away from this link type with some elisp. I don&amp;rsquo;t use other features like org-roam-daily, org-roam-bibtex, or org-roam-ui at all. The last step before removing Org-roam is probably migrating the existing links.&lt;/p&gt;</description></item><item><title>Denote vs Org-roam</title><link>https://whhone.com/posts/denote-vs-org-roam/</link><pubDate>Sun, 27 Nov 2022 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/denote-vs-org-roam/</guid><description>&lt;p&gt;&lt;strong&gt;Updates&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2022-12-08: Check out the followup post &lt;a href="https://whhone.com/posts/denote-with-subdirectories/"&gt;Using Denote with Subdirectories&lt;/a&gt; to improve the out-of-the-box experience of Denote.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href="https://protesilaos.com/emacs/denote"&gt;Denote&lt;/a&gt; is getting popular in the Emacs community recently. I decided to give it a shot, learn how it works and see if I should migrate from Org-roam to it. I am writing this blog post to compares Denote with Org-roam.&lt;/p&gt;
&lt;p&gt;In essence, Denote is a note-taking package for Emacs based on its file-naming scheme, which works for all kinds of files. Org-roam is a note-taking package for Org-mode, utilizing a database indexing org files.&lt;/p&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;h3 id="identifier-and-linking"&gt;Identifier and Linking&lt;/h3&gt;
&lt;p&gt;Denote uses the timestamp in its file-naming scheme as the identifier and uses its link type &lt;code&gt;denote:&lt;/code&gt; for linking. This approach allows notes to be taken in other formats (&lt;code&gt;.md&lt;/code&gt; and &lt;code&gt;.txt&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Org-roam uses the &lt;code&gt;ID&lt;/code&gt; properties as the identifier and the built-in link type &lt;code&gt;id:&lt;/code&gt; for linking. It sticks with the Org-mode ecosystem. It utilizes an external database to indexes note metadata, like titles, tags, and locations and searching notes efficiently without parsing any org files. When working on multiple computers, the user might need to re-index the org files.&lt;/p&gt;
&lt;p&gt;This is the biggest architectural difference between Denote and Org-roam. If all notes are written in Org-mode, I find Org-roam provides cleaner org files because it does not introduce a new link type.&lt;/p&gt;
&lt;h3 id="supported-formats"&gt;Supported Formats&lt;/h3&gt;
&lt;p&gt;Denote works for all files. Not only can the user take notes in Org-mode, Markdown, or Txt, but also name a PDF file so that it could be identified, linked, searched and tagged.&lt;/p&gt;
&lt;p&gt;Org-roam works for only Org-mode files. It leverages the mature Org-mode ecosystem. For example, the user can use &lt;code&gt;org-attach&lt;/code&gt; to associate a PDF file with a note.&lt;/p&gt;
&lt;p&gt;Frankly speaking, Denote is designed for Emacs users. Org-roam is designed for Org-mode users.&lt;/p&gt;
&lt;h3 id="file-naming-scheme"&gt;File-naming Scheme&lt;/h3&gt;
&lt;p&gt;In a file-based note system, it is inevitable to have coupling between the filename and the content (e.g., keeping &lt;code&gt;#+TITLE:&lt;/code&gt; and file name in Org-mode).&lt;/p&gt;
&lt;p&gt;Denote answers with its file-naming scheme and need to keep filename consistent and update-to-date so that search results remain valid after changing title or tags. Maintaining the naming scheme could become a burden without any automation. Denote provides &lt;code&gt;denote-rename-*&lt;/code&gt; and &lt;code&gt;denote-dired-rename-*&lt;/code&gt; for the job.&lt;/p&gt;
&lt;p&gt;Org-roam does not enforce any file-naming scheme. The default &lt;code&gt;org-roam-capture-templates&lt;/code&gt; uses the creation timestamp and title slug as the filename, and that is all.&lt;/p&gt;
&lt;p&gt;Note that the Denote file-naming scheme has a limitation: cannot define a tag with multiple words. For example, the tag &amp;ldquo;note-taking&amp;rdquo; becomes two tags &amp;ldquo;note&amp;rdquo; and &amp;ldquo;taking&amp;rdquo; unless combining them to &amp;ldquo;notetaking&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="searching-mechanism"&gt;Searching Mechanism&lt;/h3&gt;
&lt;p&gt;Utilizing its file-naming scheme, Denote can efficiently search for a note by filename without parsing the content of the file. Users can search notes with titles and tags by prefix &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt; respectively. The out-of-the-box experience does not fit my need. Users need to write some emacs lisp or learn Dired to search efficiently.&lt;/p&gt;
&lt;p&gt;Org-roam provides better out-of-the-box searching experience. With &lt;a href="https://whhone.com/emacs-config/#taking-note-with-org-roam"&gt;a little customization&lt;/a&gt;, I can search notes by folder name, title, and tags, recursively across sub-folders, sorted by the last modification time. This suits most of my needs.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/org-roam-node-find.png"&gt;
&lt;/figure&gt;
&lt;p&gt;It is nice to eradicate the dependency on a database with Denote. However, I found it hard to replicate the Org-roam searching experience with Denote, especially sorting notes by last modification time within subdirectories. Note that full-text search could be achieved by &lt;code&gt;counsel-rg&lt;/code&gt; for both.&lt;/p&gt;
&lt;h2 id="denote-comparison-to-org-roam"&gt;Denote Comparison to Org-roam&lt;/h2&gt;
&lt;h3 id="pros"&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;No database is needed. Seamless across multiple machines.&lt;/li&gt;
&lt;li&gt;A simple but powerful file-naming scheme that works for all files.&lt;/li&gt;
&lt;li&gt;Allow notes in other formats (txt, md). (Not for me. All my notes are in org.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cons"&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Uses non-standard link type &lt;code&gt;denote:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Worse out-of-the-box searching experience. (I believe it is hackable)&lt;/li&gt;
&lt;li&gt;Tag with multiple words like &amp;ldquo;note-taking&amp;rdquo; becomes &amp;ldquo;note&amp;rdquo; and &amp;ldquo;taking&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="my-choice"&gt;My Choice&lt;/h2&gt;
&lt;p&gt;It is tempting to eradicate the dependency on a database with a simple file-naming scheme. However, I am hesitant to introduce &lt;code&gt;denote:&lt;/code&gt; to my org files and want to develop a searching experience that on is par with Org-roam first.&lt;/p&gt;
&lt;p&gt;I decided to stick with Org-roam but keep exploring Denote in the next couple of weeks. Stay tune :-)&lt;/p&gt;
&lt;h2 id="appendix"&gt;Appendix&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://whhone.com/emacs-config/#note-taking-with-denote"&gt;My Denote configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Use Syncthing with WebDAV to Sync Everywhere</title><link>https://whhone.com/posts/webdav-syncthing/</link><pubDate>Fri, 04 Nov 2022 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/webdav-syncthing/</guid><description>&lt;p&gt;I have been using Syncthing with &lt;a href="https://caddyserver.com/docs/modules/http.handlers.webdav"&gt;Caddy&amp;rsquo;s WebDAV&lt;/a&gt; to synchronize my &lt;a href="https://orgmode.org/"&gt;Org Mode&lt;/a&gt; files across computers and mobiles for few weeks. I am satisfied with it and writing this post about the setup.&lt;/p&gt;
&lt;h2 id="why-not-just-syncthing"&gt;Why not just Syncthing?&lt;/h2&gt;
&lt;p&gt;My previous setup use solely Syncthing to synchronize them across computers and mobiles. While Syncthing works great on computers, it is problematic on mobiles because mobile can switch networks, have a limited battery, and have limited background sync capability (especially on iOS). Out-dated files or conflicts happen more often to me when I use a mobile device.&lt;/p&gt;
&lt;p&gt;I noticed some Org Mode mobile apps (like &amp;ldquo;beorg&amp;rdquo; and &amp;ldquo;Orgzly&amp;rdquo;) support WebDAV sync. Hence, I tried to add a WebDAV entry point for my org files and see how it goes.&lt;/p&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/webdav.jpg"&gt;
&lt;/figure&gt;
&lt;p&gt;I run Syncthing and WebDAV both watching the same org directory on a tiny server, Raspberry Pi 3B. Computers and mobiles are connected with Syncthing and WebDAV respectively.&lt;/p&gt;
&lt;h2 id="webdav-setup"&gt;WebDAV Setup&lt;/h2&gt;
&lt;p&gt;I first tried Nextcloud but chose Caddy eventually to set up WebDAV.&lt;/p&gt;
&lt;h3 id="attempt-1-nextcloud"&gt;Attempt #1: Nextcloud&lt;/h3&gt;
&lt;p&gt;My first attempt was using Nextcloud&amp;rsquo;s WebDAV, after searching &amp;ldquo;Syncthing WebDAV&amp;rdquo; and found &lt;a href="https://forum.syncthing.net/t/mixed-syncthing-with-nextcloud/10499"&gt;this story&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I added the Syncthing&amp;rsquo;s local folder to Nextcloud via &lt;a href="https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/external_storage_configuration_gui.html"&gt;external storage&lt;/a&gt; so it could be accessed from WebDAV. It worked. It proved that Syncthing with WebDAV is a viable solution.&lt;/p&gt;
&lt;p&gt;However, Nextcloud is too heavy for my Raspberry Pi 3. The latency is high enough to degrade the mobile experience. This could be a good solution for existing Nextcloud users with a faster hosting machine.&lt;/p&gt;
&lt;h3 id="attempt-2-caddy-webdav"&gt;Attempt #2: Caddy WebDAV&lt;/h3&gt;
&lt;p&gt;Since my Raspberry Pi is already using Caddy as the web server, why not give &lt;a href="https://caddyserver.com/docs/modules/http.handlers.webdav"&gt;Caddy&amp;rsquo;s WebDAV&lt;/a&gt; a shot? Caddy is written in Go and should have better performance.&lt;/p&gt;
&lt;p&gt;The WebDAV module was not built-in and I need to built a new Caddy binary with &lt;code&gt;xcaddy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To get the file permission right, I previously ran the Caddy web server as a non-caddy user (the same user as syncthing). My new approach is to use &lt;code&gt;bindfs&lt;/code&gt; to bind the syncthing directory to a directory readable and writable by the &lt;code&gt;caddy&lt;/code&gt; user. This way, I can securely run the Caddy process as the &lt;code&gt;caddy&lt;/code&gt; user.&lt;/p&gt;
&lt;p&gt;Here is the &lt;code&gt;bindfs&lt;/code&gt; command I use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo bindfs --mirror&lt;span class="o"&gt;=&lt;/span&gt;caddy /path/to/my/syncthing/dir /path/to/webdav
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To persist this over system restarts, I added the following line to &lt;code&gt;/etc/fstab&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cfg" data-lang="cfg"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /etc/fstab&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;/path/to/my/syncthing/dir /path/to/webdav fuse.bindfs mirror&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;caddy,allow_other,x-systemd.automount 0 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It worked and indeed ran so much faster than Nextcloud. I am satisfied!&lt;/p&gt;
&lt;p&gt;Here is the snippet from my Caddyfile WebDAV config.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-caddyfile" data-lang="caddyfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;my.webdav.host&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;basicauth&lt;/span&gt; &lt;span class="nd"&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;&amp;lt;user&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;hash&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;root&lt;/span&gt; &lt;span class="nd"&gt;*&lt;/span&gt; &lt;span class="s"&gt;/path/to/webdav&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="nd"&gt;@get&lt;/span&gt; &lt;span class="k"&gt;method&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;route&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;file_server&lt;/span&gt; &lt;span class="nd"&gt;@get&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;hide&lt;/span&gt; &lt;span class="s"&gt;.git&lt;/span&gt; &lt;span class="s"&gt;.gitignore&lt;/span&gt; &lt;span class="s"&gt;.stfolder&lt;/span&gt; &lt;span class="s"&gt;.stversions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;browse&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;webdav&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;h2 id="android-and-ios-setup"&gt;Android and iOS Setup&lt;/h2&gt;
&lt;p&gt;I use beorg on iOS and Orgzly on Android. Both of them support &amp;ldquo;WebDAV&amp;rdquo; with &amp;ldquo;Auto-sync&amp;rdquo;. &amp;ldquo;Auto-sync&amp;rdquo; performs a sync when a note is changed or when the app is resumed. This greatly reduces the chance of conflict, when compared with Syncthing.&lt;/p&gt;
&lt;p&gt;In addition, I found beorg also comes with a LISP runtime (&lt;a href="https://www.biwascheme.org/"&gt;BiwaScheme&lt;/a&gt;) and allows users to customize the app &lt;a href="https://beorg.app/manual/scripting/"&gt;in a way similar to Emacs&lt;/a&gt;. The customization is limited at the moment but it has the potential!&lt;/p&gt;
&lt;h2 id="syncthing-setup"&gt;Syncthing Setup&lt;/h2&gt;
&lt;p&gt;I use most of the default configuration with two tweaks.&lt;/p&gt;
&lt;h3 id="tweak-1-reduce-file-watch-delay"&gt;Tweak #1: Reduce File Watch Delay&lt;/h3&gt;
&lt;p&gt;I reduce the &lt;a href="https://docs.syncthing.net/users/config.html#config-option-folder.fswatcherdelays"&gt;&lt;code&gt;fsWatcherDelayS&lt;/code&gt;&lt;/a&gt; from 10s to 1s in all instances. This makes the synchronization happens more instantly and seamlessly between devices. For example, when I make a change on the phone, it updates the Raspberry Pi and synchronizes across other computers in a second.&lt;/p&gt;
&lt;h3 id="tweak-2-restart-syncthing-on-wakeup"&gt;Tweak #2: Restart Syncthing on Wakeup&lt;/h3&gt;
&lt;p&gt;When waking up from sleep, Syncthing can take quite a while to recover the connection.&lt;/p&gt;
&lt;p&gt;Syncthing used to provide an option called &lt;code&gt;restartOnWakeup&lt;/code&gt; to restart Syncthing on wakeup and avoid this long delay. However, this option &lt;a href="https://github.com/syncthing/syncthing/issues/8448"&gt;has been removed in 2022&lt;/a&gt;. I replicated this behavior myself, by writing a script to restart Syncthing, and running it on wake up.&lt;/p&gt;
&lt;h4 id="a-script-to-restart-syncthing"&gt;A Script to Restart Syncthing&lt;/h4&gt;
&lt;p&gt;Creates a script to hit the REST endpoint (&lt;code&gt;/rest/system/restart&lt;/code&gt;) on the local Syncthing instance.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;LOCAL_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8384
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;LOCAL_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;API_KEY&amp;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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Restarting Local Syncthing&amp;#34;&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;curl --silent -H &lt;span class="s2"&gt;&amp;#34;X-API-Key: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOCAL_API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --connect-timeout &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -X POST &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOCAL_ENDPOINT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/rest/system/restart
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="run-the-script-on-wakeup--macos"&gt;Run the Script on Wakeup (MacOS)&lt;/h4&gt;
&lt;p&gt;For MacOS, uses &lt;code&gt;sleepwatcher&lt;/code&gt; to detect wakeup event and execute the above script.&lt;/p&gt;
&lt;p&gt;First, install and start &lt;code&gt;sleepwatcher&lt;/code&gt; service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# To install sleepwatcher&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install sleepwatcher
&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="c1"&gt;# To start sleepwatcher now and restart at login&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew services start sleepwatcher
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Second, creates another script &lt;code&gt;~/.wakeup&lt;/code&gt;, which will be executed by &lt;code&gt;sleepwatcher&lt;/code&gt; on system wakeup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/path/to/syncthing-restart.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="run-the-script-on-wakeup--linux"&gt;Run the Script on Wakeup (Linux)&lt;/h4&gt;
&lt;p&gt;For Linux, creates a script under &lt;code&gt;/usr/lib/systemd/system-sleep/&lt;/code&gt;, which will be executed both &amp;ldquo;pre&amp;rdquo; and &amp;ldquo;post&amp;rdquo; system sleep.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/sh
&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;case&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; post&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /path/to/syncthing-restart.sh
&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;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Credit to &lt;a href="https://blog.christophersmart.com/2016/05/11/running-scripts-before-and-after-suspend-with-systemd/"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="run-the-script-on-wakeup--chromeos"&gt;Run the Script on Wakeup (ChromeOS)&lt;/h4&gt;
&lt;p&gt;Unfortunately, the Linux systemd method does not work for ChromeOS via Crostini. My solution is to use a script to check the system timestamp every second. If the current timestamp and the last timestamp differ more than 3 seconds, then I consider it as a system wakeup.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&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="nv"&gt;last&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date +%s&lt;span class="k"&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;while&lt;/span&gt; true&lt;span class="p"&gt;;&lt;/span&gt; &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="nv"&gt;current&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date +%s&lt;span class="k"&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="o"&gt;[[&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="nv"&gt;$current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$last&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt; -ge &lt;span class="m"&gt;3&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; &lt;span class="s2"&gt;&amp;#34;Detected wake up. Restarting Syncthing.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /path/to/syncthing-restart.sh
&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;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="nv"&gt;last&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep &lt;span class="m"&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;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, I create a systemd service (&lt;code&gt;~/.config/systemd/user/sleep-watcher.service&lt;/code&gt;) to run this script in the background.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-systemd" data-lang="systemd"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Sleep Watcher Service&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;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;StandardOutput&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;journal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/path/to/the/above/sleep-watcher.sh&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;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once it is ready, reload systemd, start and enable the service.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl --user daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl --user &lt;span class="nb"&gt;enable&lt;/span&gt; --now sleep-watcher
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Syncthing works brilliant on computers and laptops with the tweaks mentioned above. WebDAV is simple and cheap to host. It is secure to use over the Internet when pairing with BasicAuth and HTTPS (both supported by Caddy). It is not a perfect synchronization tool but good enough to fill the mobile gap.&lt;/p&gt;</description></item><item><title>More Effective ADB Logcat</title><link>https://whhone.com/posts/adb-logcat/</link><pubDate>Sun, 12 Jun 2022 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/adb-logcat/</guid><description>&lt;p&gt;Our team works on Android and uses &lt;code&gt;adb logcat&lt;/code&gt; a lot. I have heard coworkers complaining about how spammy the logs are and why some logs are missing from the output of &lt;code&gt;adb logcat&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Most of us, including me, start with piping the output to &lt;code&gt;grep&lt;/code&gt; for filtering, (i.e., &lt;code&gt;adb logcat | grep &amp;lt;pattern&amp;gt;&lt;/code&gt;). This is intuitive for command-line users. This works in practice but there are downsides if it is the only way to do Android logging (i.e., not using tag and priority.)&lt;/p&gt;
&lt;h2 id="trick-0-set-the-minimum-log-priority-for-tag"&gt;Trick 0: Set the Minimum Log Priority for Tag&lt;/h2&gt;
&lt;p&gt;The first downside of not using tags and priorities is that code writers tend to print all logs to at least the default priority, &lt;strong&gt;Debug (D)&lt;/strong&gt;. This is because they cannot see the lowest priority &lt;strong&gt;Verbose (V)&lt;/strong&gt; logs from &lt;code&gt;adb logcat&lt;/code&gt;. As a result, some logs that should be used in debugging a specific class are printed all the time, spamming everyone.&lt;/p&gt;
&lt;p&gt;The second downside is not knowing how to see the &lt;strong&gt;Verbose (V)&lt;/strong&gt; log. For a log to appear in &lt;code&gt;adb logcat&lt;/code&gt;, its log priority needs to be at least the minimum log priority set for its tag. For example, the following code will make all logs with the tag &lt;code&gt;tag1&lt;/code&gt; appear in &lt;code&gt;adb logcat&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set the minimum log priority to &amp;#34;VERBOSE&amp;#34; for &amp;lt;tag1&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell setprop log.tag.&amp;lt;tag1&amp;gt; VERBOSE
&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="c1"&gt;# Set the minimum log priority to &amp;#34;VERBOSE&amp;#34; for &amp;lt;tag1&amp;gt; persistently&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell setprop persist.log.tag.&amp;lt;tag1&amp;gt; VERBOSE
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="trick-1-filter-with-tags-and-priority"&gt;Trick 1: Filter with Tags and Priority&lt;/h2&gt;
&lt;p&gt;If I am interested in only some tags and priorities, then filter logs &lt;code&gt;&amp;lt;tag&amp;gt;[:priority]&lt;/code&gt;. In practice, I usually do this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Suppress all logs and print all logs for &amp;lt;tag1&amp;gt; and &amp;lt;tag2&amp;gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb logcat -s &amp;lt;tag1&amp;gt; &amp;lt;tag2&amp;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="c1"&gt;# Print all fatal logs + error logs for &amp;lt;tag1&amp;gt; + all logs for &amp;lt;tag2&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb logcat *:F &amp;lt;tag1&amp;gt;:E &amp;lt;tag2&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/adb-tag-priority.png"&gt;
&lt;/figure&gt;
&lt;p&gt;You can also use highlighted color to match priority by adding &lt;code&gt;-v color&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb logcat -v color &amp;lt;other arguments&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/adb-color.png"&gt;
&lt;/figure&gt;
&lt;h2 id="trick-2-filter-with-process"&gt;Trick 2: Filter with Process&lt;/h2&gt;
&lt;p&gt;If I am interested in the log from a specific process, then print only the logs from that process with &lt;code&gt;--pid&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Print logs from the &amp;lt;package&amp;gt;:&amp;lt;process&amp;gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb logcat --pid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;adb shell pidof -s &amp;lt;package&amp;gt;:&amp;lt;process&amp;gt;&lt;span class="k"&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="c1"&gt;# Combining trick 1 and trick 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;# Print the logs for &amp;lt;tag&amp;gt; from the &amp;lt;package&amp;gt;:&amp;lt;process&amp;gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb logcat --pid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;adb shell pidof -s &amp;lt;package&amp;gt;:&amp;lt;process&amp;gt;&lt;span class="k"&gt;)&lt;/span&gt; -s &amp;lt;tag&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that if the process crashes and restarts, the pid will change and we need to return the command with the new pid.&lt;/p&gt;
&lt;h2 id="trick-3-filter-with-regex"&gt;Trick 3: Filter with Regex&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;adb logcat&lt;/code&gt; itself can filter logs with regular expression without relying on &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb logcat -e &amp;lt;regex&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is similar to &lt;code&gt;adb logcat | grep -E &amp;lt;regex&amp;gt;&lt;/code&gt; but should be more efficient since the filtered logs do not even get outputted. This is usually not a big deal but can save some bandwidth if the log is sent through the network (working remotely).&lt;/p&gt;
&lt;h2 id="trick-4-read-the-documentation"&gt;Trick 4: Read the Documentation&lt;/h2&gt;
&lt;p&gt;All my tricks above are well documented in &lt;code&gt;adb logcat -h&lt;/code&gt; or the &lt;a href="https://developer.android.com/studio/command-line/logcat"&gt;official doc&lt;/a&gt;. You might find even that can fit your need by reading it.&lt;/p&gt;</description></item><item><title> Ignore "save-buffer" Unless Visiting a File</title><link>https://whhone.com/posts/my-save-buffer/</link><pubDate>Sat, 09 Apr 2022 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/my-save-buffer/</guid><description>&lt;p&gt;I have a habit of hitting the save shortcut (&lt;code&gt;C-x C-s&lt;/code&gt;) occasionally. However, if the buffer does not have a visited file (like treemacs, vterm, scratch, help, etc), Emacs asks me where to save the file and I need to cancel with triple &lt;code&gt;ESC&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, hitting &lt;code&gt;C-x C-s&lt;/code&gt; within the &lt;code&gt;*scratch*&lt;/code&gt; buffer.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/save-buffer.png"&gt;
&lt;/figure&gt;
&lt;p&gt;This is annoying. To fix it, I remap &lt;code&gt;save-buffer&lt;/code&gt; to the function below&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; which does nothing for non-file-visiting buffer.&lt;/p&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;defun&lt;/span&gt; &lt;span class="nv"&gt;my/save-buffer&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;optional&lt;/span&gt; &lt;span class="nv"&gt;arg&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="s"&gt;&amp;#34;Like &lt;/span&gt;&lt;span class="ss"&gt;`save-buffer&amp;#39;&lt;/span&gt;&lt;span class="s"&gt;, but does nothing if buffer is not visiting a file.&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 class="nb"&gt;interactive&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;p&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;unless&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buffer-file-name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;; regular buffer&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="nf"&gt;buffer-file-name&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buffer-base-buffer&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;; indirect buffer&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="ne"&gt;user-error&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Use &amp;#39;M-x save-buffer&amp;#39; explicitly to save this buffer with no visited file&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;save-buffer&lt;/span&gt; &lt;span class="nv"&gt;arg&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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;global-set-key&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;remap&lt;/span&gt; &lt;span class="nv"&gt;save-buffer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;my/save-buffer&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;p&gt;Now, I get the message below and do not need to cancel the save operation.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/my-save-buffer.png"&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/em&gt;: My friend AhLeung has an alternative using &lt;code&gt;save-all-buffers&lt;/code&gt;:&lt;/p&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;defun&lt;/span&gt; &lt;span class="nv"&gt;my/save-all-buffers&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="s"&gt;&amp;#34;Save all motified file-visiting buffers without asking.&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 class="nb"&gt;interactive&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;save-some-buffers&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&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;global-set-key&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;remap&lt;/span&gt; &lt;span class="nv"&gt;save-buffer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;my/save-all-buffers&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 class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;This elisp is updated with &lt;a href="https://www.reddit.com/r/emacs/comments/u09yf9/comment/i4522jb/"&gt;the suggestion&lt;/a&gt; from Phil-Hudson. Thanks!&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>Automating i3 Scratchpad Setup</title><link>https://whhone.com/posts/automating-i3-scratchpad-setup/</link><pubDate>Thu, 17 Mar 2022 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/automating-i3-scratchpad-setup/</guid><description>&lt;h2 id="problem-statement"&gt;Problem Statement&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://i3wm.org/docs/userguide.html#_scratchpad"&gt;Scratchpad&lt;/a&gt; is a useful i3 feature to always have your favorite apps at hand. A typical configuration is to define a key binding to show or hide the scratchpad window. For example,&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;# Show the Emacs scratchpad window, if any.
bindsym $mod+j [class=&amp;#34;(i?)emacs&amp;#34;] scratchpad show
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, i3 does nothing if the targeted scratchpad window does not exist. When it happens, I need to set up the scratchpad window manually by&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;creating the Emacs window,&lt;/li&gt;
&lt;li&gt;moving it to the scratchpad workspace, and&lt;/li&gt;
&lt;li&gt;pressing the key binding again&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I found it is annoying. This post describes my solution to automate these manual steps.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Create a script called &lt;code&gt;i3_scratchpad_show_or_create.sh&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/sh
&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="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; -ne &lt;span class="m"&gt;2&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; &lt;span class="s2"&gt;&amp;#34;Usage: &amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34; &amp;lt;i3_mark&amp;gt; &amp;lt;launch_cmd&amp;gt;&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;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Example: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;#39;scratch-emacs&amp;#39; &amp;#39;emacsclient -c -a emacs&amp;#39;&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;exit&lt;/span&gt; &lt;span class="m"&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;fi&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="nv"&gt;I3_MARK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="si"&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;LAUNCH_CMD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="si"&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;scratchpad_show&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; i3-msg &lt;span class="s2"&gt;&amp;#34;[con_mark=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;I3_MARK&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]&amp;#34;&lt;/span&gt; scratchpad show
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&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="c1"&gt;# try showing the scratchpad window&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; ! scratchpad_show&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="c1"&gt;# if there is no such window...&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="c1"&gt;# launch the application.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LAUNCH_CMD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&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="c1"&gt;# Wait for the next window event.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; i3-msg -t subscribe &lt;span class="s1"&gt;&amp;#39;[ &amp;#34;window&amp;#34; ]&amp;#39;&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="c1"&gt;# Set a mark&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; i3-msg mark &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;I3_MARK&lt;/span&gt;&lt;span class="si"&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="c1"&gt;# Move it to the scratchpad workspace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; i3-msg move scratchpad
&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="c1"&gt;# show the scratchpad window&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; scratchpad_show
&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;p&gt;The key ideas are to&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;automates the manual scratchpad setup mentioned above, and&lt;/li&gt;
&lt;li&gt;speeds up by querying with &lt;code&gt;con_mark=&lt;/code&gt; instead of &lt;code&gt;title=&lt;/code&gt; so that it does not need to wait for the application title, which might not be ready at launch.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Update the i3 config&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;# Show or create the Emacs scratchpad window.
bindsym $mod+j exec /path/to/i3_scratchpad_show_or_create.sh \
&amp;#39;scratch-emacs&amp;#39; &amp;#39;emacsclient -c -a emacs&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That is! When pressing &lt;code&gt;$mod+j&lt;/code&gt;, I can always get the scratchpad window.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/em&gt;: After writing this post, I found &lt;a href="https://gitlab.com/aquator/i3-scratchpad"&gt;https://gitlab.com/aquator/i3-scratchpad&lt;/a&gt; to be a more sophisticated solution. It brings its own features like setting position, dimension and display for the window but seems to be laggy when showing/hiding window.&lt;/p&gt;</description></item><item><title>Niz Micro 82 Keyboard Review</title><link>https://whhone.com/posts/niz-micro-82-review/</link><pubDate>Sat, 04 Dec 2021 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/niz-micro-82-review/</guid><description>&lt;p&gt;For many years, I&amp;rsquo;ve used full-size keyboards, but they&amp;rsquo;ve presented a couple of recurring ergonomic problems: the mouse feels too far away from the typing area, and I often find mouse movement to the left restricted when it hits the keyboard. These issues have led me to explore smaller keyboard layouts. The legendary HHKB is one I&amp;rsquo;ve heard about, but I&amp;rsquo;m concerned about adapting to life without dedicated arrow keys or relying on navigation like &lt;code&gt;hjkl&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-research"&gt;The Research&lt;/h2&gt;
&lt;p&gt;After some research, I concluded my two requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;75% layout&lt;/li&gt;
&lt;li&gt;silent switches.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;75% is a fairly new keyboard layout. There are not many choices in the market (which might be a good thing).&lt;/p&gt;
&lt;p&gt;Finally, I picked Niz Micro 82 (35g) because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It comes with electrostatic capacitance (EC) switches. I would like to try them.&lt;/li&gt;
&lt;li&gt;It provides the option to have no RGB lighting, which is a negative feature to me, even if I can turn it off.&lt;/li&gt;
&lt;li&gt;It has good reviews on the Internet.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/niz-micro-82.jpg"&gt;
&lt;/figure&gt;
&lt;h2 id="the-75-layout"&gt;The 75% Layout&lt;/h2&gt;
&lt;p&gt;The 75% layout gave more room for the mouse and solved my original headache. I can reach the arrow keys at a shorter distance and get rid of the &lt;code&gt;INS&lt;/code&gt; key that I don&amp;rsquo;t use but mishit occasionally.&lt;/p&gt;
&lt;p&gt;However, there were issues in the first week:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I could not locate the arrow keys accurately.&lt;/li&gt;
&lt;li&gt;I could not hit the right shift key accurately.&lt;/li&gt;
&lt;li&gt;I missed the Numpad.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I wondered if I should get a less aggressive keyboard like a TKL. Fortunately, issues 1 and 2 were gone after a month. My muscle memory has adjusted to the new keyboard.&lt;/p&gt;
&lt;h2 id="the-ec-switches"&gt;The EC Switches&lt;/h2&gt;
&lt;p&gt;The 35g EC switches are incredibly silent and comfortable to type. The &amp;ldquo;silent red&amp;rdquo; is the closest Cherry MX switch to it, IMO.&lt;/p&gt;
&lt;p&gt;I sometimes misfired a key while &amp;ldquo;placing&amp;rdquo; my finger on it in the first two weeks. The problem disappeared gradually with time. It has not happened in the last two weeks!&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Overall, I much prefer this 75% EC keyboard to my old 100% Filco Ninja Majestouch-2 (Brown Switch).&lt;/p&gt;
&lt;p&gt;The primary challenge is the loss of the Numpad&amp;rsquo;s ability for simple one-handed number touch-typing. This isn&amp;rsquo;t easily resolved. Potential workarounds include simply getting used to the new layout, leveraging Fn key combinations for numbers, custom reprogramming the keyboard, adding a standalone Numpad, or adopting a split keyboard setup with a Numpad in between.&lt;/p&gt;</description></item><item><title>Write with Emacs 2: Synonyms</title><link>https://whhone.com/posts/write-with-emacs-2/</link><pubDate>Fri, 05 Nov 2021 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/write-with-emacs-2/</guid><description>&lt;blockquote&gt;
&lt;p&gt;The limits of language are the limits of one&amp;rsquo;s world. ~Ludwig Wittgenstein&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My limited vocabulary should be a limit of my writing.&lt;/p&gt;
&lt;h2 id="looking-up-synonyms-with-power-thesaurus"&gt;Looking up Synonyms with Power Thesaurus&lt;/h2&gt;
&lt;p&gt;I find looking up synonyms is a way to extend one&amp;rsquo;s &lt;a href="https://en.wiktionary.org/wiki/active_vocabulary"&gt;active vocabulary&lt;/a&gt;. For instance, I can replace &lt;strong&gt;beautiful&lt;/strong&gt; (active vocabulary) with something more specific and accurate, like &lt;em&gt;attractive&lt;/em&gt;, &lt;em&gt;lovely&lt;/em&gt;, &lt;em&gt;gorgeous&lt;/em&gt;, &lt;em&gt;charming&lt;/em&gt;, &lt;em&gt;splendid&lt;/em&gt;, &lt;em&gt;appealing&lt;/em&gt; (passive vocabulary).&lt;/p&gt;
&lt;p&gt;In Emacs, &lt;a href="https://github.com/SavchenkoValeriy/emacs-powerthesaurus"&gt;emacs-powerthesaures&lt;/a&gt; is a package to look up synonyms by fetching data from &lt;a href="https://www.powerthesaurus.org/"&gt;Power Thesaurus&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is a mini demo:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/powerthesaurus.gif"&gt;
&lt;/figure&gt;
&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;
&lt;p&gt;The package defines three interactive functions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;powerthesaurus-lookup-word&lt;/code&gt;: look up synonyms of a provided word&lt;/li&gt;
&lt;li&gt;&lt;code&gt;powerthesaurus-lookup-word-at-point&lt;/code&gt;: look up synonyms of the word at point&lt;/li&gt;
&lt;li&gt;&lt;code&gt;powerthesaurus-lookup-word-dwim&lt;/code&gt;: combine the above two,
&lt;ul&gt;
&lt;li&gt;if the current point (cursor) has a word, then calls (2)&lt;/li&gt;
&lt;li&gt;otherwise, calls (1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is my configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&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;powerthesaurus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:bind&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="s"&gt;&amp;#34;M-`&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;powerthesaurus-lookup-word-dwim&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;h2 id="caveat"&gt;Caveat&lt;/h2&gt;
&lt;p&gt;Use words that you know. Otherwise, check the meaning and learn that new word first!&lt;/p&gt;</description></item><item><title>Write with Emacs 1: Spell-Checking</title><link>https://whhone.com/posts/write-with-emacs-1/</link><pubDate>Fri, 29 Oct 2021 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/write-with-emacs-1/</guid><description>&lt;p&gt;I am bad at writing, especially in English, which is not my native language. I misspell words, have limited vocabulary, and misuse grammar without being aware of it.&lt;/p&gt;
&lt;p&gt;On the other hand, I write and need to write a lot. Emacs is my main writing tool and I use it to take notes, compose commit messages and blog posts, etc. Over the years, I have learned some tricks to write better with this awesome tool. I am going to share them in the next couple of posts,&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with the basic &amp;ndash; spell-checking.&lt;/p&gt;
&lt;h2 id="check-and-correct-spelling-with-flyspell"&gt;Check and Correct Spelling with Flyspell&lt;/h2&gt;
&lt;p&gt;I use &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Spelling.html"&gt;Flyspell&lt;/a&gt;, an Emacs built-in minor mode, for performing on-the-fly spelling checking and correcting.&lt;/p&gt;
&lt;p&gt;When enabled, misspelled words will be highlighted. I can use &lt;code&gt;(flyspell-auto-correct-word)&lt;/code&gt; to correct the word at the point. Most of the time, the first candidate is the correct one. It is smart enough to me.&lt;/p&gt;
&lt;p&gt;I also use the context menu a lot (by middle-clicking the misspelled word). I can choose the right word from all candidates or save the word, so that it does not complain anymore.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/flyspell-mouse.gif"&gt;
&lt;/figure&gt;
&lt;p&gt;If you don&amp;rsquo;t like touching the mouse, &lt;a href="https://github.com/d12frosted/flyspell-correct"&gt;flyspell-correct&lt;/a&gt; is a package to correct misspelled words with other interfaces, like helm and ivy, all inside the keyboard.&lt;/p&gt;
&lt;p&gt;Here is a demo for &lt;code&gt;(flyspell-correct-wrapper)&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/flyspell-ivy.gif"&gt;
&lt;/figure&gt;
&lt;h2 id="configuration"&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Flyspell provides two minor modes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;flyspell-mode&lt;/code&gt; for writing&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flyspell-prog-mode&lt;/code&gt; for programming (only text inside comments and strings is checked)ema&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is how I enable them respectively and set up the packages mentioned above.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&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;text-mode-hook&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;flyspell-mode&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;add-hook&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;prog-mode-hook&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;flyspell-prog-mode&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="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;use-package&lt;/span&gt; &lt;span class="nv"&gt;flyspell-correct&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:after&lt;/span&gt; &lt;span class="nv"&gt;flyspell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:bind&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;:map&lt;/span&gt; &lt;span class="nv"&gt;flyspell-mode-map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;C-;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;flyspell-correct-wrapper&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="c1"&gt;;; Replace with flyspell-correct-helm if you are a helm person.&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;use-package&lt;/span&gt; &lt;span class="nv"&gt;flyspell-correct-ivy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;:after&lt;/span&gt; &lt;span class="nv"&gt;flyspell-correct&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;h2 id="caveat"&gt;Caveat&lt;/h2&gt;
&lt;p&gt;This trick does not work if the misspelled word is valid. For example, when misspelling &amp;ldquo;summit&amp;rdquo; as &amp;ldquo;submit&amp;rdquo;. So, you should still check the spelling even with tool like this.&lt;/p&gt;</description></item><item><title>Another Emacs i3 Integration</title><link>https://whhone.com/posts/emacs-i3-integration/</link><pubDate>Fri, 15 Oct 2021 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/emacs-i3-integration/</guid><description>&lt;p&gt;Pavel Korytov wrote an inspiring &lt;a href="https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/"&gt;blog post&lt;/a&gt; to get a consistent set of keybindings between i3 and Emacs. It caught my attention because I use exactly these two tools heavily. Before the integration, I have to define a different keybinding for switching window inside Emacs and i3. After the integration, I can use the same keybinding everywhere. Check out the video in the original &lt;a href="https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/"&gt;blog post&lt;/a&gt; to see how cool it is!&lt;/p&gt;
&lt;p&gt;I followed the blog post and implemented it immediately. It worked and I loved that! However, the proposed (i3 -&amp;gt; Emacs -&amp;gt; i3) solution introduced a coupling between i3 and Emacs. i3 has to know Emacs, and Emacs has to know i3. It does not fit into the &lt;a href="https://en.wikipedia.org/wiki/Law_of_Demeter"&gt;Law of Demeter&lt;/a&gt;. It makes the solution harder to scale to other tiling window managers or applications.&lt;/p&gt;
&lt;p&gt;Hence, I implemented another (i3 -&amp;gt; Emacs) solution with less coupling (only the &lt;code&gt;focus&lt;/code&gt; command for now).&lt;/p&gt;
&lt;h2 id="1-add-an-elisp-function-my-wm-integration"&gt;1: Add an Elisp function &lt;code&gt;my/wm-integration&lt;/code&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-elisp" data-lang="elisp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;windmove&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;defun&lt;/span&gt; &lt;span class="nv"&gt;my/wm-integration&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;command&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;pcase&lt;/span&gt; &lt;span class="nv"&gt;command&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;rx&lt;/span&gt; &lt;span class="nv"&gt;bos&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;focus&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;windmove-do-window-select&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="nf"&gt;intern&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;elt&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;split-string&lt;/span&gt; &lt;span class="nv"&gt;command&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 class="nf"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;error&lt;/span&gt; &lt;span class="nv"&gt;command&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;p&gt;&lt;code&gt;my/wm-integration&lt;/code&gt; handles the command from the window manager.&lt;/p&gt;
&lt;p&gt;Emacs does not need to know what the window manager is, and do not need to call &lt;code&gt;i3-msg&lt;/code&gt;. It return a non-zero code if the command is not handled, or a zero code if handled.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;nit. Emacs still use the protocol defined by i3, like the &lt;code&gt;focus&lt;/code&gt; command. Well&amp;hellip; we have to use a protocol anyway. Let&amp;rsquo;s pick the i3 protocol in this post.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="2-create-a-script-i3-msg-proxy"&gt;2: Create a Script &lt;code&gt;i3-msg-proxy&lt;/code&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/sh
&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="c1"&gt;# Proxy the `i3-msg` command to the focused window.&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="c1"&gt;# Proxy to Emacs if it is the active window&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;xdotool getactivewindow getwindowclassname&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;Emacs&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="nv"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(my/wm-integration \&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&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="k"&gt;if&lt;/span&gt; emacsclient -e &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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;exit&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;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;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="c1"&gt;# fallback to i3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;i3-msg &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This script proxies the &lt;code&gt;i3-msg&lt;/code&gt; command to the focused window. If the focused window has handled it (return zero), then exit. Otherwise (return non-zero), fallback to i3.&lt;/p&gt;
&lt;p&gt;This mechanism (using the return code) can work with other applications, like Tmux.&lt;/p&gt;
&lt;h2 id="3-update-the-i3-config"&gt;3: Update the i3 config&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt;bindsym $mod+Left exec i3-msg-proxy focus left
bindsym $mod+Down exec i3-msg-proxy focus down
bindsym $mod+Up exec i3-msg-proxy focus up
bindsym $mod+Right exec i3-msg-proxy focus right
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, update the i3 config to proxy the command to the focused window. Now, I can move my focus between i3 and Emacs!&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The key difference between this and the original solution is how Emacs calls i3 back when it failed to handle the command. The original solution calls &lt;code&gt;i3-msg&lt;/code&gt; inside Emacs. This solution returns non-zero from Emacs.&lt;/p&gt;</description></item><item><title>pass: the Unix Password Manager</title><link>https://whhone.com/posts/pass/</link><pubDate>Tue, 05 Oct 2021 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/pass/</guid><description>&lt;p&gt;&lt;a href="https://www.passwordstore.org/"&gt;&lt;code&gt;pass&lt;/code&gt;&lt;/a&gt; is a simple command-line password manager that follows the Unix philosophy.&lt;/p&gt;
&lt;p&gt;It encrypts passwords with &lt;code&gt;gpg&lt;/code&gt; and stores them in the file system. It has &lt;code&gt;git&lt;/code&gt; integration so one can track the password versioning and synchronize the password store with a git repository. It has clients across different platforms and applications, including Android, iOS, Chrome, Firefox, and also Emacs! I know this tool probably from its &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/auth/The-Unix-password-store.html"&gt;Emacs doc&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pass&lt;/code&gt; is nice and all, but I don&amp;rsquo;t find a strong reason to use it or switch from BitWarden, which is also open while not requiring me to set up &lt;code&gt;gpg&lt;/code&gt; on the phone and browser.&lt;/p&gt;</description></item><item><title>A Temporary Stay-At-Home Father</title><link>https://whhone.com/posts/a-temporary-stay-at-home-father/</link><pubDate>Tue, 06 Jul 2021 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/a-temporary-stay-at-home-father/</guid><description>&lt;p&gt;My mother-in-law has left three months before, leaving my wife and me to care for our son at home. I took a leave as a stay-at-home father, focusing on child care and housework like cooking.&lt;/p&gt;
&lt;p&gt;With the end of July 4 (Independence Day holiday), I am ending this duty and go back to work. My wife will also be taking time off for the next two months before we can send our son to daycare.&lt;/p&gt;
&lt;p&gt;Here are the things I have learned in the last three months.&lt;/p&gt;
&lt;h2 id="1-dot-can-focus-on-one-thing"&gt;1. Can Focus on One Thing&lt;/h2&gt;
&lt;p&gt;Compared to doing work and providing child-care at the same time, my
mental health has improved during the leave. I can cook when I cook,
feed when I feed. I don&amp;rsquo;t think about work or rush to the next meeting
while parenting.&lt;/p&gt;
&lt;h2 id="2-dot-stronger-bonding"&gt;2. Stronger Bonding&lt;/h2&gt;
&lt;p&gt;Spent more time with my son, we have developed stronger bonding. He
calls me all the time now. We played and drew together. I have taken
lots of valuable photos and videos for the moments that never repeat.&lt;/p&gt;
&lt;h2 id="3-dot-lacking-personal-time"&gt;3. Lacking Personal Time&lt;/h2&gt;
&lt;p&gt;My personal time dropped dramatically. I only have it when my son is
sleeping. I can do some simple stuff (surfing the internet, read a few
pages) but never deep and focused works (writing, coding). I also did
much less aerobic exercise.&lt;/p&gt;
&lt;h2 id="4-dot-parenting-is-wearying"&gt;4. Parenting is Wearying&lt;/h2&gt;
&lt;p&gt;The tiredness of parenting is different from the tiredness of working.
At work, I communicate with typical people and can space out if I am
tired. On the other hand, kids always request attention from adults. It
drains the mental power of adults. In addition to sleep deprivation and
other mundane chores, the adults could become extremely tired and
emotional.&lt;/p&gt;
&lt;p&gt;I especially like how this mom describes her experience&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;figure&gt;&lt;img src="https://whhone.com/ox-hugo/brain-rules-mom.jpg"&gt;
&lt;/figure&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;From the book &lt;a href="https://www.goodreads.com/book/show/8445273"&gt;Brain Rules for Baby&lt;/a&gt;&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>Cholesterol</title><link>https://whhone.com/posts/cholesterol/</link><pubDate>Mon, 07 Jun 2021 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/cholesterol/</guid><description>&lt;p&gt;I have heard of &amp;ldquo;saturated fat&amp;rdquo;, &amp;ldquo;unsaturated fat&amp;rdquo;, &amp;ldquo;high-density lipoprotein&amp;rdquo; (HDL), and &amp;ldquo;low-density lipoprotein&amp;rdquo; (LDL) many times, but have never known or cared which are good and which are bad. I thought my exercise habit could burn everything and I could eat anything without much concern.&lt;/p&gt;
&lt;p&gt;In a recent annual physical exam, my LDL (bad cholesterol) was 107 mg/dL, higher than the suggested maximum of 100 mg/dL. The doctor said that high LDL is caused by diet.&lt;/p&gt;
&lt;p&gt;Even though the level isn&amp;rsquo;t much above the limit, it&amp;rsquo;s still cause for concern. I googled those terms above to learn about them. In short, trans fat, saturated fat, and cholesterol increase LDL.&lt;/p&gt;
&lt;p&gt;After that, I acquired the superpower to see through the amount of &amp;ldquo;cholesterol&amp;rdquo; and &amp;ldquo;saturated fat&amp;rdquo; in foods. Before, I spread a big block of butter on bread. Now, plain bread just works. Before, I drank whole milk like regular milk. Now, I reduce my whole milk intake.&lt;/p&gt;</description></item><item><title>The Hardest Make 24 Puzzles</title><link>https://whhone.com/posts/the-hardest-make-24-puzzles/</link><pubDate>Fri, 25 Dec 2020 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/the-hardest-make-24-puzzles/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/24_game"&gt;Make 24&lt;/a&gt; is a traditional math game with simple rule &amp;ndash; given 4 numbers, you are asked to make 24 with addition &lt;code&gt;+&lt;/code&gt;, subtraction &lt;code&gt;-&lt;/code&gt;, multiplication &lt;code&gt;*&lt;/code&gt; and division &lt;code&gt;/&lt;/code&gt;. Each number should be used once and only once.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;(3, 7, 2, 1) --&amp;gt; 3 * (7 + 2 - 1) = 24&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Most Make 24 puzzles are easy but a few are extremely hard. This post collects some of the hardest puzzles and discuss them in 3 types. Before jumping into the solutions, let’s take a while to try these 6 puzzles.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1, 3, 9, 10)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(4, 4, 10, 10)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1, 5, 5, 5)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(3, 3, 7, 7)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(3, 3, 8, 8)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1, 4, 5, 6)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="type-1-puzzles"&gt;Type 1 Puzzles&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1, 3, 9, 10) --&amp;gt; (1 + 10) * 3 - 9
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(4, 4, 10, 10) --&amp;gt; (10 * 10 - 4) / 4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These puzzles have only one solution that are not simply multiplication like &lt;code&gt;(3 * 8)&lt;/code&gt;, &lt;code&gt;(4 * 6)&lt;/code&gt; or &lt;code&gt;(2 * 12)&lt;/code&gt;. The solutions look simple but they are hard to come up with for most people.&lt;/p&gt;
&lt;h2 id="type-2-puzzles"&gt;Type 2 Puzzles&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1, 5, 5, 5) --&amp;gt; 5 * (5 - 1 / 5)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(3, 3, 7, 7) --&amp;gt; 7 * (3 + 3 / 7)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These puzzles use division but the dividend is not divisible by the divisor. Some people might think this is violating the rule but, of course, it is not.&lt;/p&gt;
&lt;p&gt;These puzzles share a common pattern -– &lt;code&gt;(x, y, z, z)&lt;/code&gt; where &lt;code&gt;x * z + y = 24&lt;/code&gt; or &lt;code&gt;x * z - y = 24&lt;/code&gt;. The extra &lt;code&gt;z&lt;/code&gt; is canceled by placing as the divisor.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let &lt;code&gt;z = 5&lt;/code&gt; in &lt;code&gt;(1, 5, 5, 5)&lt;/code&gt;, then we have &lt;code&gt;5 * 5 - 1 = 24&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;z = 7&lt;/code&gt; in &lt;code&gt;(3, 3, 7, 7)&lt;/code&gt;, then we have &lt;code&gt;3 * 7 + 3 = 24&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That means, since &lt;code&gt;2 * 9 + 6 = 24&lt;/code&gt;, I can add a &lt;code&gt;9&lt;/code&gt; to create a puzzle in this type.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(2, 6, 9, 9) --&amp;gt; 9 * (2 + 6 / 9)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="type-3-puzzles"&gt;Type 3 Puzzles&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(3, 3, 8, 8) --&amp;gt; 8 / (3 - 8 / 3)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(1, 4, 5, 6) --&amp;gt; 4 / (1 - 5 / 6)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These puzzles are the hardest of the hardest. Similar to type 2 puzzles, they use division while the dividend is not divisible by the divisor, but they don’t have a simple pattern to spot them out easily.&lt;/p&gt;
&lt;p&gt;Fortunately, these puzzles are extremely rare. These two are the only two if all numbers are less than or equal to 10. Simply remember them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;By the way, there is one more solution for &lt;code&gt;(1, 4, 5, 6)&lt;/code&gt;. I will leave it for the clever readers.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Update on 2021-10-05&lt;/em&gt;: &lt;a href="https://www.4nums.com/game/difficulties/"&gt;https://www.4nums.com/game/difficulties/&lt;/a&gt; has ranked for all combinations by difficulties. You can find other hard puzzles there. For examples, &lt;code&gt;(1, 3, 4, 6)&lt;/code&gt;, &lt;code&gt;(3, 3, 5, 7)&lt;/code&gt;, &lt;code&gt;(2, 7, 7, 10)&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>An Org-mode Workflow for Task Management</title><link>https://whhone.com/posts/org-mode-task-management/</link><pubDate>Sat, 31 Oct 2020 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/org-mode-task-management/</guid><description>&lt;p&gt;As mentioned in the &lt;a href="https://whhone.com/posts/from-evernote-to-org-mode/"&gt;last post&lt;/a&gt;, I switched to Org-mode. I kept adjusting my workflow with this new tool and it has been stabilized for a month. I think it is time to talk about the new workflow for task/time management with Org-mode. This blog post consists of four parts: the principles, the definitions, the workflows, and finally the implementations.&lt;/p&gt;
&lt;h2 id="1-the-principles"&gt;1 The Principles&lt;/h2&gt;
&lt;p&gt;Principles remain valid no matter what the tool is.&lt;/p&gt;
&lt;h3 id="1-dot-1-do-not-add-tasks-indiscriminately"&gt;1.1 Do Not Add Tasks Indiscriminately&lt;/h3&gt;
&lt;p&gt;Not every task should go into the system. Avoid filling the system with bullshits and diluting the things that matter. I only add tasks that I really want or need to do.&lt;/p&gt;
&lt;p&gt;To clarify&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;, the task management system described below is not the &amp;ldquo;inbox&amp;rdquo; in GTD. I still capture things into my inbox but not all of them will be converted to a task in the task management system (org agenda files) eventually.&lt;/p&gt;
&lt;h3 id="1-dot-2-not-all-tasks-have-to-be-done"&gt;1.2 Not All Tasks Have To Be Done&lt;/h3&gt;
&lt;p&gt;There are two reasons for this. First, tasks could be deprioritized or even unnecessary. Second, we have limited time and cannot do everything. We should have an opinion on the priority.&lt;/p&gt;
&lt;h3 id="1-dot-3-reduce-the-number-of-open-loops"&gt;1.3 Reduce The Number Of Open Loops&lt;/h3&gt;
&lt;p&gt;Open loops are tasks that have been started but not finished. They stay in our minds and occupy some of our limited working memory so that we cannot focus on another task we are working on.&lt;/p&gt;
&lt;p&gt;Also, open loops reduce agility, according to &lt;a href="https://en.wikipedia.org/wiki/Little%27s_law"&gt;Little&amp;rsquo;s Law&lt;/a&gt;. The more the open loops, the longer time to finish each of them on average.&lt;/p&gt;
&lt;h3 id="1-dot-4-reduce-decision-making-of-what-to-do-next"&gt;1.4 Reduce Decision Making Of What To Do Next&lt;/h3&gt;
&lt;p&gt;The system should suggest to the user what to do next so that the user can reserve the will power to the real task. This also avoids skipping hard tasks with easy tasks unconsciously.&lt;/p&gt;
&lt;h2 id="2-the-definitions"&gt;2 The Definitions&lt;/h2&gt;
&lt;p&gt;Each task in Org-mode has a &lt;a href="https://orgmode.org/manual/Workflow-states.html"&gt;TODO keyword&lt;/a&gt;, optionally &lt;a href="https://orgmode.org/manual/Deadlines-and-Scheduling.html"&gt;a scheduled date, and a deadline&lt;/a&gt;. For example,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-org" data-lang="org"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;*&lt;/span&gt;&lt;span class="gs"&gt; PROG Write a blog post on task management with Org-mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DEADLINE: &amp;lt;&lt;span class="s"&gt;2020-11-07 Sat&lt;/span&gt;&amp;gt; SCHEDULED: &amp;lt;&lt;span class="s"&gt;2020-10-31 Sat&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each Org-mode user could define their own set of TODO keywords and use scheduled dates and deadlines differently. For example, some people use only two TODO keywords, &amp;ldquo;TODO&amp;rdquo; and &amp;ldquo;DONE&amp;rdquo;, while some use more. Some people set &amp;ldquo;scheduled dates&amp;rdquo; to all the tasks while some people set it to some of the tasks. These nuances could result in a hugely different workflow, although they are using the same Org-mode. Let’s take a look at how I use them.&lt;/p&gt;
&lt;h3 id="2-dot-1-todo-keywords"&gt;2.1 TODO Keywords&lt;/h3&gt;
&lt;p&gt;I use as few TODO keywords as possible but not too few. For example, it is common to use only two states (&amp;ldquo;TODO&amp;rdquo; and &amp;ldquo;DONE&amp;rdquo;) but this does not align with the principles I mentioned above. I need a state for &amp;ldquo;open loops&amp;rdquo; so that I can keep the number of them small. I also need to distinguish a smaller set of &amp;ldquo;next actions&amp;rdquo; from all tasks.&lt;/p&gt;
&lt;p&gt;So far, I defined these five keywords:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TODO Keyword&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TODO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Tasks that are not started and not planned.&lt;/strong&gt; They could be the backlogs or the GTD’s someday/maybe. These tasks could be converted to &lt;code&gt;NEXT&lt;/code&gt; during a review.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NEXT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Tasks that are not started but planned to do as soon as I can.&lt;/strong&gt; When there is no actionable &lt;code&gt;PROG&lt;/code&gt; (e.g., blocked), I start one of those and convert it to &lt;code&gt;PROG&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Tasks that are working in progress (open loops).&lt;/strong&gt; I work on these tasks before starting another &lt;code&gt;NEXT&lt;/code&gt; task to avoid too many open loops at any moment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INTR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Tasks that are interruptions.&lt;/strong&gt; They are urgent things that I should drop everything else and work on it. For example, production issues.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The tasks that are completed.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This diagram illustrates the transition of those states.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-nil" data-lang="nil"&gt; +------+
| INTR |
+------+
|
v
+------+ +------+ +------+ +------+
| TODO |--&amp;gt;| NEXT |--&amp;gt;| PROG |--&amp;gt;| DONE |
+------+ +------+ +------+ +------+
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2-dot-2-scheduled-and-deadline"&gt;2.2 Scheduled and Deadline&lt;/h3&gt;
&lt;p&gt;In the past, I tended to set a date for all tasks. If I want to do A, B, and C on Monday, then I schedule them for Monday. This sounds intuitive but, in reality, I ended up rescheduling many incompleted tasks at the end of every day. It was not only wasting time but also depressing.&lt;/p&gt;
&lt;p&gt;Later, I changed to rely more on the TODO keywords. For example, if a task is still in progress, I keep the state unchanged as &lt;code&gt;PROG&lt;/code&gt; instead of rescheduling it every day until it is done. I am now using the &amp;ldquo;scheduled date&amp;rdquo; to hide a task until the date I should look at it again. Similar to the snooze feature in Gmail.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SCHEDULED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hide the task until the scheduled date.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEADLINE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The deadline of the task.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For example, when a &lt;code&gt;PROG&lt;/code&gt; task is being blocked, I set the &lt;code&gt;SCHEDULED&lt;/code&gt; date to hide it until the date I want to revisit. On the scheduled date, if the task is unblocked, I will remove the &lt;code&gt;SCHEDULED&lt;/code&gt; date. If the task is still blocked, I reschedule it again. It acts as the &lt;a href="https://hamberg.no/gtd#the-waiting-for-list"&gt;waiting for list&lt;/a&gt; in GTD.&lt;/p&gt;
&lt;h2 id="3-the-workflow"&gt;3 The Workflow&lt;/h2&gt;
&lt;p&gt;I customize my org agenda view to drive my daily workflow. The customized agenda view has four sections. From the top to bottom, they are the tasks scheduled today, the &lt;code&gt;INTR&lt;/code&gt; tasks, the &lt;code&gt;PROG&lt;/code&gt; tasks, and finally the &lt;code&gt;NEXT&lt;/code&gt; tasks.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/org-agenda.png"&gt;
&lt;/figure&gt;
&lt;p&gt;My daily workflow goes from the top to the bottom.&lt;/p&gt;
&lt;h3 id="3-dot-1-update-tasks-scheduled-today"&gt;3.1 Update Tasks Scheduled Today&lt;/h3&gt;
&lt;p&gt;At the beginning of the day, I review the tasks that are scheduled for today. The goal here is not to finish them, but to update or remove the scheduled date so that there is nothing left.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the task is still blocked, reschedule it&lt;/li&gt;
&lt;li&gt;If the task could be done in a few minutes, then do it and mark it as &lt;code&gt;DONE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Otherwise, remove the scheduled date and optionally update the &lt;code&gt;TODO&lt;/code&gt; keywords.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Removing the scheduled date is the best outcome. It indicates the previous estimation was correct, at least not too early. Rescheduling indicates the previous estimation is inaccurate. I would avoid rescheduling the task to tomorrow indiscriminately and try to make a good estimation to reduce the number of rescheduling.&lt;/p&gt;
&lt;h3 id="3-dot-2-find-the-next-task-to-work-on"&gt;3.2 Find the Next Task to Work On&lt;/h3&gt;
&lt;p&gt;After reviewing all tasks scheduled for today, it is time to pick a task and do some real works. This step is straight-forward with the customized agenda view above.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pick an &lt;code&gt;INTR&lt;/code&gt; task if there is any.&lt;/li&gt;
&lt;li&gt;If there is no &lt;code&gt;INTR&lt;/code&gt; task, then pick a &lt;code&gt;PROG&lt;/code&gt; task and work on it. If that task is blocked, set a &lt;code&gt;SCHEDULED&lt;/code&gt; date to hide it.&lt;/li&gt;
&lt;li&gt;If there is no &lt;code&gt;INTR&lt;/code&gt; and &lt;code&gt;PROG&lt;/code&gt; task, then start a &lt;code&gt;NEXT&lt;/code&gt; task.&lt;/li&gt;
&lt;li&gt;If there is no task in the agenda view, then review the &lt;code&gt;TODO&lt;/code&gt; tasks and convert some to &lt;code&gt;NEXT&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="3-dot-3-review-the-system"&gt;3.3 Review the System&lt;/h3&gt;
&lt;p&gt;The secret of having a system that works in the long-term is regular maintenance. I do it at least once a week. For examples,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Promote some tasks from &lt;code&gt;TODO&lt;/code&gt; to &lt;code&gt;NEXT&lt;/code&gt;. Demote or even delete deprioritized tasks.&lt;/li&gt;
&lt;li&gt;Review the &lt;a href="https://whhone.com/posts/daily-journal/"&gt;journal&lt;/a&gt; and add &lt;code&gt;TODO&lt;/code&gt; if something needs follow-up.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orgmode.org/manual/Archiving.html"&gt;Archive&lt;/a&gt; completed tasks and extract to permanent notes&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-the-configuration"&gt;4 The Configuration&lt;/h2&gt;
&lt;p&gt;Finally, here is the configuration for the above workflow.&lt;/p&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="c1"&gt;;; TODO keywords.&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-todo-keywords&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="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;NEXT(n)&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;PROG(p)&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;INTR(i)&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; Show the daily agenda by default.&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-agenda-span&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;day&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="c1"&gt;;; Hide tasks that are scheduled in the future.&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-agenda-todo-ignore-scheduled&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;future&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="c1"&gt;;; Use &amp;#34;second&amp;#34; instead of &amp;#34;day&amp;#34; for time comparison.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; It hides tasks with a scheduled time like &amp;#34;&amp;lt;2020-11-15 Sun 11:30&amp;gt;&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 class="nb"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;org-agenda-todo-ignore-time-comparison-use-seconds&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;;; Hide the deadline prewarning prior to scheduled date.&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-agenda-skip-deadline-prewarning-if-scheduled&lt;/span&gt; &lt;span class="ss"&gt;&amp;#39;pre-scheduled&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="c1"&gt;;; Customized view for the daily workflow. (Command: &amp;#34;C-c a n&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 class="nb"&gt;setq&lt;/span&gt; &lt;span class="nv"&gt;org-agenda-custom-commands&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;n&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;Agenda / INTR / PROG / NEXT&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 class="nv"&gt;agenda&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="no"&gt;nil&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;todo&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;INTR&amp;#34;&lt;/span&gt; &lt;span class="no"&gt;nil&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;todo&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;PROG&amp;#34;&lt;/span&gt; &lt;span class="no"&gt;nil&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;todo&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;NEXT&amp;#34;&lt;/span&gt; &lt;span class="no"&gt;nil&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="no"&gt;nil&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 class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Thanks for &lt;a href="https://www.reddit.com/r/orgmode/comments/jmf8dw/an_orgmode_workflow_for_task_management/gavkv1r/?context=3"&gt;this comment&lt;/a&gt; in Reddit.&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;li id="fn:2"&gt;
&lt;p&gt;There will be another post for Org-mode note-taking workflow.&amp;#160;&lt;a href="#fnref:2" 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>From Evernote to Org-mode</title><link>https://whhone.com/posts/from-evernote-to-org-mode/</link><pubDate>Thu, 03 Sep 2020 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/from-evernote-to-org-mode/</guid><description>&lt;p&gt;Since 2017, Evernote has been my &lt;a href="https://whhone.com/posts/productivity-system/"&gt;productivity system&lt;/a&gt; for note-taking and task management. I thought it was my ultimate system to stick with. It has served me so well and I have even became a paid user. However, mentioned in the &lt;a href="https://whhone.com/posts/arch-linux-full-disk-encryption/"&gt;last post&lt;/a&gt;, I am pursuing more privacy in my digital life. &lt;strong&gt;I want to take my notes with a peace of mind, without worrying they are being watched or leaked.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This post talks about the privacy concern, my research on the alternatives, and the switch to Org-mode.&lt;/p&gt;
&lt;h2 id="1-dot-the-privacy-concern-of-evernote"&gt;1. The Privacy Concern of Evernote&lt;/h2&gt;
&lt;p&gt;The user data in Evernote is &lt;a href="https://evernote.com/security"&gt;encrypted &amp;ldquo;during transport&amp;rdquo; and &amp;ldquo;at-rest&amp;rdquo;&lt;/a&gt;. This sounds secure to readers but actually not enough because Evernote can still see the content. Technically, it is still server-side encryption instead the client-side encryption (a.k.a. end-to-end encryption). The encryption key of the note content is held by the server instead of the client. If the server is hacked, or there is an evil employee, or Evernote needs to comply with legal obligations (i.e., the government asks for user data, and &lt;a href="https://evernote.com/privacy/transparency-report"&gt;it does&lt;/a&gt;), those notes could be leaked.&lt;/p&gt;
&lt;h2 id="2-dot-researching-the-alternatives"&gt;2. Researching the Alternatives&lt;/h2&gt;
&lt;p&gt;I started researching the alternatives which do not compromise privacy. That means end-to-end encryption is required if the data is hosted on a third-party. This requirement actually simplifies my curation because many solutions are ruled out, including the popular Notion and Roam (o:&lt;/p&gt;
&lt;p&gt;My final shortlist included &lt;a href="https://orgmode.org/"&gt;&lt;strong&gt;Org-mode&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://joplinapp.org/"&gt;&lt;strong&gt;Joplin&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://standardnotes.org/"&gt;&lt;strong&gt;Standard Note&lt;/strong&gt;&lt;/a&gt;, &lt;a href="https://taskwarrior.org/"&gt;&lt;strong&gt;Taskwarrior&lt;/strong&gt;&lt;/a&gt;, &lt;a href="http://todotxt.org/"&gt;&lt;strong&gt;TODO.txt&lt;/strong&gt;&lt;/a&gt;, and &lt;a href="https://tiddlywiki.com/"&gt;&lt;strong&gt;TiddlyWiki&lt;/strong&gt;&lt;/a&gt;. I gave each of them a try, reviewed and scored them with my requirements below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Privacy&lt;/strong&gt; Can I seclude my data?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Task Management&lt;/strong&gt;: Can I set a date for a task and then view all tasks for today?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task Context&lt;/strong&gt;: Can I add references (instruction, link) to the task?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux Usability&lt;/strong&gt;: How usable is it on Linux?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Android Usability&lt;/strong&gt;: How usable is it on Android?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synchronization&lt;/strong&gt;: How hard is it to synchronize data between Linux and Android?&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Evernote&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Org-mode&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Joplin&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard Note&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Taskwarrior&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TODO.txt&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TiddlyWiki&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Privacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Task Management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Task Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux Usability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Android Usability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Synchronization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="review-evernote--24"&gt;Review: Evernote (24)&lt;/h3&gt;
&lt;p&gt;Evernote has a reminder feature for simple task management, which is already enough for me. Since it is a note-taking app, I can add rich context like images or even audio seamlessly. Tusk makes it usabe on Linux and the official Android app is simply awesome. Synchronization works out-of-the-box.&lt;/p&gt;
&lt;p&gt;However, it fails on the privacy bit.&lt;/p&gt;
&lt;h3 id="review-org-mode--26"&gt;Review: Org-mode (26)&lt;/h3&gt;
&lt;p&gt;Org-mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system. It is absolutely capable.&lt;/p&gt;
&lt;p&gt;However, the learning curve of Org-mode is steep and even steeper for non-Emacs users. Users are expected to read documentation and practice. Once familiarized with Emacs, its usability on Linux will jump from 0 to 5 (or even infinity). The Android client Orgzly is usable but could be better. Synchronization works with SyncThing.&lt;/p&gt;
&lt;h3 id="review-joplin--22--and-standard-note--24"&gt;Review: Joplin (22) and Standard Note (24)&lt;/h3&gt;
&lt;p&gt;Joplin and Standard Note are both strong note-taking alternatives to Evernote. However, they suck in task management because they simply cannot show all tasks scheduled for today. They both have a decent desktop app written in Electron. Synchronization works natively and users can turn on end-to-end encryption for privacy/security.&lt;/p&gt;
&lt;p&gt;If task management is not a requirement, both Joplin and Standard Note are very good alternatives.&lt;/p&gt;
&lt;h3 id="review-taskwarrior--21--and-todo-dot-txt--20"&gt;Review: Taskwarrior (21) and TODO.txt (20)&lt;/h3&gt;
&lt;p&gt;Taskwarrior and TODO.txt are both FOSS for task management. However, they both allow only one line per task, which makes adding extra context to the task very hard. Their clients are barely usable to me (or I have not learned enough). Secure synchronization works with a self-hosted server or with SyncThing for Taskwarrior and TODO.txt respectively.&lt;/p&gt;
&lt;h3 id="review-tiddlywiki"&gt;Review: TiddlyWiki (?)&lt;/h3&gt;
&lt;p&gt;TiddlyWiki is a &amp;ldquo;non-linear personal web notebook&amp;rdquo;. It looks fancy but I haven&amp;rsquo;t tried it intensively because Org-mode seems working for me already at this point. I will leave it as a question mark.&lt;/p&gt;
&lt;h2 id="3-dot-switching-to-org-mode"&gt;3. Switching to Org-mode&lt;/h2&gt;
&lt;p&gt;After all these explorations, Org-mode looks promising to me and I started the Org-mode/Emacs 30-day challenge &amp;ndash; keep using Org-mode/Emacs for a month. Note that I was still using Evernote during the transition but gradually reducing the dependency on it.&lt;/p&gt;
&lt;p&gt;I have now finished the challenge and can claim myself an Org-mode user. I am happy with Org-mode and the new workflow. Since the org files are in plain text, I manage my notes with Git, review, and polish all the changes before committing it. It helps to improve the quality of my notes!&lt;/p&gt;
&lt;p&gt;Beyond Org-mode, I also started seeing the beauty of Emacs and why people use it instead of Vim or other modern editors. Emacs is ostensibly an editor but actually a powerful Lisp platform/runtime, which makes it super extensible and capable. Users can customize Emacs and make it their own systems for their own purposes.&lt;/p&gt;
&lt;p&gt;I am now using Emacs even more. For example, I replace Visual Studio Code, my previous editor/git porcelain, with &lt;a href="https://magit.vc/"&gt;Magit&lt;/a&gt;. This blog post is written in Emacs with &lt;a href="https://ox-hugo.scripter.co/"&gt;ox-hugo&lt;/a&gt;. I probably will blog on these topics sometime in the future.&lt;/p&gt;
&lt;p&gt;All in all, I am very happy with the privacy gain and the learning from this switch!&lt;/p&gt;</description></item><item><title>Installing Arch Linux with Full Disk Encryption</title><link>https://whhone.com/posts/arch-linux-full-disk-encryption/</link><pubDate>Wed, 29 Jul 2020 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/arch-linux-full-disk-encryption/</guid><description>&lt;p&gt;&lt;strong&gt;Updates&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2022-12-17: The official &lt;a href="https://wiki.archlinux.org/title/archinstall"&gt;&lt;code&gt;archinstall&lt;/code&gt;&lt;/a&gt; script greatly simplify the Arch Linux installation, even with full disk encryption. Most steps in this guide (except 18 unlock automatically) are automated by that script. Check it out!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;I recently re-installed my Arch Linux with &lt;a href="https://en.wikipedia.org/wiki/Disk_encryption"&gt;full disk encryption (FDE)&lt;/a&gt;,
as one of the first steps, to bring &lt;a href="https://www.ted.com/talks/glenn_greenwald_why_privacy_matters"&gt;privacy&lt;/a&gt; into my life. This guide
documents the installation process in a step-by-step manner. I hope
this can help people who also want to practice privacy.&lt;/p&gt;
&lt;p&gt;Each step in this guide are linked to the corresponding ArchWiki,
precised to section level. You are suggested to read these references
and, of course, the &lt;a href="https://wiki.archlinux.org/index.php/installation_guide"&gt;official installation guide&lt;/a&gt; because&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it is better to understand the process instead of blindly
copy-and-pase, and&lt;/li&gt;
&lt;li&gt;I had omitted some uncommon steps that you might need (e.g., non US
locale and keyboard layout)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide uses modern options like:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Options&lt;/th&gt;
&lt;th&gt;This Setup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disk Encryption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes, No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firmware&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BIOS, UEFI&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;UEFI&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disk Partition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MBR, GPT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;GPT&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Boot Loader&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GRUB, Syslinux, systemd-boot, etc&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;systemd-boot&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In addition, I have verified this guide twice by installing on both
hard disk and virtual machine respectively. I think it is
reproducible.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start!&lt;/p&gt;
&lt;h2 id="1-dot-verify-the-boot-mode--ref"&gt;1. Verify the boot mode (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Verify_the_boot_mode"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;This guide assumes we use UEFI. You must ensure that the system is
booted in UEFI mode. To verify the boot mode, list the efivars
directory:&lt;/p&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;# ls /sys/firmware/efi/efivars
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the command shows the directory without error, then the system is
booted in UEFI mode.&lt;/p&gt;
&lt;h2 id="2-dot-update-the-system-clock--ref"&gt;2. Update the system clock (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Update_the_system_clock"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;timedatectl&lt;/code&gt; to ensure the system clock is accurate:&lt;/p&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;# timedatectl set-ntp true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="3-dot-partition-the-disks--ref-gpt-fdisk"&gt;3. Partition the disks (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Partition_the_disks"&gt;ref&lt;/a&gt;, &lt;a href="https://wiki.archlinux.org/index.php/GPT_fdisk#Create_a_partition_table_and_partitions"&gt;GPT fdisk&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;The final disk layout from this guide contains two partitions,&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Partition&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boot (/dev/sda1)&lt;/td&gt;
&lt;td&gt;512.0 MiB&lt;/td&gt;
&lt;td&gt;EF00&lt;/td&gt;
&lt;td&gt;EFI system partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root (/dev/sda2)&lt;/td&gt;
&lt;td&gt;Rest of the disk&lt;/td&gt;
&lt;td&gt;8300 (default of gdisk)&lt;/td&gt;
&lt;td&gt;Linux filesystem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Traditionally, it is suggested to create an extra swap partition. I
don&amp;rsquo;t because there are more flexible alternatives over allocating a
fixed partition for swap. For example, uses &lt;a href="https://wiki.archlinux.org/index.php/swap#Swap_file"&gt;swap file&lt;/a&gt;, or &lt;a href="https://wiki.archlinux.org/index.php/swap#systemd-swap"&gt;systemd-swap&lt;/a&gt;
to automate the swap file on demand.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;gdisk&lt;/code&gt; to partition the disk. See this &lt;a href="https://whhone.com/ox-hugo/gdisk.mp4"&gt;video&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When completed, &lt;code&gt;gdisk -l /dev/sda&lt;/code&gt; should print the disk partitions
like these:&lt;/p&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;GPT fdisk (gdisk) version 1.0.5
&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;Partition table scan:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; MBR: protective
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; BSD: not present
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; APM: not present
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; GPT: present
&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;Found valid GPT with protective MBR; using GPT.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Disk /dev/sda: 41943040 sectors, 20.0 GiB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Model: VBOX HARDDISK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sector size (logical/physical): 512/512 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Disk identifier (GUID): 8EFD04A2-473C-4FCA-9C89-459EEB658DB0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Partition table holds up to 128 entries
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Main partition table begins at sector 2 and ends at sector 33
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;First usable sector is 34, last usable sector is 41943006
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Partitions will be aligned on 2048-sector boundaries
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Total free space is 2014 sectors (1007.0 KiB)
&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;Number Start (sector) End (sector) Size Code Name
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1 2048 1050623 512.0 MiB EF00 EFI system partition
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2 1050624 41943006 19.5 GiB 8300 Linux filesystem
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="4-dot-prepare-the-encrypted-root-partition--ref"&gt;4. Prepare the encrypted root partition (&lt;a href="https://wiki.archlinux.org/index.php/Dm-crypt/Encrypting_an_entire_system#LUKS_on_a_partition"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Create and mount the encrypted root partition. You will need to choose
the passphrase for the encryption!&lt;/p&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;# cryptsetup luksFormat /dev/sda2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# cryptsetup open /dev/sda2 cryptroot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# mkfs.ext4 /dev/mapper/cryptroot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# mount /dev/mapper/cryptroot /mnt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="5-dot-prepare-the-boot-partition--ref"&gt;5. Prepare the boot partition (&lt;a href="https://wiki.archlinux.org/index.php/Dm-crypt/Encrypting_an_entire_system#Preparing_the_boot_partition"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Create and mount the non-encrypted boot partition.&lt;/p&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;# mkfs.fat -F32 /dev/sda1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# mkdir /mnt/boot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# mount /dev/sda1 /mnt/boot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="6-dot-generate-an-fstab-file--ref"&gt;6. Generate an fstab file (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Fstab"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Run:&lt;/p&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;# genfstab -U /mnt &amp;gt;&amp;gt; /mnt/etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="7-dot-install-essential-packages--ref"&gt;7. Install essential packages (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Install_essential_packages"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Use the &lt;code&gt;pacstrap&lt;/code&gt; script to install these packages. I added &lt;code&gt;vim&lt;/code&gt; for
editing config files and &lt;code&gt;dhcpcd&lt;/code&gt; for connecting to the Internet after
reboot.&lt;/p&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;# pacstrap /mnt base linux linux-firmware vim dhcpcd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="8-dot-chroot--ref"&gt;8. Chroot (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Chroot"&gt;ref&lt;/a&gt;)&lt;/h2&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;# arch-chroot /mnt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="9-dot-time-zone--ref"&gt;9. Time zone (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Time_zone"&gt;ref&lt;/a&gt;)&lt;/h2&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;# ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# hwclock --systohc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="10-dot-localization--ref"&gt;10. Localization (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Localization"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/locale.gen&lt;/code&gt; and uncomment &lt;code&gt;en_US.UTF-8 UTF-8&lt;/code&gt; and other
needed locales. Generate the locales by running:&lt;/p&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;# locale-gen
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# localectl set-locale LANG=en_US.UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="11-dot-network-configuration--ref"&gt;11. Network configuration (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Fstab"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Add the &lt;code&gt;/etc/hosts&lt;/code&gt;:&lt;/p&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;127.0.0.1 localhost
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;::1 localhost
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="12-dot-configuring-mkinitcpio--ref"&gt;12. Configuring mkinitcpio (&lt;a href="https://wiki.archlinux.org/index.php/Dm-crypt/Encrypting_an_entire_system#Configuring_mkinitcpio"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/mkinitcpio.conf&lt;/code&gt;, - add the &lt;code&gt;encrypt&lt;/code&gt; hooks - move the
&lt;code&gt;keyboard&lt;/code&gt; hooks before &lt;code&gt;encrypt&lt;/code&gt; ( so that you can type the
passphrase :p )&lt;/p&gt;
&lt;p&gt;For example, after this step, &lt;code&gt;HOOKS&lt;/code&gt; should look like:&lt;/p&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;HOOKS=(base udev autodetect modconf block keyboard encrypt filesystems fsck)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="13-dot-generate-the-initramfs--ref"&gt;13. Generate the initramfs (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Initramfs"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Since we have changed to &lt;code&gt;/etc/mkinitcpio.conf&lt;/code&gt; manually, we have to
re-generates the boot images (e.g., &lt;code&gt;/boot/initramfs-linux.img&lt;/code&gt;). Run
this command:&lt;/p&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;# mkinitcpio -P
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="14-dot-set-the-root-password--ref"&gt;14. Set the root password (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Root_password"&gt;ref&lt;/a&gt;)&lt;/h2&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;# passwd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="15-dot-patch-the-cpu-s-microcode--ref"&gt;15. Patch the CPU&amp;rsquo;s microcode (&lt;a href="https://wiki.archlinux.org/index.php/Microcode"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For AMD processors, install the &lt;code&gt;amd-ucode&lt;/code&gt; package.&lt;/li&gt;
&lt;li&gt;For Intel processors, install the &lt;code&gt;intel-ucode&lt;/code&gt; package.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For exmaple, run&lt;/p&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;# pacman -S intel-ucode
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="16-dot-configure-the-boot-loader-with-systemd-boot--ref-systemd-boot"&gt;16. Configure the Boot Loader with &lt;code&gt;systemd-boot&lt;/code&gt; (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Boot_loader"&gt;ref&lt;/a&gt;, &lt;a href="https://wiki.archlinux.org/index.php/Systemd-boot"&gt;systemd-boot&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;16.1. Install the EFI boot manager&lt;/strong&gt;&lt;/p&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;# bootctl install
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;16.2. Create &lt;code&gt;/boot/loader/entries/arch.conf&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;intel-ucode.img&lt;/code&gt; with &lt;code&gt;amd-ucode.img&lt;/code&gt; if you have an AMD CPU&lt;/li&gt;
&lt;li&gt;Replace the &lt;code&gt;UUID&lt;/code&gt; (not &lt;code&gt;PARTUUID&lt;/code&gt;) to the one mapping to &lt;code&gt;/dev/sda2&lt;/code&gt;
(Run &lt;code&gt;blkid&lt;/code&gt; to find out)&lt;/li&gt;
&lt;/ul&gt;
&lt;!--listend--&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;title Arch Linux
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;linux /vmlinuz-linux
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;initrd /intel-ucode.img
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;initrd /initramfs-linux.img
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;options cryptdevice=UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:cryptroot root=/dev/mapper/cryptroot rw
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;16.3. Replace &lt;code&gt;/boot/loader/loader.conf&lt;/code&gt; to&lt;/strong&gt;&lt;/p&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;default arch.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;timeout 5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;console-mode max
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;editor no
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;16.4. Review the configuration&lt;/strong&gt;&lt;/p&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;# bootctl list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Boot Loader Entries:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; title: Arch Linux (default)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; id: arch.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; source: /boot/loader/entries/arch.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; linux: /vmlinuz-linux
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; initrd: /intel-ucode.img
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /initramfs-linux.img
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; options: cryptdevice=UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:cryptroot root=/dev/mapper/cryptroot rw
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="17-dot-reboot--ref"&gt;17. Reboot (&lt;a href="https://wiki.archlinux.org/index.php/installation_guide#Reboot"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Exit the chroot environment by typing &lt;code&gt;exit&lt;/code&gt; or pressing &lt;code&gt;Ctrl+d&lt;/code&gt;. Then
run &lt;code&gt;reboot&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If everything works, it should ask for a password to access the
cryptroot, like this screenshot below:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/passphrase.png"&gt;
&lt;/figure&gt;
&lt;p&gt;One last thing, if you computer, like mine, is sitting behind a DHCP
(e.g., a typical router), you will need to enable &lt;code&gt;dhcpcd&lt;/code&gt; to access the
Internet. Run,&lt;/p&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;# systemctl enable dhcpcd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Congratulations! You have installed Arch Linux with Full Disk
Encryption!&lt;/p&gt;
&lt;h2 id="18-dot--optional--unlocking-the-root-partition-at-boot--ref"&gt;18. (Optional) Unlocking the Root Partition at boot (&lt;a href="https://wiki.archlinux.org/title/dm-crypt/Device_encryption#Unlocking_the_root_partition_at_boot"&gt;ref&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;The above setup requires manual interruption on each reboot by entering
the password to unlock the disk. There are ways to avoid at the expense
of reducing the security level because the decryption key is exposed.&lt;/p&gt;
&lt;p&gt;The one I use is to include the decryption key to the _{initram-fs.img}
so that the systemd module &lt;code&gt;encrypt&lt;/code&gt; can use it to decrypt the root
partition automatically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;adds a key file to &lt;code&gt;/crypto_keyfile.bin&lt;/code&gt;, the default location read
by the &lt;code&gt;encrypt&lt;/code&gt; module used above.&lt;/li&gt;
&lt;li&gt;adds the &lt;code&gt;/crypto_keyfile.bin&lt;/code&gt; to &lt;code&gt;FILES=()&lt;/code&gt; array in
&lt;code&gt;/etc/mkinitcpio.conf&lt;/code&gt;, so that this file is copied to the image
(e.g., _{initram-fs.img}).&lt;/li&gt;
&lt;li&gt;regenerates the images with &lt;code&gt;mkinitcpio -P&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Switching Windows in i3 Window Manager</title><link>https://whhone.com/posts/i3-switching-windows/</link><pubDate>Sun, 21 Jun 2020 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/i3-switching-windows/</guid><description>&lt;p&gt;3 years ago, I switched from Mac OS back to Linux. The main reason was
&lt;a href="https://i3wm.org/"&gt;i3wm&lt;/a&gt; &amp;mdash; a tiling window manager for Linux &amp;mdash; and its way to switch
between windows.&lt;/p&gt;
&lt;p&gt;In this post, I am going to talk about how to switch windows on i3.
But before that, let&amp;rsquo;s take a look at something we all know &amp;mdash;
&lt;code&gt;Alt+Tab&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-ergonomics-problem-with-alt-plus-tab"&gt;The Ergonomics Problem with Alt+Tab&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Alt-Tab"&gt;&lt;code&gt;Alt+Tab&lt;/code&gt;&lt;/a&gt; (or &lt;code&gt;Command+Tab&lt;/code&gt; on Mac) is the most typical way to switch
windows. It shows the list of opened windows, then the user presses
&lt;code&gt;Tab&lt;/code&gt; repeatedly to go to the next window, and release all keys to
select.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/alt_tab.gif"&gt;
&lt;/figure&gt;
&lt;p&gt;These &amp;ldquo;find&amp;quot;s are distractions. Users need to switch context off from
the real task, find the other window, switch context back to the real
tasks. These &amp;ldquo;find&amp;quot;s could happen over a hundred times a day and they
become more complex when the number of opened windows goes up.&lt;/p&gt;
&lt;p&gt;For example, when coding, I switch between 4 windows, the IDE, the
terminal, the browser, and the note app very often. Switching window
with &lt;code&gt;Alt+Tab&lt;/code&gt; could drain my mental energy surreptitiously. These
four are just the minimum. In reality, the number could be double or
triple, as there are multiple browser windows (not to mention the tabs
inside&amp;hellip;), and other random applications.&lt;/p&gt;
&lt;p&gt;When I first met i3, it blew my mind with its alternatives to switch
windows. Here are three ways I like,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Scratchpad&lt;/strong&gt;: Switch focus directly, without finding&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Move&lt;/strong&gt;: Move to an adjacent window&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Match&lt;/strong&gt;: Move to a window by string matching&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="1-dot-scratchpad-switch-to-my-favorites"&gt;1. Scratchpad &amp;ndash; Switch to My Favorites&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;As the name indicates, this is useful for having a window with your
favorite editor always at hand.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;i3 User&amp;rsquo;s Guide&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first alternative is &lt;a href="https://i3wm.org/docs/userguide.html#_scratchpad"&gt;Scratchpad&lt;/a&gt;. It summons the targeted window
directly, without finding it from the window list.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/i3-scratchpad.gif"&gt;
&lt;/figure&gt;
&lt;p&gt;In the above screencast, &lt;code&gt;Super+k&lt;/code&gt; focuses to the editor. &lt;code&gt;Super+Enter&lt;/code&gt;
focuses to the terminal. There is no finding in the whole flow, although
I have more than 10 opened windows.&lt;/p&gt;
&lt;p&gt;It works by putting the application to the background and focus to it
when a user-defined hot-key is pressed. &amp;ldquo;Summoning&amp;rdquo; requires much less
mental energy than &amp;ldquo;finding&amp;rdquo; when the hot-key becomes muscle memory.&lt;/p&gt;
&lt;p&gt;The configuration is well-documented in &lt;a href="https://i3wm.org/docs/userguide.html#command_criteria"&gt;the user guide&lt;/a&gt;. Here is the
config for the above screencast:&lt;/p&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;# Make the currently focused window a scratchpad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym $mod+Shift+minus move scratchpad
&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;# Show the first scratchpad window
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym $mod+minus scratchpad show
&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;# Show the terminal with Super+Enter
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym $mod+Return [class=&amp;#34;Terminator&amp;#34;] scratchpad show
&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;# Show the code editor with Super+k
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bindsym $mod+k [class=&amp;#34;code-oss&amp;#34;] scratchpad show
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These window properties could find by &lt;code&gt;xprop&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="2-dot-move-switch-by-what-i-see"&gt;2. Move &amp;ndash; Switch by What I See&lt;/h2&gt;
&lt;p&gt;The second alternative is moving to an adjacent window. In my opinion,
it is more visually-intuitive and predictable than the &lt;code&gt;Alt+Tab&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/i3-match.gif"&gt;
&lt;/figure&gt;
&lt;p&gt;Moving to an adjacent window is the default mechanism of window
switching, for most of the tiling window managers. I am not going to
belabor here.&lt;/p&gt;
&lt;h2 id="3-dot-match-switch-by-what-i-know"&gt;3. Match &amp;ndash; Switch by What I Know&lt;/h2&gt;
&lt;p&gt;The last one in this post is matching. It matches and narrows down the
list of windows.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/i3-match.gif"&gt;
&lt;/figure&gt;
&lt;p&gt;In the above screencast, Super+g shows the list of all windows and I
search for gedit by typing gedit.&lt;/p&gt;
&lt;p&gt;It is powered by &lt;a href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;, a window switcher, and application launcher,
similar to &lt;a href="https://www.alfredapp.com/"&gt;Alfred for Mac OS&lt;/a&gt;. Here is the one-line configuration.&lt;/p&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;bindsym $mod+g exec &amp;#34;rofi -show window&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scratchpad&lt;/strong&gt; has constant complexity &amp;mdash; O(1) &amp;mdash; no matter how much
windows are there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Move&lt;/strong&gt; is intuitive and predictable way to switch to what I can see.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Match&lt;/strong&gt; is especially useful to go to a particular window in another
workspace.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Among all three, &lt;strong&gt;Scratchpad&lt;/strong&gt; is the one I love most.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Credit: The GIFs in this post are created with &lt;a href="https://github.com/phw/peek"&gt;peek&lt;/a&gt; and &lt;a href="https://www.thregr.org/~wavexx/software/screenkey/"&gt;screenkey&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>From Hugo to WordPress</title><link>https://whhone.com/posts/hugo-to-wordpress/</link><pubDate>Mon, 06 Apr 2020 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/hugo-to-wordpress/</guid><description>&lt;p&gt;&lt;strong&gt;Update 2020-10&lt;/strong&gt;: I am going back to Hugo and writing blog post with Emacs+Org-Mode+ox-hugo.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I have been blogging with &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;, a static site generator, for 5 months.
Recently, I decided to ditch it and migrate to &lt;a href="https://wordpress.org/"&gt;WordPress.Org&lt;/a&gt; for a
more writer-friendly workflow. I want to spend more time and efforts
on writing instead of coding.&lt;/p&gt;
&lt;h2 id="the-developer-writer-dilemma"&gt;The Developer-Writer Dilemma&lt;/h2&gt;
&lt;p&gt;Hugo is developer-friendly. It allows me to customize my site, in
terms of the theme, layout, the content structure and also the &lt;a href="https://gohugo.io/content-management/shortcodes/"&gt;Hugo
Shortcodes&lt;/a&gt; very easily. I learned a lot when building the static
site, the JAM stack concept, SCSS, HTML5 semantic tags, &lt;a href="https://ogp.me/"&gt;Opengraph&lt;/a&gt;,
&lt;a href="https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started"&gt;Twitter Card&lt;/a&gt;, et cetera. I like how simple it is to bring up the site,
without setting up the LAMP or LEMP stack.&lt;/p&gt;
&lt;p&gt;WordPress is writer-friendly. It allows me to write anytime, anywhere.
I can write posts from my computer or my phone. Its mobile app even
works offline, without an internet connection. This is a gamer changer
to me because I can spend my time writing while holding my little son
Casper to sleep. Editing with the new &lt;a href="https://wordpress.org/gutenberg/"&gt;WordPress Block Editor
(Gutenberg)&lt;/a&gt;, shipped with WordPress 5.0, is much efficient, when
compared to editing Markdown.&lt;/p&gt;
&lt;p&gt;Developer and writer are both the identities I assigned to myself.
This creates a dilemma. When I think about the customization, being
able to build my site, Hugo gets me. When I think about the writing
experience, being able to write whenever and wherever I am, and focus
on the sentences instead of the Markdown syntax, WordPress gets me.&lt;/p&gt;
&lt;p&gt;Then, I asked myself &amp;mdash; why did I start blogging as &lt;a href="https://whhone.com/posts/2019-year-end-reflection/"&gt;the 2020 new year
revolution&lt;/a&gt;?&lt;/p&gt;
&lt;h2 id="the-original-intention"&gt;The Original Intention&lt;/h2&gt;
&lt;p&gt;Why did I start this blog? It was actually not because of the
developer identity. I have already been a developer for years and this
identity is entrenched, without a shadow of a doubt. Instead, the
original intention was to become a writer, which is a new identity I
am pursuing.&lt;/p&gt;
&lt;p&gt;When I was using Hugo, I spent more time on tweaking the codes instead
of writing things. I researched the best way of rendering an image in
Markdown with Hugo. I organized the contents and reorganized them
again to a &amp;ldquo;better&amp;rdquo; structure. I forked the theme I was using because
I don&amp;rsquo;t like how it was implemented.&lt;/p&gt;
&lt;p&gt;These are all fun things from a developer&amp;rsquo;s perspective, but not for a
writer. Playing around the Markdown syntax is a distraction to the
writing of the underlying sentences and ideas. Also, managing the
posts in the WordPress admin console is actually more efficient,
compared to the content directory.&lt;/p&gt;
&lt;h2 id="the-verdict-wordpress"&gt;The Verdict &amp;ndash; WordPress&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Redesign your life so the actions that matter most are also the
actions that are easiest to do.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Atomic Habits, James Clear&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;ldquo;Be a writer&amp;rdquo; was what brought me to the blogging new year revolution.
WordPress allows me to spend more time and efforts on writing. Open
the browser or app, write and publish. Writing in rich text is just
much more intuitive than plain text markdown, even though I am very
proficient at Markdown.&lt;/p&gt;
&lt;p&gt;After the migration, I found two things that stand out.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The writing experience with the WordPress Block Editor
(Gutenberg)&lt;/strong&gt;. It is definitely much more powerful and easy than
Markdown to build a web page with a rich layout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search without an external search engine.&lt;/strong&gt; Get the latest search
result within the site. The expense is the ability to customize the
site easily and being able to manage all the configurations with
our beloved version control git.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While the current trend in the industry seems to be moving from the
LAMP-like stack or MEAN-like stack (Node.js) to the JAM-like stack
(static site generator). I am in the opposite direction with most
people. :p&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My ideal workflow is to have WordPress-like (anytime, anywhere with a
decent editor) writer experience and Hugo-like developer experience.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="parting-with-hugo"&gt;Parting with Hugo&lt;/h2&gt;
&lt;p&gt;This is an amicable parting! I have to say that Hugo is an awesome
static site generator. I enjoyed the experiences with it when building
the first version of this blog and published the first few posts. Here
are the merits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hugo is easy to install.&lt;/strong&gt; I just need to download a single binary.
While Jekyll (another static site generator need to pull a whole
bunch of Ruby dependency.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static site is very lightweight.&lt;/strong&gt; I can deploy the site to the
free f1-micro instance with only 0.2 shared CPU and 0.6 GB memory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hugo is very developer-friendly.&lt;/strong&gt; It has good documentation. The
Hugo Pipe makes it super easy to customize the site with SASS/SCSS.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, Hugo is not perfect and I struggled with it. Here are some
complaints:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Markdown is limited.&lt;/strong&gt; e.g., create a link that opens in a new tab
(&amp;lt;a&amp;gt; with target=“_blank”) is not straight-forward and not
standardized by the Markdown spec.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hugo does not have a good plugin system.&lt;/strong&gt; All features / plugins
are coupled to the theme. If the theme does not provide the
functionality I need, I am going to suffer, write my own, or fork
the theme.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cannot write anytime, anywhere.&lt;/strong&gt; When I find a typo on my phone, I
need to correct it right away.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I have a feeling that I will meet Hugo again, in another scenario, in
the future.&lt;/p&gt;</description></item><item><title>Building Habits</title><link>https://whhone.com/posts/building-habits/</link><pubDate>Sun, 05 Apr 2020 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/building-habits/</guid><description>&lt;p&gt;The recent shelter-in-place / stay-at-home order has dramatically changed our routines. I don&amp;rsquo;t need to commute to the office so I do not listen to podcasts. I don&amp;rsquo;t have morning coffee since I have no access to the micro-kitchen in the office. I walk less as I stay at home all day. I started doing daily push ups because it is less awkward to do at home, compared to the office. I spend more time with Casper because he is just nearby. Even on my Strava feed, some friends seem to have disappeared while some friends start showing up more days.&lt;/p&gt;
&lt;p&gt;Technically speaking, this changed tons of &lt;a href="https://jamesclear.com/habit-triggers"&gt;&amp;ldquo;habit cues&amp;rdquo;&lt;/a&gt;, like the activity, place, people, and even our mood. Hence, it will definitely change our habits as well.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You never want a serious crisis to go to waste. And what I mean by that is an opportunity to do things that you think you could not do before.&lt;/em&gt;
&amp;ndash; Rahm Emanuel&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In terms of personal development, it is a waste if we don&amp;rsquo;t deliberately build good habits from this big change. Don&amp;rsquo;t expect good habits to come automatically. They usually don&amp;rsquo;t! Instead, bad habits are more likely to form because their consequences are delayed while the rewards are immediate. Like eating junk food, surfing social media, just to name a few.&lt;/p&gt;
&lt;p&gt;If good habits do not come automatically, how to build them effectively?&lt;/p&gt;
&lt;h2 id="the-keystone-habits"&gt;The Keystone Habits&lt;/h2&gt;
&lt;p&gt;According to Charles Duhigg, author of &lt;a href="https://amzn.to/2wUsd65"&gt;The Power of Habit&lt;/a&gt;,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Keystone habits help other habits to flourish by creating new structures, and establish cultures where change becomes contagious.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In other words, keystone habits are habits that build other habits. Some common keystone habits are exercising, meditation, good sleep, journaling etc. &lt;em&gt;(These examples might sound boring and not as cool as the &amp;ldquo;keystone habits&amp;rdquo; :-p )&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Good keystone habits are worth investing in and their domino effects will make huge impacts in the long term. Time will magnify the results. Although we know keystone habits are great, the problem is, like all habits, they could break. According to US News, &lt;a href="https://health.usnews.com/health-news/blogs/eat-run/articles/2015-12-29/why-80-percent-of-new-years-resolutions-fail"&gt;80% of new year&amp;rsquo;s resolutions fail by the second week of Feburary&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How to make good (keystone) habits stick? Well, let&amp;rsquo;s build the keystone habit of keystone habits.&lt;/p&gt;
&lt;h2 id="habit-tracker--the-keystone-habit-of-keystone-habits"&gt;Habit Tracker (The Keystone Habit of Keystone Habits)&lt;/h2&gt;
&lt;p&gt;In my humble opinion, habit tracker is the keystone habit of keystone habits because it holds the explicit cues to other habits / keystone habits. It is beautiful because it allows me to keep only one, instead of all immature habits in mind. It makes habit building much easier!&lt;/p&gt;
&lt;p&gt;My first habit tracker was my &lt;a href="https://whhone.com//posts/productivity-system/"&gt;Evernote Productivity System&lt;/a&gt;. It simply held all my TODOs and daily habits.&lt;/p&gt;
&lt;p&gt;However, TODOs and habits are fundamentally different. For example, habits repeat and repeat very often but TODOs are not. Habits are more okay to break if the reclaiming is fast. The value of habits are measured over time. That said, mixing habits and TODOs in the same list obscures the important TODOs.&lt;/p&gt;
&lt;p&gt;Hence, I split them into a standalone habit tracker app. My second (current) habit tracker becomes a standalone app.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/habit-tracker-app.png"&gt;
&lt;/figure&gt;
&lt;p&gt;That is how I build habits now.&lt;/p&gt;
&lt;h2 id="the-two-books"&gt;The Two Books&lt;/h2&gt;
&lt;p&gt;Habit is an interesting topic and it has gotten traction in recent years, thanks to two best seller books.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/12609433"&gt;The Power of Habit, by Charles Duhigg, 2017&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goodreads.com/book/show/40121378"&gt;Atomic Habit, by James Clear, 2018&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I learned the habit concepts from these two books. &amp;ldquo;The Power of Habit&amp;rdquo; tells more stories behind habits while &amp;ldquo;Atomic Habit&amp;rdquo; gives more practical advice. Both are good books but if you want to pick only one, then &amp;ldquo;Atomic Habit&amp;rdquo; is the way to go!&lt;/p&gt;
&lt;p&gt;Finally, thanks for reading til now to this very last sentence :-)&lt;/p&gt;</description></item><item><title>Daily Journal</title><link>https://whhone.com/posts/daily-journal/</link><pubDate>Sun, 15 Mar 2020 00:00:00 -0700</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/daily-journal/</guid><description>&lt;p&gt;In the previous post &lt;a href="https://whhone.com/posts/2019-year-end-reflection/"&gt;2019 Year-End Reflection&lt;/a&gt;, I wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I kept a daily journal. When people ask me why keeping a journal, my
2019 answer was to track TODO and activity log, then review at the end
of the day. My 2020 answer is to make every day meaningful and counted.
I will share the nuances in another post later.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this post, I will share how I got started and the 3 components of my
daily journal.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Activity Log&lt;/li&gt;
&lt;li&gt;Most Important TODOs&lt;/li&gt;
&lt;li&gt;Daily Highlights&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/daily-journal.jpg"&gt;
&lt;/figure&gt;
&lt;h2 id="activity-log"&gt;Activity Log&lt;/h2&gt;
&lt;p&gt;The activity log is what brought me to journaling after reading Cal
Newport&amp;rsquo;s &lt;a href="https://www.goodreads.com/book/show/25744928-deep-work"&gt;Deep Work&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For an individual focused on deep work, it&amp;rsquo;s easy to identify the
relevant lead measure: time spent in a state of deep work dedicated
toward your wildly important goal. &amp;hellip; When I shifted to tracking deep
work hours, suddenly these measures became relevant to my day-to-day:
Every hour extra of deep work was immediately reflected in my tally.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How much hours do I spend on deep and shallow work respectively? I don&amp;rsquo;t
really know. So I started tracking my activities and annotating their
deepnesses.&lt;/p&gt;
&lt;p&gt;This was my very first daily journal :-)&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/deep-work-hours.png"&gt;
&lt;/figure&gt;
&lt;p&gt;I found the activity log does not only track the deep and shallow
hours, but also proved the &lt;a href="https://en.wikipedia.org/wiki/Planning_fallacy"&gt;Planning Fallacy&lt;/a&gt; to me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The planning fallacy, &amp;hellip;, is a phenomenon in which predictions about
how much time will be needed to complete a future task display an
optimism bias and underestimate the time needed.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have estimated a task that takes one day but it took me 3 days to
finish. Activity log gave me a sense of how my time is spent and how
much time do I need for a specific kind of task. Later at night, I can
also use this activity log to review what I have done in a day.&lt;/p&gt;
&lt;h2 id="most-important-todos"&gt;Most Important TODOs&lt;/h2&gt;
&lt;p&gt;I have two TODO lists for each day. The &lt;strong&gt;everything list&lt;/strong&gt; and the &lt;strong&gt;most
important list&lt;/strong&gt;. As the names imply, the everything list holds all TODOs
scheduled for the day and the most important list holds the most
important TODOs of the day.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/most-important-todos.jpg"&gt;
&lt;/figure&gt;
&lt;p&gt;So, why do I need two lists?&lt;/p&gt;
&lt;p&gt;Without the important list, the everything list, which consists of many
important, unimportant, urgent and nonurgent TODOs, could be
overwhelming and confusing. The volume of TODOs usually exceed one&amp;rsquo;s
capacity. As the
&lt;a href="https://www.thecut.com/2016/03/dont-let-completion-bias-derail-you-at-work.html"&gt;Completion
Bias&lt;/a&gt; states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;It feels good to chip away at the smaller tasks nibbling at your brain,
but the time you spend doing so is time you&amp;rsquo;re not spending on bigger,
more important projects.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That means I tend to check off many small and easy TODOs but not
getting the important and hard ones done. To fix it, each morning, I
go through the everything list from my &lt;a href="https://whhone.com/posts/productivity-system/"&gt;Productivity System&lt;/a&gt; and curate
TODOs to form the most important list. Then, focus on the most
important list before going back to the everything list.&lt;/p&gt;
&lt;p&gt;That does not mean the everything list is useless. These two lists are
complementary. Without the everything list, brainstorming the most
important list out of nothing is an arduous task. Those important
TODOs could be too ad hoc and hence not really important.&lt;/p&gt;
&lt;p&gt;The most important list also has a variation called the &amp;ldquo;1-3-5 Rule&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="daily-highlight"&gt;Daily Highlight&lt;/h2&gt;
&lt;p&gt;Daily highlight is my 2020 answer to daily journal.&lt;/p&gt;
&lt;p&gt;The activity log and the most important TODOs list mentioned above had
worked very well for me, until I was reviewing the journal in the &lt;a href="https://whhone.com/posts/2019-year-end-reflection/"&gt;last
year end&lt;/a&gt;. I found my journal is boring and full of little things
that are not interesting to me anymore. Those &amp;ldquo;fix bug XXX&amp;rdquo;, &amp;ldquo;weekly
YYY meeting&amp;rdquo; or &amp;ldquo;implement feature ZZZ&amp;rdquo; got the things done but I no
longer care about them.&lt;/p&gt;
&lt;p&gt;I asked myself, how can I make every day I lived meaningful and
counted, so that some days in the future, I can reminisce?&lt;/p&gt;
&lt;p&gt;Then, I added the daily highlight section to the daily journal. The
daily highlights could be anything, without any format but have to be
worth reading again. For examples, - an inspiring excerpt I read, - an
unforgettable mistake I made, - a bloody lesson I learnt, - a nice
chat with someone, - a work I am proud of.&lt;/p&gt;
&lt;p&gt;Finally, thanks for reading til now to this very last sentence :-)&lt;/p&gt;</description></item><item><title>Effective Reading</title><link>https://whhone.com/posts/effective-reading/</link><pubDate>Sun, 02 Feb 2020 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/effective-reading/</guid><description>&lt;p&gt;It is probably easier to explain what ineffective reading is first.&lt;/p&gt;
&lt;p&gt;If you are &amp;ldquo;seeing&amp;rdquo; the words without processing, no matter how many
words you &amp;ldquo;see&amp;rdquo; per minute, it is ineffective. If you cannot recall or
refer to the key concepts of an inspiring book you have read in the
past, no matter how many books you have read, it is ineffective. If you
cannot implement what you read and make some differences in real life,
no matter how many books you are aiming to read in a year, it is
ineffective.&lt;/p&gt;
&lt;p&gt;So, what is effective reading? &lt;strong&gt;&amp;ldquo;Effectiveness&amp;rdquo;&lt;/strong&gt; here means maximizing
the understanding, the usage and the retention of knowledge for the
future per unit of time spent reading.&lt;/p&gt;
&lt;p&gt;Ineffective reading is always a problem. So far, I have developed three
ways to make reading less ineffective.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mindful Reading - for understanding&lt;/li&gt;
&lt;li&gt;Summarization - for discoverability&lt;/li&gt;
&lt;li&gt;Digital Flashcard - for retention&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="1-dot-mindful-reading"&gt;1. Mindful Reading&lt;/h2&gt;
&lt;p&gt;Mindful reading is the first key to read effectively. It ensures you are
really reading and understanding what the author is saying.&lt;/p&gt;
&lt;p&gt;The converse of mindful reading is mindless reading. During mindless
reading, we are seeing the words instead of understanding them. We are
not able to recall what we read one minute ago. When I realize I am
reading mindlessly, I stop, do something else or take some rest.
Mindless reading is ineffective. It is a waste of time and energy.&lt;/p&gt;
&lt;h2 id="2-dot-summarization"&gt;2. Summarization&lt;/h2&gt;
&lt;p&gt;Understanding decays because we, humans, forget. It is okay to forget.
However, when we need to use a piece of forgotten knowledge, we want to
be able to reload the knowledge as fast as possible.&lt;/p&gt;
&lt;p&gt;Summarization is like building a personal index. It allows you to
navigate through important concepts and to retrieve context and
knowledge swiftly. Without summarization, we will need to spend a huge
amount of time reading the material again to find what we need. This is
definitely ineffective.&lt;/p&gt;
&lt;p&gt;The method I use is Tiago Forte&amp;rsquo;s &lt;a href="https://praxis.fortelabs.co/progressive-summarization-a-practical-technique-for-designing-discoverable-notes-3459b257d3eb/"&gt;Progressive Summarization&lt;/a&gt;. It
highlights and formats the readings into layers. The higher layers are
compressed to make the notes discoverable and the lower layers provide
context.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/progressive-summarization.jpeg"&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Layer 0&lt;/strong&gt;: the original, full-length source text&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer 1&lt;/strong&gt;: the content that initially bring into note-taking program.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer 2&lt;/strong&gt;: the first round of summarization, the best parts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer 3&lt;/strong&gt;: the second round of summarization, among all Layer 2, &amp;ldquo;the
best of the best&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer 4&lt;/strong&gt;: the summary in my own words.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer 5&lt;/strong&gt;: add my own personality and creativity and turn them into
something else.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="a-real-example-1"&gt;A real example 1&lt;/h3&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/progressive-summarization-example.png"&gt;
&lt;/figure&gt;
&lt;p&gt;This is my summary for &amp;ldquo;Clean Architecture Ch.22&amp;rdquo;. When I read the
note, I first look at the top summary (layer 4), and then the yellow
highlight (layer 3). This should give me most idea of what this
chapter is about. If I need more, then I read the bold (layer 2)
highlights and then the plain text highlights to get more context. If
I still need more, then I could go to the original chapter.&lt;/p&gt;
&lt;h2 id="3-dot-digital-flashcards"&gt;3. Digital Flashcards&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;ve got a leaky bucket, you&amp;rsquo;re better off fixing the leak before
pouring water in the top.&lt;/p&gt;
&lt;p&gt;I mentioned above that we, humans, forget and it is okay to forget. What
if we need to retain something in the brain? For instance, memorizing
vocabularies could reduce the effort of checking a dictionary (and thus
making reading more effective!). Internalizing key concepts could make
it easier to utilize them.&lt;/p&gt;
&lt;p&gt;My method is to formulate the knowledge into digital flashcards and
review them with spaced repetition. (I review the cards every night when
I am holding my sleeping Casper.)&lt;/p&gt;
&lt;p&gt;SuperMemo and Anki are among the popular software in this realm. No
matter which software to use, read these
&lt;a href="https://www.supermemo.com/en/archives1990-2015/articles/20rules"&gt;twenty
rules&lt;/a&gt; published by SuperMemo about the best practices of formulating
knowledge. &amp;ldquo;Do not learn if you do not understand&amp;rdquo; is the wisdom.&lt;/p&gt;
&lt;h3 id="a-real-example-2"&gt;A real example 2&lt;/h3&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/anki-example.png"&gt;
&lt;/figure&gt;
&lt;p&gt;After creating the summary above, I think &amp;ldquo;Dependency Rule&amp;rdquo; is a very
key concept that is worth memorizing, so I create a corresponding
flashcard. When reviewing the card, if this feels &amp;ldquo;easy&amp;rdquo; to me, I will
tap &amp;ldquo;16d Easy&amp;rdquo; to snooze the card 16 days later. If I cannot recall
the meaning in 10 seconds, then I tap &amp;ldquo;&amp;lt;10 min Again&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;The only way to go fast is to go well&amp;rdquo;, Robert C. Martin&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think this applies to many things, including coding, driving and
reading. All of mindful reading, summarization, and making flashcards
slow us down in terms of the number of words and books you read in a
given time. However, they are ways to go fast in terms of
effectiveness.&lt;/p&gt;
&lt;p&gt;I am still learning how to make the reading process more effective. If
you have any tips, please let me know. :)&lt;/p&gt;</description></item><item><title>Productivity System</title><link>https://whhone.com/posts/productivity-system/</link><pubDate>Sun, 12 Jan 2020 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/productivity-system/</guid><description>&lt;p&gt;In this era, there are &amp;ldquo;productive people&amp;rdquo; who use &amp;ldquo;productivity
systems&amp;rdquo; to manage day-to-day chores, TODOs, notes and so on. These
productivity systems are usually note taking apps (Apple Notes,
Evernote, Google Keep, OneNote), task manager apps (Todoist,
Wunderlist, TickTick), paper notebooks or the combination of them.&lt;/p&gt;
&lt;p&gt;These &amp;ldquo;productive people&amp;rdquo; could be divided into two kinds.&lt;/p&gt;
&lt;p&gt;The first kind works for the system. They fill the system with
unactionable tasks that will never get done and unreadable notes that
will never get used again. They do not maintain the system and let it
become a chaos. The value added to the system is more than the value
generated from it. At the end, they might stop using the system,
switch to a &amp;ldquo;better&amp;rdquo; app and hope the new one will work out.&lt;/p&gt;
&lt;p&gt;The second kind has the system working for them. They might still add
unactionable tasks but will make them actionable. They might add
unreadable notes but will turn them into references that are
searchable and reusable. They maintain the system regularly and are
able to stick with it for a long time. The value provided by the
system is more than the value added to the system.&lt;/p&gt;
&lt;p&gt;I was the first kind and now the second kind. So, what made the
difference? I would say it is the methodology. A grand piano helps but
one still need to know how to play it.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t get me wrong. I am not saying tools are not important. Both
tools and methodologies are important. It is just easier to acquire
tools than methodologies. One can buy a grand piano in one day but it
takes years of practice to be a great pianist.&lt;/p&gt;
&lt;p&gt;Also, I don&amp;rsquo;t know what the best methodology is, whether it is &lt;a href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;David
Allen&amp;rsquo;s Getting Things Done (GTD)&lt;/a&gt;, &lt;a href="https://bulletjournal.com/"&gt;Ryder Carroll&amp;rsquo;s Bullet Journal&lt;/a&gt;,
or your own method. The answer is probably different for different
people.&lt;/p&gt;
&lt;p&gt;For my own system, it is influenced by GTD but with some
modifications. I sticked with the 5 key steps but replaced the Tickler
system with Evernote Reminder.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Capture&lt;/strong&gt;: Whenever I have something that might need to follow up, I
put them into my inbox. It could be a TODO, a random thought, a link,
a screenshot, a photo, a new vocabulary, a web clip, or, of course,
unactionable tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clarify&lt;/strong&gt;: Later, I convert them to actionable tasks, usable
references, someday/maybe, or delete them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Organize&lt;/strong&gt;: I organize those items with
&lt;a href="https://fortelabs.co/blog/para/"&gt;Tiago Forte&amp;rsquo;s P.A.R.A. method&lt;/a&gt;,
which divides everything into 4 groups, Projects (short-term), Areas
of Responsibility (long-term), Resource (topics interested) and
Archived. This is the most practical organizational method I have
seen so far.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review&lt;/strong&gt;: Every Sunday, I review all notes created in the last week
and plan for the next week. I reschedule tasks so that I won&amp;rsquo;t be too
free or too busy next week. Before a long weekend or holiday, I would
pull the someday/maybe items and actualize some.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engage&lt;/strong&gt;: When I have free time, I open my task list and do the most
important tasks. This have become the &amp;ldquo;keystone habit&amp;rdquo; to build other
habits, like memorizing vocuburaries and meditation. This also
embolden me that I can finish bigger projects, like blogging and
reading books, because the task list always reminds me.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lastly, adding two screenshots which might tell more than the words.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/notebook-list.png"&gt;
&lt;/figure&gt;
&lt;figure&gt;&lt;img src="https://whhone.com/ox-hugo/reminder.png"&gt;
&lt;/figure&gt;</description></item><item><title>2019 Year-End Reflection</title><link>https://whhone.com/posts/2019-year-end-reflection/</link><pubDate>Tue, 31 Dec 2019 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/2019-year-end-reflection/</guid><description>&lt;p&gt;2019 was a year of changes for me, marked by newly developed habits
and the new responsibility of becoming a father.&lt;/p&gt;
&lt;p&gt;Here is what I have done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I completed 9 English books.&lt;/strong&gt; This number was a shameful ZERO for the
28 years before 2019. Even when I was a student and tasked to write
book reports, I just paraphrased the introduction and/or some random
pages of the books. Reading has instilled new ideas in me and also
ignited other habits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I crammed 2000+ new vocabularies into my head.&lt;/strong&gt; Last year, I learned
about spaced repetition from &lt;a href="https://www.coursera.org/learn/learning-how-to-learn"&gt;Learning How to Learn&lt;/a&gt; and started
memorizing vocabularies. Whenever I encounter new vocabularies (mostly
from reading), I add them to my Anki collection and review daily. I
use &amp;ldquo;crammed&amp;rdquo; instead of &amp;ldquo;learned&amp;rdquo; because I still cannot use most of
them. This is something I need to fix in 2020.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I meditated.&lt;/strong&gt; After reading a few books that recommended meditation,
I started with Headspace. Being mindful, I must say, has the power to
positively impact other areas of life.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I kept a daily journal.&lt;/strong&gt; When people ask me why I keep a journal, my
2019 answer was to track my to-dos and activity log, then review at
the end of the day. My 2020 answer is to make every day count. I will
share the nuances in another post later. (Update on 2020-03-15: See
&lt;a href="https://whhone.com/posts/daily-journal/"&gt;Daily Journal&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I logged 1000 miles into Strava.&lt;/strong&gt; This is actually far less than my
targeted 1500 miles. I have been running for 17 years and have no
intention of stopping, although with Casper, squeezing time for
exercising will be more challenging. On the bright side, this could be
an excuse to be less strict about pacing and mileage :p&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We delivered Casper!&lt;/strong&gt; This is marvelous even with the sleepless
nights and all the other compromises. Taking good care of the little
one and my wife is the overarching priority in 2020.&lt;/p&gt;
&lt;p&gt;Life events are interconnected. If I hadn&amp;rsquo;t taken &amp;ldquo;Learning How to
Learn,&amp;rdquo; I wouldn&amp;rsquo;t have started memorizing vocabularies. If I hadn&amp;rsquo;t
memorized vocabularies, I wouldn&amp;rsquo;t have started reading to collect
more English vocabularies. If I hadn&amp;rsquo;t read books like &lt;a href="https://www.goodreads.com/book/show/34536488"&gt;Ray Dalio&amp;rsquo;s
Principles&lt;/a&gt;, I wouldn&amp;rsquo;t have started meditation.&lt;/p&gt;
&lt;p&gt;As a new year resolution, I am going to start writing (yes, this
blog), about my thoughts, learning methods, and engineering. Let&amp;rsquo;s see
what chain reactions will happen when I look back at the end of 2020.&lt;/p&gt;</description></item><item><title>Kubernetes for Personal Projects</title><link>https://whhone.com/posts/kubernetes-for-personal-projects/</link><pubDate>Sun, 29 Dec 2019 00:00:00 -0800</pubDate><author>whhone@gmail.com (Wai Hon Law)</author><guid>https://whhone.com/posts/kubernetes-for-personal-projects/</guid><description>&lt;p&gt;&lt;em&gt;This post explains why I was not using Kubernetes for personal
projects. If you are reading this post trying to set up Kubernetes, it
will disappoint you.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yesterday, I saw a very cool self-hosted project (&lt;a href="https://github.com/ankicommunity/anki-sync-server"&gt;anki-sync-server&lt;/a&gt;)
and I would like to host it myself.&lt;/p&gt;
&lt;p&gt;It had happened to me repeatedly that I hosted something (e.g.,
Wordpress) and ended up abandoning it. It was mostly because I forgot
the machine setup or how those apps were configured. They became
burdens, and I killed them to save hosting costs.&lt;/p&gt;
&lt;p&gt;However, technology keeps evolving! We now have Docker and Kubernetes
for running portable containers. Configurations can be stored as
Dockerfiles in a git repository. The apps can be restored accurately.
How cool is it to own a personal cluster? How cool is it to be able to
launch random Docker containers in the cluster? It is just super cool,
at least from a software engineer&amp;rsquo;s perspective.&lt;/p&gt;
&lt;p&gt;Hence, I decided to set up a Kubernetes cluster on DigitalOcean this
afternoon. My goal was to set up a cluster that allows me to launch
whatever containers as easily as possible. My direction was to set up
Ingress and IngressController for routing traffic to the right pod. If
possible, I would try using &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types"&gt;NodePort instead of LoadBalancer&lt;/a&gt; to avoid
the extra $10 per month.&lt;/p&gt;
&lt;p&gt;A few hours passed, I took a pause and went for a run. During the run,
I decided to give up. Here are the reasons.&lt;/p&gt;
&lt;p&gt;First, the learning curve of Kubernetes is steep. That means the
re-learning curve is also steep. Imagine that even if the cluster is
set up today but I do not touch it for a few months (because I focus
on other things later or life just does not allow me to spend more
time on it), it is very likely that I will not be able to maintain it
without going through a steep re-learning curve. It will likely become
another burden that I choose to get rid of, again. The time I spent on
setting up the cluster is gone.&lt;/p&gt;
&lt;p&gt;Second, it is expensive for small projects. Do you need a load
balancer for an app that has very little QPS? It is simply overkill.
You might argue that it is possible to &lt;a href="https://stackoverflow.com/questions/37792641/how-can-you-publish-a-kubernetes-service-without-using-the-type-loadbalancer-on"&gt;avoid the load balancer&lt;/a&gt;.
However, these solutions (like setting up an extra Nginx with a public
IP as a proxy) are hacky to me. Hacky solutions increase complexity /
maintenance cost, and thus it is more likely to become a burden in the
future.&lt;/p&gt;
&lt;p&gt;Everything comes with costs; the biggest is time, simply because it is
limited. When you choose to do something, you are giving up another at
the same time, inevitably. As of now, my priority is no longer playing
with those self-hosted services. Kubernetes is nice and all, but just
not for me at this moment.&lt;/p&gt;</description></item></channel></rss>