Luke Hsiao's blog Zola 2026-02-20T00:00:00+00:00 https://luke.hsiao.dev/atom.xml Using Changesets in a polyglot monorepo Luke Hsiao 2026-02-20T00:00:00+00:00 2026-02-20T00:00:00+00:00 https://luke.hsiao.dev/blog/changesets-polyglot-monorepo/ Some notes on how to use Changesets for versioning packages in a polyglot monorepo. <p>One of the nice things about working in a smaller business is you can enjoy using things that don't need to scale to extreme sizes. One example in the software world is <a rel="external" href="https://en.wikipedia.org/wiki/Monorepo">monorepos</a>. While monorepos <em>can</em> scale well (see Google, Facebook, and others), doing so requires special tooling and infrastructure. With plain <code>git</code>, <a rel="external" href="https://wellarchitected.github.com/library/architecture/recommendations/scaling-git-repositories/repository-architecture-strategy/">you can only go so far</a>. While you can use it, it has meaningful advantages, like being able to make atomic changes that affect many parts of the system in a single commit, which eliminates whole classes of compatibility and integration issues. You can always split a monorepo later (see <a rel="external" href="https://github.com/newren/git-filter-repo"><code>git-filter-repo</code></a>).</p> <p>So, suppose you're a small-to-medium team using a monorepo. Let's go further and say that this monorepo stores all your company's code, meaning it spans many different programming languages—it's a polyglot monorepo. What tool can you use to manage versioning in a consistent way?</p> <p>I argue that <a rel="external" href="https://github.com/changesets/changesets"><code>changesets</code></a> is a solid choice, even if it's primarily focused on the JavaScript/TypeScript ecosystem.</p> <div class="aside aside--note"> <div class="aside__header" role="heading" aria-level="3"> <div class="aside__icon" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-info-circle"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /> <path d="M12 9h.01" /> <path d="M11 12h1v4h1" /> </svg> </div> <span class="aside__title">Note</span> </div> <div class="aside__body"><p>There is an <a rel="external" href="https://github.com/changesets/changesets/issues/665">open discussion about native polyglot monorepo support</a>. However, even without native support, <code>changesets</code> has the hooks needed to implement this as-is for many setups.</p> </div> </div><h2 id="background">Background<a class="zola-anchor" href="#background" aria-label="Anchor link for: background" title="Anchor link for: background"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>For any versioning tool, you are typically looking for how to:</p> <ul> <li>define what content appears in the changelog/release notes</li> <li>influence the version numbers of the packages</li> <li>automate the commits doing the actual metadata bumps and tagging</li> <li>automate the builds that happen in response</li> </ul> <p><code>changesets</code> assumes per-package <a rel="external" href="https://semver.org/">semantic versioning</a> (i.e., all packages have their own version). In addition, each package has its own <code>CHANGELOG.md</code>.</p> <p>The <code>changesets</code> team also has a GitHub Action, <a rel="external" href="https://github.com/changesets/action"><code>changesets/action</code></a> which importantly allows specifying custom scripts for the <code>version</code> and <code>publish</code> commands. That customization is what gives <code>changesets</code> support for polyglot repositories.</p> <p>In <code>changesets</code>, engineers commit "changeset" files to the repository that define what content ends up in changelogs, and what packages versions are bumped (i.e., major, minor, patch).</p> <p>See the <a rel="external" href="https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md"><code>changesets</code> documentation</a> for more details.</p> <h2 id="implementing-an-automated-release-process-on-github">Implementing an automated release process on GitHub<a class="zola-anchor" href="#implementing-an-automated-release-process-on-github" aria-label="Anchor link for: implementing-an-automated-release-process-on-github" title="Anchor link for: implementing-an-automated-release-process-on-github"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I'm a fan of <a rel="external" href="https://just.systems/"><code>just</code></a>. I also really like <a rel="external" href="https://docs.astral.sh/uv/guides/scripts/"><code>uv</code> scripts</a>. The example below uses both.</p> <p>I'm also going to assume you are in a enterprise setting where all your monorepo is private, not open-source.</p> <h3 id="repository-setup">Repository setup<a class="zola-anchor" href="#repository-setup" aria-label="Anchor link for: repository-setup" title="Anchor link for: repository-setup"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>My recommended organization (at least at time of writing) is something like the following.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span>.</span></span> <span class="giallo-l"><span>├── .changeset</span></span> <span class="giallo-l"><span>│ ├── config.json</span></span> <span class="giallo-l"><span>│ └── README.md</span></span> <span class="giallo-l"><span>├── contrib</span></span> <span class="giallo-l"><span>│ └── utils</span></span> <span class="giallo-l"><span>├── docker</span></span> <span class="giallo-l"><span>│ └── Dockerfile</span></span> <span class="giallo-l"><span>├── docs</span></span> <span class="giallo-l"><span>│ ├── package.json</span></span> <span class="giallo-l"><span>│ ├── pnpm-lock.yaml</span></span> <span class="giallo-l"><span>│ ├── ...</span></span> <span class="giallo-l"><span>│ └── pnpm-workspace.yaml</span></span> <span class="giallo-l"><span>├── Justfile</span></span> <span class="giallo-l"><span>├── package-lock.json</span></span> <span class="giallo-l"><span>├── package.json</span></span> <span class="giallo-l"><span>├── packages</span></span> <span class="giallo-l"><span>│ ├── python-one</span></span> <span class="giallo-l"><span>│ │ ├── ...</span></span> <span class="giallo-l"><span>│ │ └── package.json</span></span> <span class="giallo-l"><span>│ ├── rust-one</span></span> <span class="giallo-l"><span>│ │ ├── ...</span></span> <span class="giallo-l"><span>│ │ └── package.json</span></span> <span class="giallo-l"><span>│ └── rust-two</span></span> <span class="giallo-l"><span>│ │ ├── ...</span></span> <span class="giallo-l"><span>│ │ └── package.json</span></span> <span class="giallo-l"><span>├── pnpm-workspace.yaml</span></span> <span class="giallo-l"><span>└── third-party</span></span></code></pre> <p>Put all packages in a <code>packages/</code> directory, no matter what language they are. I also enjoy having <a rel="external" href="https://docsascode.org/">documentation as code</a>, so let's say you have a <code>docs/</code> directory, too, and that your docs is written in a javascript-based frontend (like <a rel="external" href="https://starlight.astro.build/">Starlight</a>), for the purposes of highlighting a nuance later.</p> <h3 id="changeset-configuration">Changeset configuration<a class="zola-anchor" href="#changeset-configuration" aria-label="Anchor link for: changeset-configuration" title="Anchor link for: changeset-configuration"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>With this setup, you can configure <code>changesets</code> with a proxy <code>pnpm</code> workspace at the root with all your packages.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># pnpm-workspace.yaml</span></span> <span class="giallo-l"><span style="color: #89B4FA;">packages</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #A6E3A1;"> &quot;packages/**&quot;</span></span></code></pre> <p>And, declare your <code>changesets</code> dependencies:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="json"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;">// package.json</span></span> <span class="giallo-l"><span style="color: #9399B2;">{</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">name</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;example-monorepo&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">private</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> true</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">devDependencies</span><span style="color: #9399B2;">&quot;: {</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">@changesets/changelog-git</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;^0.2.0&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">@changesets/cli</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;^2.29.0&quot;</span></span> <span class="giallo-l"><span style="color: #9399B2;"> }</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span></code></pre> <p>You should now also update your <code>.gitignore</code>:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span>node_modules/</span></span></code></pre> <p>Because <code>changesets</code> is built for JavaScript, we also need "proxy" <code>package.json</code> files for all of our packages; <code>changesets</code> uses these to perform version bumps.</p> <p>These can be as simple as:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="json"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;">// packages/python-one/package.json</span></span> <span class="giallo-l"><span style="color: #9399B2;">{</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">name</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;python-one&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">version</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;0.1.0&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">private</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> true</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span></code></pre> <p>With this setup, note how we are intentionally trying to <em>exclude</em> our internal <code>docs/</code> as a pnpm workspace member—we only want to version packages. To do so, declare the <code>docs/</code> directory its <em>own</em> <code>pnpm</code> workspace, otherwise it will try and combine the <code>docs/</code> dependencies into the root <code>package-lock.json</code>. This can be as simple as:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># docs/pnpm-workspace.pyml</span></span> <span class="giallo-l"><span style="color: #89B4FA;">packages</span><span style="color: #94E2D5;">:</span><span style="color: #9399B2;"> []</span></span></code></pre> <p>Next, we can add our changeset configuration:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="json"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;">// .changeset/config.json</span></span> <span class="giallo-l"><span style="color: #9399B2;">{</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">$schema</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;https://unpkg.com/@changesets/config@3.1.1/schema.json&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">changelog</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;@changesets/changelog-git&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">commit</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> false</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">fixed</span><span style="color: #9399B2;">&quot;: [],</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">linked</span><span style="color: #9399B2;">&quot;: [],</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">access</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;restricted&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">baseBranch</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;main&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">updateInternalDependencies</span><span style="color: #9399B2;">&quot;:</span><span style="color: #A6E3A1;"> &quot;patch&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">ignore</span><span style="color: #9399B2;">&quot;: [],</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">privatePackages</span><span style="color: #9399B2;">&quot;: {</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">version</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> true</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">tag</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> true</span></span> <span class="giallo-l"><span style="color: #9399B2;"> },</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH</span><span style="color: #9399B2;">&quot;: {</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">onlyUpdatePeerDependentsWhenOutOfRange</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> true</span></span> <span class="giallo-l"><span style="color: #9399B2;"> }</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span></code></pre><h3 id="automating-releases-with-github">Automating releases with GitHub<a class="zola-anchor" href="#automating-releases-with-github" aria-label="Anchor link for: automating-releases-with-github" title="Anchor link for: automating-releases-with-github"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <h4 id="the-glue-to-create-polyglot-versioning-prs">The glue to create polyglot versioning PRs<a class="zola-anchor" href="#the-glue-to-create-polyglot-versioning-prs" aria-label="Anchor link for: the-glue-to-create-polyglot-versioning-prs" title="Anchor link for: the-glue-to-create-polyglot-versioning-prs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <p>Next, we want to automate our releases. That is, generating the changelog PRs, bumping package metadata, pushing tags, and triggering builds on those tags.</p> <p>Let's start with our GitHub Workflow definition, and unpack the scripts it calls.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #89B4FA;">name</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> Release</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FAB387;">on</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> push</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> branches</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #A6E3A1;"> main</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #89B4FA;">concurrency</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> ${{ github.workflow }}-${{ github.ref }}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #89B4FA;">permissions</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> contents</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> write</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> pull-requests</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> write</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #89B4FA;">jobs</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> release</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> name</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> Release</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> runs-on</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> ubuntu-latest</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> steps</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #89B4FA;"> uses</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> actions/checkout@v6</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #89B4FA;"> uses</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> actions/setup-node@v4</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> with</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> cache</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> npm</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #89B4FA;"> uses</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> astral-sh/setup-uv@v7</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #89B4FA;"> uses</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> taiki-e/install-action@just</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #89B4FA;"> run</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> npm install</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #89B4FA;"> name</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> Create Release Pull Request or Tag</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> uses</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> changesets/action@v1</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> with</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> version</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> just version</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> publish</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> npx @changesets/cli publish</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> # I like conventional commits</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> commit</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> &quot;chore(release): version packages&quot;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> title</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> &quot;chore(release): version packages&quot;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> env</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> GITHUB_TOKEN</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> ${{ secrets.CHANGESETS_PAT }}</span></span></code></pre> <div class="aside aside--tip"> <div class="aside__header" role="heading" aria-level="3"> <div class="aside__icon" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-rocket"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3" /> <path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3" /> <path d="M15 9m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /> </svg> </div> <span class="aside__title">Tip</span> </div> <div class="aside__body"><p>The usage of a special personal access token (PAT) is important. If you just use <code>secrets.GITHUB_TOKEN</code>, later workflows that depend on tag pushes <a rel="external" href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow#triggering-a-workflow-from-a-workflow">will not trigger</a>.</p> </div> </div> <p>Setting <code>version: just version</code> is the key to polyglot support.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="Just"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># Version packages based on changesets</span></span> <span class="giallo-l"><span>[doc(</span><span style="color: #A6E3A1;">&#39;Consume changesets: bump versions, update changelogs, sync native version files.&#39;</span><span>)]</span></span> <span class="giallo-l"><span>[group(</span><span style="color: #A6E3A1;">&#39;release&#39;</span><span>)]</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">version</span><span>:</span></span> <span class="giallo-l"><span> npx @changesets/cli version</span></span> <span class="giallo-l"><span> uv run --script contrib/utils/sync-versions.py</span></span></code></pre> <p>The meat of the glue for polyglot support then, is how you implement <code>sync-versions.py</code>.</p> <p>The key bit here is we rely on <code>changesets</code> to bump the versions in <code>package.json</code> for us when we call <code>npx @changesets/cli version</code>, but then it is up to us to propagate that version to the respective language's metadata appropriately.</p> <p>Here is an example that uses pretty naive parsing. You can write something similar (or better!) for the languages you use.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="python"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;">#!/usr/bin/env -S uv run --script</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;">#</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># /// script</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># requires-python = &quot;&gt;=3.12&quot;</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># dependencies = []</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># ///</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;">#</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># Sync versions from package.json files (updated by changesets) to native</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># package manifests (Cargo.toml, pyproject.toml, etc.).</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">import</span><span> json</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">import</span><span> re</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">import</span><span> subprocess</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">from</span><span> enum</span><span style="color: #CBA6F7;"> import</span><span> Enum</span><span style="color: #9399B2;">,</span><span> auto</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">from</span><span> pathlib</span><span style="color: #CBA6F7;"> import</span><span> Path</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>PACKAGES_DIR</span><span style="color: #94E2D5;"> =</span><span style="color: #89B4FA;"> Path</span><span style="color: #9399B2;">(</span><span>__file__</span><span style="color: #9399B2;">).</span><span style="color: #89B4FA;">resolve</span><span style="color: #9399B2;">().</span><span>parent</span><span style="color: #9399B2;">.</span><span>parent</span><span style="color: #9399B2;">.</span><span>parent</span><span style="color: #94E2D5;"> /</span><span style="color: #A6E3A1;"> &quot;packages&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">class</span><span style="color: #F9E2AF;font-style: italic;"> SyncResult</span><span style="color: #9399B2;">(</span><span style="color: #F9E2AF;font-style: italic;">Enum</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> NOT_FOUND</span><span style="color: #94E2D5;"> =</span><span style="color: #89B4FA;"> auto</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span> UP_TO_DATE</span><span style="color: #94E2D5;"> =</span><span style="color: #89B4FA;"> auto</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span> UPDATED</span><span style="color: #94E2D5;"> =</span><span style="color: #89B4FA;"> auto</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">def</span><span style="color: #89B4FA;font-style: italic;"> read_package_json</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">pkg_dir</span><span style="color: #9399B2;">:</span><span style="color: #EBA0AC;"> Path</span><span style="color: #9399B2;">) -&gt;</span><span style="color: #CBA6F7;font-style: italic;"> dict</span><span style="color: #94E2D5;"> |</span><span style="color: #FAB387;"> None</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> &quot;&quot;&quot;Read and parse a package.json file.&quot;&quot;&quot;</span></span> <span class="giallo-l"><span> pkg_json</span><span style="color: #94E2D5;"> =</span><span> pkg_dir</span><span style="color: #94E2D5;"> /</span><span style="color: #A6E3A1;"> &quot;package.json&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if not</span><span> pkg_json</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">exists</span><span style="color: #9399B2;">():</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span style="color: #FAB387;"> None</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> json</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">loads</span><span style="color: #9399B2;">(</span><span>pkg_json</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">read_text</span><span style="color: #9399B2;">())</span></span> <span class="giallo-l"></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">def</span><span style="color: #89B4FA;font-style: italic;"> update_cargo_toml</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">pkg_dir</span><span style="color: #9399B2;">:</span><span style="color: #EBA0AC;"> Path</span><span style="color: #9399B2;">,</span><span style="color: #EBA0AC;font-style: italic;"> version</span><span style="color: #9399B2;">:</span><span style="color: #CBA6F7;font-style: italic;"> str</span><span style="color: #9399B2;">) -&gt;</span><span> SyncResult</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> &quot;&quot;&quot;Update version in [package] section of Cargo.toml.&quot;&quot;&quot;</span></span> <span class="giallo-l"><span> cargo_toml</span><span style="color: #94E2D5;"> =</span><span> pkg_dir</span><span style="color: #94E2D5;"> /</span><span style="color: #A6E3A1;"> &quot;Cargo.toml&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if not</span><span> cargo_toml</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">exists</span><span style="color: #9399B2;">():</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>NOT_FOUND</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> lines</span><span style="color: #94E2D5;"> =</span><span> cargo_toml</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">read_text</span><span style="color: #9399B2;">().</span><span style="color: #89B4FA;">splitlines</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">keepends</span><span style="color: #94E2D5;">=</span><span style="color: #FAB387;">True</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span> in_package_section</span><span style="color: #94E2D5;"> =</span><span style="color: #FAB387;"> False</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;"> for</span><span> i</span><span style="color: #9399B2;">,</span><span> line</span><span style="color: #CBA6F7;"> in</span><span style="color: #FAB387;font-style: italic;"> enumerate</span><span style="color: #9399B2;">(</span><span>lines</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> stripped</span><span style="color: #94E2D5;"> =</span><span> line</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">strip</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> # Track which TOML section we&#39;re in</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> stripped</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">startswith</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;[&quot;</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> in_package_section</span><span style="color: #94E2D5;"> =</span><span> stripped</span><span style="color: #94E2D5;"> ==</span><span style="color: #A6E3A1;"> &quot;[package]&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> continue</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> in_package_section</span><span style="color: #CBA6F7;"> and</span><span> stripped</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">startswith</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;version&quot;</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> new_line</span><span style="color: #94E2D5;"> =</span><span> re</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">sub</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #A6E3A1;font-style: italic;"> r</span><span style="color: #F5C2E7;">&#39;</span><span style="color: #A6E3A1;">^</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">\s</span><span style="color: #94E2D5;">*</span><span style="color: #A6E3A1;">version\s</span><span style="color: #94E2D5;">*</span><span style="color: #A6E3A1;">=\s</span><span style="color: #94E2D5;">*</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)([</span><span style="color: #94E2D5;">^</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">]</span><span style="color: #94E2D5;">+</span><span style="color: #9399B2;">)(</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)</span><span style="color: #F5C2E7;">&#39;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #A6E3A1;font-style: italic;"> rf</span><span style="color: #A6E3A1;">&quot;\g&lt;1&gt;</span><span style="color: #F5C2E7;">{</span><span>version</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;">\3&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span> line</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> )</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> new_line</span><span style="color: #94E2D5;"> !=</span><span> line</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> lines</span><span style="color: #9399B2;">[</span><span style="color: #EBA0AC;font-style: italic;">i</span><span style="color: #9399B2;">]</span><span style="color: #94E2D5;"> =</span><span> new_line</span></span> <span class="giallo-l"><span> cargo_toml</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">write_text</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;&quot;</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">join</span><span style="color: #9399B2;">(</span><span>lines</span><span style="color: #9399B2;">))</span></span> <span class="giallo-l"><span> rel</span><span style="color: #94E2D5;"> =</span><span> cargo_toml</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">relative_to</span><span style="color: #9399B2;">(</span><span>PACKAGES_DIR</span><span style="color: #9399B2;">.</span><span>parent</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;font-style: italic;">f</span><span style="color: #A6E3A1;">&quot; Updated </span><span style="color: #F5C2E7;">{</span><span>rel</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>UPDATED</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>UP_TO_DATE</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>UP_TO_DATE</span></span> <span class="giallo-l"></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">def</span><span style="color: #89B4FA;font-style: italic;"> update_pyproject_toml</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">pkg_dir</span><span style="color: #9399B2;">:</span><span style="color: #EBA0AC;"> Path</span><span style="color: #9399B2;">,</span><span style="color: #EBA0AC;font-style: italic;"> version</span><span style="color: #9399B2;">:</span><span style="color: #CBA6F7;font-style: italic;"> str</span><span style="color: #9399B2;">) -&gt;</span><span> SyncResult</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> &quot;&quot;&quot;Update version in [project] section of pyproject.toml.&quot;&quot;&quot;</span></span> <span class="giallo-l"><span> pyproject</span><span style="color: #94E2D5;"> =</span><span> pkg_dir</span><span style="color: #94E2D5;"> /</span><span style="color: #A6E3A1;"> &quot;pyproject.toml&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if not</span><span> pyproject</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">exists</span><span style="color: #9399B2;">():</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>NOT_FOUND</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> lines</span><span style="color: #94E2D5;"> =</span><span> pyproject</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">read_text</span><span style="color: #9399B2;">().</span><span style="color: #89B4FA;">splitlines</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">keepends</span><span style="color: #94E2D5;">=</span><span style="color: #FAB387;">True</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span> in_project_section</span><span style="color: #94E2D5;"> =</span><span style="color: #FAB387;"> False</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;"> for</span><span> i</span><span style="color: #9399B2;">,</span><span> line</span><span style="color: #CBA6F7;"> in</span><span style="color: #FAB387;font-style: italic;"> enumerate</span><span style="color: #9399B2;">(</span><span>lines</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> stripped</span><span style="color: #94E2D5;"> =</span><span> line</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">strip</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> # Track which TOML section we&#39;re in</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> stripped</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">startswith</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;[&quot;</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> in_project_section</span><span style="color: #94E2D5;"> =</span><span> stripped</span><span style="color: #94E2D5;"> ==</span><span style="color: #A6E3A1;"> &quot;[project]&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> continue</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> in_project_section</span><span style="color: #CBA6F7;"> and</span><span> stripped</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">startswith</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;version&quot;</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> new_line</span><span style="color: #94E2D5;"> =</span><span> re</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">sub</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #A6E3A1;font-style: italic;"> r</span><span style="color: #F5C2E7;">&#39;</span><span style="color: #A6E3A1;">^</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">\s</span><span style="color: #94E2D5;">*</span><span style="color: #A6E3A1;">version\s</span><span style="color: #94E2D5;">*</span><span style="color: #A6E3A1;">=\s</span><span style="color: #94E2D5;">*</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)([</span><span style="color: #94E2D5;">^</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">]</span><span style="color: #94E2D5;">+</span><span style="color: #9399B2;">)(</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)</span><span style="color: #F5C2E7;">&#39;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #A6E3A1;font-style: italic;"> rf</span><span style="color: #A6E3A1;">&quot;\g&lt;1&gt;</span><span style="color: #F5C2E7;">{</span><span>version</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;">\3&quot;</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span> line</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> )</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> new_line</span><span style="color: #94E2D5;"> !=</span><span> line</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> lines</span><span style="color: #9399B2;">[</span><span style="color: #EBA0AC;font-style: italic;">i</span><span style="color: #9399B2;">]</span><span style="color: #94E2D5;"> =</span><span> new_line</span></span> <span class="giallo-l"><span> pyproject</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">write_text</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;&quot;</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">join</span><span style="color: #9399B2;">(</span><span>lines</span><span style="color: #9399B2;">))</span></span> <span class="giallo-l"><span> rel</span><span style="color: #94E2D5;"> =</span><span> pyproject</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">relative_to</span><span style="color: #9399B2;">(</span><span>PACKAGES_DIR</span><span style="color: #9399B2;">.</span><span>parent</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;font-style: italic;">f</span><span style="color: #A6E3A1;">&quot; Updated </span><span style="color: #F5C2E7;">{</span><span>rel</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>UPDATED</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>UP_TO_DATE</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;"> return</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>UP_TO_DATE</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">def</span><span style="color: #89B4FA;font-style: italic;"> refresh_lockfiles</span><span style="color: #9399B2;">() -&gt;</span><span style="color: #FAB387;"> None</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> &quot;&quot;&quot;Refresh all lockfiles under the repo to match updated versions.&quot;&quot;&quot;</span></span> <span class="giallo-l"><span> repo_root</span><span style="color: #94E2D5;"> =</span><span> PACKAGES_DIR</span><span style="color: #9399B2;">.</span><span>parent</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;Refreshing lockfiles...&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> # Cargo.lock — root workspace + any standalone crate lockfiles</span></span> <span class="giallo-l"><span> cargo_locks</span><span style="color: #94E2D5;"> =</span><span style="color: #FAB387;font-style: italic;"> sorted</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #CBA6F7;font-style: italic;"> set</span><span style="color: #9399B2;">(</span><span>repo_root</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">glob</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;Cargo.lock&quot;</span><span style="color: #9399B2;">))</span><span style="color: #94E2D5;"> |</span><span style="color: #CBA6F7;font-style: italic;"> set</span><span style="color: #9399B2;">(</span><span>PACKAGES_DIR</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">rglob</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;Cargo.lock&quot;</span><span style="color: #9399B2;">))</span></span> <span class="giallo-l"><span style="color: #9399B2;"> )</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> for</span><span> cargo_lock</span><span style="color: #CBA6F7;"> in</span><span> cargo_locks</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span> lock_dir</span><span style="color: #94E2D5;"> =</span><span> cargo_lock</span><span style="color: #9399B2;">.</span><span>parent</span></span> <span class="giallo-l"><span> rel</span><span style="color: #94E2D5;"> =</span><span> lock_dir</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">relative_to</span><span style="color: #9399B2;">(</span><span>repo_root</span><span style="color: #9399B2;">)</span><span style="color: #CBA6F7;"> or</span><span style="color: #89B4FA;"> Path</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;.&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;font-style: italic;">f</span><span style="color: #A6E3A1;">&quot; cargo update --workspace in </span><span style="color: #F5C2E7;">{</span><span>rel</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span> subprocess</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">run</span><span style="color: #9399B2;">([</span><span style="color: #A6E3A1;">&quot;cargo&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;update&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;--workspace&quot;</span><span style="color: #9399B2;">],</span><span style="color: #EBA0AC;font-style: italic;"> cwd</span><span style="color: #94E2D5;">=</span><span>lock_dir</span><span style="color: #9399B2;">,</span><span style="color: #EBA0AC;font-style: italic;"> check</span><span style="color: #94E2D5;">=</span><span style="color: #FAB387;">True</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> # uv.lock — Python packages</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> for</span><span> uv_lock</span><span style="color: #CBA6F7;"> in</span><span style="color: #FAB387;font-style: italic;"> sorted</span><span style="color: #9399B2;">(</span><span>PACKAGES_DIR</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">rglob</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;uv.lock&quot;</span><span style="color: #9399B2;">)):</span></span> <span class="giallo-l"><span> lock_dir</span><span style="color: #94E2D5;"> =</span><span> uv_lock</span><span style="color: #9399B2;">.</span><span>parent</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;font-style: italic;">f</span><span style="color: #A6E3A1;">&quot; uv lock in </span><span style="color: #F5C2E7;">{</span><span>lock_dir</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">relative_to</span><span style="color: #9399B2;">(</span><span>repo_root</span><span style="color: #9399B2;">)</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span> subprocess</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">run</span><span style="color: #9399B2;">([</span><span style="color: #A6E3A1;">&quot;uv&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;lock&quot;</span><span style="color: #9399B2;">],</span><span style="color: #EBA0AC;font-style: italic;"> cwd</span><span style="color: #94E2D5;">=</span><span>lock_dir</span><span style="color: #9399B2;">,</span><span style="color: #EBA0AC;font-style: italic;"> check</span><span style="color: #94E2D5;">=</span><span style="color: #FAB387;">True</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">def</span><span style="color: #89B4FA;font-style: italic;"> main</span><span style="color: #9399B2;">() -&gt;</span><span style="color: #FAB387;"> None</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;Syncing versions from package.json to native manifests...&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> updated</span><span style="color: #94E2D5;"> =</span><span style="color: #FAB387;"> 0</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> for</span><span> pkg_json</span><span style="color: #CBA6F7;"> in</span><span style="color: #FAB387;font-style: italic;"> sorted</span><span style="color: #9399B2;">(</span><span>PACKAGES_DIR</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">rglob</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;package.json&quot;</span><span style="color: #9399B2;">)):</span></span> <span class="giallo-l"><span> pkg_dir</span><span style="color: #94E2D5;"> =</span><span> pkg_json</span><span style="color: #9399B2;">.</span><span>parent</span></span> <span class="giallo-l"><span> pkg_data</span><span style="color: #94E2D5;"> =</span><span style="color: #89B4FA;"> read_package_json</span><span style="color: #9399B2;">(</span><span>pkg_dir</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> pkg_data</span><span style="color: #CBA6F7;"> is</span><span style="color: #FAB387;"> None</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> continue</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> version</span><span style="color: #94E2D5;"> =</span><span> pkg_data</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">get</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;version&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span> version</span><span style="color: #CBA6F7;"> is</span><span style="color: #FAB387;"> None</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> continue</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> name</span><span style="color: #94E2D5;"> =</span><span> pkg_data</span><span style="color: #9399B2;">.</span><span style="color: #89B4FA;">get</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;name&quot;</span><span style="color: #9399B2;">,</span><span> pkg_dir</span><span style="color: #9399B2;">.</span><span>name</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;font-style: italic;">f</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #F5C2E7;">{</span><span>name</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;"> @ </span><span style="color: #F5C2E7;">{</span><span>version</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span> results</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> update_cargo_toml</span><span style="color: #9399B2;">(</span><span>pkg_dir</span><span style="color: #9399B2;">,</span><span> version</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> update_pyproject_toml</span><span style="color: #9399B2;">(</span><span>pkg_dir</span><span style="color: #9399B2;">,</span><span> version</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ]</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;"> if</span><span style="color: #FAB387;font-style: italic;"> any</span><span style="color: #9399B2;">(</span><span>r</span><span style="color: #94E2D5;"> ==</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>UPDATED</span><span style="color: #CBA6F7;"> for</span><span> r</span><span style="color: #CBA6F7;"> in</span><span> results</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span> updated</span><span style="color: #94E2D5;"> +=</span><span style="color: #FAB387;"> 1</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> elif</span><span style="color: #FAB387;font-style: italic;"> all</span><span style="color: #9399B2;">(</span><span>r</span><span style="color: #94E2D5;"> ==</span><span> SyncResult</span><span style="color: #9399B2;">.</span><span>NOT_FOUND</span><span style="color: #CBA6F7;"> for</span><span> r</span><span style="color: #CBA6F7;"> in</span><span> results</span><span style="color: #9399B2;">):</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot; (no native manifest found)&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;"> else</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot; (already up to date)&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;font-style: italic;">f</span><span style="color: #A6E3A1;">&quot;Synced </span><span style="color: #F5C2E7;">{</span><span>updated</span><span style="color: #F5C2E7;">}</span><span style="color: #A6E3A1;"> package(s).&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #89B4FA;"> refresh_lockfiles</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span style="color: #FAB387;font-style: italic;"> print</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;Done.&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">if</span><span> __name__</span><span style="color: #94E2D5;"> ==</span><span style="color: #A6E3A1;"> &quot;__main__&quot;</span><span style="color: #9399B2;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> main</span><span style="color: #9399B2;">()</span></span></code></pre><h4 id="reacting-to-package-tags">Reacting to package tags<a class="zola-anchor" href="#reacting-to-package-tags" aria-label="Anchor link for: reacting-to-package-tags" title="Anchor link for: reacting-to-package-tags"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <p>In the standard <code>changesets</code> flow, you will now have a pull request on GitHub with the appropriate <code>CHANGELOG.md</code> updates, as well as the metadata updates for all the relevant packages.</p> <p>Once that is merged, the very same action will run, realize all the <code>.changeset</code> files are consumed, and push tags.</p> <div class="aside aside--note"> <div class="aside__header" role="heading" aria-level="3"> <div class="aside__icon" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-info-circle"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /> <path d="M12 9h.01" /> <path d="M11 12h1v4h1" /> </svg> </div> <span class="aside__title">Note</span> </div> <div class="aside__body"><p>With our example configuration, <code>changesets</code> will only push tags, not publish packages, because we set</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="json"><span class="giallo-l"><span style="color: #A6E3A1;">&quot;privatePackages&quot;</span><span>: </span><span style="color: #9399B2;">{</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">version</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> true</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> &quot;</span><span style="color: #89B4FA;">tag</span><span style="color: #9399B2;">&quot;:</span><span style="color: #FAB387;"> true</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span></code></pre> <p>in our <code>.changeset/config.json</code> and have all the packages set to <code>private: true</code>.</p> </div> </div> <p>Typically, you'll then want to react to these pushed tags. For example, to build new docker images.</p> <p>For that, just set up your docker build workflows to trigger on the relevant tags for your package. For example, maybe you have a few docker images, one of which should trigger only when certain packages are updated.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #FAB387;">on</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> push</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> tags</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> # trigger only on these packages updating</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #A6E3A1;"> &#39;rust-one@*&#39;</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #A6E3A1;"> &#39;python-one@*&#39;</span></span></code></pre><pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="yaml"><span class="giallo-l"><span style="color: #FAB387;">on</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> push</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> tags</span><span style="color: #94E2D5;">:</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> # trigger on all packages updating except python-one</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #A6E3A1;"> &#39;*@*&#39;</span></span> <span class="giallo-l"><span style="color: #9399B2;"> -</span><span style="color: #A6E3A1;"> &#39;!python-one@*&#39;</span></span></code></pre><h2 id="summary">Summary<a class="zola-anchor" href="#summary" aria-label="Anchor link for: summary" title="Anchor link for: summary"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p><code>changesets</code> can manage per-package semantic versioning and changelogs in polyglot monorepos today, even without direct native support for multiple languages. The trick is to treat JavaScript package manifests as the canonical source of version bumps, and then syncing those bumps via your own scripts to the language-native manifests.</p> <p>A few gotchas exist (like explicitly making independent <code>pnpm-workspace.yaml</code> files for subdirectories you want to be independent, or using a separate personal access token to push tags), but none are blockers to being able to benefit from <code>changesets</code>'s convenient workflow.</p> <p>I used to suggest versioning monorepos with a single global version using a tool like <a rel="external" href="https://github.com/semantic-release/semantic-release"><code>semantic-release</code></a>. Since trying <code>changesets</code>, I'm sold on the benefits of letting people write commit messages for future internal engineers while also adding a separate changelog note for end users. These are often two distinct audiences, and relying on a single conventional commit to serve both is often suboptimal.</p> Fixing missing Omarchy boot entries Luke Hsiao 2026-01-24T00:00:00+00:00 2026-01-24T00:00:00+00:00 https://luke.hsiao.dev/blog/omarchy-boot-entries/ Some notes to self about how to use efibootmgr to fix missing boot entries from a fresh Omarchy install. <p>In a normal Omarchy installation, the expected behavior is that you will see two new boot options in your BIOS: <code>Omarchy</code> and <code>Limine</code>. There are two for good reason. <code>Omarchy</code> boots straight to Omarchy, and is what should be first in your boot order, typically. <code>Limine</code> goes to the Limine boot loader menu, which allows you to <a rel="external" href="https://learn.omacom.io/2/the-omarchy-manual/101/system-snapshots">restore from snapshots</a>, which is useful.</p> <p>However, in my experience, sometimes a fresh install will fail to set up these boot entries. Unfortunately, I have not spent the effort to discover the true root cause. Regardless, fixing it is easy.</p> <p>As a concrete example, I recently installed Omarchy on a ThinkPad T14 Gen 4, and after what appeared to be a successful installation, I had the <code>Limine</code> boot entry, but was missing the <code>Omarchy</code> one, meaning when I booted, I was wasting time in the Limine menu each boot.</p> <p>The <code>/boot</code> partition was set up as expected, though.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">❯</span><span style="color: #A6E3A1;"> lt /boot/EFI/</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">drwxr-xr-x</span><span style="color: #A6E3A1;"> - root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:50 /boot/EFI</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">drwxr-xr-x</span><span style="color: #A6E3A1;"> - root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:48 ├── BOOT</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">.rwxr-xr-x</span><span style="color: #A6E3A1;"> 291k root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:48 │ ├── BOOTIA32.EFI</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">.rwxr-xr-x</span><span style="color: #A6E3A1;"> 283k root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:48 │ └── BOOTX64.EFI</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">drwxr-xr-x</span><span style="color: #A6E3A1;"> - root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:49 ├── limine</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">.rwxr-xr-x</span><span style="color: #A6E3A1;"> 287k root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:49 │ ├── limine_x64.bak</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">.rwxr-xr-x</span><span style="color: #A6E3A1;"> 283k root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:49 │ └── limine_x64.efi</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">drwxr-xr-x</span><span style="color: #A6E3A1;"> - root</span><span style="color: #FAB387;"> 23</span><span style="color: #A6E3A1;"> Jan 15:50 └── Linux</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">.rwxr-xr-x</span><span style="color: #A6E3A1;"> 47M root</span><span style="color: #FAB387;"> 6</span><span style="color: #A6E3A1;"> Jan 10:29 └── omarchy_linux.efi</span></span></code></pre> <p>And, with that, it's very easy to fix the boot entries with <a rel="external" href="https://wiki.archlinux.org/title/EFI_boot_stub"><code>efibootmgr</code></a>.</p> <p>First, let's look at drive names.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">❯</span><span style="color: #A6E3A1;"> df -h</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Filesystem</span><span style="color: #A6E3A1;"> Size Used Avail Use% Mounted on</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">dev</span><span style="color: #A6E3A1;"> 7.7G</span><span style="color: #FAB387;"> 0</span><span style="color: #A6E3A1;"> 7.7G 0% /dev</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">run</span><span style="color: #A6E3A1;"> 7.7G 1.9M 7.7G 1% /run</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">efivarfs</span><span style="color: #A6E3A1;"> 196K 118K 74K 62% /sys/firmware/efi/efivars</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">/dev/mapper/root</span><span style="color: #A6E3A1;"> 237G 21G 214G 9% /</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">tmpfs</span><span style="color: #A6E3A1;"> 7.7G 580K 7.7G 1% /dev/shm</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">none</span><span style="color: #A6E3A1;"> 1.0M</span><span style="color: #FAB387;"> 0</span><span style="color: #A6E3A1;"> 1.0M 0% /run/credentials/systemd-journald.service</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">none</span><span style="color: #A6E3A1;"> 1.0M</span><span style="color: #FAB387;"> 0</span><span style="color: #A6E3A1;"> 1.0M 0% /run/credentials/systemd-resolved.service</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">none</span><span style="color: #A6E3A1;"> 1.0M</span><span style="color: #FAB387;"> 0</span><span style="color: #A6E3A1;"> 1.0M 0% /run/credentials/systemd-networkd.service</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">tmpfs</span><span style="color: #A6E3A1;"> 7.7G 8.0K 7.7G 1% /tmp</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">/dev/mapper/root</span><span style="color: #A6E3A1;"> 237G 21G 214G 9% /var/log</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">/dev/mapper/root</span><span style="color: #A6E3A1;"> 237G 21G 214G 9% /var/cache/pacman/pkg</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">/dev/mapper/root</span><span style="color: #A6E3A1;"> 237G 21G 214G 9% /home</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">/dev/nvme0n1p1</span><span style="color: #A6E3A1;"> 2.0G 144M 1.9G 8% /boot</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">tmpfs</span><span style="color: #A6E3A1;"> 1.6G 228K 1.6G 1% /run/user/1000</span></span></code></pre> <p>With these two pieces of information, adding an <code>Omarchy</code> boot entry is simple.</p> <p>First, you can run <code>sudo efibootmgr</code> to see your existing boot entries. You'll be able to confirm whether you're missing entries here.</p> <div class="aside aside--note"> <div class="aside__header" role="heading" aria-level="3"> <div class="aside__icon" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-info-circle"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /> <path d="M12 9h.01" /> <path d="M11 12h1v4h1" /> </svg> </div> <span class="aside__title">Note</span> </div> <div class="aside__body"><p>I should've recorded the output before the fix, but didn't think I'd write a post about it until after the fact. Hindsight is 20/20.</p> </div> </div> <p>Then, to add a new boot entry you can simply run:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">sudo</span><span style="color: #A6E3A1;"> efibootmgr</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --create</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --disk /dev/{drive-name}</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --part {partition-num}</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --loader /path/from/boot/to/efi</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --label &quot;YourLabel&quot;</span></span></code></pre> <p>So, given the output of <code>df</code>, where I see my <code>/boot</code> is mounted to <code>/dev/nvme0n1p1</code>, I know <code>/dev/nvme0n1</code> is my drive name, and it's on <code>p1</code> or partition <code>1</code>. Thus, my specific call would be the following.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">sudo</span><span style="color: #A6E3A1;"> efibootmgr</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --create</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --disk /dev/nvme0n1</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --part</span><span style="color: #FAB387;"> 1</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --loader /EFI/Linux/omarchy_linux.efi</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --label &quot;Omarchy&quot;</span></span></code></pre> <p>Same deal if you were missing a Limine entry (using my specifics as an example again).</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">sudo</span><span style="color: #A6E3A1;"> efibootmgr</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --create</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --disk /dev/nvme0n1</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --part</span><span style="color: #FAB387;"> 1</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --loader /EFI/limine/limine_x64.efi</span><span style="color: #F5C2E7;"> \</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> --label &quot;Limine&quot;</span></span></code></pre> <p>Once that's done, you should be able to see the boot entries in <code>efibootmgr</code>, as well as in your BIOS.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">❯</span><span style="color: #A6E3A1;"> sudo efibootmgr</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">BootCurrent:</span><span style="color: #FAB387;"> 0000</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Timeout:</span><span style="color: #FAB387;"> 0</span><span style="color: #A6E3A1;"> seconds</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">BootOrder:</span><span style="color: #A6E3A1;"> 0000,0001,001E,001F,0021,0020,0022,0023,0024,0025,0026</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0000*</span><span style="color: #A6E3A1;"> Omarchy HD</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">1,GPT,e7363f48-0ae8-47bb-bb0f-298baceaff27,0x800,0x400000</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/</span><span style="color: #F5C2E7;">\E</span><span style="color: #A6E3A1;">FI</span><span style="color: #F5C2E7;">\L</span><span style="color: #A6E3A1;">inux</span><span style="color: #F5C2E7;">\o</span><span style="color: #A6E3A1;">marchy_linux.efi</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0001*</span><span style="color: #A6E3A1;"> Limine HD</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">1,GPT,e7363f48-0ae8-47bb-bb0f-298baceaff27,0x800,0x400000</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/</span><span style="color: #F5C2E7;">\E</span><span style="color: #A6E3A1;">FI</span><span style="color: #F5C2E7;">\l</span><span style="color: #A6E3A1;">imine</span><span style="color: #F5C2E7;">\l</span><span style="color: #A6E3A1;">imine_x64.efi</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0010</span><span style="color: #A6E3A1;"> Setup FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">721c8b66-426c-4e86-8e99-3457c46ab0b9</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0011</span><span style="color: #A6E3A1;"> Boot Menu FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">126a762d-5758-4fca-8531-201a7f57f850</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0012</span><span style="color: #A6E3A1;"> Diagnostic Splash Screen FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">a7d8d9a6-6ab0-4aeb-ad9d-163e59a7a380</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0013</span><span style="color: #A6E3A1;"> Lenovo Diagnostics FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">3f7e615b-0d45-4f80-88dc-26b234958560</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0014</span><span style="color: #A6E3A1;"> Asset Information FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">da465b87-a26f-4c12-b78a-0361428fa026</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0015</span><span style="color: #A6E3A1;"> Regulatory Information FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">478c92a0-2622-42b7-a65d-5894169e4d24</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0016</span><span style="color: #A6E3A1;"> ThinkShield secure wipe FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">3593a0d5-bd52-43a0-808e-cbff5ece2477</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0017</span><span style="color: #A6E3A1;"> ThinkShield Passwordless Power-On Device Manager FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">08448b41-7f83-49be-82a7-0e84790ab133</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0018</span><span style="color: #A6E3A1;"> Wi-Fi Configuration FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">d3aaff0f-cb22-4792-896c-802c2e9383ba</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">2d004100700070000000</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0019</span><span style="color: #A6E3A1;"> Reinstall Windows from Cloud FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">3edbaac4-5017-4870-8cc4-721f9ef1974f</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">2d004100700070000000</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot001A</span><span style="color: #A6E3A1;"> Intel</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">R</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;"> MEBx FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">29a70110-7762-4211-ae88-fab19b7665be</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot001B</span><span style="color: #A6E3A1;"> Startup Interrupt Menu FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">f46ee6f4-4785-43a3-923d-7f786c3c8479</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot001C</span><span style="color: #A6E3A1;"> Rescue and Recovery FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">665d3f60-ad3e-4cad-8e26-db46eee9f1b5</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot001D</span><span style="color: #A6E3A1;"> Lenovo Memory Self Repair FvFile</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">4ddd67e7-bdf5-4473-8ab0-02821c084338</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot001E</span><span style="color: #A6E3A1;"> USB CD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,86701296aa5a7848b66cd49dd3ba6a55</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot001F</span><span style="color: #A6E3A1;"> USB FDD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,6ff015a28830b543a8b8641009461e49</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0020</span><span style="color: #A6E3A1;"> NVMe0 VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,001c199932d94c4eae9aa0b6e98eb8a400</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0021*</span><span style="color: #A6E3A1;"> USB HDD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,33e821aaaf33bc4789bd419f88c50803</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0022</span><span style="color: #A6E3A1;"> PXE BOOT VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,78a84aaf2b2afc4ea79cf5cc8f3d3803</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0023</span><span style="color: #A6E3A1;"> LENOVO CLOUD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,ad38ccbbf7edf04d959cf42aa74d3650</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/Uri</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">https://download.lenovo.com/pccbbs/cdeploy/efi/boot.efi</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0024</span><span style="color: #A6E3A1;"> ON-PREMISE VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,ad38ccbbf7edf04d959cf42aa74d3650</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/Uri</span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0025</span><span style="color: #A6E3A1;"> Other CD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,aea2090adfde214e8b3a5e471856a35400</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0026</span><span style="color: #A6E3A1;"> Other HDD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,ca88c2349e7ae947beeb43038a5aeae700</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0027*</span><span style="color: #A6E3A1;"> IDER BOOT CDROM PciRoot</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">0x0</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/Pci</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">0x14,0x0</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/USB</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">11,1</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0028*</span><span style="color: #A6E3A1;"> IDER BOOT Floppy PciRoot</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">0x0</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/Pci</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">0x14,0x0</span><span style="color: #9399B2;">)</span><span style="color: #A6E3A1;">/USB</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">11,0</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot0029*</span><span style="color: #A6E3A1;"> ATA HDD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,91af625956449f41a7b91f4f892ab0f6</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Boot002A*</span><span style="color: #A6E3A1;"> ATAPI CD VenMsg</span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">bc7838d2-0f82-4d60-8316-c068ee79d25b,aea2090adfde214e8b3a5e471856a354</span><span style="color: #9399B2;">)</span></span></code></pre> <p>Setting <code>Omarchy</code> to your first boot option will then boot straight to Omarchy.</p> The outsized impact of cultural idiosyncrasies Luke Hsiao 2026-01-21T00:00:00+00:00 2026-01-23T00:00:00+00:00 https://luke.hsiao.dev/blog/cultural-idiosyncrasies/ Cultural idiosyncrasies reveal what a company actually does, not just what it says. They can have an outsized impact on both public company image and internal culture and job satisfaction. <p>In the context of companies, cultural idiosyncrasies are fascinating to me. I'm of the opinion that culture (including mission, principles, and values) is one of the biggest factors that differentiates companies from one another. Sure, the product or service is often the largest differentiator, but culture—and in particular, cultural idiosyncrasies—has an outsized impact on both public company image as well as internal culture and job satisfaction.</p> <p>Why?</p> <p>I believe it's because these idiosyncrasies are evidence of what a company <em>does</em>, and not just what it <em>says</em> it does. They reflect principles and values in uniquely powerful ways to both potential customers and employees.</p> <h2 id="some-examples">Some examples<a class="zola-anchor" href="#some-examples" aria-label="Anchor link for: some-examples" title="Anchor link for: some-examples"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>Let me draw some examples from my own lived experience, and I'm sure I'm missing some compelling examples further back (Sun Microsystems, Xerox PARC).</p> <p>Also, and importantly, I'm not saying any of these idiosyncrasies are "right", but I am saying they all have an outsized impact on my perception of the companies.</p> <h3 id="google">Google<a class="zola-anchor" href="#google" aria-label="Anchor link for: google" title="Anchor link for: google"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>One of the earliest examples from my own memory is Google. Not only did I experience how Google Search took over the market with its minimalism and effectiveness, but I also grew up hearing all sorts of news about how Google was "the best place to work". In those articles, did they talk about search? Nope. They talked about cultural idiosyncrasies: free meals from on-site cooks, nap pods, 20%-projects, slides in the lobbies, micro-kitchens, laundry services!</p> <h3 id="oxide-computer">Oxide Computer<a class="zola-anchor" href="#oxide-computer" aria-label="Anchor link for: oxide-computer" title="Anchor link for: oxide-computer"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Oxide is a prime example. They frequently feature in online discussion, but in my perception, the majority of this discussion isn't about their product; it's about their culture, and often with highly positive sentiment. They find this so core to their company that they <a rel="external" href="https://oxide-and-friends.transistor.fm/episodes/cultural-idiosyncrasies">dedicated a podcast episode to it</a>.</p> <p>Some examples are demo Fridays, morning water-cooler, <a rel="external" href="https://rfd.shared.oxide.computer/rfd/0083">no-meet Wednesdays</a>, recorded meetings, dog-pile debugging, <a rel="external" href="https://rfd.shared.oxide.computer/rfd/0001">RFDs</a> (requests for discussion), no performance reviews, an overwhelmingly <a rel="external" href="https://rfd.shared.oxide.computer/rfd/0003">writing-focused hiring process</a>, and <a rel="external" href="https://oxide.computer/blog/compensation-as-a-reflection-of-values">uniform and transparent pay</a>, to name a few.</p> <h3 id="gitlab">GitLab<a class="zola-anchor" href="#gitlab" aria-label="Anchor link for: gitlab" title="Anchor link for: gitlab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>GitLab may not have the popularity of GitHub, but they have made a strong impression on me for at least two of their idiosyncrasies.</p> <p>One of the first of these was particularly prominent during the COVID-19 pandemic: <a rel="external" href="https://handbook.gitlab.com/handbook/company/culture/all-remote/guide/">a global, all-remote workforce</a>. They are one of the largest all-remote companies with over 1,500 members in 65+ countries.</p> <p>They are also known for their <a rel="external" href="https://handbook.gitlab.com/">transparent, handbook-first approach</a>. They publish their handbook for all to see. This is not brief, either. It comprehensively covers everything: engineering, finance, sales, legal, and more.</p> <p>This is an incredible show of transparency and goodwill in sharing hard-earned lessons publicly.</p> <h3 id="37signals">37signals<a class="zola-anchor" href="#37signals" aria-label="Anchor link for: 37signals" title="Anchor link for: 37signals"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Not to be outdone, the 37signals folks also <a rel="external" href="https://basecamp.com/handbook">publish their handbook</a>.</p> <p>In their case, they have also published <a rel="external" href="https://basecamp.com/books">highly profitable books</a> about their idiosyncrasies and culture, such as <a rel="external" href="https://basecamp.com/shapeup"><em>Shape Up</em></a> (an engineering methodology that many companies are now adopting), <em>REWORK</em> (unconventional business advice), and <em>REMOTE</em> (on remote work).</p> <p>They also have idiosyncrasies like <a rel="external" href="https://world.hey.com/dhh/all-in-on-omarchy-at-37signals-68162450">Omarchy</a>, an opinionated distribution of Arch Linux. They liked it so much they ditched MacBooks as the standard-issue developer laptop and switched to Framework laptops.</p> <h3 id="tonari">Tonari<a class="zola-anchor" href="#tonari" aria-label="Anchor link for: tonari" title="Anchor link for: tonari"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Tonari is a small Japanese tech startup making some of the most interesting audio/video portals available.</p> <p>Despite being very small, they contribute significant pieces of their stack to open source, such as <a rel="external" href="https://github.com/tonarino/innernet">innernet</a>, a private network system using WireGuard under the hood.</p> <p>They also value a heterogeneous compute mix for their developers and were early on <a rel="external" href="https://blog.tonari.no/why-we-love-rust">the Rust train</a> (note how each of the four engineers mentioned in this blog runs a different operating system: macOS, Arch, Ubuntu, and Pop!_OS).</p> <h3 id="palantir">Palantir<a class="zola-anchor" href="#palantir" aria-label="Anchor link for: palantir" title="Anchor link for: palantir"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Palantir breaks the mold a bit in this list, because most of the public discussion around it is controversial and focused on the ethics of its business.</p> <p>That said, they are known for their idiosyncratic <a rel="external" href="https://www.linkedin.com/posts/saileshpattnaik_inside-palantir-where-chaos-is-culture-and-activity-7397188784699666433-7usw/">chaos culture</a>, where "everything is up for debate"—even a random engineer confronting the CEO at all-hands—and where they do almost anything to move fast—like chartering a private jet to Palo Alto to get the engineering team together during the COVID-19 pandemic.</p> <p>People I know who have worked there tell me this is an accurate representation.</p> <h3 id="jane-street">Jane Street<a class="zola-anchor" href="#jane-street" aria-label="Anchor link for: jane-street" title="Anchor link for: jane-street"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Notoriously, it's <em>the</em> OCaml shop people know about. Compensation so high, even for interns, that it's an idiosyncrasy.</p> <p>And, most notably to me, they have one of the most interesting tech blogs around. For example, their <a rel="external" href="https://www.janestreet.com/tech-talks/janestreet-code-review/">code review process</a> has such a good reputation that other tech bloggers <a rel="external" href="https://tigerbeetle.com/blog/2025-08-04-code-review-can-be-better/">carve out exceptions for it</a> when talking about code review problems. They care so much about rigor in tests that they not only leverage <a rel="external" href="https://blog.janestreet.com/getting-from-tested-to-battle-tested/">bleeding-edge strategies</a> like deterministic simulation testing (DST) but they also <a rel="external" href="https://www.prnewswire.com/in/news-releases/jane-street-leads-antithesiss-105m-series-a-to-make-deterministic-simulation-testing-the-new-standard-302631118.html">led the funding round of Antithesis, the pioneers in DST</a>. This is highly idiosyncratic.</p> <h3 id="amazon">Amazon<a class="zola-anchor" href="#amazon" aria-label="Anchor link for: amazon" title="Anchor link for: amazon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Even for massive companies like Amazon, some cultural idiosyncrasies still have an outsized impact.</p> <p>For example, I suspect most people have heard of <a rel="external" href="https://www.forbes.com/councils/forbescommunicationscouncil/2022/08/30/why-and-how-every-company-should-use-amazons-six-page-memo-format/">Amazon's 6-page memo</a> approach to meetings. In this approach, no attendees prepare beforehand, and instead of using PowerPoint, Amazon executives sit around a table and read six-page memos in silence before discussion commences.</p> <p>Likewise, Amazon is well known for its <a rel="external" href="https://martinfowler.com/bliki/TwoPizzaTeam.html">two-pizza teams</a>, where the idea is that a team should not be larger than can be fed with two pizzas. That way it remains small, cohesive, and has efficient working relationships.</p> <h3 id="zappos">Zappos<a class="zola-anchor" href="#zappos" aria-label="Anchor link for: zappos" title="Anchor link for: zappos"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Zappos is well known for its unique <a rel="external" href="https://www.businessinsider.com/zappos-tony-hsieh-paid-new-workers-to-quit-the-offer-2020-11?op=1">pay-to-quit</a> approach to making sure the team is invested. It provides an attractive off-ramp for those who are not as committed or interested in the company after their training period, instead of staying despite lacking passion the work.</p> <h3 id="patagonia">Patagonia<a class="zola-anchor" href="#patagonia" aria-label="Anchor link for: patagonia" title="Anchor link for: patagonia"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Patagonia is well known for its <a rel="external" href="https://www.patagonia.com/product/let-my-people-go-surfing-revised-paperback-book/BK067.html"><em>Let My People Go Surfing</em></a> policy, which prioritizes work-life balance, purpose, and employee autonomy. The policy encourages staff to embrace flexibility and spontaneity in balancing work and play. If the waves are good or the powder fresh, staff are trusted to step away, surf, or enjoy nature, returning to work with renewed purpose.</p> <h3 id="valve">Valve<a class="zola-anchor" href="#valve" aria-label="Anchor link for: valve" title="Anchor link for: valve"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Valve is the famously "flat" company, or "flatland", <a rel="external" href="http://media.steampowered.com/apps/valve/Valve_Handbook_LowRes.pdf">as their public handbook calls it</a>. They "don't have any management, and nobody reports to anybody." They playfully poke at Google's 20% time by saying that at Valve, it's 100%. Of course, this comes with its challenges, but this idiosyncrasy has become an integral part of my perception of Valve.</p> <h3 id="vannevar-labs">Vannevar Labs<a class="zola-anchor" href="#vannevar-labs" aria-label="Anchor link for: vannevar-labs" title="Anchor link for: vannevar-labs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Sample size of 1, but I've had a friend that really appreciates Vannevar's $250/month stipend for mental/physical health and $300/month stipend to pay for house cleaners. These dedicated benefits are particularly on-point for a mostly remote team. As a company that heavily hires forward deployed engineers (FDEs), they also have very high autonomy.</p> <h2 id="thoughts">Thoughts<a class="zola-anchor" href="#thoughts" aria-label="Anchor link for: thoughts" title="Anchor link for: thoughts"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I'm sure this list could be ten times longer with some more thought. But perhaps that supports the point. All of these examples came almost immediately off the top of my head!</p> <p>If you're running a company, I think it's important to be intentional about your culture. Both in the mission, principles, and values you profess and in the idiosyncrasies that uniquely evolve because of them.</p> <p>People <em>will</em> talk about your cultural idiosyncrasies to the benefit or detriment of your company. Your employees will reach for your idiosyncrasies when they talk about what is good or bad about working for your company. Your customers will point to your idiosyncrasies as evidence for or against value alignment. Your future employees will compare and contrast your idiosyncrasies with their experiences as they consider their next career move.</p> <p>And random people like me will evidently spend (too much?) time thinking about your idiosyncrasies and trying to theory-craft the perfect set of cultural idiosyncrasies for their hypothetical future company.</p> 2026 is the year I return to rigorous planning Luke Hsiao 2026-01-08T00:00:00+00:00 2026-01-08T00:00:00+00:00 https://luke.hsiao.dev/blog/planning/ 2026 is the year I return to time-block planning and full-task capture. Here are some thoughts around my implementation. <p>2026 is the year I return to some rigorous planning and productivity habits I've let slip over the years. Specifically, <a rel="external" href="https://www.timeblockplanner.com/">time-block planning</a> (à la Cal Newport) and full-task capture (à la <a rel="external" href="https://en.wikipedia.org/wiki/Getting_Things_Done">Getting Things Done (GTD)</a> by David Allen).</p> <p>The motivation for this change is that I anticipate this will be an unusually busy year for me at work, at home, and in my communities. I want to make sure I make the most of the opportunities.</p> <p>The motivation for this post is twofold. First, for a bit of documentation and musing on how I have specifically implemented these ideas this time around. Second, for a little bit of self-accountability. Who knows, maybe this will also motivate someone else to reevaluate their productivity habits.</p> <p>While we're only a week into this year, this is a system I have used extensively since early high school—just less so recently. Consequently, I already know that it is an effective catalyst for my own productivity.</p> <h2 id="the-methods">The methods<a class="zola-anchor" href="#the-methods" aria-label="Anchor link for: the-methods" title="Anchor link for: the-methods"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I'll refer you to the links at the top of this post for more thorough exploration on what these systems mean. However, for the purpose of this post, I want to briefly describe these two methods.</p> <h3 id="time-block-planning">Time-block planning<a class="zola-anchor" href="#time-block-planning" aria-label="Anchor link for: time-block-planning" title="Anchor link for: time-block-planning"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Time-block planning is an intentional method for planning your days—hopefully a method that leads to being able to do <a rel="external" href="https://www.goodreads.com/book/show/30241038-cal-newport-s-deep-work">deep work</a>, which I, in agreement with Newport, believe is vitally important in our modern distracted world. This is particularly well-suited to knowledge workers, and it also served me well when I was a student.</p> <p>Many knowledge workers spend their day <em>reacting</em>. Reacting to messages, emails, changing priorities, changing requirements, meetings, etc. This often leads to anxiety, overload, a lack of clarity, a feeling of chaos, and often impedes progress on things that require deep, focused effort.</p> <p>Time-blocking is all about proactive <em>intention</em>. Each day you fill out your calendar with time blocks representing a preliminary plan that gives every minute a purpose. If you get knocked off this schedule, you simply update it the next time you have a chance.</p> <p>This sounds simple.</p> <p>It is.</p> <p>You can <a rel="external" href="https://staysaasy.com/leadership/2024/03/12/Getting-Things-Done.html">get things done in a chaotic environment</a>.</p> <h3 id="full-task-capture">Full task capture<a class="zola-anchor" href="#full-task-capture" aria-label="Anchor link for: full-task-capture" title="Anchor link for: full-task-capture"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This probably isn't the right term in a GTD context, but it conveys the methodology I find useful. The idea here is that you should <strong>capture everything</strong> that has your attention (tasks, ideas, etc.) into an external system so that you can free your mind from lingering unproductively and unreliably on them. This capture should be both complete and as immediate as possible, and should go into a single designated spot (e.g., an "inbox"). Then, you can process this list later with intention, either scheduling things, stashing things for later, or intentionally discarding things.</p> <p>If you're the type of person who frequently has things "fall through the cracks" or finds yourself up at night repeatedly looping through things you need to do, you might find this technique refreshingly freeing.</p> <h2 id="my-implementation">My implementation<a class="zola-anchor" href="#my-implementation" aria-label="Anchor link for: my-implementation" title="Anchor link for: my-implementation"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>So, now that you have the basic ideas, I want to share my current implementation.</p> <p>Implementation is a highly personal choice. Some people prefer pen and paper (like Newport does). To go further, some people might implement these ideas in the context of notebook-specific systems, like <a rel="external" href="https://bulletjournal.com/">bullet journaling</a>, or a <a rel="external" href="https://www.jetpens.com/blog/Guide-to-the-Hobonichi-Techo-Planner/pt/900">Hobonichi Techo</a>.</p> <p>Others might use special, purpose-built apps on their computer or phone. Others might use intentionally basic apps or systems like <a rel="external" href="https://todotxt.org/">todo.txt</a>. Others might use a big whiteboard in their office.</p> <p>While those who know me know I love stationery, I've found that the single most important factor for success with these systems is low friction. If I remember a task in the middle of the night, and that means I need to go find a pen and my notebook, it's not happening. But I can always find my phone for a quick note.</p> <p>So, I use Google Calendar and Google Tasks. They are already on my phone. They are easily accessible on both phone and computer. They have sufficient features to implement the systems.</p> <h3 id="time-block-planning-in-google-calendar">Time block planning in Google Calendar<a class="zola-anchor" href="#time-block-planning-in-google-calendar" aria-label="Anchor link for: time-block-planning-in-google-calendar" title="Anchor link for: time-block-planning-in-google-calendar"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>A good work week usually looks something like this.</p> <figure > <a href="img&#x2F;gcal.png"> <img src="img&#x2F;gcal.png" alt="Time-blocking in Google Calendar" style="background-color: white;" /> </a> <figcaption> Time-blocking in Google Calendar. </figcaption> </figure> <div class="aside aside--note"> <div class="aside__header" role="heading" aria-level="3"> <div class="aside__icon" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-info-circle"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /> <path d="M12 9h.01" /> <path d="M11 12h1v4h1" /> </svg> </div> <span class="aside__title">Note</span> </div> <div class="aside__body"><p>I have several Google accounts, so they all feed into a master view on my phone that includes time blocking before and after work hours.</p> </div> </div> <p>While the image above shows a fully-populated week, I actually only time-block one day ahead. That is, during the "shutdown ritual" of the day, I review the tasks and ideas I may have captured within the context of my current task lists, then time-block the next day based on those tasks. One exception here is recurring events are scheduled as recurring, and thus are already populated on their respective days. While the screenshot above doesn't show this well, it's also important to have some empty buffer time to absorb unexpected events, or variance in how long things take. This is another advantage of digital over paper: I can easily schedule events far into the future and not lose the information. With a paper planner, it's hard to schedule someone's wedding in a year.</p> <p>As the day proceeds, if things do not go according to plan, I adjust the blocks on the calendar to reflect reality. This is a valuable exercise both as an artifact to review where I actually spent my time on a given day and as useful feedback to improve my time estimations. For example, you may notice I spent about three full days testing in the screenshot above. I had initially anticipated that would take a single day. The act of making things reflect reality is a good way to calibrate myself on how long to expect for similar work. It goes the other way too: sometimes things take much less time than expected.</p> <p>When it comes to deep work, it feels good to see those larger, uninterrupted blocks of time. If those blocks were short and highly interrupted, I'd take it as a signal to make adjustments that allow me to do deeper, more meaningful work with less context switching.</p> <p>You can see how time-block planning would work effectively in a knowledge worker's or student's life, but likely not in the life of an emergency room nurse.</p> <h3 id="full-task-capture-in-google-tasks">Full task capture in Google Tasks<a class="zola-anchor" href="#full-task-capture-in-google-tasks" aria-label="Anchor link for: full-task-capture-in-google-tasks" title="Anchor link for: full-task-capture-in-google-tasks"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>The last time I used Google Tasks was a few years ago, and they've made some nice improvements since. For the purpose of full task capture, I keep a relatively simple system. Not quite as simple as a <code>todo.txt</code>, but still minimal while offering nice organization features like recurring tasks, scheduled tasks, and sub-tasks.</p> <figure > <a href="img&#x2F;gtasks.png"> <img src="img&#x2F;gtasks.png" alt="Full task capture in Google Tasks" style="background-color: white;" /> </a> <figcaption> Full task capture with Google Tasks. </figcaption> </figure> <p>I organize Google Tasks somewhat Kanban-style.</p> <p>Google Tasks has a default list you cannot delete. I use that as my "inbox", but I title it "Maybe?". All quick notes go into here for later processing if I don't have time to put it in the right list immediately. If I decide to do it, it goes into the appropriate list. If I don't, it gets deleted from here. I sort this with "My order" sorting so I can arbitrarily sort it with highest priority at the top.</p> <p>On the left, I have my "Not now" list. These are things I want to do at some point, but they aren't urgent. I just don't want to lose them, so this is much like a backlog. This is also sorted "My order", with highest priority at the top.</p> <p>In the middle, I have "In progress", which could also be named "To do". These are things I'm actively working on. Everything in this list has a date indicating when I intend to work on it. I might add deadlines if relevant. I sort this list by "Date" so that the things I need to do today are always up top, and I can quickly see what I need to do next.</p> <p>This is the <strong>only</strong> list that I mark things as "complete" from!</p> <p>Conveniently, these also show up in Google Calendar as you do them, providing a useful artifact of tasks you completed that day.</p> <p>Next, I have "On hold". These are things I was working on, but are now blocked for some reason. A holding area. When they become unblocked, they will go back to "In progress" with the appropriate dates. Sorted in "My order", with highest priority at the top.</p> <p>Finally, "Ideas". This is just a list of ideas sorted in "My order" with most interesting at the top. I also find it handy to jot down things like gift ideas for people in the moment I discover them.</p> <p>When I add these items, I also occasionally use <code>todo.txt</code>-style tags like <code>+{project}</code> or <code>@{context}</code>, even though Google Tasks has now way to filter for those.</p> <p>When I was a student, this was a superpower; I <a rel="external" href="https://luke.hsiao.dev/blog/freshman-success/#syllabus-ingestion-and-organization">still give the advice today</a>. As a student, I would start each semester by inputting all coursework dates from every class syllabus. I always knew what was due, which weeks would be heavy, and which would be light.</p> <p>Another common question from fellow knowledge workers is how this fits in with other task tracking systems you might have at work (e.g., Linear, Jira, etc.). The answer there is that this is far more lightweight. I'm not writing lengthy descriptions or logging progress and notes on these items. These are written as "just enough" context to know what they refer to clearly. This is a personal system that complements the systems your teams might be using.</p> <h2 id="thoughts-so-far">Thoughts so far<a class="zola-anchor" href="#thoughts-so-far" aria-label="Anchor link for: thoughts-so-far" title="Anchor link for: thoughts-so-far"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I should never have stopped doing this! When you develop a productivity system that works for you personally, it feels like a bandwidth multiplier.</p> <p>There is also much more to discuss about setting goals, <a rel="external" href="https://swizec.com/blog/your-career-needs-a-vision/">having a vision for your career</a>, and other high-level planning that ultimately feeds your day-to-day work, but those are out of scope for this particular post.</p> <p>This is all about the nitty-gritty of day-to-day productivity.</p> <p>This system might not work for you, but I hope it has sparked some ideas as you work on your own systems.</p> My favorite software in 2025 Luke Hsiao 2025-12-17T00:00:00+00:00 2025-12-20T00:00:00+00:00 https://luke.hsiao.dev/blog/favorite-software-2025/ A big list of my favorite software tools in 2025. <p>As an engineer, I'm always refining which tools are in my toolbox. Here's a big list of software I enjoy using in 2025. Like <a href="https://luke.hsiao.dev/blog/favorite-software-2024/">2024</a>, I'm omitting things I don't touch at least once in a month. So, please refer to <a href="https://luke.hsiao.dev/blog/favorite-software-2023/">2023</a> for some honorable, but less regular, mentions.</p> <span id="continue-reading"></span><h2 id="the-list">The List<a class="zola-anchor" href="#the-list" aria-label="Anchor link for: the-list" title="Anchor link for: the-list"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <h3 id="aerc-golang-mit-license"><a rel="external" href="https://aerc-mail.org/">aerc</a> <img src="https://img.shields.io/badge/lang-go-cadetblue" alt="golang" /> <a rel="external" href="https://git.sr.ht/~rjarry/aerc/tree/master/item/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#aerc-golang-mit-license" aria-label="Anchor link for: aerc-golang-mit-license" title="Anchor link for: aerc-golang-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This is my email client of choice. I tried <code>mutt</code>, but the configuration was too complex for me. <code>aerc</code>, on the other hand, is very simple to configure, and works great in the terminal. I'm an advocate for <a rel="external" href="https://useplaintext.email/">using plaintext email</a>, and <code>aerc</code> works great for using <a rel="external" href="https://git-send-email.io/">email for git patches</a>. It lets me use my <code>$EDITOR</code> of choice, search and filtering works well, and it's quick. Let me also call out a new discovery here: <a rel="external" href="https://man.archlinux.org/man/carddav-query.1.en"><code>carddav-query</code></a>. This works far better than the largely unmaintained <code>goobook</code> I was using previously.</p> <h3 id="atuin-rust-mit-license"><a rel="external" href="https://aerc-mail.org/">atuin</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/atuinsh/atuin/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#atuin-rust-mit-license" aria-label="Anchor link for: atuin-rust-mit-license" title="Anchor link for: atuin-rust-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>You don't realize how useful synced shell history is until you try it. You should absolutely try it. Installed it on a whim, and have been delighted since.</p> <h3 id="bat-rust-mit-or-apache-2-0"><a rel="external" href="https://github.com/sharkdp/bat">bat</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#bat-rust-mit-or-apache-2-0" aria-label="Anchor link for: bat-rust-mit-or-apache-2-0" title="Anchor link for: bat-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I've aliased <code>cat</code> to <code>bat</code> and never looked back. The syntax highlighting, the line numbers, the git integration, the paging, it all just makes it nice to use.</p> <h3 id="berkeley-mono-tx-02-proprietary"><a rel="external" href="https://berkeleygraphics.com/typefaces/berkeley-mono/">Berkeley Mono</a> (TX-02) <img src="https://img.shields.io/badge/closed%20source-black" alt="proprietary" /><a class="zola-anchor" href="#berkeley-mono-tx-02-proprietary" aria-label="Anchor link for: berkeley-mono-tx-02-proprietary" title="Anchor link for: berkeley-mono-tx-02-proprietary"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This is my favorite coding font, and this year <a rel="external" href="https://usgraphics.com/products/berkeley-mono/releases">they released new versions</a>! Not only was it worth the initial $75, I paid more for a new web license. Importantly, the new version includes more slender versions and variable fonts, so I really don't miss <a rel="external" href="https://github.com/be5invis/Iosevka">Iosevka</a> anymore.</p> <h3 id="bottom-rust-mit-license"><a rel="external" href="https://clementtsang.github.io/bottom/0.9.3/">bottom</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/ClementTsang/bottom/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#bottom-rust-mit-license" aria-label="Anchor link for: bottom-rust-mit-license" title="Anchor link for: bottom-rust-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>btm</code> is a better <code>top</code> (and <code>htop</code>). I originally thought I wouldn't need more than <code>htop</code>, until I tried to run it on a massive server with over 150 cores. <code>htop</code> you need to configure differently to see anything at all (dropping all the CPU bars), and it is <em>slow</em>. <code>btm</code> handles it just fine out of the box. The filtering on the processes widget is very convenient. The ability to see all the other stuff like temps, GPU memory usage, network utilization, disk utilization, those are all cherries on top. I'll also call out that <a rel="external" href="https://github.com/aristocratos/btop">btop</a> is also very fun, but I find bottom more useful.</p> <h3 id="chezmoi-golang-mit-license"><a rel="external" href="https://www.chezmoi.io/">chezmoi</a> <img src="https://img.shields.io/badge/lang-go-cadetblue" alt="golang" /> <a rel="external" href="https://github.com/twpayne/chezmoi/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#chezmoi-golang-mit-license" aria-label="Anchor link for: chezmoi-golang-mit-license" title="Anchor link for: chezmoi-golang-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I've tried a few different ways to manage <a rel="external" href="https://github.com/lukehsiao/dotfiles">my personal dotfiles</a>. Before <code>chezmoi</code>, I stored them <a rel="external" href="https://www.atlassian.com/git/tutorials/dotfiles">as a bare git repo</a>, as found on <a rel="external" href="https://news.ycombinator.com/item?id=11070797">HN</a>. But, the killer feature for me was templating. Even though I don't use much of <code>chezmoi</code>'s fancier features, just being able to use templates so that I could populate dotfiles based on the machine I was on was great.</p> <p>Ever since switching to Omarchy, I've revamped usage of my dotfile repo.</p> <h3 id="choose-rust-gpl-3-0"><a rel="external" href="https://github.com/theryangeary/choose">choose</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/theryangeary/choose/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-GPL--3.0-whitesmoke" alt="GPL-3.0" /></a><a class="zola-anchor" href="#choose-rust-gpl-3-0" aria-label="Anchor link for: choose-rust-gpl-3-0" title="Anchor link for: choose-rust-gpl-3-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>choose</code> is a human-friendly alternative to <code>cut</code> and (sometimes) <code>awk</code>. I used to use <code>awk</code> for most of this type of task, but choose just makes it easier. It makes it easy to chop out columns I want while piping things around.</p> <h3 id="codebook-rust-mit-license"><a rel="external" href="https://github.com/blopker/codebook">codebook</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/blopker/codebook/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#codebook-rust-mit-license" aria-label="Anchor link for: codebook-rust-mit-license" title="Anchor link for: codebook-rust-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>codebook</code> is another spell-checker for code, like <code>typos</code>. It's high quality and I enjoy it as a default LSP for text.</p> <h3 id="d2-golang-mpl-2-0"><a rel="external" href="https://d2lang.com/">D2</a> <img src="https://img.shields.io/badge/lang-go-cadetblue" alt="golang" /> <a rel="external" href="https://github.com/terrastruct/d2/blob/master/LICENSE.txt"><img src="https://img.shields.io/badge/license-MPL--2.0-whitesmoke" alt="MPL-2.0" /></a><a class="zola-anchor" href="#d2-golang-mpl-2-0" aria-label="Anchor link for: d2-golang-mpl-2-0" title="Anchor link for: d2-golang-mpl-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This is a new addition. In general, I'm a big fan of <a rel="external" href="https://text-to-diagram.com/">"declarative diagraming"</a>, and D2 is the new one on the block (vs <a rel="external" href="https://plantuml.com/">PlantUML</a>, <a rel="external" href="https://mermaid.js.org/">MermaidJS</a>, <a rel="external" href="https://graphviz.org/">GraphViz</a>, etc.) So far, I love the syntax and functionality. If I'm drawing up something quick that doesn't warrant something like <code>draw.io</code>, I've been reaching for D2 first and <a rel="external" href="https://excalidraw.com/">excalidraw</a> second. Their <a rel="external" href="https://d2lang.com/tour/tala/">TALA layout engine</a> is proprietary, but looking quite nice.</p> <h3 id="delta-rust-mit"><a rel="external" href="https://dandavison.github.io/delta/">delta</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/dandavison/delta/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#delta-rust-mit" aria-label="Anchor link for: delta-rust-mit" title="Anchor link for: delta-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>delta</code> is the best syntax-highlighting pager for git, diff, and grep output I've seen. I used to use <a rel="external" href="https://github.com/so-fancy/diff-so-fancy"><code>diff-so-fancy</code></a>, but since switching to <code>delta</code>, I haven't looked back. This makes the experience of using git in the terminal much improved.</p> <h3 id="difftastic-rust-mit"><a rel="external" href="https://difftastic.wilfred.me.uk/">difftastic</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/Wilfred/difftastic/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#difftastic-rust-mit" aria-label="Anchor link for: difftastic-rust-mit" title="Anchor link for: difftastic-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>difftastic</code> is the another key tool I use side-by-side with <code>delta</code>. It is a <em>structural diff tool that understand syntax</em>. This means that many times, it is a much more succinct diff for files. I have this aliased in <code>git</code> as well, and often use both <code>delta</code> and <code>difftastic</code> when looking at diffs.</p> <h3 id="dust-rust-apache-2-0"><a rel="external" href="https://github.com/bootandy/dust">dust</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/bootandy/dust/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-whitesmoke" alt="Apache-2.0" /></a><a class="zola-anchor" href="#dust-rust-apache-2-0" aria-label="Anchor link for: dust-rust-apache-2-0" title="Anchor link for: dust-rust-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This is like <code>du</code>, but better. It's faster, and its visually nicer. This is my go-to way for figuring out where my disk space is being used.</p> <h3 id="eza-rust-mit-license"><a rel="external" href="https://eza.rocks/">eza</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/ogham/exa/blob/master/LICENCE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#eza-rust-mit-license" aria-label="Anchor link for: eza-rust-mit-license" title="Anchor link for: eza-rust-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This is a replacement for <code>ls</code> that is just better. Similar to <code>bat</code>, after aliasing <code>ls</code> to <code>eza</code>, I haven't looked back.</p> <h3 id="fd-rust-mit-or-apache-2-0"><a rel="external" href="https://github.com/sharkdp/fd">fd</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#fd-rust-mit-or-apache-2-0" aria-label="Anchor link for: fd-rust-mit-or-apache-2-0" title="Anchor link for: fd-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>fd</code> is a replacement for <code>find</code>. Similar to <code>exa</code> and <code>bat</code>, this is another rust-based tool that feels more intuitive and does the job better.</p> <h3 id="fish-rust-gpl-2-0"><a rel="external" href="https://fishshell.com/">fish</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/fish-shell/fish-shell/blob/master/COPYING"><img src="https://img.shields.io/badge/license-GPL--2.0-whitesmoke" alt="GPL-2.0" /></a><a class="zola-anchor" href="#fish-rust-gpl-2-0" aria-label="Anchor link for: fish-rust-gpl-2-0" title="Anchor link for: fish-rust-gpl-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Fish has been a great shell. I just used plain bash before this. I switched a couple of years ago, and while I don't think there has been any particularly large "killer feature" for me, I have had no reason to switch to anything else since. The UI niceties (e.g., autosuggestions, tab completions, syntax highlighting, 24-bit color) all make the terminal experience pleasant. It's also being slowly <a rel="external" href="https://github.com/fish-shell/fish-shell/pull/9512">rewritten in rust</a>.</p> <p>I'm interested in trying <a rel="external" href="https://www.nushell.sh/"><code>nushell</code></a> at some point soon, but I haven't jumped in yet.</p> <h3 id="ghostty-zig-mit-license"><a rel="external" href="https://ghostty.org/">Ghostty</a> <img src="https://img.shields.io/badge/lang-Zig-orange" alt="zig" /> <a rel="external" href="https://github.com/ghostty-org/ghostty/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#ghostty-zig-mit-license" aria-label="Anchor link for: ghostty-zig-mit-license" title="Anchor link for: ghostty-zig-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I've switched from <a rel="external" href="https://wezterm.org/">Wezterm</a> to Ghostty. While there is still <a rel="external" href="https://github.com/ghostty-org/ghostty/discussions/2394#discussioncomment-148329330">1 killer feature I'm missing</a>, I've thoroughly enjoyed Ghostty's performance and simplicity. I appreciate the <a rel="external" href="https://www.jeffquast.com/post/state-of-terminal-emulation-2025/">dedication to correctness</a> and performance.</p> <h3 id="git-c-gpl-2-0"><a rel="external" href="https://git-scm.com/">git</a> <img src="https://img.shields.io/badge/lang-C-darkslategray" alt="c" /> <a rel="external" href="https://github.com/git/git/blob/master/COPYING"><img src="https://img.shields.io/badge/license-GPL--2.0-whitesmoke" alt="GPL-2.0" /></a><a class="zola-anchor" href="#git-c-gpl-2-0" aria-label="Anchor link for: git-c-gpl-2-0" title="Anchor link for: git-c-gpl-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>git</code> is my favorite version control software. Yes, that might be because it's pretty much ubiquitous, and I've spent the time to get over the learning curve. It's a fantastic tool.</p> <p>If you haven't leveraged aliases (<a rel="external" href="https://github.com/lukehsiao/dotfiles/blob/a8aa023528d832d562c449c77e5dc578db110e7d/dot_gitconfig.tmpl#L38-L74">here are mine</a>), then I hope reading this is a plug to go enjoy those.</p> <p>Since it's git-related, I'll also mention I'm a strong proponent of <a rel="external" href="https://www.conventionalcommits.org/en/v1.0.0/">conventional commits</a>, and using useful <a rel="external" href="https://git-scm.com/docs/SubmittingPatches">git trailers</a> like they do for git itself and Linux.</p> <p>While we're all used to the PR/MR type of workflow, if you haven't experienced working with git the truly distributed way (via <a rel="external" href="https://git-send-email.io/">git-send-email</a>) its also worth trying.</p> <h3 id="git-absorb-rust-bsd-3-clause"><a rel="external" href="https://github.com/tummychow/git-absorb">git-absorb</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/tummychow/git-absorb/blob/master/LICENSE.md"><img src="https://img.shields.io/badge/license-BSD--3--Clause-whitesmoke" alt="BSD-3-Clause" /></a><a class="zola-anchor" href="#git-absorb-rust-bsd-3-clause" aria-label="Anchor link for: git-absorb-rust-bsd-3-clause" title="Anchor link for: git-absorb-rust-bsd-3-clause"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This fits exactly into my normal git workflow, and it's algorithm is simple and effective. If you are used to making <code>fixup</code> commits yourself, definitely try this out!</p> <h3 id="git-cliff-rust-mit-or-apache-2-0"><a rel="external" href="https://git-cliff.org/">git-cliff</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#git-cliff-rust-mit-or-apache-2-0" aria-label="Anchor link for: git-cliff-rust-mit-or-apache-2-0" title="Anchor link for: git-cliff-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>If you're following <a rel="external" href="https://www.conventionalcommits.org/en/v1.0.0/">conventional commits</a>, then <code>git-cliff</code> is a fantastic tool for automatically generating useful changelogs. I use this in the vast majority of my repositories.</p> <h3 id="git-grab-rust-mit-or-apache-2-0"><a rel="external" href="https://github.com/wezm/git-grab">git-grab</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#git-grab-rust-mit-or-apache-2-0" aria-label="Anchor link for: git-grab-rust-mit-or-apache-2-0" title="Anchor link for: git-grab-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This is a small tool that clones a git repository to a standard location, organized by domain name and path. I used to just have a <code>repos/</code> directory full of random repos. At one point, I tried to organize these better (e.g., "personal", "forks", etc.), but it didn't work well. Now, I just use <code>git-grab</code> and stick with that structure.</p> <h3 id="gping-rust-mit-license"><a rel="external" href="https://github.com/orf/gping">gping</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/orf/gping/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#gping-rust-mit-license" aria-label="Anchor link for: gping-rust-mit-license" title="Anchor link for: gping-rust-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>"Ping, but with a graph." That's an accurate description, but don't underestimate the niceness of a graph. It also has features like graphing multiple hosts at the same time, or graphing execution times of commands. Similar to <code>bat</code>, <code>exa</code>, <code>fd</code>, <code>btm</code>, if you have <code>gping</code>, you really don't need to go to <code>ping</code> anymore.</p> <h3 id="helix-rust-mpl-2-0"><a rel="external" href="https://helix-editor.com/">helix</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/helix-editor/helix/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-whitesmoke" alt="MPL-2.0" /></a><a class="zola-anchor" href="#helix-rust-mpl-2-0" aria-label="Anchor link for: helix-rust-mpl-2-0" title="Anchor link for: helix-rust-mpl-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I'm embarrassed to say, but I didn't <em>really</em> learn how to use <code>vim</code> until 2017. But, that was such a positive experience that I've been on modal editors since. I went from <code>vim</code>, to <a rel="external" href="https://neovim.io/"><code>neovim</code></a>, and then, most recently, to <code>hx</code>.</p> <p>Going from electron-based editors to <code>vim</code> was a big jump. Going from <code>vim</code> to these others was just incremental. I had developed a very custom <code>nvim</code> experience with plugins and all sorts of customizations.</p> <p>That, surprisingly, is part of the reason I've enjoyed <code>hx</code> so much: I use it pretty much stock. It's got all the right basics built-in (LSP, file pickers, tree-sitter, etc.) such that I don't <em>need</em> to customize it much.</p> <p>If you're interested in experimenting with a new editor, I highly recommend helix.</p> <h3 id="hurl-rust-apache-2-0"><a rel="external" href="https://hurl.dev/">hurl</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/Orange-OpenSource/hurl/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-whitesmoke" alt="Apache-2.0" /></a><a class="zola-anchor" href="#hurl-rust-apache-2-0" aria-label="Anchor link for: hurl-rust-apache-2-0" title="Anchor link for: hurl-rust-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>hurl</code> is an easy way to test HTTP requests with plain text. Since it can chain requests, capture values, etc., I actually often use it to "script" some request chains for testing purposes. I haven't used this extensively, but so far, it seems very useful.</p> <h3 id="hyperfine-rust-mit-or-apache-2-0"><a rel="external" href="https://github.com/sharkdp/hyperfine">hyperfine</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#hyperfine-rust-mit-or-apache-2-0" aria-label="Anchor link for: hyperfine-rust-mit-or-apache-2-0" title="Anchor link for: hyperfine-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>hyperfine</code> is the best command-line benchmarking tool around. It should be the standard, in my opinion. If you ever want to benchmark how long a particular command-line command takes in a robust way, look no further.</p> <h3 id="jaq-rust-mit"><a rel="external" href="https://github.com/01mf02/jaq">jaq</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/01mf02/jaq/blob/main/LICENSE-MIT"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#jaq-rust-mit" aria-label="Anchor link for: jaq-rust-mit" title="Anchor link for: jaq-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>jaq</code> is a clone of the ubiquitous <a rel="external" href="https://stedolan.github.io/jq/"><code>jq</code></a> JSON processing tool. But, it is more correct and faster. As long as you aren't doing crazy stuff with <code>jq</code>, <code>jaq</code> might be an easy alias to make for some small wins.</p> <h3 id="jless-rust-mit"><a rel="external" href="https://jless.io/">jless</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/PaulJuliusMartinez/jless/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#jless-rust-mit" aria-label="Anchor link for: jless-rust-mit" title="Anchor link for: jless-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>jless</code> is a command-line JSON viewer. If you're viewing JSON files with some combination of <code>jq</code> (or <code>jaq</code>), <code>less</code>, <code>cat</code>, etc., then <code>jless</code> might be just what you're looking for. It makes it easy to quickly peruse a JSON file and espand/collapse objects and arrays.</p> <h3 id="jujutsu-rust-apache-2-0"><a rel="external" href="https://github.com/jj-vcs/jj">Jujutsu</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/jj-vcs/jj/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-whitesmoke" alt="Apache-2.0" /></a><a class="zola-anchor" href="#jujutsu-rust-apache-2-0" aria-label="Anchor link for: jujutsu-rust-apache-2-0" title="Anchor link for: jujutsu-rust-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>jj</code> is a git-compatible version-control system that is both simple and powerful. This is the <em>surprise</em> of the year for me. While it doesn't really solve any concrete problems for me from <code>git</code>, it does do some fun things, like let me rebase all of my branches at once.</p> <p>I've enjoyed the experiment using this over the past couple months, and suspect I will continue to do so.</p> <h3 id="just-rust-cc0-1-0"><a rel="external" href="https://just.systems/man/en/">just</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/casey/just/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-CC0--1.0-whitesmoke" alt="CC0-1.0" /></a><a class="zola-anchor" href="#just-rust-cc0-1-0" aria-label="Anchor link for: just-rust-cc0-1-0" title="Anchor link for: just-rust-cc0-1-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>just</code> is a versatile command runner that takes inspiration from <code>make</code>, but without the baggage. This is a new entry on this list, and not one I have used as extensively, but so far, it seems nice. There are many situations where I have a <code>Makefile</code> with a bunch of <code>.PHONY</code> targets because I'm just trying to use <code>make</code> as a command runner. Well, now I have a nice command runner.</p> <p>There are several repositories I've worked with with a <code>scripts/</code> folder that could just be absorbed into a <code>justfile</code>.</p> <h3 id="lychee-rust-mit-or-apache-2-0"><a rel="external" href="https://lychee.cli.rs/#/">lychee</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#lychee-rust-mit-or-apache-2-0" aria-label="Anchor link for: lychee-rust-mit-or-apache-2-0" title="Anchor link for: lychee-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>The web is great, but it is unfortunate when you come across broken links. <code>lychee</code> is a great way to help prevent that (they even have a <a rel="external" href="https://github.com/lycheeverse/lychee-action">GitHub Action</a>). It's incredibly fast. I use it to check most of my repositories for dead links, as well as my blogs.</p> <h3 id="mosh-cpp-gpl-3-0"><a rel="external" href="https://mosh.org/">Mosh</a> <img src="https://img.shields.io/badge/lang-C++-purple" alt="cpp" /> <a rel="external" href="https://github.com/mobile-shell/mosh/blob/master/COPYING"><img src="https://img.shields.io/badge/license-GPL--3.0-whitesmoke" alt="GPL-3.0" /></a><a class="zola-anchor" href="#mosh-cpp-gpl-3-0" aria-label="Anchor link for: mosh-cpp-gpl-3-0" title="Anchor link for: mosh-cpp-gpl-3-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>The mobile shell that allows roaming, supports intermittent connectivity, and provides intelligent local echo. A <a rel="external" href="https://mosh.org/mosh-1.4.0-released.html">recent release</a> also brought true color support, which was the only thing that bothered me before. 99% of the time I say I'm "ssh"-ing, I'm actually just using <code>mosh</code>. The ability to start a session, get a <code>tmux</code> session all set up, close my laptop, go somewhere else, open it and just pick up where I was is too good to ignore.</p> <p>This is an example of software that is pretty much "complete". Lack of activity on the repository shouldn't scare you one bit, its because it has a rock solid history and does what it sets out to do.</p> <h3 id="mpv-c-many"><a rel="external" href="https://mpv.io/">mpv</a> <img src="https://img.shields.io/badge/lang-C-darkslategray" alt="c" /> <img src="https://img.shields.io/badge/license-GPL--2.0+,LGPL--2.1+,others-whitesmoke" alt="many" /><a class="zola-anchor" href="#mpv-c-many" aria-label="Anchor link for: mpv-c-many" title="Anchor link for: mpv-c-many"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>mpv</code> is the best media player I know of. I prefer this to VLC or others. It "just works" and is extremely minimal and performant. It's integration with <code>yt-dl</code> (or better yet, <a rel="external" href="https://github.com/yt-dlp/yt-dlp"><code>yt-dlp</code></a>) means I often just play web videos (even YouTube) via <code>mpv</code>.</p> <h3 id="omarchy-shell-mit-license"><a rel="external" href="https://omarchy.org/">Omarchy</a> <img src="https://img.shields.io/badge/lang-shell-lawngreen" alt="shell" /> <a rel="external" href="https://github.com/basecamp/omarchy/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT License" /></a><a class="zola-anchor" href="#omarchy-shell-mit-license" aria-label="Anchor link for: omarchy-shell-mit-license" title="Anchor link for: omarchy-shell-mit-license"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Omarchy is a beautiful, modern, and opinionated Linux distribution build on <a rel="external" href="https://hypr.land/">hyprland</a> and <a rel="external" href="https://archlinux.org/">Arch</a>. <strong>This is my favorite find of the year.</strong> I'm a big fan of tiling window managers and Linux on the desktop. Earlier in the year, I built my own spin of Wayblue sway, but since Omarchy came out, it has checked every box for me.</p> <p>There are many things that are just integrated incredibly well within this distro. I won't bother calling them out explicitly as items on this list, but I'll highlight that Omarchy uses tools like <a rel="external" href="https://github.com/pythops/impala"><code>impala</code></a>, <a rel="external" href="https://git.dec05eba.com/gpu-screen-recorder/about/"><code>gpu-screen-recorder</code></a>, or things like waybar, hyprland, walker, limine, etc., all beautifully. I wouldn't know about many of these were it not for Omarchy, and now I just use them.</p> <h3 id="passage-shell-gpl-2-0"><a rel="external" href="https://github.com/FiloSottile/passage">passage</a> <img src="https://img.shields.io/badge/lang-shell-lawngreen" alt="shell" /> <a rel="external" href="https://github.com/FiloSottile/passage/blob/main/COPYING"><img src="https://img.shields.io/badge/license-GPL--2.0+-whitesmoke" alt="GPL-2.0+" /></a><a class="zola-anchor" href="#passage-shell-gpl-2-0" aria-label="Anchor link for: passage-shell-gpl-2-0" title="Anchor link for: passage-shell-gpl-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>passage</code> is a fork of <a rel="external" href="https://www.passwordstore.org/"><code>pass</code></a>, very simple password store that follows <a rel="external" href="http://en.wikipedia.org/wiki/Unix_philosophy">Unix philosophy</a>. Unlike <code>pass</code>, <code>passage</code> uses <a rel="external" href="https://age-encryption.org/"><code>age</code></a> encryption as the backend.</p> <p>I don't miss having a password manager app on my mobile phone, so on desktop, this has served me well. I'm a big fan of its simplicity, and the fact that my passwords are just flat files I can version in a <code>git</code> repo.</p> <p>In addition, I love being able to use a yubikey, and <a rel="external" href="https://github.com/str4d/age-plugin-yubikey"><code>age-plugin-yubikey</code></a> makes this very easy.</p> <h3 id="ripgrep-rust-mit-or-unlicense"><a rel="external" href="https://github.com/BurntSushi/ripgrep">ripgrep</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Unlicense-whitesmoke" alt="MIT or Unlicense" /><a class="zola-anchor" href="#ripgrep-rust-mit-or-unlicense" aria-label="Anchor link for: ripgrep-rust-mit-or-unlicense" title="Anchor link for: ripgrep-rust-mit-or-unlicense"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This is <code>grep</code> on steroids. Stop using <code>grep</code> and start using <code>ripgrep</code> and you'll never go back. It is <em>significantly</em> faster, and has a bunch of great features (e.g., searching file types, regex, automatic filtering, etc.). It is often thought of as the gold standard for excellent Rust code.</p> <h3 id="satty-rust-mpl-2-0"><a rel="external" href="https://github.com/Satty-org/Satty">Satty</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/Satty-org/Satty/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-whitesmoke" alt="MPL-2.0" /></a><a class="zola-anchor" href="#satty-rust-mpl-2-0" aria-label="Anchor link for: satty-rust-mpl-2-0" title="Anchor link for: satty-rust-mpl-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>When it comes to taking screenshots, Satty is my favorite on Linux. (I still use <a rel="external" href="https://flameshot.org/">Flameshot</a> on macOS.) Easy to adjust, annotate (i.e., arrows, boxes, text, blur, etc.), and save to clipboard or file. Integrates well in Omarchy.</p> <h3 id="sd-rust-mit"><a rel="external" href="https://github.com/chmln/sd">sd</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/chmln/sd/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#sd-rust-mit" aria-label="Anchor link for: sd-rust-mit" title="Anchor link for: sd-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>sd</code> is an intuitive rust rewrite of <code>sed</code>. Much like the other tools in this list (e.g., <code>bat</code>, <code>exa</code>, etc.) it is just a better option in most cases. I frequently use it in combination with <code>fd</code> to find and replace across a project directory.</p> <h3 id="sshx-rust-typescript-mit"><a rel="external" href="https://github.com/ekzhang/sshx">sshx</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> &amp; <img src="https://img.shields.io/badge/lang-typescript-dodgerblue" alt="typescript" /> <a rel="external" href="https://github.com/ekzhang/sshx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#sshx-rust-typescript-mit" aria-label="Anchor link for: sshx-rust-typescript-mit" title="Anchor link for: sshx-rust-typescript-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>sshx</code> is the best live terminal sharing application I've ever used. I used to reach for things like <code>tmate</code> for this, but it's more constraining (e.g., I want to still use zellij). It also does not look as nice. <code>sshx</code> has a level of polish no other tool I've tried in this space has had. This is by go-to tool for pair programming. They also just released the killer feature of <a rel="external" href="https://github.com/ekzhang/sshx/releases/tag/v0.3.0">read-only sharing</a>, which makes this extremely handy in a whole slew of scenarios. This largely solved the screen sharing problem you might have if you use Slack or similar, but have an ultrawide monitor and your coworkers do not. With <code>sshx</code>, the latency is impressively low and the text will be crystal clear. In a screen share, you have a compressed mess.</p> <h3 id="starlight-typescript-mit"><a rel="external" href="https://starlight.astro.build/">Starlight</a> <img src="https://img.shields.io/badge/lang-typescript-dodgerblue" alt="typescript" /> <a rel="external" href="https://github.com/withastro/starlight/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#starlight-typescript-mit" aria-label="Anchor link for: starlight-typescript-mit" title="Anchor link for: starlight-typescript-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Starlight is my new go-to documentation framework. It does all of the important things I used from Material for MkDocs, but often in more intuitive, ergonomic ways. It is blazingly fast. It looks good. I don't have to deal with ridiculous Python virtual environments.</p> <p>While we're talking about documentation, I'll mention that I'm a strong proponent of the <a rel="external" href="https://diataxis.fr/">Diátaxis documentation system</a>. If you're wondering how you should organize your documentation, please consider it!</p> <h3 id="starship-rust-isc"><a rel="external" href="https://starship.rs/">starship</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/starship/starship/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-ISC-whitesmoke" alt="ISC" /></a><a class="zola-anchor" href="#starship-rust-isc" aria-label="Anchor link for: starship-rust-isc" title="Anchor link for: starship-rust-isc"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I started using <code>starship</code> about the same time I started using <code>fish</code>. It's a fun, fast, extremely customizable prompt with nice defaults. I've enjoyed it enough that I just haven't stopped using it since first adding it to try. If you're the type that has some line in your <code>.bashrc</code> to know what git branch your on, you might enjoy starship as well.</p> <h3 id="tailscale-golang"><a rel="external" href="https://tailscale.com/">Tailscale</a> <img src="https://img.shields.io/badge/lang-go-cadetblue" alt="golang" /><a class="zola-anchor" href="#tailscale-golang" aria-label="Anchor link for: tailscale-golang" title="Anchor link for: tailscale-golang"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Thanks to <a rel="external" href="https://www.wireguard.com/">wireguard</a>, tailscale makes private mesh networking stupid easy. It's my go-to tool when it comes to sharing things on a private network (e.g., self-hosted stuff with my family, ssh-ing into my desktop when out and about, etc.). It's performant, well documented, and pretty much just works and gets out of your way.</p> <h3 id="tokei-rust-mit-or-apache-2-0"><a rel="external" href="https://github.com/XAMPPRocky/tokei">tokei</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#tokei-rust-mit-or-apache-2-0" aria-label="Anchor link for: tokei-rust-mit-or-apache-2-0" title="Anchor link for: tokei-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>tokei</code> is the best way to get statistics around counting code in a repository. It shows you the total number of files and total lines within those files broken down by code, comments, blanks, and organized by language.</p> <p>It seems both faster and more accurate than tools like <a rel="external" href="https://cloc.sourceforge.net/">cloc</a>, <a rel="external" href="https://dwheeler.com/sloccount/">sloccount</a>, etc. There are other <a rel="external" href="https://boyter.org/posts/sloc-cloc-code/">great blogs posts about code counters</a>.</p> <h3 id="typos-rust-mit-or-apache-2-0"><a rel="external" href="https://github.com/crate-ci/typos">typos</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <img src="https://img.shields.io/badge/license-MIT%20or%20Apache--2.0-whitesmoke" alt="MIT or Apache-2.0" /><a class="zola-anchor" href="#typos-rust-mit-or-apache-2-0" aria-label="Anchor link for: typos-rust-mit-or-apache-2-0" title="Anchor link for: typos-rust-mit-or-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>typos</code> is a very handy spellchecker that seems to work in a code context better than other things I've tried. They have a CLI, as well as an LSP. It's worth checking out!</p> <p>I usually have a config like this:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span>[files]</span></span> <span class="giallo-l"><span>extend-exclude = [&quot;*.svg&quot;]</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>[default]</span></span> <span class="giallo-l"><span>extend-ignore-re = [&quot;\\w{51,}&quot;]</span></span></code></pre><h3 id="typst-rust-apache-2-0"><a rel="external" href="https://typst.app/">Typst</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/typst/typst/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-whitesmoke" alt="Apache-2.0" /></a><a class="zola-anchor" href="#typst-rust-apache-2-0" aria-label="Anchor link for: typst-rust-apache-2-0" title="Anchor link for: typst-rust-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Almost anytime I would've reached for LaTeX, I now reach for <code>typst</code>. The ecosystem has seriously grown over the years. The performance, control, and error messages remain excellent.</p> <p>And, it looks just as good as LaTeX.</p> <p>The only thing still on my wishlist is <a rel="external" href="https://github.com/typst/typst/issues/185#issuecomment-3448484559">variable font support</a>.</p> <h3 id="watchexec-rust-apache-2-0"><a rel="external" href="https://watchexec.github.io/">watchexec</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/watchexec/watchexec/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-whitesmoke" alt="Apache-2.0" /></a><a class="zola-anchor" href="#watchexec-rust-apache-2-0" aria-label="Anchor link for: watchexec-rust-apache-2-0" title="Anchor link for: watchexec-rust-apache-2-0"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>watchexec</code> simply runs a command when files in the current directory change (and it's awesome). Made my the same folks who make <a rel="external" href="https://github.com/watchexec/cargo-watch">cargo watch</a>, this is a general purpose tool that can make your development loop <em>feel</em> faster when immediately starting long processes (linting, tests, etc.) immediately in response to code changes. Oftentimes, the checks/build/tests are finished by the time I'm ready to craft my commit. Definitely worth trying.</p> <h3 id="xh-rust-mit"><a rel="external" href="https://github.com/ducaale/xh">xh</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/ducaale/xh/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#xh-rust-mit" aria-label="Anchor link for: xh-rust-mit" title="Anchor link for: xh-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p><code>xh</code> is very similar to <a rel="external" href="https://httpie.io/">HTTPie</a>. While I haven't used the latter, I'm a big fan of <code>xh</code> over <code>curl</code>. The interface just seems more intuitive and friendly. And, it still has a <code>--curl</code> flag so I can get a curl command in case I need to share it with a teammate.</p> <h3 id="ynab-proprietary"><a rel="external" href="https://www.ynab.com/">YNAB</a> <img src="https://img.shields.io/badge/closed%20source-black" alt="proprietary" /><a class="zola-anchor" href="#ynab-proprietary" aria-label="Anchor link for: ynab-proprietary" title="Anchor link for: ynab-proprietary"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I used to use Mint, but when they shut down, I migrated to YNAB. I wanted to be more hands on, more intentional, and more aware of my spending, and YNAB seems to be a great tool for that job.</p> <h3 id="zellij-rust-mit"><a rel="external" href="https://github.com/zellij-org/zellij">zellij</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/zellij-org/zellij/blob/main/LICENSE.md"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#zellij-rust-mit" aria-label="Anchor link for: zellij-rust-mit" title="Anchor link for: zellij-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I spend the vast majority of my time every day inside a <code>zellij</code> session. I'm a fairly basic user, but the terminal multiplexing, scrollback buffer/search, ability to have multiple sessions and attach/detach is all I really need. <code>zellij</code> and <code>mosh</code> are a perfect combo. I like the visuals and UX enough to have had this replace <code>tmux</code>.</p> <h3 id="zola-rust-mit"><a rel="external" href="https://www.getzola.org/">Zola</a> <img src="https://img.shields.io/badge/lang-rust-firebrick" alt="rust" /> <a rel="external" href="https://github.com/getzola/zola/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-whitesmoke" alt="MIT" /></a><a class="zola-anchor" href="#zola-rust-mit" aria-label="Anchor link for: zola-rust-mit" title="Anchor link for: zola-rust-mit"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>For non-documentation sites, my favorite static site generator is Zola. The template engine is great, and its fairly simple and easy to grok how themes work and can be modified. I use it for this site, as well as several others I've made.</p> <h2 id="final-thoughts">Final thoughts<a class="zola-anchor" href="#final-thoughts" aria-label="Anchor link for: final-thoughts" title="Anchor link for: final-thoughts"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>Omarchy and the cascade of software that that showed me was the big change this year, with a couple of other fun highlights like Ghostty, <code>jj</code>, and <code>age</code>.</p> <p>Otherwise, the trusty toolbox has largely remained trusty!</p> Switching from GPG to age Luke Hsiao 2025-11-04T00:00:00+00:00 2025-11-26T00:00:00+00:00 https://luke.hsiao.dev/blog/gpg-to-age/ Some notes about my switch from using GPG for encryption to using age, and how it has changed my workflows. <p>It's been several years since I went through all the trouble of setting up my own <a rel="external" href="https://gnupg.org/">GPG</a> keys and securing them in YubiKeys following <a rel="external" href="https://drduh.github.io/YubiKey-Guide/">drduh's guide</a>. With that approach, you generate one key securely offline and store it on multiple YubiKeys, along with a backup.</p> <p>It has worked well for me for years, and as the <a rel="external" href="https://en.wikipedia.org/wiki/Lindy_effect">Lindy effect</a> suggests, it would almost certainly continue to.</p> <p>But as my sub-keys were nearing expiration, I was faced with either renewing (more convenient, no <a rel="external" href="https://en.wikipedia.org/wiki/Forward_secrecy">forward secrecy</a>) or rotating them (rather painful, but potentially more secure). However, I've realized that I essentially only use these keys for encryption, and almost never for signing. So, instead of doing either of the usual options, I'm going to let my keys expire entirely.</p> <p>I'm now experimenting with <a rel="external" href="https://age-encryption.org/"><code>age</code></a>, which touts itself as "simple, modern, and secure encryption". If needed, I will use <a rel="external" href="https://github.com/jedisct1/minisign"><code>minisign</code></a> for signatures.</p> <h2 id="workflow-changes">Workflow changes<a class="zola-anchor" href="#workflow-changes" aria-label="Anchor link for: workflow-changes" title="Anchor link for: workflow-changes"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>This required changing a couple of things in my typical workflow.</p> <h3 id="password-manager">Password manager<a class="zola-anchor" href="#password-manager" aria-label="Anchor link for: password-manager" title="Anchor link for: password-manager"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>First, and foremost, I needed to switch from <a rel="external" href="https://www.passwordstore.org/"><code>pass</code></a> to <a rel="external" href="https://github.com/FiloSottile/passage"><code>passage</code></a>, a fork of <code>pass</code> that uses <code>age</code> as the backend. This was actually surprisingly easy because <code>passage</code> includes a simple bash script to do the migration.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #F5C2E7;font-style: italic;">#! /usr/bin/env bash</span></span> <span class="giallo-l"><span style="color: #F38BA8;font-style: italic;">set</span><span style="color: #A6E3A1;"> -eou pipefail</span></span> <span class="giallo-l"><span style="color: #F38BA8;font-style: italic;">cd</span><span style="color: #A6E3A1;"> &quot;</span><span>${PASSWORD_STORE_DIR</span><span style="color: #94E2D5;">:-</span><span>$HOME</span><span style="color: #94E2D5;">/</span><span style="color: #A6E3A1;">.</span><span>password-store}</span><span style="color: #A6E3A1;">&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">while</span><span style="color: #F38BA8;font-style: italic;"> read</span><span style="color: #A6E3A1;"> -r -d &quot;&quot; passfile</span><span style="color: #9399B2;">;</span><span style="color: #CBA6F7;"> do</span></span> <span class="giallo-l"><span> name</span><span style="color: #94E2D5;">=</span><span style="color: #A6E3A1;">&quot;</span><span>${passfile</span><span style="color: #94E2D5;">#</span><span style="color: #A6E3A1;">.</span><span style="color: #94E2D5;">/</span><span>}</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">;</span><span> name</span><span style="color: #94E2D5;">=</span><span style="color: #A6E3A1;">&quot;</span><span>${name</span><span style="color: #94E2D5;">%</span><span style="color: #A6E3A1;">.</span><span>gpg}</span><span style="color: #A6E3A1;">&quot;</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [[</span><span style="color: #94E2D5;"> -f</span><span style="color: #A6E3A1;"> &quot;</span><span>${PASSAGE_DIR</span><span style="color: #94E2D5;">:-</span><span>$HOME</span><span style="color: #94E2D5;">/</span><span style="color: #A6E3A1;">.</span><span>passage</span><span style="color: #94E2D5;">/</span><span>store}</span><span style="color: #A6E3A1;">/</span><span>$name</span><span style="color: #A6E3A1;">.age&quot;</span><span style="color: #9399B2;"> ]] &amp;&amp;</span><span style="color: #CBA6F7;"> continue</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> pass</span><span style="color: #A6E3A1;"> &quot;</span><span>$name</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #94E2D5;"> |</span><span style="color: #89B4FA;font-style: italic;"> passage</span><span style="color: #A6E3A1;"> insert -m &quot;</span><span>$name</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #94E2D5;"> ||</span><span style="color: #9399B2;"> {</span><span style="color: #89B4FA;font-style: italic;"> passage</span><span style="color: #A6E3A1;"> rm &quot;</span><span>$name</span><span style="color: #A6E3A1;">&quot;</span><span style="color: #9399B2;">;</span><span style="color: #CBA6F7;"> break</span><span style="color: #9399B2;">; }</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">done</span><span style="color: #94E2D5;"> &lt;</span><span style="color: #A6E3A1;"> &lt;(</span><span style="color: #89B4FA;font-style: italic;">find</span><span style="color: #A6E3A1;"> . -path &#39;*/.git&#39; -prune -o -iname &#39;*.gpg&#39; -print0)</span></span></code></pre> <p>There is no installer for <code>passage</code>, and no Arch packages. But it's easy enough to install because it's just a shell script you can throw on your <code>$PATH</code>. Note that for Arch, I also needed to install <code>tree</code>, which it assumes you have.</p> <p>I also name <code>passage</code> as <code>pass</code> on my machine.</p> <p>The benefit of this is everything that had <code>pass</code> integration has continued to "just work". For example, <code>aerc</code>, my email client of choice, behaved exactly the same after the migration.</p> <h3 id="no-more-gpg-agent">No more <code>gpg-agent</code><a class="zola-anchor" href="#no-more-gpg-agent" aria-label="Anchor link for: no-more-gpg-agent" title="Anchor link for: no-more-gpg-agent"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I would occasionally use <code>gpg-agent</code> as my SSH agent on my machines. It was convenient. However, I also like the idea of having a dedicated SSH key per machine. It makes monitoring their usage and revoking them much finer-grained.</p> <p>The absence of <code>gpg-agent</code> forced me to set up new keys on all my machines and add them to various servers/services.</p> <h2 id="benefits-of-the-age-approach">Benefits of the <code>age</code> approach<a class="zola-anchor" href="#benefits-of-the-age-approach" aria-label="Anchor link for: benefits-of-the-age-approach" title="Anchor link for: benefits-of-the-age-approach"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <h3 id="easy-encryption-with-chezmoi">Easy encryption with chezmoi<a class="zola-anchor" href="#easy-encryption-with-chezmoi" aria-label="Anchor link for: easy-encryption-with-chezmoi" title="Anchor link for: easy-encryption-with-chezmoi"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>While I was tending to the encryption area of my personal tech "garden", I also started leveraging <a rel="external" href="https://www.chezmoi.io/user-guide/encryption/age/">chezmoi's encryption features</a>. I already use <code>chezmoi</code> for my configuration files, but with encryption, I could also easily add "secrets" to my public <a rel="external" href="https://github.com/lukehsiao/dotfiles">dotfiles repo</a>. In my case so far, this just means my copies of my favorite paid font: <a rel="external" href="https://usgraphics.com/products/berkeley-mono?ref=featuredtype.com">Berkeley Mono</a>.</p> <p><code>chezmoi</code> also has a nice <a rel="external" href="https://www.chezmoi.io/user-guide/frequently-asked-questions/encryption/">guide for configuring chezmoi to encrypt while asking for a passphrase only once</a> for <code>age</code>.</p> <h3 id="easy-yubikey-configuration">Easy YubiKey configuration<a class="zola-anchor" href="#easy-yubikey-configuration" aria-label="Anchor link for: easy-yubikey-configuration" title="Anchor link for: easy-yubikey-configuration"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I was also very pleasantly surprised with how easy it was to switch to <code>age</code>! Last time I set up the GPG keys on my YubiKeys, I spent several hours.</p> <p>This time, with the help of <a rel="external" href="https://github.com/str4d/age-plugin-yubikey"><code>age-plugin-yubikey</code></a> and embracing the idea of having unique keys on each YubiKey, but encrypting everything for multiple recipients, setting up my keys was surprisingly trivial. It also generates the keys securely on the hardware key itself, which is nice. The whole process probably took 30 minutes. It was so easy that in the future, I'm very much <em>not</em> intimidated by the thought of rotating keys.</p> <h2 id="thoughts">Thoughts<a class="zola-anchor" href="#thoughts" aria-label="Anchor link for: thoughts" title="Anchor link for: thoughts"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>Did I <em>need</em> to switch to <code>age</code>? Of course not.</p> <p>However, over my career, I repeatedly find that exploring new tools for your core workflows (part of <a href="https://luke.hsiao.dev/blog/investing-in-interfaces/">investing in interfaces</a>) is just plain fun. I often learn new ways of thinking about problems. Sometimes, you walk away with a new default that brings some fresh ideas and some delight to your life. Other times, you walk away with your trusty old tool, with greater appreciation for its history and the hard-earned approach it has established. For my uses at the moment, <code>age</code> definitely falls into the former camp.</p> <div class="aside aside--tip"> <div class="aside__header" role="heading" aria-level="3"> <div class="aside__icon" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-rocket"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3" /> <path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3" /> <path d="M15 9m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /> </svg> </div> <span class="aside__title">Tip</span> </div> <div class="aside__body"><p>This post made it to Hacker News. See the <a rel="external" href="https://news.ycombinator.com/item?id=45825040">thread</a> for some additional discussion.</p> </div> </div> First impressions of jj from a git fan Luke Hsiao 2025-10-25T00:00:00+00:00 2026-02-07T00:00:00+00:00 https://luke.hsiao.dev/blog/initial-thoughts-jj/ Some notes about jj, a fresh version control system, as a user that is deeply familiar with and fond of git. <h2 id="what-is-jj">What is <code>jj</code>?<a class="zola-anchor" href="#what-is-jj" aria-label="Anchor link for: what-is-jj" title="Anchor link for: what-is-jj"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p><a rel="external" href="https://github.com/martinvonz/jj">Jujutsu</a> (<code>jj</code>) is a distributed version control system (like <code>git</code>, <code>hg</code>, <code>darcs</code>, <code>pijul</code>, etc.). However, it is rather unique in that it is <code>git</code>-compatible—it uses <code>git</code> as a storage layer, meaning you can use it right now on your existing <code>git</code> repos without disrupting anyone else.</p> <p>It also provides other features, such as</p> <ul> <li>conflicts as a first-class object</li> <li>no explicit index</li> <li><a rel="external" href="https://jj-vcs.github.io/jj/latest/revsets/">revset</a> language</li> <li>operation log and powerful undo</li> <li>automatic rebase and conflict resolution</li> </ul> <p>It also clearly has a (currently small but) passionate group of users, which is a good sign of a useful tool.</p> <p>Some of these users strongly dislike <code>git</code>, finding its user interface unintuitive and clunky. I, however, am very much not part of that group. I <em>love</em> <code>git</code>. Over the years, I've <a rel="external" href="https://github.com/lukehsiao/dotfiles/tree/main/private_dot_config/git">curated a configuration</a> that I find a joy to use. I've built up years' worth of muscle memory. As a result, even though I love <a href="https://luke.hsiao.dev/blog/investing-in-interfaces/">investing in interfaces</a>, such as new software tools, I haven't ever felt motivated enough to try <code>jj</code>. That changed this past week when I learned that <a rel="external" href="https://steveklabnik.com/writing/i-see-a-future-in-jj/">Steve Klabnik found <code>jj</code> so interesting that he's leaving Oxide to pursue it further</a>.</p> <p>As an experiment, I've committed to using only <code>jj</code> for at least a couple of weeks.</p> <p>As a <code>git</code> user, the tutorial that clicked best for me is <a rel="external" href="https://steveklabnik.github.io/jujutsu-tutorial/introduction/introduction.html">Steve's</a>. So, if you're a <code>git</code> fan, I suggest starting there.</p> <h2 id="first-impressions">First impressions<a class="zola-anchor" href="#first-impressions" aria-label="Anchor link for: first-impressions" title="Anchor link for: first-impressions"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <h3 id="what-i-miss-from-git">What I miss from <code>git</code><a class="zola-anchor" href="#what-i-miss-from-git" aria-label="Anchor link for: what-i-miss-from-git" title="Anchor link for: what-i-miss-from-git"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>I quickly ran into two features that I wish were supported.</p> <p>First, <code>jj</code> <a rel="external" href="https://github.com/jj-vcs/jj/issues/494">does not support submodules</a>. Sure, submodules are not great in many cases. However, one very common case I use them for is <a rel="external" href="https://www.getzola.org/">Zola</a> themes, such as the theme for this blog. My customization lives in my repository, and the theme for the site lives in a <code>git</code> submodule. However, it looks like they are working on that actively and with great care.</p> <p>Second, I missed <code>git-format-patch</code>. This one is more minor, because I can always just fall back to <code>git</code> to do so. Even in just a few days, I've wanted to serialize a patch to a file (i.e., <code>git format-patch</code>) or apply some patch I have sitting around with <code>git am</code>. One example is a patch file I keep around that customizes an environment for debugging purposes. As a <code>jj</code>-native approach, I've been keeping a change separate that I can rebase into my patch series and move out later if needed.</p> <p>Then, there are a handful of more rarely used things that I have not needed yet, and consequently have not figured out equivalents for. For example, I'm not sure about an equivalent for view release notes via tags. With <code>git</code>, I used an alias like</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="ini"><span class="giallo-l"><span style="color: #9399B2;">[</span><span style="color: #F9E2AF;">alias</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> tlog</span><span style="color: #94E2D5;"> =</span><span> tag --</span><span style="color: #89B4FA;">sort</span><span style="color: #94E2D5;">=</span><span>-v:refname -l --</span><span style="color: #89B4FA;">format</span><span style="color: #94E2D5;">=</span><span style="color: #A6E3A1;">&#39;%(color:red)%(refname:strip=2)%(color:reset) - %(color:yellow)%(contents:subject)%(color:reset) by %(taggername) on %(taggerdate:human)</span><span style="color: #F5C2E7;">\n\n</span><span style="color: #A6E3A1;">%(contents:body)&#39;</span></span></code></pre> <p>But, then it seems right now <code>jj</code> only supports lightweight tags, not annotated git tags.</p> <p>I'm not sure an equivalent for <code>git shortlog</code>. Most likely would need to build some template.</p> <p>I don't have some <code>jj</code> equivalent of <a rel="external" href="https://github.com/lukehsiao/git-stats"><code>git-stats</code></a>. But again, it's <code>git</code>-compatible, so I can just keep using <code>git-stats</code>.</p> <p>I'm sure there will be a long tail of things like this.</p> <h3 id="things-i-like-more-about-jj">Things I like more about <code>jj</code><a class="zola-anchor" href="#things-i-like-more-about-jj" aria-label="Anchor link for: things-i-like-more-about-jj" title="Anchor link for: things-i-like-more-about-jj"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Steve and others have described <code>jj</code> as "both simpler and easier than git, but at the same time, it is more powerful." I'm not sure I fully agree. In some ways, it has better defaults (e.g., <code>jj split</code>, <code>jj absorb</code>), but in other ways, it is complex (e.g., revset language). That said, I do see how it is more powerful.</p> <p>You cannot exactly your <code>git</code> mental model to directly onto <code>jj</code>'s. That said, one of the main things I enjoy at $WORK is that for a repository, <code>git</code>'s default "view" (for lack of a better word) is a branch, whereas <code>jj</code>'s default view is much higher level. I find <code>jj</code>'s approach more intuitive: it's always pretty trivial to see what branches/PRs I have ongoing at a glance (i.e., with <code>jj log -r "@ | ancestors(trunk()..(visible_heads() &amp; mine()), 2) | trunk()"</code>). It also means you can easily do fun stuff like rebase <em>all</em> of your branches at the same time, and push them all simultaneously to your remote! This is made even better by the fact that conflicts are first-class objects, meaning you can rebase all your branches, and deal with conflicts later.</p> <p>In a similar vein: <code>jj</code> emphasizes commits or changes in a way I find pleasing. Even though in <code>git</code> I have ways to do similar things, <code>jj</code> does indeed make them easier. For example, splitting one commit into multiple is easier. In <code>git</code>, you'd probably do something like</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">git</span><span style="color: #A6E3A1;"> rebase -i</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># select the commit you want to split for editing</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">git</span><span style="color: #A6E3A1;"> reset HEAD~1 --mixed</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># add the subset of changes you want in the first commit</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">git</span><span style="color: #A6E3A1;"> commit -c ORIG_HEAD</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># tweak if necessary</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">git</span><span style="color: #A6E3A1;"> add -u</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">git</span><span style="color: #A6E3A1;"> commit</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># write a commit message for the 2nd patch from scratch</span></span></code></pre> <p>In <code>jj</code>, <code>jj split</code> just does the more intuitive thing. Instead, the same process looks more like</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">jj</span><span style="color: #A6E3A1;"> split -r {REVISION}</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># select the subset of changes you want in the first commit</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># automatically presented with the original commit message, tweak if necessary</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># remaining changes automatically put in 2nd commit, presented with original commit message to tweak again</span></span></code></pre> <p>As another example, I'm a huge fan of <a rel="external" href="https://github.com/tummychow/git-absorb"><code>git-absorb</code></a>. It makes folding follow-up changes into an appropriate earlier commit easy and effective.</p> <p>However, it isn't built into <code>git</code>. Meanwhile, <code>jj</code> ships with <code>jj absorb</code>, which does the same thing.</p> <p>I found that in <code>git</code>, I was already following a workflow close to the <a rel="external" href="https://steveklabnik.github.io/jujutsu-tutorial/real-world-workflows/the-squash-workflow.html">squash workflow</a> recommended by <code>jj</code>'s creator, Martin.</p> <h3 id="things-i-still-find-awkward">Things I still find awkward<a class="zola-anchor" href="#things-i-still-find-awkward" aria-label="Anchor link for: things-i-still-find-awkward" title="Anchor link for: things-i-still-find-awkward"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>The main thing I still feel clumsy with is named bookmarks (similar to <code>git</code> branches). Specifically, I find it hard to remember to <code>set</code> the bookmark when I make updates, and how to rebase them without looking up commands. That said, it's becoming more familiar over time.</p> <h2 id="my-configs">My configs<a class="zola-anchor" href="#my-configs" aria-label="Anchor link for: my-configs" title="Anchor link for: my-configs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>Much like <code>git</code>, I've also found that <code>jj</code>'s defaults aren't entirely sufficient for me. I needed to make some tweaks. You can see my <a rel="external" href="https://github.com/lukehsiao/dotfiles/blob/main/private_dot_config/private_jj/config.toml.tmpl">up-to-date dotfiles here</a>, but as a snapshot, these are changes I found to be particularly valuable right from the start.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="toml"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;">#:schema https://jj-vcs.github.io/jj/latest/config-schema.json</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>user</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>name</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;Luke Hsiao&quot;</span></span> <span class="giallo-l"><span>email</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;{{ .email }}&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>git</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>auto-local-bookmark</span><span style="color: #94E2D5;"> =</span><span style="color: #FAB387;"> true</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># Prevent pushing work in progress or anything explicitly labeled &quot;private&quot;</span></span> <span class="giallo-l"><span>private-commits</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;description(glob:&#39;wip:*&#39;) | description(glob:&#39;private:*&#39;)&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>ui</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>editor</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;hx&quot;</span></span> <span class="giallo-l"><span>pager</span><span style="color: #94E2D5;">=</span><span style="color: #9399B2;">[</span><span style="color: #A6E3A1;">&quot;delta&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;--pager&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;less -FRX&quot;</span><span style="color: #9399B2;">]</span><span style="color: #9399B2;font-style: italic;"> # Keeps output from the terminal after closing the pager</span></span> <span class="giallo-l"><span>diff-formatter</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;:git&quot;</span><span style="color: #9399B2;font-style: italic;"> # required by `delta`</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># These options allow for `jj diff` and `jj show` to clear the output from the terminal after closing the pager.</span></span> <span class="giallo-l"><span style="color: #9399B2;">[[</span><span>--scope</span><span style="color: #9399B2;">]]</span></span> <span class="giallo-l"><span>--when</span><span style="color: #9399B2;">.</span><span>commands</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span><span style="color: #A6E3A1;">&quot;diff&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;show&quot;</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>--scope.ui</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>pager</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;delta&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>template-aliases</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>commit_template</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &#39;&#39;&#39;&quot;JJ: &lt;type&gt;(&lt;scope&gt;): (If applied, this commit will...) &lt;subject&gt; (Max 50 char)</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: |&lt;---- Try to limit to a max of 50 char ----&gt;|</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Explain why this change is being made</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: |&lt;----- Try to limit each line to a maximum of 72 characters -----&gt;|</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Provide links or keys to any relevant tickets, articles or other resources</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Example: GitHub issue #23, BREAKING CHANGE: &lt;description&gt;, Ref: 5119ae</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: --- COMMIT END ---</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Type can be</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: build: changes that affect the build system or external dependencies</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: chore: updating grunt tasks; no production code change</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: ci: changes to our ci configuration files and scripts</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: docs: changes to documentation</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: feat: new feature, correlates with MINOR</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: fix: bug fix, correlates with PATCH</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: perf: a code change that improves performance</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: refactor: refactoring production code; neither fixes a bug nor adds a feature</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: revert: for reverts, revert: &lt;old subject&gt;; state reverted hash in body</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: style: formatting, missing semicolons, etc; no change in code behavior</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: test: adding or refactoring tests; no production code change</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ:</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Scope refers to the file, directory, or system that is being modified.</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ:</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Follow the type(scope) with an &#39;!&#39; to indicate a breaking change.</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Example: refactor(api)!: remove the accident-prone delete_all button</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: --------------------</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Remember to</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Use the imperative mood in the subject line</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Do not end the subject line with a period</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Separate subject from body with a blank line</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Use the body to explain what and why vs. how</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: Can use multiple lines with &#39;-&#39; for bullet points in body</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: BREAKING CHANGE: to correlate with MAJOR</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ:</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">JJ: See: https://www.conventionalcommits.org/en/v1.0.0/)&quot;</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">&#39;&#39;&#39;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>templates</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>git_push_bookmark</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &#39;&quot;lwh/&quot; ++ change_id.short()&#39;</span></span> <span class="giallo-l"><span>draft_commit_description</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;">&#39;&#39;&#39;</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> concat(</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> coalesce(description, commit_template, &quot;\n&quot;),</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> surround(</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> &quot;\nJJ: This commit contains the following changes:\n&quot;, &quot;&quot;,</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> indent(&quot;JJ: &quot;, diff.stat(72)),</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> ),</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> &quot;\nJJ: ignore-rest\n&quot;,</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> diff.git(),</span></span> <span class="giallo-l"><span style="color: #A6E3A1;"> )</span></span> <span class="giallo-l"><span style="color: #A6E3A1;">&#39;&#39;&#39;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>aliases</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>cl</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span><span style="color: #A6E3A1;">&quot;log&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;-r&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;trunk()..@-&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;--reversed&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;-T&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;description ++ &#39;</span><span style="color: #F5C2E7;">\n</span><span style="color: #A6E3A1;">---</span><span style="color: #F5C2E7;">\n\n</span><span style="color: #A6E3A1;">&#39;&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;--no-graph&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;--no-pager&quot;</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>l</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span><span style="color: #A6E3A1;">&quot;log&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;-r&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;@ | ancestors(trunk()..(visible_heads() &amp; mine()), 2) | trunk()&quot;</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>la</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span><span style="color: #A6E3A1;">&quot;log&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;-r&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;all()&quot;</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>pba</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span><span style="color: #A6E3A1;">&quot;git&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;push&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;-b&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;glob:lwh/*&quot;</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>rba</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span><span style="color: #A6E3A1;">&quot;rebase&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;-s&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;all:roots(mutable())&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;-d&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;trunk()&quot;</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span>tug</span><span style="color: #94E2D5;"> =</span><span style="color: #9399B2;"> [</span><span style="color: #A6E3A1;">&quot;bookmark&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;move&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;--from&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;heads(::@- &amp; bookmarks())&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;--to&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;@-&quot;</span><span style="color: #9399B2;">]</span></span></code></pre> <p>First, I strongly prefer <a rel="external" href="https://github.com/dandavison/delta"><code>delta</code></a> over the default viewer. <code>jj</code> also makes it easy to view with another tool with <code>--tool</code>. For example, I also like using <a rel="external" href="https://github.com/Wilfred/difftastic"><code>difft</code></a>.</p> <p>I added a config from <a rel="external" href="https://github.com/jj-vcs/jj/discussions/4690">this discussion</a> so <code>jj log</code> doesn't clear the screen on quit.</p> <p>Second, I like having my <a rel="external" href="https://www.conventionalcommits.org/en/v1.0.0/">conventional commit</a> template automatically populated on <code>jj describe</code>. I also have the <a rel="external" href="https://github.com/jj-vcs/jj/issues/1946#issuecomment-2561045057">diff included</a>, which mimics the config I had with <code>git</code> using</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="ini"><span class="giallo-l"><span style="color: #9399B2;">[</span><span style="color: #F9E2AF;">commit</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> template</span><span style="color: #94E2D5;"> =</span><span> ~/.config/git/commit-template</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> verbose</span><span style="color: #94E2D5;"> =</span><span> true</span></span></code></pre> <p>Third, I changed the default <code>git push</code> bookmark prefix to <code>lwh/</code>.</p> <p>Finally, I added a bunch of aliases I use frequently. I use <code>jj cl</code> to copy-paste commit descriptions into PR descriptions. I use <code>jj l</code> to view all the logs most relevant to me. This was discovered courtesy of <a rel="external" href="https://willhbr.net/2024/08/18/understanding-revsets-for-a-better-jj-log-output/">Will Richardson</a>. I use <code>jj la</code> to view all logs. I use <code>jj pba</code> and <code>jj rba</code> to push all my bookmarks, and rebase all my changes on main, respectively. Finally, I use <code>jj tug</code> to move my bookmark to the latest change (I found this one thanks to <a rel="external" href="https://shaddy.dev/notes/jj-tug/">Shaddy</a>).</p> <h2 id="thoughts-so-far">Thoughts so far<a class="zola-anchor" href="#thoughts-so-far" aria-label="Anchor link for: thoughts-so-far" title="Anchor link for: thoughts-so-far"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I actually find <code>jj</code> quite fun! Again, I do not think it "solved" any pain points I had with <code>git</code>. I was already a very happy user. Ultimately, <code>jj</code>'s interface and mental model do feel refreshing enough that I'm likely to continue using it.</p> ASUS PA32QCV: a programmer's review Luke Hsiao 2025-10-15T00:00:00+00:00 2026-01-17T00:00:00+00:00 https://luke.hsiao.dev/blog/pa32qcv/ A short-term review of the PA32QCV from a programmer, as well as a compatibility tip for Linux. <p>Those who know me know that I <a href="https://luke.hsiao.dev/blog/investing-in-interfaces/">invest in interfaces</a>. Previously, I had an <a rel="external" href="https://www.lg.com/us/monitors/lg-38gn950-b-gaming-monitor">LG 38GN950-B</a> (38" Ultrawide, 3840×1600 @ 144Hz). It was expensive, but had a little extra vertical resolution vs its competitors, and the high refresh rate was great for casual gaming. But, over the years, I play games less, and my eyes are aging! I find myself appreciating larger and more crisp fonts for programming.</p> <p>With that in mind, I started looking for a high resolution "retina"-class display. There are only a handful of these around at the time of writing. In the 27" 5K group: <a rel="external" href="https://www.apple.com/studio-display/">Apple Studio Display</a>/<a rel="external" href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-5k-pa27jcv/">ProArt PA27JCV</a>/<a rel="external" href="https://www.samsung.com/us/computing/monitors/5k/27-viewfinity-s9-5k-monitor-with-thunderbolt-4-matte-display-and-smart-features-ls27c900panxza/">Samsung S9</a>/<a rel="external" href="https://kuycon.us/monitors/G27P">Kuycon G27P</a>. Then, there is the 32" 6K group: <a rel="external" href="https://www.apple.com/pro-display-xdr">Apple XDR</a>/<a rel="external" href="https://www.asus.com/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/">ProArt PA32QCV</a>/<a rel="external" href="https://kuycon.us/monitors/G32P/">Kuycon G32P</a>/<a rel="external" href="https://www.dell.com/en-us/shop/dell-ultrasharp-32-6k-monitor-u3224kb/apd/210-bhbz/monitors-monitor-accessories">Dell U3225KB</a>/<a rel="external" href="https://www.lg.com/ca_en/monitors/uhd-4k-5k/32u990a-s/">LG 32U990A-S</a>. Then, there is the even more sparse 8K group: <a rel="external" href="https://www.dell.com/en-us/shop/dell-ultrasharp-32-8k-monitor-up3218k/apd/210-alez/monitors-monitor-accessories">Dell UP3218K</a>/<a rel="external" href="https://www.asus.com/displays-desktops/monitors/proart/proart-display-8k-pa32kcx/">ProArt PA32KCX</a>.</p> <p>I know I wanted to avoid fractional scaling, so I was searching for either 6K or 8K.</p> <p>I immediately discovered that 8K is way out of my price range.</p> <p>In the land of 6K, this <a rel="external" href="https://www.asus.com/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/">ProArt PA32QCV</a> met a sweet spot: (1) being available for purchase right now, (2) having relatively modern hardware, and (3) not being ridiculously expensive. So, I bought it.</p> <h2 id="first-impressions">First impressions<a class="zola-anchor" href="#first-impressions" aria-label="Anchor link for: first-impressions" title="Anchor link for: first-impressions"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p><strong>WOW.</strong></p> <p>I'm a big fan. As soon as I switched, I understood why people like these high-resolution displays. Text is crisp and delightful to look at! I do not miss the ultrawide at all.</p> <p>I run at 2× scaling, and have been loving looking at it.</p> <p>Yes, I do notice the drop from 144Hz to 60Hz. However, because I'm mostly programming, I really don't notice it during a typical workday unless I go looking for it (e.g., dragging a window quickly).</p> <p>The colors look great.</p> <p>In other words, when it comes to the display itself, it seems fantastic, which is what I was looking for.</p> <p>Some people complain about the matte "LuxPixel" finish. I think it looks fantastic, and appreciate the lack of glare. This is an important feature, in my opinion.</p> <h3 id="the-less-good">The less good<a class="zola-anchor" href="#the-less-good" aria-label="Anchor link for: the-less-good" title="Anchor link for: the-less-good"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>When it comes to some of the other features, they are notably less useful.</p> <h4 id="speakers">Speakers<a class="zola-anchor" href="#speakers" aria-label="Anchor link for: speakers" title="Anchor link for: speakers"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <p>These are fairly awful, as one would expect. What confuses me is why these are here at all. This is a "pro" monitor, and I see no value in having speakers. I wish they saved some cost or reduced size/weight instead.</p> <h4 id="kvm">KVM<a class="zola-anchor" href="#kvm" aria-label="Anchor link for: kvm" title="Anchor link for: kvm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <p>I frequently multiplex between a work laptop and a personal desktop. So, a built-in KVM is actually very appealing! However, I've never found one that works well for my needs, so I've always reverted to separate display cables and a USB switch. The same is true here.</p> <p>Yes, this monitor has a KVM, but it does not have enough USB ports for my needs. It has 3 USB ports, and 1 thunderbolt port. The good news is that this lets me use 4 USB devices just fine on MacOS. The bad news is that the 1 thunderbolt port does NOT work on my desktop, leaving me with just 3 devices. Typically, I want four: a mouse, keyboard, webcam, and audio interface. Five, if you count a security key. So, good idea and will work for just a keyboard an mouse, but not enough for me.</p> <p>Another fatal flaw is that the KVM doesn't appear to activate immediately upon switching input. This is vital because if your other machine goes to sleep, the input button <em>will not switch</em> until there is a display signal on that input—a signal that you have no way to initiate without a keyboard or mouse active! With a USB switch, that means I typically switch, wake up the machine, and then switch the input. With the built-in KVM, that means there is simply <em>no way</em> to actually switch without waking up the machine first some other way. Not cool.</p> <p>That said, my normal USB switch setup works well.</p> <h4 id="light-sync">Light sync<a class="zola-anchor" href="#light-sync" aria-label="Anchor link for: light-sync" title="Anchor link for: light-sync"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <p>The "Light Sync" feature actually sounded great: I work right by a window, and frequently do adjust brightness to compensate for the ambient light. It would be awesome if it could do it for me!</p> <p>However, after using it, as far as I can tell there is no way to set the baseline/target brightness you want. Instead, it ranges from acceptable (in <code>Native</code> color preset) to unusably dim (in <code>M Model-P3</code>). It is a good idea, but the implementation seems to only be viable in <code>Native</code> mode of the ones I tried, which means this forces you to make a color preset trade-off. Instead, they should've just let me set the target brightness.</p> <p>That said, as someone <em>not</em> using this monitor for color accuracy, specifically, I've been quite satisfied with <code>Native</code> mode's behavior thus far.</p> <h3 id="the-surprising">The surprising<a class="zola-anchor" href="#the-surprising" aria-label="Anchor link for: the-surprising" title="Anchor link for: the-surprising"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Some features/design choices I was pleasantly surprised at. Not surprised they existed, but surprised how much I liked them.</p> <ul> <li>I really like buttons being on the front of the monitor, rather than needing to blindly click around on a side or bottom.</li> <li>No giant power supply! I appreciate the saved desk space of having the power supply built-in.</li> </ul> <h2 id="compatibility">Compatibility<a class="zola-anchor" href="#compatibility" aria-label="Anchor link for: compatibility" title="Anchor link for: compatibility"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>The box came with the display itself, a stand, a Thunderbolt 4 cable, an "ultra 8K" HDMI cable, and a power cable.</p> <p>After reading <a rel="external" href="https://michael.stapelberg.ch/posts/2023-07-03-dell-u3224kba-32-inch-6k-monitor/">Michael Stapelberg's review of the 6K Dell</a>, I was a little worried about compatibility. I was optimistic that with a brand-new release, more compatibility issues would've been ironed out.</p> <p>I have two machines I use with this display: an M1 MacBook Pro, and my <a href="https://luke.hsiao.dev/blog/2023-workstation-build/">custom workstation</a> (Radeon RX 7800 XT), running both Windows and Linux.</p> <p>The monitor was clearly built with a MacBook in mind. Using the Thunderbolt 4 cable, I instantly got power and display—full 6K no problems. Colors do indeed seem highly matched with the <code>M Model-P3</code> color preset.</p> <p>On my desktop with Windows via the included HDMI cable there was no problem. It instantly recognized full 6K@60Hz.</p> <p>My desktop with Linux via the included HDMI cable? No 6K at all.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="fish"><span class="giallo-l"><span>❯ hyprctl monitors</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Monitor</span><span> HDMI-A-2 </span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">ID</span><span> 0</span><span style="color: #9399B2;">)</span><span>:</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 3008x1692</span><span>@59.96700 at 0x0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> description</span><span>: ASUSTek COMPUTER INC PA32QCV T7LMSV006894</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> make</span><span>: ASUSTek COMPUTER INC</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> model</span><span>: PA32QCV</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> physical</span><span> size </span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">mm</span><span style="color: #9399B2;">)</span><span>: 700x390</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> serial</span><span>: T7LMSV006894</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> active</span><span> workspace: 1 </span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">1</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> special</span><span> workspace: 0 </span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> reserved</span><span>: 0 26 0 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> scale</span><span>: 1.00</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> transform</span><span>: 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> focused</span><span>: yes</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> dpmsStatus</span><span>: 1</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> vrr</span><span>: false</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> solitary</span><span>: 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> solitaryBlockedBy</span><span>: windowed mode,missing candidate</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> activelyTearing</span><span>: false</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> tearingBlockedBy</span><span>: next frame is not torn,user settings,missing candidate</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> directScanoutTo</span><span>: 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> directScanoutBlockedBy</span><span>: user settings,missing candidate</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> disabled</span><span>: false</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> currentFormat</span><span>: XRGB8888</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> mirrorOf</span><span>: none</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> availableModes</span><span>:</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 3008x1692</span><span>@59.97Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@60.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@59.94Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@50.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@30.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@29.97Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@25.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@24.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@23.98Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 3840x2160</span><span>@60.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 3840x2160</span><span>@59.94Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 3840x2160</span><span>@60.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> ...</span></span></code></pre> <p>I first went down a bit of a rabbit hole assuming it was a Linux software issue (the same hardware works with Windows!). But, I'm running Arch, with a very recent kernel and all up-to-date drivers. I decided to try a DisplayPort cable instead.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="fish"><span class="giallo-l"><span>❯ hyprctl monitors</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">Monitor</span><span> DP-1 </span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">ID</span><span> 0</span><span style="color: #9399B2;">)</span><span>:</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 6016x3384</span><span>@59.99200 at 0x0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> description</span><span>: ASUSTek COMPUTER INC PA32QCV T7LMSV006894</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> make</span><span>: ASUSTek COMPUTER INC</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> model</span><span>: PA32QCV</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> physical</span><span> size </span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">mm</span><span style="color: #9399B2;">)</span><span>: 700x390</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> serial</span><span>: T7LMSV006894</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> active</span><span> workspace: 1 </span><span style="color: #9399B2;">(</span><span style="color: #89B4FA;font-style: italic;">1</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> special</span><span> workspace: 0 </span><span style="color: #9399B2;">()</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> reserved</span><span>: 0 26 0 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> scale</span><span>: 2.00</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> transform</span><span>: 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> focused</span><span>: yes</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> dpmsStatus</span><span>: 1</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> vrr</span><span>: false</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> solitary</span><span>: 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> solitaryBlockedBy</span><span>: windowed mode,missing candidate</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> activelyTearing</span><span>: false</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> tearingBlockedBy</span><span>: next frame is not torn,user settings,missing candidate</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> directScanoutTo</span><span>: 0</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> directScanoutBlockedBy</span><span>: user settings,missing candidate</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> disabled</span><span>: false</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> currentFormat</span><span>: XRGB8888</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> mirrorOf</span><span>: none</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> availableModes</span><span>:</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 6016x3384</span><span>@59.99Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 3008x1692</span><span>@59.97Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 6016x3384</span><span>@29.99Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@60.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@59.94Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@50.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> 4096x2160</span><span>@30.00Hz</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> ...</span></span></code></pre> <p>Whew! Instantly recognized the 6K@60Hz.</p> <p>So, that's the tip: use DisplayPort on Linux.</p> <h2 id="verdict">Verdict<a class="zola-anchor" href="#verdict" aria-label="Anchor link for: verdict" title="Anchor link for: verdict"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>So far, after a bit more than 3 months of daily use, I'm <em>very</em> pleased.</p> <p>One of the biggest evidences of how nice it is was when I went back to my old monitor for a little bit. The lack of text clarity was just as apparent as my initial shock at the clarity going the other direction.</p> <p>We'll see how it fares long term, but this is absolutely a keeper right now.</p> AI etiquette Luke Hsiao 2025-10-05T00:00:00+00:00 2025-12-05T00:00:00+00:00 https://luke.hsiao.dev/blog/ai-etiquette/ Reflections on proof-of-thought and why showing AI output to people is rude, along with my pledge to keep this place human-authored. <h2 id="it-s-the-thought-that-counts">It's the thought that counts<a class="zola-anchor" href="#it-s-the-thought-that-counts" aria-label="Anchor link for: it-s-the-thought-that-counts" title="Anchor link for: it-s-the-thought-that-counts"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I recently read a great <a rel="external" href="https://distantprovince.by/posts/its-rude-to-show-ai-output-to-people/">blog post from Alex Martsinovich</a>. It expressed a concept that I've been feeling, too: <em>proof-of-thought</em>.</p> <blockquote> <p>For the longest time, writing was more expensive than reading. If you encountered a body of written text, you could be sure that at the very least, a human spent some time writing it down. The text used to have an innate proof-of-thought, a basic token of humanity.</p> <p>Now, AI has made text very, very, very cheap. Not only text, in fact. Code, images, video. All kinds of media. We can't rely on proof-of-thought anymore. Any text can be AI slop. If you read it, you're injured in this war. You engaged and replied – you're as good as dead. The dead internet is not just dead it's poisoned. So what do we do?</p> <p>Luckily for us, AI only talks in response. Unlike Earth, AI does not emit comedy sketches into outer space on its own. To get AI slop, somebody needs to ask for it. To send it further, someone needs to retransmit it. Our problem is other humans, really.</p> <p>There's nothing wrong with using AI. When you do, you know what you're getting. The transaction is fully consensual. But whenever you propagate AI output, you're at risk of intentionally or unintentionally legitimizing it with your good name, providing it with a fake proof-of-thought. In some cases, it's fine, because you did think it through and adopted the AI output as your own. But in other cases, it is not, and our scrambler brain feels violated.</p> </blockquote> <p>He goes on to propose a simple new sense of etiquette: <strong>AI output can only be relayed if it's either adopted as your own or there is explicit consent from the receiving party</strong>, otherwise, it is horribly rude. I'd add that essentially, you either are providing proof-of-thought, or, you're explicitly acknowledging the lack of it.</p> <p>This resonates.</p> <h2 id="relatable-example-in-the-wild">Relatable example in the wild<a class="zola-anchor" href="#relatable-example-in-the-wild" aria-label="Anchor link for: relatable-example-in-the-wild" title="Anchor link for: relatable-example-in-the-wild"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>It reminds me of <a rel="external" href="https://mrgan.com/ai-email-from-a-friend/">Neven Mrgan's reflection on how it feels to get an AI email from a friend</a>.</p> <blockquote> <p>Recently I received an AI-written email from a friend. It wasn't sent to test AI, or to show it off, as in "ha ha check this out"; my friend had a question to ask me, and the email asked it over the course of a few paragraphs. It then disclosed that, oh by the way, I used AI to write this. My reaction to this surprised me: I was repelled, as if digital anthrax had poured out of the app. I'm trying to figure out why.</p> </blockquote> <p>He then really breaks down how it did and didn't feel as a recipient. For example, it didn't feel like they were using it as an autocorrect to type better. It did feel like a family fridge decorated with printed stock art of children's drawings.</p> <blockquote> <p>Years from now, could an AI that was trained on all of my friend’s emails and texts and personal documents sound convincingly like them? Could it be so advanced that I wouldn’t even be able to tell that my friend hadn’t written to me at all? Possibly. And that idea saddens me the most.</p> </blockquote> <p>Another is the bleakly entertaining rant by Bryan Cantrill: <a rel="external" href="https://bcantrill.dtrace.org/2025/12/05/your-intellectual-fly-is-open/">Your intellectual fly is open</a>.</p> <blockquote> <p>When you use an LLM to author a post, you may think you are generating plausible writing, but you aren't: to anyone who has seen even a modicum of LLM-generated content (a rapidly expanding demographic!), the LLM tells are impossible to ignore. Bluntly, your intellectual fly is open: lots of people notice — but no one is pointing it out. And the problem isn't merely embarrassment: when you — person whose perspective I want to hear! — are obviously using an LLM to write posts for you, I don't know what's real and what is in fact generated fanfic. You definitely don't sound like you, so...is the actual content real? I mean, maybe? But also maybe not. Regardless, I stop reading — and so do lots of others.</p> </blockquote> <h2 id="my-pledge">My pledge<a class="zola-anchor" href="#my-pledge" aria-label="Anchor link for: my-pledge" title="Anchor link for: my-pledge"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>The words on this blog have been and will always be, manually and intentionally typed by me (on a keyboard that I find delightful). Yes, I am a big em dash (and en dash!) user—I even have <a rel="external" href="https://github.com/lukehsiao/dotfiles/blob/f1c6c95e1ac79ff1bf937a0a0a70dc1ae12cacb3/private_dot_config/helix/config.toml#L33">macros in my editor</a> to make the substitutions. No, that's not AI. Yes, you'll find typos and grammar errors in my posts; I'm human (but if you do, please send me a note so I can fix it). Still, I find writing a good way to clarify my own thoughts and, while I primarily write for myself, occasionally someone else benefits, too.</p> <p>While we have no formal way to provide proof-of-thought, my pledge to both you and myself is that this place will remain a place for human thought, and more specifically, my personal ramblings.</p> Repairing my Windows bootloader after Omarchy Luke Hsiao 2025-09-09T00:00:00+00:00 2025-09-09T00:00:00+00:00 https://luke.hsiao.dev/blog/repairing-windows-bootloader/ Some quick notes on repairing my Windows bootloader after installing Omarchy. <h2 id="the-problem">The problem<a class="zola-anchor" href="#the-problem" aria-label="Anchor link for: the-problem" title="Anchor link for: the-problem"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>Before I switched to Omarchy, I was running <code>omakase-blue</code> on one NVME drive, and Windows on another. Part of this is because I want to use full disk encryption, and part of this is because I was originally playing with <a rel="external" href="https://docs.projectbluefin.io/installation/#requirements">Project Bluefin</a>, which doesn't support dual-boot off of a single disk.</p> <p>I installed Omarchy via online ISO, and pointed it at my Linux drive.</p> <p>After it installed, I no longer could boot into Windows. My BIOS didn't even show the 2nd NVME drive as a boot option.</p> <h2 id="the-solution">The solution<a class="zola-anchor" href="#the-solution" aria-label="Anchor link for: the-solution" title="Anchor link for: the-solution"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>After some digging, I figured out a way to get things back without needing to reinstall any operating systems. My system looked like this after the Omarchy install:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span>❯ lsblk --output NAME,PTTYPE,PARTLABEL,FSTYPE,FSVER,LABEL,FSSIZE,FSAVAIL,FSUSE%,MOUNTPOINTS</span></span> <span class="giallo-l"><span>NAME PTTYPE PARTLABEL FSTYPE FSVER LABEL FSSIZE FSAVAIL FSUSE% MOUNTPOINTS</span></span> <span class="giallo-l"><span>zram0 swap 1 zram0 [SWAP]</span></span> <span class="giallo-l"><span>nvme0n1 gpt</span></span> <span class="giallo-l"><span>├─nvme0n1p1 gpt vfat FAT32 2G 1.8G 9% /boot</span></span> <span class="giallo-l"><span>└─nvme0n1p2 gpt crypto_LUKS 2</span></span> <span class="giallo-l"><span> └─root btrfs 1.8T 1.8T 2% /var/log</span></span> <span class="giallo-l"><span> /home</span></span> <span class="giallo-l"><span> /var/cache/pacman/pkg</span></span> <span class="giallo-l"><span> /</span></span> <span class="giallo-l"><span>nvme1n1 gpt</span></span> <span class="giallo-l"><span>├─nvme1n1p1 gpt Microsoft reserved partition</span></span> <span class="giallo-l"><span>├─nvme1n1p2 gpt Basic data partition ntfs</span></span> <span class="giallo-l"><span>└─nvme1n1p3 gpt ntfs</span></span></code></pre> <p>That is, the <code>vfat</code>, <code>FAT32</code> partition with the bootloader was on <code>nvme0n1</code>, and it didn't seem there was any corresponding partition on <code>nvme1n1</code>.</p> <p>The good news is that this indicated that I could probably just restore the required boot files to <code>nvme0n1p1</code> for Windows and be on my way.</p> <h3 id="repairing-the-windows-bootloader-files">Repairing the Windows bootloader files<a class="zola-anchor" href="#repairing-the-windows-bootloader-files" aria-label="Anchor link for: repairing-the-windows-bootloader-files" title="Anchor link for: repairing-the-windows-bootloader-files"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>To do so, I used <a rel="external" href="https://www.hirensbootcd.org/">Hiren's BootCD PE</a> on a USB drive with <a rel="external" href="https://www.ventoy.net/en/index.html">Ventoy</a>.</p> <p>Once in the Windows PE environment, we can restore the files with <a rel="external" href="https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/diskpart"><code>diskpart</code></a> and <a rel="external" href="https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/bcdboot-command-line-options-techref-di?view=windows-11"><code>bcdboot</code></a> in the command prompt.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span>diskpart</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>&gt; list volumes</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># find the fat32 one, let&#39;s call it X</span></span> <span class="giallo-l"><span>&gt; select volume X</span></span> <span class="giallo-l"><span>&gt; assign letter K:</span></span></code></pre> <p>Now that we've assigned a letter, we can fix the files:</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span> bcdboot C:\Windows /s K: /f ALL</span></span></code></pre> <p>where <code>/s</code> specifies the volume letter of the system partition, and <code>/f</code> specifies the firmware type (<code>ALL</code> copies all of them).</p> <p>At this point, I could once again boot from this drive and land in Windows.</p> <h3 id="adding-windows-to-limine">Adding Windows to Limine<a class="zola-anchor" href="#adding-windows-to-limine" aria-label="Anchor link for: adding-windows-to-limine" title="Anchor link for: adding-windows-to-limine"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>But, we could do even better! Having to use the BIOS boot menu is annoying: you typically have to spam an <code>F</code>-key at startup, and if you miss your opportunity, need to restart again. It would be nice to be able to just boot to the same bootloader and select Windows, like in a traditional single-drive, dual-boot setup.</p> <p>Turns out that isn't hard, since Omarchy uses <a rel="external" href="https://wiki.archlinux.org/title/Limine#Windows_entry_(UEFI)">Limine</a> out of the box.</p> <p>In Linux, we can just edit the boot config.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="shellscript"><span class="giallo-l"><span style="color: #89B4FA;font-style: italic;">sudoedit</span><span style="color: #A6E3A1;"> /boot/limine.conf</span></span></code></pre> <p>Then, I added the following entry after the Omarchy ones, and before the EFI fallback.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span>/Windows</span></span> <span class="giallo-l"><span>comment: Windows</span></span> <span class="giallo-l"><span>comment: order-priority=40</span></span> <span class="giallo-l"><span> protocol: efi</span></span> <span class="giallo-l"><span> path: boot():/EFI/Microsoft/Boot/bootmgfw.efi</span></span></code></pre> <p>With this, I then configured my BIOS to with Limine as the first boot option, and disabled the others. Now, it boots to Limine on startup, and my Windows drive is once again an easy option to select. Nice.</p> Berkeley Mono Variable (TX-02) in Ghostty Luke Hsiao 2025-09-07T00:00:00+00:00 2025-09-07T00:00:00+00:00 https://luke.hsiao.dev/blog/berkeley-mono-ghostty/ How to use Berkeley Mono Variable (TX-02) in Ghostty <p>Inspired by <a rel="external" href="https://michaelbommarito.com/wiki/programming/tools/ghostty-berkeley-font-configuration/">Michael Bommarito's post</a>, I'm just dropping some quick notes on getting Berkeley Mono Variable (TX-02) to work in Ghostty. Specifically, Berkeley Mono <code>2.002</code>, released on 2024-12-31, running in Ghostty <code>1.1.3-arch1</code>. Using the variable version of the font is highly convenient: it is very fast to change styles and tweak things until it is exactly how you like it, without having to iterate with installing static fonts.</p> <p>I suggest you read his post first for lots of nice context on fonts and their features.</p> <p>Then, the key bit of information I needed to get this working was the following. TX-02, the updated version of Berkeley Mono, has different OpenType features than the original. There is no documentation I could find on exactly what they mean, but via some trial and error, I've landed on the following config.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="plain"><span class="giallo-l"><span>font-family = &quot;Berkeley Mono Variable&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Font width (60 to 100)</span></span> <span class="giallo-l"><span># 60 = UltraCondensed</span></span> <span class="giallo-l"><span># 70 = ExtraCondensed</span></span> <span class="giallo-l"><span># 80 = Condensed</span></span> <span class="giallo-l"><span># 90 = SemiCondensed</span></span> <span class="giallo-l"><span># 100 = Normal</span></span> <span class="giallo-l"><span>font-variation = wdth=90</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Font weight (100 to 900)</span></span> <span class="giallo-l"><span># 100 = Thin</span></span> <span class="giallo-l"><span># 200 = ExtraLight</span></span> <span class="giallo-l"><span># 300 = Light</span></span> <span class="giallo-l"><span># 350 = SemiLight</span></span> <span class="giallo-l"><span># 400 = Regular</span></span> <span class="giallo-l"><span># 500 = Medium</span></span> <span class="giallo-l"><span># 600 = SemiBold</span></span> <span class="giallo-l"><span># 700 = Bold</span></span> <span class="giallo-l"><span># 800 = ExtraBold</span></span> <span class="giallo-l"><span># 900 = Black</span></span> <span class="giallo-l"><span>font-variation = wght=400</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># Font slant (0 to -16)</span></span> <span class="giallo-l"><span># 0 = Regular</span></span> <span class="giallo-l"><span># -16 = Oblique</span></span> <span class="giallo-l"><span>font-variation = slnt=0</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span># otfinfo -f ~/.local/share/fonts/Berkeley\ Mono\ Variable.otf</span></span> <span class="giallo-l"><span># aalt Access All Alternates</span></span> <span class="giallo-l"><span># calt Contextual Alternates</span></span> <span class="giallo-l"><span># ccmp Glyph Composition/Decomposition</span></span> <span class="giallo-l"><span># mark Mark Positioning</span></span> <span class="giallo-l"><span># mkmk Mark to Mark Positioning</span></span> <span class="giallo-l"><span># salt Stylistic Alternates</span></span> <span class="giallo-l"><span># ss01 Stylistic Set 1 &lt;- slashed 0</span></span> <span class="giallo-l"><span># ss02 Stylistic Set 2 &lt;- dotted 0</span></span> <span class="giallo-l"><span># ss03 Stylistic Set 3 &lt;- gap 0</span></span> <span class="giallo-l"><span># ss04 Stylistic Set 4 &lt;- slashed 0, slash 7</span></span> <span class="giallo-l"><span># ss05 Stylistic Set 5 &lt;- dotted 0, slash 7</span></span> <span class="giallo-l"><span># ss06 Stylistic Set 6 &lt;- gap 0, slash 7</span></span> <span class="giallo-l"><span>font-feature = +calt</span></span> <span class="giallo-l"><span>font-feature = +ss02</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span>font-size = 13</span></span> <span class="giallo-l"><span># Personal preference</span></span> <span class="giallo-l"><span>adjust-cell-height = -2%</span></span></code></pre> <p>Specifically, I found that <code>aalt</code> and <code>salt</code> appear to change what the stylistic sets do (to different things than my comments). <code>ccmp</code>, <code>mark</code>, and <code>mkmk</code> don't do anything that I noticed. So, effectively, it seems to me that the only two features you actually care about are your <code>font-variation</code> settings, and then whether you want ligatures with <code>calt</code>, and what style of <code>0</code>/<code>7</code> you want via stylistic sets.</p> <p>Another tip: if you have static versions of Berkeley Mono installed, I noticed that that sometimes breaks Ghostty from loading Berkeley Mono Variable. I'm unsure why, but I was able to resolve it by removing the static fonts, configuring things, and then putting them back.</p> The youngest sibling advantage Luke Hsiao 2025-08-31T00:00:00+00:00 2025-09-30T00:00:00+00:00 https://luke.hsiao.dev/blog/youngest-sibling/ Some musings about what I think is the biggest advantage of being a youngest sibling. <p>I am the youngest child in my family.</p> <p>I've heard a lot of discussion around the stereotypical traits of the youngest child. In fact, many of these are so commonly echoed that they have their own name: <a rel="external" href="https://health.clevelandclinic.org/oldest-child-syndrome-and-birth-order">youngest child syndrome</a>. Sure, some of these arguably apply to me (certainly my older siblings would label me spoiled), and others do not. But, what I haven't heard in many of these discussions is what I consider to be the biggest single advantage: <strong>life scouts</strong>. No, not like Boys Scouts of America "Life" scouts, like people scouting ahead for you in life.</p> <p>The youngest child often has older siblings they have observed keenly, maintained a strong and open rapport with, and with whom they have shared significant life experiences. They know them at a profound level.</p> <p>They then get to observe these older siblings blaze a variety of trails through life, years ahead of them. The youngest get to observe the decisions the older siblings make, often well aware of context, and then see the consequences of those decisions play out. Sometimes those consequences are positive, and sometimes they are not. Sometimes the outcome seems causal, and sometimes it seems that luck played a large role. Sometimes that whole decision-consequence cycle can happen well before a younger sibling might be faced with a similar decision.</p> <p>In many ways, this is like mentorship on an amplified level. Not mentorship in terms of career or school advice, but mentorship in life—hard earned advice and examples in real time, with real stakes, resulting in real impact.</p> <p>The advantage of having life scouts naturally follows. The youngest now has deeply detailed experience of highly trusted people to draw from when navigating the world ahead of them. As a kid, you already know the best parks or playgrounds nearby. You also know what areas of the neighborhood are dangerous. As a student, you're confident in navigating the school system, and the trade-offs of different schedule choices. As an adult, you have gleaned some insight on dating and partner choices, and how those can affect directions in life. You have observed different fields of study or career choices, and how those areas affect stress, income, flexibility, and more. You have witnessed glimpses of different parenting styles and parenting choices. The examples are endless.</p> <p>Sure, not everything directly transfers, but I've found these "life scouts" to be priceless in my life. A shout-out to all the older siblings out there who pave paths and building organizational knowledge for those of us who come afterward. We appreciate you!</p> <p>As for me, I think benefiting from such mentorship (from siblings) is also why I value mentorship in general so highly. As the youngest sibling, I might not be able to provide powerful mentorship this particular way, but I still can hopefully pay it forward to nieces and nephews and other youth in my community.</p> Comparing UTOPIA ISPs Luke Hsiao 2025-08-03T00:00:00+00:00 2025-08-15T00:00:00+00:00 https://luke.hsiao.dev/blog/utopia-isps/ A snapshot comparison of the internet service providers that UTOPIA fiber supports. <p>I currently use <a rel="external" href="https://www.utopiafiber.com/">UTOPIA</a> for my fiber connection. Their model is that they charge for the infrastructure, and then they have many ISPs on top that provide competing internet services.</p> <h2 id="pricing">Pricing<a class="zola-anchor" href="#pricing" aria-label="Anchor link for: pricing" title="Anchor link for: pricing"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>As a snapshot comparison (primarily for my own notes), here's a comparison of pricing for <em>residential</em> service, on the tier of 1Gbps download and upload (symmetric). This is the tier I'm currently on.</p> <table><thead><tr><th style="text-align: left">ISP</th><th style="text-align: right">Price ($)</th><th style="text-align: left">Notes</th></tr></thead><tbody> <tr><td style="text-align: left"><a rel="external" href="https://centrafiox.com/utopia">Centra</a></td><td style="text-align: right"><code>43.95</code></td><td style="text-align: left">Appear more honest on their Broadband label (showing 841Mbps/884Mbps down/up, rather than 1000Mbps).</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://etstelco.com/residential#service-lookup">ETS</a></td><td style="text-align: right"><code>45.00</code></td><td style="text-align: left">Up to 10Gbps.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.fusionnetworks.me/services/fiber-internet/">Fusion Networks</a></td><td style="text-align: right"><code>45.00</code></td><td style="text-align: left">1Gb is their fastest plan.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.3wave.net/?0#HomePlansAndPricing">3wave</a></td><td style="text-align: right"><code>48.00</code></td><td style="text-align: left"></td></tr> <tr><td style="text-align: left"><a rel="external" href="https://portal.senawave.com/utopiasignup/">SenaWave</a></td><td style="text-align: right"><code>48.00</code></td><td style="text-align: left">Very sparse on details.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.voonami.com/internet/residential-internet/utopia/">Voonami</a></td><td style="text-align: right"><code>48.95</code></td><td style="text-align: left">Comes with a free static IP on request. Also offers colo services. 2.5 Gbps fastest.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://sumofiber.com/internet/">SumoFiber</a></td><td style="text-align: right"><code>49.95</code></td><td style="text-align: left">Appear slightly more honest, stating 980 Mbps up/down.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://signup.intellipop.com/plan/internet">IntelliPop</a></td><td style="text-align: right"><code>49.95</code></td><td style="text-align: left"></td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.connectfast.net/Residential/">ConnectFast</a></td><td style="text-align: right"><code>53.95</code></td><td style="text-align: left">Only a single plan: 1Gb</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.fiber.net/internet-services/residential-internet/">FiberNet</a></td><td style="text-align: right"><code>53.95</code></td><td style="text-align: left">1Gb is their fastest plan, but they have infra (i.e., offers colo services).</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://xmission.com/utopia?cid=utopia_internet-service-providers">XMission</a></td><td style="text-align: right"><code>54.00</code></td><td style="text-align: left">Runs an extra firewall layer than can break some websites.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://milesbroadband.com/#/pricing">Miles Broadband</a></td><td style="text-align: right"><code>54.00</code></td><td style="text-align: left"></td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.wifipros.co/utopiafiber">WiFiPros</a></td><td style="text-align: right"><code>55.00</code></td><td style="text-align: left">I dislike the company name.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.advancedstream.com/broadband_disclosure_labels/F002228961500001GbpsUTOPIA.html">Advanced Stream</a></td><td style="text-align: right"><code>55.00</code></td><td style="text-align: left">10 TB data cap, others are unlimited. Honest with specific speeds.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.brigham.net/residential-pricing/">Brigham</a></td><td style="text-align: right"><code>58.95</code></td><td style="text-align: left">Wordpress site, sloppy errors like discrepancies in pricing.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://infowest.com/">InfoWest</a></td><td style="text-align: right"><code>80.00</code></td><td style="text-align: left">Unclear to me if this $80 includes UTOPIA's $30. Probably?</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.beehive.net/">Beehive Broadband</a></td><td style="text-align: right">N/A</td><td style="text-align: left">Hard to find pricing is a dark pattern.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://www.risebroadband.com/fiber-internet/">Rise Broadband</a></td><td style="text-align: right">N/A</td><td style="text-align: left">Couldn't find price.</td></tr> <tr><td style="text-align: left"><a rel="external" href="https://fastel.com/internet/">Fastel</a></td><td style="text-align: right">N/A</td><td style="text-align: left">Sketchily sparse website and no pricing. Dark patterns.</td></tr> </tbody></table> <p>At some point, I want to go through and learn more about their nuances. For example, XMission has an annoying trait of running their own firewall, which breaks websites occasionally. But, they also run their own datacenters and have some nice peering agreements that likely improve latency and performance.</p> <p>Here's a <a rel="external" href="https://www.utopiafiber.com/wp-content/uploads/2025/01/New-ISP-Comparison-Sheet_2025.pdf">UTOPIA-provided comparison sheet</a>, too. There is some discrepancy in the pricing and the ISPs themselves, though.</p> Variable fonts and italics across browsers Luke Hsiao 2025-05-25T00:00:00+00:00 2025-05-25T00:00:00+00:00 https://luke.hsiao.dev/blog/variable-fonts/ Some notes on getting variable web fonts to work with italics across Chrome and Firefox. <p>I'm a big fan of <a rel="external" href="https://www.brailleinstitute.org/freefont/">Atkinson Hyperlegible Next</a> and <a rel="external" href="https://berkeleygraphics.com/typefaces/berkeley-mono/">Berkeley Mono</a> (TX-02). It is my opinion that they both look great, unique, and provide valuable legibility features.</p> <p>I recently refactored this site to use the variable, web versions of these fonts. I was happy that I now had only a single <code>@font-face</code> definition, not a handful, and went on my way. (I had haphazardly picked up these parameters on the Internet somewhere.)</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="css"><span class="giallo-l"><span style="color: #CBA6F7;">@font-face</span><span style="color: #9399B2;"> {</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-family</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> &#39;Berkeley Mono&#39;</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> src</span><span style="color: #94E2D5;">:</span><span style="color: #89B4FA;font-style: italic;"> url</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;../fonts/Berkeley Mono Variable.woff2&#39;</span><span style="color: #9399B2;">)</span><span style="color: #89B4FA;font-style: italic;"> format</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;woff2&#39;</span><span style="color: #9399B2;">);</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-stretch</span><span style="color: #94E2D5;">:</span><span style="color: #FAB387;"> 60 100</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-weight</span><span style="color: #94E2D5;">:</span><span style="color: #FAB387;"> 100 900</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-style</span><span style="color: #94E2D5;">:</span><span> oblique</span><span style="color: #FAB387;"> -16</span><span style="color: #CBA6F7;">deg</span><span style="color: #FAB387;"> 0</span><span style="color: #CBA6F7;">deg</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-feature-settings</span><span style="color: #94E2D5;">:</span><span> calt</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-variant-ligatures</span><span style="color: #94E2D5;">:</span><span> contextual</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">@font-face</span><span style="color: #9399B2;"> {</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-family</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> &#39;Atkinson Hyperlegible Next&#39;</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> src</span><span style="color: #94E2D5;">:</span><span style="color: #89B4FA;font-style: italic;"> url</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;../fonts/AtkinsonHyperlegibleNextVF-Variable.woff2&#39;</span><span style="color: #9399B2;">)</span><span style="color: #89B4FA;font-style: italic;"> format</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;woff2&#39;</span><span style="color: #9399B2;">);</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-weight</span><span style="color: #94E2D5;">:</span><span style="color: #FAB387;"> 100 900</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-style</span><span style="color: #94E2D5;">:</span><span> oblique</span><span style="color: #FAB387;"> -16</span><span style="color: #CBA6F7;">deg</span><span style="color: #FAB387;"> 0</span><span style="color: #CBA6F7;">deg</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span></code></pre> <p>Then, just recently, I realized that my italics were broken on this site for weeks! They worked on Chrome as I had it configured. They did not work on Firefox.</p> <p>I tried a few things.</p> <p>First, I tried duplicating the <code>@font-face</code> definition, but with <code>font-style: italic</code>. This made it work on Firefox, but broke Chrome.</p> <p>I then found there is a new way to specify italics for variable web fonts, specifically (assuming it is supported by the font): <code>font-variation-settings</code>. In the case of Atkinson Hyperlegible Next, I could get my italics looking right on both Firefox and Chrome by adding the following CSS.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="css"><span class="giallo-l"><span style="color: #89B4FA;">em</span><span style="color: #9399B2;"> {</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-variation-settings</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> &quot;ital&quot;</span><span style="color: #FAB387;"> 1</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-synthesis</span><span style="color: #94E2D5;">:</span><span> none</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span></code></pre> <p>But, this didn't work for Berkeley Mono.</p> <p>I also tried things like using <code>woff2-variations</code> in my <code>@font-face</code> definitions and other little tweaks to no avail.</p> <p>Finally, after more experimentation, I was pleasantly surprised that I could get everything working just by <em>simplifying</em>.</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="css"><span class="giallo-l"><span style="color: #CBA6F7;">@font-face</span><span style="color: #9399B2;"> {</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-family</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> &#39;Berkeley Mono&#39;</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> src</span><span style="color: #94E2D5;">:</span><span style="color: #89B4FA;font-style: italic;"> url</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;../fonts/Berkeley Mono Variable.woff2&#39;</span><span style="color: #9399B2;">)</span><span style="color: #89B4FA;font-style: italic;"> format</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;woff2&#39;</span><span style="color: #9399B2;">);</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-stretch</span><span style="color: #94E2D5;">:</span><span style="color: #FAB387;"> 60 100</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-weight</span><span style="color: #94E2D5;">:</span><span style="color: #FAB387;"> 100 900</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-feature-settings</span><span style="color: #94E2D5;">:</span><span> calt</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-variant-ligatures</span><span style="color: #94E2D5;">:</span><span> contextual</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #CBA6F7;">@font-face</span><span style="color: #9399B2;"> {</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-family</span><span style="color: #94E2D5;">:</span><span style="color: #A6E3A1;"> &#39;Atkinson Hyperlegible Next&#39;</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> src</span><span style="color: #94E2D5;">:</span><span style="color: #89B4FA;font-style: italic;"> url</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;../fonts/AtkinsonHyperlegibleNextVF-Variable.woff2&#39;</span><span style="color: #9399B2;">)</span><span style="color: #89B4FA;font-style: italic;"> format</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&#39;woff2&#39;</span><span style="color: #9399B2;">);</span></span> <span class="giallo-l"><span style="color: #89B4FA;"> font-weight</span><span style="color: #94E2D5;">:</span><span style="color: #FAB387;"> 100 900</span><span style="color: #9399B2;">;</span></span> <span class="giallo-l"><span style="color: #9399B2;">}</span></span></code></pre> <p>I dropped the (likely incorrect) <code>font-style</code> attribute. No need for the <code>em</code> class override, now, either.</p> <p>With that, I have <em>italics</em>, both for my normal font, and for my <em><code>monospace </code></em> font that work on both Firefox and Chrome. All I needed to do was delete the right two lines.</p> Performance gains of undervolting a T480s Luke Hsiao 2025-05-23T00:00:00+00:00 2025-12-04T00:00:00+00:00 https://luke.hsiao.dev/blog/undervolting-t480s/ I undervolted and reapplied thermal paste to an old T480s to find surprising improvements of 12% and 24% CPU performance for single- and multi-core workloads, respectively. <p>I have been using a self-refurbished <a rel="external" href="https://psref.lenovo.com/syspool/Sys/PDF/withdrawnbook/ThinkPad_X230.pdf">Thinkpad X230</a> for quite a while as a personal machine. I believe in the benefits of continuing to use old laptops [<a rel="external" href="https://drewdevault.com/2020/02/18/Fucking-laptops.html">1</a>, <a rel="external" href="https://solar.lowtechmagazine.com/2020/12/how-and-why-i-stopped-buying-new-laptops.html">2</a>, <a rel="external" href="https://atthis.link/blog/2021/reassesstech.html">3</a>]. However, recently, I've started to need to use a laptop to do presentations frequently, and the HDMI port on my X230 is not functioning fully, resulting in distorted colors and other distracting artifacts. I might try and repair that, someday.</p> <p>However, due to pressing need, I instead picked up a used <a rel="external" href="https://psref.lenovo.com/syspool/Sys/PDF/withdrawnbook/ThinkPad_X230.pdf">T480s</a> from a good friend, which had a working HDMI port.</p> <p>Over the first couple days of use, I noticed that this thing ran <em>hot</em>. Sure, I have the <code>i7-8650u</code> version, but the fans were spinning up far more than I'd like, and CPU temps would frequently exceed 95 °C for bursts at a time.</p> <p>Naturally, I did a little digging.</p> <p>It turns out, Linux and the T480s have a longstanding problem! I don't understand the problem well enough to explain it here. But, essentially, people were seeing throttling and temperature issues with these machines on Linux (but not Windows). To be clear, this issue was found on an old Linux kernel (4.15, I'm on 6.14). I do not know if this specific problem is affecting me. However, through this, I learned about a fun tool for undervolting a T480s: <a rel="external" href="https://github.com/erpalma/throttled"><code>throttled</code></a>.</p> <p>Even if this issue was resolved in newer kernels, I imagined that some undervolting could still help my thermals and performance. So, I tried it. In addition, to give the attempt the best chance it had, I also replaced the thermal paste with fresh <a rel="external" href="https://www.thermal-grizzly.com/en/duronaut/s-tg-d">Duronaut</a>.</p> <p>I was surprised to find a <strong>12% improvement</strong> in single-core performance and a <strong>24% improvement</strong> in multi-core on Geekbench 6!</p> <div align="center" > <table><thead><tr><th style="text-align: left">Metric</th><th style="text-align: right"><a rel="external" href="https://browser.geekbench.com/v6/cpu/12075976">Original</a></th><th style="text-align: right"><a rel="external" href="https://browser.geekbench.com/v6/cpu/12105681">Optimized</a></th><th style="text-align: right">Change (%)</th></tr></thead><tbody> <tr><td style="text-align: left">Single-core</td><td style="text-align: right"><code>1341</code></td><td style="text-align: right"><code>1499</code></td><td style="text-align: right"><code>+11.8</code></td></tr> <tr><td style="text-align: left">Multi-core</td><td style="text-align: right"><code>3989</code></td><td style="text-align: right"><code>4948</code></td><td style="text-align: right"><code>+24.0</code></td></tr> </tbody></table> </div> <p>The laptop runs much cooler, and the fans spin up much less. It's much more pleasant to use.</p> <p>I ultimately landed on the following undervolt for this machine. (Remember, each chip is different, you might not be able to use these values.)</p> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="toml"><span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># All voltage values are expressed in mV and *MUST* be negative (i.e. undervolt)!</span></span> <span class="giallo-l"><span style="color: #9399B2;">[</span><span>UNDERVOLT</span><span style="color: #9399B2;">]</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># CPU core voltage offset (mV)</span></span> <span class="giallo-l"><span>CORE: -135</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># Integrated GPU voltage offset (mV)</span></span> <span class="giallo-l"><span>GPU: -105</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># CPU cache voltage offset (mV)</span></span> <span class="giallo-l"><span>CACHE: -135</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># System Agent voltage offset (mV)</span></span> <span class="giallo-l"><span>UNCORE: -105</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"># Analog I/O voltage offset (mV)</span></span> <span class="giallo-l"><span>ANALOGIO: 0</span></span></code></pre> <p>What a satisfying way to squeeze a little more performance out of a still-great laptop.</p> Steel man arguments, or arguing intelligently Luke Hsiao 2025-03-22T00:00:00+00:00 2025-03-22T00:00:00+00:00 https://luke.hsiao.dev/blog/steel-man/ If the goal is to actually come to some solution, consider using a steel man approach, it's a more effective way to debate. <p>I'm a big fan of the idea of "steel man" argument. However, I've found that this term is far less widely known than its "straw man" counterpart. I've heard some people just call this "arguing intelligently".</p> <p>Here are two good definitions if you're unfamiliar with the idea. First, Wikipedia's for the more abstract definition, and then Robin Sloan's practical example.</p> <p><a rel="external" href="https://en.wikipedia.org/wiki/Straw_man">Wikipedia</a>:</p> <blockquote> <p>A steel man argument (or steelmanning) is the opposite of a straw man argument. Steelmanning is the practice of applying the rhetorical principle of charity through addressing the strongest form of the other person's argument, even if it is not the one they explicitly presented. Creating the strongest form of the opponent's argument may involve removing flawed assumptions that could be easily refuted or developing the strongest points which counter one's own position. Developing counters to steel man arguments may produce a stronger argument for one's own position.</p> </blockquote> <p><a rel="external" href="https://archive.is/5xJKc">Robin Sloan</a></p> <blockquote> <p>There are two debaters, Alice and Bob. Alice takes the podium, makes her argument. Then Bob takes her place, but before he can present his counter-argument, he must summarize Alice's argument to her satisfaction — a demonstration of respect and good faith. Only when Alice agrees that Bob has got it right is he permitted to proceed with his own argument — and then, when he's finished, Alice must summarize it to his satisfaction.</p> <p>The first time I saw one of these debates, it blew my mind.</p> </blockquote> <p>Or, as Stephen R. Covey put it: "<a rel="external" href="https://en.wikipedia.org/wiki/The_7_Habits_of_Highly_Effective_People">Seek first to understand, then to be understood</a>".</p> <p>Imagine if the default mode of debate was like this. A tool for persuasion, consideration, exploration, and achieving consensus (or at least clearly understanding the shape of disagreement). Instead, culturally, we're more used to debate as a stage for name-calling, politics, distraction, point-scoring, and drama.</p> <p>Next time you're debating something, consider arguing more intelligently, and treating the other parties with that same human respect.</p> Sharing intro cards Luke Hsiao 2025-03-16T00:00:00+00:00 2025-06-22T00:00:00+00:00 https://luke.hsiao.dev/blog/intro-cards/ Have you ever considered giving out "intro cards" to neighbors or other people in your close proximity? <p>Those who know me know that I am fairly introverted. But, I am also someone who strongly values community, and thinks there are huge benefits to be had by building or connecting to one.</p> <p>A big part of building or integrating into communities is getting to know the people in them. Some people have a natural talent for this. Others have built that skill through many years of deliberate practice. There are also some people that use software to manage and track these relationships "personal CRM"-style (e.g., <a rel="external" href="https://www.monicahq.com/">Monica</a>, or really detailed notes in Google Contacts).</p> <p>I am currently none of these people.</p> <p>But, I would like to build the skill.</p> <p>One reason knowing people is valuable is because it helps facilitate connection. "Oh, I know <code>X</code> is in that industry, let me connect you." "The <code>Y</code> family has kids that line up with your kids age and go to <code>Z</code> Junior High, too." "Oh <code>A</code> used to live right around there, let's ask them for advice." These types of serendipitous connections can build strong bonds and strengthen a community, but you can only make them if you know enough.</p> <p>To that end, I think "intro cards" are an interesting idea. Imagine that when you moved in somewhere, or went to a new group meeting, or church event, or met with the other parents for your kid's soccer team, that you could get a small <em>physical</em> card of pertinent information about these new people you met.</p> <p>In big social settings like that, where you're meeting a lot of new people, it's very easy (for me, anyway) to have forgotten the names of the people I <em>just</em> talked to. Now, instead imagine that throughout the event, you were exchanging intro cards, something like the following.</p> <figure > <a href=".&#x2F;bart.png"> <img src=".&#x2F;bart.png" alt="An example intro card" style="background-color: white;" /> </a> <figcaption> An example intro card, 3&quot;×5&quot; sized. </figcaption> </figure> <p>During the event, you could focus completely on the current conversations, knowing you already have some notes provided to you to recall the previous ones.</p> <p>Is the concept so wild? In Japan, there is a whole interesting culture around <a rel="external" href="https://www.japanlivingguide.com/career/business-etiquette/japan-business-card-etiquette/">exchanging business cards</a>. In the US, we rarely do so. Japan recently went even further, making a whole <a rel="external" href="https://www.tokyoweekender.com/entertainment/middle-aged-man-trading-cards-go-viral-in-japan/">trading card game</a> for the middle-aged members of their small rural town, connecting the youth to older generations in a heartwarming way.</p> <p>Naturally, these intro cards might include personal information you probably wouldn't put on the Internet, but yet might want to provide to people depending on the context. So, of course, you should tune the content to the purpose. You should also feel things out first, before you just go handing these to everyone. For example, in the context of introducing yourself to neighbors, maybe it is something like Bart's above. If it's meeting coworkers, maybe it highlights parts of the system you're familiar with, or your favorite software or languages. If it's a hobby group, maybe it highlights more about your specific niche interests.</p> <p>I've started trying this idea out in a few scenarios, to positive reception. To make it easy, just print these on 3"×5" index cards at home. An example Typst document for it is included below.</p> <hr /> <details open><summary>Source Code: Example Intro Card in <a href="https://typst.app/docs/">Typst</a></summary> <pre class="giallo" style="color: #CDD6F4; background-color: #1E1E2E;"><code data-lang="typst"><span class="giallo-l"><span style="color: #CBA6F7;">#set</span><span style="color: #89B4FA;font-style: italic;"> text</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">font</span><span style="color: #9399B2;">:</span><span style="color: #A6E3A1;"> &quot;Berkeley Mono&quot;</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">#set</span><span style="color: #89B4FA;font-style: italic;"> text</span><span style="color: #9399B2;">(</span><span style="color: #FAB387;">9pt</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">#set</span><span style="color: #89B4FA;font-style: italic;"> par</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">leading</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 0.35em</span><span style="color: #9399B2;">)</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">#set</span><span style="color: #89B4FA;font-style: italic;"> page</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> width</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 3in</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> height</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 5in</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> margin</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 0.25in</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> // background: rect(width: 100%, height: 100%, stroke: 1pt + black),</span></span> <span class="giallo-l"><span style="color: #9399B2;">)</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;">// Placed here because these are reused to make links</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">#let</span><span> telephone_number</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;xxx-xxx-xxxx&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">#let</span><span> email_address</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;xxxx@example.com&quot;</span></span> <span class="giallo-l"><span style="color: #CBA6F7;">#let</span><span> website</span><span style="color: #94E2D5;"> =</span><span style="color: #A6E3A1;"> &quot;www.example.com&quot;</span></span> <span class="giallo-l"></span> <span class="giallo-l"><span style="color: #9399B2;">#</span><span style="color: #89B4FA;font-style: italic;">grid</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> columns</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 100%</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> rows</span><span style="color: #9399B2;">: (</span><span style="color: #FAB387;">1%</span><span style="color: #9399B2;">,</span><span style="color: #FAB387;"> 1%</span><span style="color: #9399B2;">,</span><span style="color: #FAB387;"> 40%</span><span style="color: #9399B2;">,</span><span style="color: #FAB387;"> 2%</span><span style="color: #9399B2;">,</span><span style="color: #FAB387;"> 2%</span><span style="color: #9399B2;">,</span><span style="color: #FAB387;"> 1fr</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> gutter</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 1.0mm</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> text</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">size</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 11pt</span><span style="color: #9399B2;">,</span><span style="color: #EBA0AC;font-style: italic;"> weight</span><span style="color: #9399B2;">:</span><span style="color: #A6E3A1;"> &quot;bold&quot;</span><span style="color: #9399B2;">,</span><span style="color: #A6E3A1;"> &quot;Bart Simpson&quot;</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">: (</span><span>left</span><span style="color: #94E2D5;"> +</span><span> horizon</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [],</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> block</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">clip</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> true</span><span style="color: #9399B2;">,</span><span style="color: #EBA0AC;font-style: italic;"> radius</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 3mm</span><span style="color: #9399B2;">,</span><span style="color: #89B4FA;font-style: italic;"> image</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;bart.png&quot;</span><span style="color: #9399B2;">)),</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">: (</span><span>center</span><span style="color: #94E2D5;"> +</span><span> horizon</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [],</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">hline</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">stroke</span><span style="color: #9399B2;">: (</span><span>paint</span><span style="color: #9399B2;">:</span><span> gray</span><span style="color: #9399B2;">,</span><span> thickness</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 0.75pt</span><span style="color: #9399B2;">,</span><span> cap</span><span style="color: #9399B2;">:</span><span style="color: #A6E3A1;"> &quot;round&quot;</span><span style="color: #9399B2;">),</span><span style="color: #EBA0AC;font-style: italic;"> y</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 4</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [],</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> grid</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> columns</span><span style="color: #9399B2;">: (</span><span style="color: #FAB387;">20%</span><span style="color: #9399B2;">,</span><span style="color: #FAB387;"> 1fr</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> column-gutter</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 2mm</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> row-gutter</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 2.20mm</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;font-style: italic;"> // stroke: 1pt + silver,</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">age</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [</span><span>Born in XXXX</span><span style="color: #9399B2;">],</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">family</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [</span><span>Spouse: Susan </span><span style="color: #9399B2;">\</span></span> <span class="giallo-l"><span> Children: Johnny (M, XXXX)</span><span style="color: #9399B2;">],</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">rel-to</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [</span><span>Marge (mom), Homer (dad), Lisa (sis), Maggie (sis)</span><span style="color: #9399B2;">],</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">from</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [</span><span>New York, NY (</span><span style="color: #9399B2;">--</span><span>XXXX) </span><span style="color: #9399B2;">\</span><span> Chicago, IL (</span><span style="color: #9399B2;">--</span><span>XXXX)</span><span style="color: #9399B2;">],</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">job</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [</span><span>Manager at </span><span style="color: #9399B2;">#</span><span style="color: #89B4FA;font-style: italic;">link</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;https://www.example.com&quot;</span><span style="color: #9399B2;">)</span><span>[Example</span><span style="color: #9399B2;">]</span><span>]</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">edu</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [</span><span>NYU (BS in PR, &#39;XX)</span><span style="color: #9399B2;">],</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">email</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> link</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;mailto:&quot;</span><span style="color: #94E2D5;"> +</span><span> email_address</span><span style="color: #9399B2;">,</span><span> email_address</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">phone</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> link</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;tel:&quot;</span><span style="color: #94E2D5;"> +</span><span> telephone_number</span><span style="color: #9399B2;">,</span><span> telephone_number</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">([*</span><span style="color: #F38BA8;font-weight: bold;">website</span><span style="color: #9399B2;">*],</span><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #9399B2;">),</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> link</span><span style="color: #9399B2;">(</span><span style="color: #A6E3A1;">&quot;https://&quot;</span><span style="color: #94E2D5;"> +</span><span> website</span><span style="color: #9399B2;">,</span><span> website</span><span style="color: #9399B2;">),</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> left</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> [],</span><span> grid.</span><span style="color: #89B4FA;font-style: italic;">cell</span><span style="color: #9399B2;">(</span></span> <span class="giallo-l"><span style="color: #89B4FA;font-style: italic;"> text</span><span style="color: #9399B2;">(</span><span style="color: #EBA0AC;font-style: italic;">size</span><span style="color: #9399B2;">:</span><span style="color: #FAB387;"> 5pt</span><span style="color: #9399B2;">,</span><span style="color: #EBA0AC;font-style: italic;"> style</span><span style="color: #9399B2;">:</span><span style="color: #A6E3A1;"> &quot;italic&quot;</span><span style="color: #9399B2;">)[</span><span>v25.03</span><span style="color: #9399B2;">],</span><span style="color: #9399B2;font-style: italic;"> // calver</span></span> <span class="giallo-l"><span style="color: #EBA0AC;font-style: italic;"> align</span><span style="color: #9399B2;">:</span><span> right</span><span style="color: #94E2D5;"> +</span><span> top</span><span style="color: #9399B2;">,</span></span> <span class="giallo-l"><span style="color: #9399B2;"> ),</span></span> <span class="giallo-l"><span style="color: #9399B2;"> )</span></span> <span class="giallo-l"><span style="color: #9399B2;">)</span></span></code></pre></details> Teammates, not coworkers Luke Hsiao 2025-03-01T00:00:00+00:00 2026-02-11T00:00:00+00:00 https://luke.hsiao.dev/blog/teammates-not-coworkers/ Company culture is important. One aspect that particularly affects people's day-to-day life is whether, as a friend said, they have "teammates, not coworkers". This post explores the distinction. <p>I was talking with a friend a while back who had gone through some jobs they didn't enjoy, and had now landed on a job they seemed to thoroughly enjoy. When I asked them about it, the key reason they provided for why this particular job seemed so much better was: <strong>"I have teammates, not coworkers."</strong></p> <p>That phrase has resonated with me since.</p> <p>What distinguishes a "teammate" from a "coworker" in a way that improves job satisfaction like it did for my friend?</p> <p>Here are some thoughts.</p> <h2 id="teammates-have-shared-principles-and-values">Teammates have shared principles and values<a class="zola-anchor" href="#teammates-have-shared-principles-and-values" aria-label="Anchor link for: teammates-have-shared-principles-and-values" title="Anchor link for: teammates-have-shared-principles-and-values"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I believe explicitly listing principles and values is <a href="https://luke.hsiao.dev/blog/family-values/">incredibly valuable in all sorts of contexts</a>. I particularly think that is true in the context of a company. <a rel="external" href="https://oxide.computer/principles">Many</a>, <a rel="external" href="https://www.jamesshore.com/v2/blog/2025/the-best-product-engineering-org-in-the-world#people">many</a> <a rel="external" href="https://vannevarlabs.com/principles/">companies</a> <a rel="external" href="https://handbook.gitlab.com/handbook/values/">do</a> <a rel="external" href="https://37signals.com/">this</a>, and know that it is a meaningful way to differentiate themselves to potential applicants as well as provide a guiding force for the business. Sometimes it is under different names, but the purpose is generally the same: broadcast their principles, values, and way of working to attract like-minded teammates. It also goes without saying that professing principles/values doesn't stop a company from being evil (see Enron).</p> <p>Sometimes, values can come into tension with each other, and that is healthy. But, it's hard to be a good teammate when there is not a large overlap in values. Instead, it will be a consistent source of friction.</p> <p>Fundamentally, I think this is the key point. Then, depending on what those shared values are, it's easy to see some concrete examples. Here are some examples that I think are teammate behavior drawn from my <a href="https://luke.hsiao.dev/blog/family-values/">personal values</a>.</p> <h3 id="teammates-reduce-entropy-coworkers-fuel-it">Teammates reduce entropy, coworkers fuel it.<a class="zola-anchor" href="#teammates-reduce-entropy-coworkers-fuel-it" aria-label="Anchor link for: teammates-reduce-entropy-coworkers-fuel-it" title="Anchor link for: teammates-reduce-entropy-coworkers-fuel-it"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Entropy in engineering shows up a lot of different ways. It could be the codebase (i.e., the software architecture, the abstractions, the tooling, etc.). It could be communication (i.e., messages that lack sufficient context, require unnecessary back-and-forth, etc.). It could be all sorts of other things (documentation, company policies, IT management, and more). I'd also classify complexity in general as a component of entropy, despite the fact that growing complexity is often fundamental to a developing product. I define complexity as <a rel="external" href="https://web.stanford.edu/~ouster/cgi-bin/aposd.php">APOSD does</a>: "anything related to the structure of a software system that makes it hard to understand and modify the system". This generally pragmatic definition also applies to things outside software. Entropy is the extra noise, disorder, confusion, etc.</p> <p>I've found that people I'd call teammates naturally reduce entropy (and strive to contain complexity) in their respective domains. Meanwhile, coworkers are often the source.</p> <p>Some examples:</p> <ul> <li>A coworker sends a bare <a rel="external" href="https://nohello.net/en/">hello</a>, a teammate includes the context necessary to make asynchronous communication effective.</li> <li>A teammate opens that PR fixing those 6 typos in the source code comments.</li> <li>A coworker sends that message tagging a bunch of people to investigate a "super flaky test", only to discover the issue is a real bug and the tests were not flaking at all after people context switch to help. They do not respect others time as much as their own. A teammate is willing to context switch to help.</li> <li>A coworker opens a PR with a bunch of code that "might be used in the future", even though it's not right now, increasing maintenance burden. A teammate deletes dead code.</li> <li>A coworker provides unhelpful commit messages like "fix stuff", or has poorly organized commits, or general doesn't explain the why. A teammate <a rel="external" href="https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes">provides thorough context</a> that often helps future debugging efforts.</li> <li>Teammates introduce tooling, frameworks, design patterns, etc. to help systematically reduce complexity.</li> <li>A coworker sends you a <a rel="external" href="https://parkscomputing.com/page/i-hate-screenshots-of-text">screenshot of text</a>, often of a token or credentials or something you need to then transcribe and enter. They claim innocence, but their actions make it clear they have no respect for your time or appreciation of optimizing communication to get things done.</li> <li>A teammate is the type of person that puts their shopping carts away, regardless of whether people are watching.</li> </ul> <h3 id="teammates-educate-build-collective-skills-coworkers-are-reluctant-to-share">Teammates educate/build collective skills, coworkers are reluctant to share<a class="zola-anchor" href="#teammates-educate-build-collective-skills-coworkers-are-reluctant-to-share" aria-label="Anchor link for: teammates-educate-build-collective-skills-coworkers-are-reluctant-to-share" title="Anchor link for: teammates-educate-build-collective-skills-coworkers-are-reluctant-to-share"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Teammates are the type of people that actively educate and build the collective skills of those they work with. They are the subject of those "Alice showed me how to"- or "I picked this up from Bob"-style comments. They are the ones who introduced you to that game-changing editor feature, or taught you about dotfiles, or helped you get up to speed and build a good mental model for that part of the codebase. They recommended that book that you read that changed how you thought about something. They are the ones you frequently have deep, meaningful conversations with. Teammates share because they want to. Teammates make the people around them more effective now, while also helping them grow and expand their potential in the future.</p> <p>Here's another way I see this happen. Suppose there is a task that requires some custom effort. Someone probably had to write a little script or something to do the job, maybe had to deal with some edge cases. Now, you are handed essentially that same ticket and a pointer that "it's just like last time".</p> <p>If you have a teammate, you would go to that other ticket, and likely find everything you need. They probably even shared the snippet they used.</p> <p>If you have a coworker, you would go that other ticket, and likely find nothing except it being marked "Done".</p> <p>Coworkers aren't interested in sharing. That's not their job.</p> <h3 id="teammates-care-about-the-team-coworkers-care-about-themselves">Teammates care about the team, coworkers care about themselves<a class="zola-anchor" href="#teammates-care-about-the-team-coworkers-care-about-themselves" aria-label="Anchor link for: teammates-care-about-the-team-coworkers-care-about-themselves" title="Anchor link for: teammates-care-about-the-team-coworkers-care-about-themselves"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>Sure, <a rel="external" href="https://signalvnoise.com/svn3/the-company-isnt-a-family/">the company isn't a family</a>. It's not meant to be. But, as DHH says,</p> <blockquote> <p>You don't have to pretend to be a family to be courteous. Or kind. Or protective. All those values can be expressed even better in principles, policies, and, most importantly, actions.</p> </blockquote> <p>Teammates care about the team. Teammates break down silos and artificial divisions, resulting in increased unity. They are people you enjoy working with, and you can tell they genuinely care about you. Their pattern of care often extends into their other communities as well. You sincerely look forward to interactions with teammates.</p> <p>Coworkers don't bother investing in relationships outside what impacts their core job. Interactions with coworkers might be laborious and taxing.</p> <h3 id="teammates-enjoy-the-craft-coworkers-only-see-it-as-a-means-to-an-end">Teammates enjoy the craft, coworkers only see it as a means to an end<a class="zola-anchor" href="#teammates-enjoy-the-craft-coworkers-only-see-it-as-a-means-to-an-end" aria-label="Anchor link for: teammates-enjoy-the-craft-coworkers-only-see-it-as-a-means-to-an-end" title="Anchor link for: teammates-enjoy-the-craft-coworkers-only-see-it-as-a-means-to-an-end"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>This one might be controversial. I think teammates enjoy the craftsmanship of whatever craft they are engaged in. They are often the people that inspire others from their enthusiasm. Maybe they play for the love of the game. Maybe they play to win. They are the ones metaphorically early to practice and always hitting the gym. Their enthusiasm and corresponding work ethic is contagious.</p> <p>Coworkers are just here to get that cash and do other things.</p> <p>Indeed, I think it is also common for a person to be a great teammate by this measure in some contexts while simultaneously not being a great teammate in others.</p> <p>(Nikola Jokić, who I'd argue is a great teammate, is one prominent example of an exception here with the way he exemplifies team play but, in the off-season, couldn't care less about basketball.)</p> <h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion" title="Anchor link for: conclusion"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>I'm sure I've missed a lot of examples here, but even so, it's been a good source of personal reflection to think about how I myself can try and be a good teammate. I certainly am convinced that striving to be a good teammate, and having good teammates, leads to improved job satisfaction and team success.</p> How to connect MTP to a kids profile on a Fire tablet Luke Hsiao 2025-02-08T00:00:00+00:00 2025-02-08T00:00:00+00:00 https://luke.hsiao.dev/blog/kids-fire-media-management/ If you're like me, this took too much searching around to find. Here's the key to managing personal media. <p>This is mostly a note-to-self.</p> <p>I'm a father with a kid that is starting to get old enough to appreciate some screen time. To that end, we got a cheap Amazon Fire tablet (sidenote, it seems Amazon has a corner on this market).</p> <p>It was nice, we set up a kid's profile, disabled the store, disabled calling, disabled the web browser, shared VLC (for personal media) and <a rel="external" href="https://apps.ankiweb.net/">AnkiDroid</a> (for educational flashcards). But, then I couldn't figure out for the life of me how to get media onto the internal storage in a way that was visible to that kid profile!</p> <p>Here's the trick:</p> <ol> <li>Log in to the kid profile.</li> <li>Bring down the settings menu by dragging down on the top.</li> <li><strong>Press and hold the Bluetooth icon</strong> until a PIN prompt appears.</li> <li>Enter your pin.</li> <li>The "Connected devices" menu will open, where you can change the USB setting to File transfer.</li> <li>Now, the internal storage reflects this kid profile specifically.</li> </ol> <p>Step 3 in particular was not obvious to me.</p> Oral histories are family treasures Luke Hsiao 2025-02-01T00:00:00+00:00 2025-03-31T00:00:00+00:00 https://luke.hsiao.dev/blog/oral-histories/ Oral histories are easy to conduct, and the recordings can have incredible family value. Here are some ideas from my experience doing them. <p>A few years ago, my father passed away from cancer after many tough years of treatment. That was a tough time. Once it became clear that there likely were not many years left, around Thanksgiving and Christmas, I took a trip home with one particular project on my mind: conducting some oral history interviews, particularly with my dad. The artifacts from that project have proved to be treasures to me.</p> <p>The goals of this post are: (1) plant a seed of persuasion to you that this might be something you might find value in, and (2) share some tips I learned along the way.</p> <h2 id="conducting-oral-history-interviews">Conducting oral history interviews<a class="zola-anchor" href="#conducting-oral-history-interviews" aria-label="Anchor link for: conducting-oral-history-interviews" title="Anchor link for: conducting-oral-history-interviews"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>First, I want to recognize the valuable content that the <a rel="external" href="https://oralhistory.library.ucla.edu/pages/interview_guidelines">UCLA Center for Oral History Research</a> has put out. Much of my preparation was simply reading these pages. In fact, for the questions section, I am going to duplicate their page nearly verbatim, as an extra archive. Before that, I want to call out two things that I found particularly valuable before getting to the questions.</p> <p>First, audio quality matters. I mean it <em>really</em> matters. When I went into this project, my original intention was to just conduct interviews, and then I had a more ambitious goal of actually writing up a family history book. In fact, I even got a couple chapters in, in LaTeX and everything. But, once my father passed, I gained knew appreciation for the high quality audio itself. Sure, a book might be the more effective way of transmitting and documenting information for information's sake. But there was so much value in hearing his voice. Hearing him tell his stories his way. Yes, you could do this with just a cell phone, and that would absolutely be better than nothing. But, if you have some time to do a little prep work, just spending a bit more time and money on getting equipment and learning how to use it brings the value of the audio to another level. The UCLA page suggests some gear. In my case, I went with a Motu M2 audio interface, along with a couple of relatively cheap dynamic XLR microphones, and it worked wonderfully. I recorded with a high bitrate and then distributed OPUS-encoded files to the family.</p> <p>Second, get a proper release form up front. Again UCLA provides <a rel="external" href="https://oralhistory.library.ucla.edu/pages/interview_guidelines">and excellent example</a>. Tune it to your needs. Even if this feels overkill, it sets a good mindset for the interviewee about how this might be used.</p> <h3 id="sample-outline-and-questions">Sample outline and questions<a class="zola-anchor" href="#sample-outline-and-questions" aria-label="Anchor link for: sample-outline-and-questions" title="Anchor link for: sample-outline-and-questions"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h3> <p>In this section, I duplicate the UCLA Center for Oral History Research's <a rel="external" href="https://oralhistory.library.ucla.edu/pages/family_history">family history sample outline and questions</a> nearly verbatim. But, I also have some suggestions on how to prepare that are likely different from a more generic process. Let's start with the outline.</p> <hr /> <details open><summary>Sample Outline and Questions</summary> <h4 id="1-early-childhood-and-family-background">1 Early Childhood and Family Background<a class="zola-anchor" href="#1-early-childhood-and-family-background" aria-label="Anchor link for: 1-early-childhood-and-family-background" title="Anchor link for: 1-early-childhood-and-family-background"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <h5 id="1-1-parents-and-family">1.1 Parents and Family<a class="zola-anchor" href="#1-1-parents-and-family" aria-label="Anchor link for: 1-1-parents-and-family" title="Anchor link for: 1-1-parents-and-family"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>When and where were you born?</li> <li>Tell me about your parents or your family background</li> <li>Where was your family originally from?</li> <li>What did your parents do for a living?</li> <li>Did you contribute to the family income or help parents in their work in any way?</li> <li>What was your parents' religious background? How was religion observed in your home?</li> <li>What were your parents' political beliefs? What political organizations were they involved in?</li> <li>What other relatives did you have contact with growing up?</li> <li>What do you remember about your grandparents?</li> <li>What stories did you hear about earlier ancestors whom you never knew?</li> <li>How many children were in the family, and where were you in the line-up?</li> <li>Describe what your siblings were like.</li> <li>Who were you closest to?</li> <li>Describe the house you grew up in. Describe your room.</li> <li>What were your family's economic circumstances? Do you remember any times when money was tight? Do you remember having to do without things you wanted or needed?</li> <li>What were your duties around the house as a child? What were the other children's duties? How did duties break down by gender?</li> <li>When did you learn to cook and who taught you? Were there any special family foods or recipes? Do you still make any traditional family foods?</li> <li>What activities did the family do together?</li> <li>What did you do on Christmas? Thanksgiving? Birthdays? Other holidays?</li> </ol> <h5 id="1-2-community-you-grew-up-in">1.2 Community You Grew Up In<a class="zola-anchor" href="#1-2-community-you-grew-up-in" aria-label="Anchor link for: 1-2-community-you-grew-up-in" title="Anchor link for: 1-2-community-you-grew-up-in"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>Describe the community you grew up in.</li> <li>Describe your neighborhood.</li> <li>Where did you shop? How far away were these shops and how did you get there?</li> <li>What's the largest town or city you remember visiting when you were young? Can you describe your impressions of it?</li> </ol> <h5 id="1-3-early-schooling">1.3 Early Schooling<a class="zola-anchor" href="#1-3-early-schooling" aria-label="Anchor link for: 1-3-early-schooling" title="Anchor link for: 1-3-early-schooling"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>What was school like for you? What did you like about it? What was hard about it for you?</li> <li>Who were your friends at school?</li> <li>Who were your favorite teachers?</li> <li>Do you remember teasing or bullying of you or anyone else?</li> </ol> <h5 id="1-4-friends-and-interests">1.4 Friends and Interests<a class="zola-anchor" href="#1-4-friends-and-interests" aria-label="Anchor link for: 1-4-friends-and-interests" title="Anchor link for: 1-4-friends-and-interests"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>What did you do in your spare time?</li> <li>Who were your friends and what did you do when you got together?</li> <li>Did you have any hobbies?</li> <li>Favorite stories? Favorite games or make-believe? Favorite toys?</li> <li>What did you want to be when you grew up?</li> </ol> <h4 id="2-teenage-years">2 Teenage Years<a class="zola-anchor" href="#2-teenage-years" aria-label="Anchor link for: 2-teenage-years" title="Anchor link for: 2-teenage-years"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <h5 id="2-1-changes-in-family">2.1 Changes in Family<a class="zola-anchor" href="#2-1-changes-in-family" aria-label="Anchor link for: 2-1-changes-in-family" title="Anchor link for: 2-1-changes-in-family"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>How did your relationship with your parents change when you became a teenager?</li> <li>If you had conflict with them, what was it over?</li> <li>Did you have chores around the house? What were they?</li> </ol> <h5 id="2-2-school">2.2 School<a class="zola-anchor" href="#2-2-school" aria-label="Anchor link for: 2-2-school" title="Anchor link for: 2-2-school"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>What were your favorite subjects? Particular interests?</li> <li>What were your least favorite subjects?</li> <li>Did you have any memorable teachers? Describe their teaching style. How did they influence you?</li> <li>What were the different groups at your school? Which did you belong to? How do you think you were perceived by others?</li> <li>Were you involved in any extracurricular activities? What were they?</li> <li>What were your plans when you finished school? Education? Work?</li> <li>What did your parents think of your plans? What did your friends think? What did your friends plan to do?</li> <li>Did the boys and girls in the family have different plans/expectations?</li> </ol> <h5 id="2-3-work">2.3 Work<a class="zola-anchor" href="#2-3-work" aria-label="Anchor link for: 2-3-work" title="Anchor link for: 2-3-work"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>Did you have jobs during your teenage years? Doing what?</li> <li>Did you contribute to the family income? If not, how did you spend your money?</li> </ol> <h5 id="2-4-social-life-and-outside-interests">2.4 Social Life and Outside Interests<a class="zola-anchor" href="#2-4-social-life-and-outside-interests" aria-label="Anchor link for: 2-4-social-life-and-outside-interests" title="Anchor link for: 2-4-social-life-and-outside-interests"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>Who were your friends? What did you do together? What individuals did you spend the most time with during this period?</li> <li>Was your group of friends single-sex, or did it include both boys and girls?</li> <li>At what age did you begin dating? What kinds of activities did you do on dates? Describe your first date.</li> <li>What was your parents' advice/rules related to dating/contact with opposite sex?</li> <li>What were your peer group's norms with regard to dating and relationships with the opposite sex?</li> <li>What were your hobbies/interests? What books did you read? What music did you listen to? What sports did you play? What crafts did you participate in?</li> </ol> <h4 id="3-adulthood">3 Adulthood<a class="zola-anchor" href="#3-adulthood" aria-label="Anchor link for: 3-adulthood" title="Anchor link for: 3-adulthood"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <h5 id="3-1-further-education">3.1 Further Education<a class="zola-anchor" href="#3-1-further-education" aria-label="Anchor link for: 3-1-further-education" title="Anchor link for: 3-1-further-education"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <h5 id="3-2-work-and-career">3.2 Work and Career<a class="zola-anchor" href="#3-2-work-and-career" aria-label="Anchor link for: 3-2-work-and-career" title="Anchor link for: 3-2-work-and-career"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <h5 id="3-3-marriage-or-formation-of-significant-relationship">3.3 Marriage or Formation of Significant Relationship<a class="zola-anchor" href="#3-3-marriage-or-formation-of-significant-relationship" aria-label="Anchor link for: 3-3-marriage-or-formation-of-significant-relationship" title="Anchor link for: 3-3-marriage-or-formation-of-significant-relationship"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>When and where did you meet? What drew you to them?</li> <li>When and how did you decide to move in together and/or marry?</li> <li>What was originally the most difficult for you about being married/being in a relationship? What was most satisfying?</li> <li>What advice would you give to someone today who was contemplating a serious relationship?</li> </ol> <h5 id="3-4-children">3.4 Children<a class="zola-anchor" href="#3-4-children" aria-label="Anchor link for: 3-4-children" title="Anchor link for: 3-4-children"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <ol> <li>Describe the birth of your children.</li> <li>What were they each like when they were young? How have they changed or not changed?</li> <li>What were their relationships with each other and with you like when they were young? Now?</li> <li>What activities did the family do together?</li> <li>What family traditions did you try to establish?</li> <li>Does your family have any heirlooms or objects of sentimental value? What is their origin, and how have they been passed down?</li> <li>What was most satisfying to you about raising children? What was most difficult?</li> <li>What values did you try to raise your children with? How did you go about doing that?</li> <li>What forms of discipline did you use and why?</li> </ol> <h5 id="3-5-ongoing-interests-and-hobbies">3.5 Ongoing Interests and Hobbies<a class="zola-anchor" href="#3-5-ongoing-interests-and-hobbies" aria-label="Anchor link for: 3-5-ongoing-interests-and-hobbies" title="Anchor link for: 3-5-ongoing-interests-and-hobbies"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h5> <h4 id="4-overview-and-evaluation">4 Overview and Evaluation<a class="zola-anchor" href="#4-overview-and-evaluation" aria-label="Anchor link for: 4-overview-and-evaluation" title="Anchor link for: 4-overview-and-evaluation"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h4> <ol> <li>What has provided you the greatest satisfaction in life?</li> <li>How would you say the world has changed since you were young?</li> </ol> <p>Also, ask about historically significant events the family member lived through:</p> <ol> <li>Was your family affected by the Depression?</li> <li>Did you or anyone close to you serve in a war? What do you remember of that experience?</li> <li>Did you support or oppose the war in Vietnam? How did you express your political opinions?</li> <li>Did you participate in, or do you have any memories of any of the movements that came out of the 1950s, '60s, and '70s, such as the civil rights movement or the women's liberation movement?</li> <li>If the family member belongs to a group that has traditionally been discriminated against: what were you told, both positive and negative, about your group inside your family? Outside? Did you experience discrimination? Who were your role models?</li> <li>If the family member is an immigrant or the child/grandchild of immigrants: what do you know of the country you or they came from? Why did you or they immigrate? How did you or they immigrate? What were some of your or their experiences and difficulties of beginning a life in a new country?</li> <li>Do you remember your first contact with such significant inventions as radio, television, or a computer? When did your family first buy these items?</li> </ol> </details> <hr /> <p>I think you'll agree with me that these are an impressive set of well-considered questions. But, here's the key: <em>customize</em>.</p> <p>Before you do the interview, maybe in collaboration with other interested parties (e.g., other family members), go through and customize this for that person in particular. For example, asking about their memories about the US civil rights movement might be a pivotal point for some, and largely irrelevant for others. Drop questions that are irrelevant. Add questions you personally want to know. One particularly effective thing to do is to seed examples you can use in the interviews.</p> <p>Here are some examples. Consider question 1.2.1: "Describe the community you grew up in." This is by all means a great question. But, if you already know something you (or your preparation team) are interested in, having those seeds will make for a better answer. You'll see this technique used in celebrity interviews frequently. For example, you might as the question "Describe the community you grew up in." Then, after they respond, if they didn't touch on a point you'd like to dig into, just a little seed can nudge them the right direction: "Wasn't there a giant tree you broke your hand on once?" If you have stories you've heard in the past you'd love to capture from the primary source in their voice, just a little spark like this often does the trick.</p> <p>"What did you do in your spare time? I remember you saying once you played a lot of snooker?"</p> <p>"How did your relationship with your parents change as you became a teenager? I remember Auntie X telling a story about how you ____ one time?"</p> <p>Finally, be sensitive to sensitive questions. Maybe you heard a story that painted some relative in a bad light. Is it worth recalling here? Does the interviewee want to immortalize this negative story with no anonymity and potentially no evidence? Provide a reminder that the goal of these is to preserve and distribute these stories and have the decency to offer alternative conversations.</p> <h2 id="conclusion">Conclusion<a class="zola-anchor" href="#conclusion" aria-label="Anchor link for: conclusion" title="Anchor link for: conclusion"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link" focusable="false"> <path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M9 15l6 -6" /> <path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /> <path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /> </svg></a></h2> <p>Conducting these interviews was a fond memory. I appreciated the recurring teasing about "all this unnecessary equipment". I enjoyed the conversations—I cannot remember ever talking to my dad directly for <em>hours</em> about his life as I did during the course of those weeks.</p> <p>But as time has passed since his passing, the value of these recordings has only continued to grow.</p>