Raine Virta
Raine Virta's blog
Zola
2026-02-22T00:00:00+00:00
https://raine.dev/atom.xml
Never leave the home row to navigate tmux panes
2026-02-22T00:00:00+00:00
2026-02-22T00:00:00+00:00
Raine Virta
https://raine.dev/blog/tmux-navigate-panes-home-row/
<p>tmux's default pane navigation requires pressing your
<span class="tooltip" tabindex="0">prefix key<span class="tooltip-tip">The key combo you press before a tmux command, e.g. Ctrl+b</span></span>
followed by an arrow key. If you switch panes dozens (or hundreds) of times a
day, that means constantly moving your right hand away from its resting position
to reach the arrow keys, then back again. It's a minor interruption each time,
but these unnecessary hand movements add up and contribute to repetitive strain
over time. My principle is that if you do something all the time, it should be
as effortless as possible.</p>
<p>The simplest fix is to rebind navigation to hjkl (familiar from vim) in tmux
directly:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> h select-pane<span class="z-variable z-parameter z-tmux"> -L</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> j select-pane<span class="z-variable z-parameter z-tmux"> -D</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> k select-pane<span class="z-variable z-parameter z-tmux"> -U</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> l select-pane<span class="z-variable z-parameter z-tmux"> -R</span>
</span></code></pre>
<p>This gets you on the home row. But pane switching is something you do
constantly, and <kbd>prefix</kbd> +
<kbd>h</kbd>/<kbd>j</kbd>/<kbd>k</kbd>/<kbd>l</kbd> is still two steps. First
hit <kbd>Ctrl</kbd>+<kbd>b</kbd> and then <kbd>h</kbd>. We can do better: bind
it to a single chord like <kbd>Cmd</kbd>+<kbd>hjkl</kbd>. For Mac users,
<kbd>Cmd</kbd> is a natural choice (or <kbd>Super</kbd> on Linux). It's easy to
reach and rarely conflicts with terminal applications.</p>
<p>There are some gotchas though. tmux itself can't see <kbd>Cmd</kbd> keypresses,
so we can't just add tmux binds and call it. It's a limitation of how terminals
work. Your terminal emulator has to intercept them and translate them into
something tmux understands. This is where it gets hard to give universal advice,
since every terminal handles custom keybinds differently. However, here's how to
set it up in Alacritty and Ghostty.</p>
<p>The trick is to have Alacritty send your tmux prefix followed by the
corresponding arrow key:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
alacritty.toml
</span></div>
<pre data-lang="toml" class="language-toml z-code"><code class="language-toml" data-lang="toml"><span class="z-source z-toml"><span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> \u0006 = C-f (my prefix). Replace with your prefix:</span>
</span><span class="z-source z-toml"><span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> C-b (default) = \u0002, C-a = \u0001, C-s = \u0013</span>
</span><span class="z-source z-toml">
</span><span class="z-source z-toml"><span class="z-punctuation z-definition z-table z-array z-begin z-toml">[[</span><span class="z-meta z-tag z-table z-array z-toml"><span class="z-entity z-name z-table z-toml">keyboard</span><span class="z-punctuation z-separator z-table z-toml">.</span><span class="z-entity z-name z-table z-toml">bindings</span></span><span class="z-punctuation z-definition z-table z-array z-end z-toml">]]</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">chars</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span><span class="z-constant z-character z-escape z-toml">\u0006</span><span class="z-constant z-character z-escape z-toml">\u001B</span>[D<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span> <span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> prefix + Left</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">key</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>H<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">mods</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml">
</span><span class="z-source z-toml"><span class="z-punctuation z-definition z-table z-array z-begin z-toml">[[</span><span class="z-meta z-tag z-table z-array z-toml"><span class="z-entity z-name z-table z-toml">keyboard</span><span class="z-punctuation z-separator z-table z-toml">.</span><span class="z-entity z-name z-table z-toml">bindings</span></span><span class="z-punctuation z-definition z-table z-array z-end z-toml">]]</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">chars</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span><span class="z-constant z-character z-escape z-toml">\u0006</span><span class="z-constant z-character z-escape z-toml">\u001B</span>[B<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span> <span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> prefix + Down</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">key</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>J<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">mods</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml">
</span><span class="z-source z-toml"><span class="z-punctuation z-definition z-table z-array z-begin z-toml">[[</span><span class="z-meta z-tag z-table z-array z-toml"><span class="z-entity z-name z-table z-toml">keyboard</span><span class="z-punctuation z-separator z-table z-toml">.</span><span class="z-entity z-name z-table z-toml">bindings</span></span><span class="z-punctuation z-definition z-table z-array z-end z-toml">]]</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">chars</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span><span class="z-constant z-character z-escape z-toml">\u0006</span><span class="z-constant z-character z-escape z-toml">\u001B</span>[A<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span> <span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> prefix + Up</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">key</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>K<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">mods</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml">
</span><span class="z-source z-toml"><span class="z-punctuation z-definition z-table z-array z-begin z-toml">[[</span><span class="z-meta z-tag z-table z-array z-toml"><span class="z-entity z-name z-table z-toml">keyboard</span><span class="z-punctuation z-separator z-table z-toml">.</span><span class="z-entity z-name z-table z-toml">bindings</span></span><span class="z-punctuation z-definition z-table z-array z-end z-toml">]]</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">chars</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span><span class="z-constant z-character z-escape z-toml">\u0006</span><span class="z-constant z-character z-escape z-toml">\u001B</span>[C<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span> <span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> prefix + Right</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">key</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>L<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">mods</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span></code></pre>
</div>
<p>In Ghostty, the same idea with different syntax:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
~/.config/ghostty/config
</span></div>
<pre class="z-code"><code><span class="z-text z-plain"># \x06 = C-f (my prefix). Replace with your prefix:
</span><span class="z-text z-plain"># C-b (default) = \x02, C-a = \x01, C-s = \x13
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">keybind = cmd+h=text:\x06\x1b[D
</span><span class="z-text z-plain">keybind = cmd+j=text:\x06\x1b[B
</span><span class="z-text z-plain">keybind = cmd+k=text:\x06\x1b[A
</span><span class="z-text z-plain">keybind = cmd+l=text:\x06\x1b[C
</span></code></pre>
</div>
<p>In both cases, <code>\u0006</code> / <code>\x06</code> is the ASCII code for
<kbd>Ctrl</kbd>+<kbd>F</kbd> (my prefix key), and <code>\u001B[D</code> / <code>\x1b[D</code> etc. are
the ANSI escape sequences for arrow keys. So pressing
<kbd>Cmd</kbd>+<kbd>h</kbd> sends <kbd>C-f</kbd> <kbd>←</kbd>, which tmux
interprets as "switch to the pane on the left." Adjust the prefix character if
yours is different. For example, <kbd>C-b</kbd> (the default) would be <code>\u0002</code>
/ <code>\x02</code>.</p>
<h2 id="hide-window-a-small-macos-annoyance"><a class="zola-anchor" href="#hide-window-a-small-macos-annoyance" aria-label="Anchor link for: hide window a small macos annoyance">#</a>Hide Window: A small macOS annoyance</h2>
<p>It doesn't end here, sadly. On Mac, <kbd>Cmd</kbd> + <kbd>h</kbd> is a reserved
global hotkey for hiding the currently active window. By default, macOS will
intercept the mapping before it reaches your terminal, performing the Hide
Window action. Fortunately there's a way around this.</p>
<p>Go to System Settings → Keyboard → Keyboard Shortcuts… → App Shortcuts, and for
your terminal app of choice, add a new custom keyboard shortcut to override the
menu item name, for example "Hide alacritty". Just map it to any random key
combination you're never going to use. At least I never hide my terminal window.</p>
<figure>
<img src="https://raine.dev/blog/tmux-navigate-panes-home-row/macos-app-shortcuts-hide-alacritty.webp" alt="macOS System Settings App Shortcuts showing Hide alacritty remapped under Alacritty.app" loading="lazy">
<figcaption>
Remapping "Hide alacritty" to a throwaway shortcut in macOS App Shortcuts.
</figcaption>
</figure>
<p>There you go. Enjoy the bliss of never leaving the home row. At least when
navigating your tmux panes.</p>
<div class="read-next">
<a href="https://raine.dev/blog/my-tmux-setup/" class="read-next-link">
<span class="read-next-label">Read next</span>
<span class="read-next-title">My setup, and why tmux workflow is better than ever</span>
<span class="read-next-description">Keybindings, popup tools, and workflows for a terminal-centric setup.</span>
<svg class="read-next-arrow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
</div>
Apple's new Icon Composer and why your Mac app icon is too big
2026-02-04T00:00:00+00:00
2026-02-04T00:00:00+00:00
Raine Virta
https://raine.dev/blog/icon-composer-mac-app-icon-size/
<p>After creating an icon for my Mac app with Icon Composer, I realized that it's
way bigger than rest of the apps when browsing the Applications directory.</p>
<p>For whatever reason, when archiving the app in Xcode, it didn't get the empty
space around the icon, even though many sources on the Internet suggested it
should work like that automatically.</p>
<p>Some people were even suggesting you're supposed to manually resize the Icon
Composer-generated icon to be smaller. This seemed odd - surely Apple doesn't
make this new fancy Icon Composer, yet require you to manually photoshop the
icon to have space around it?</p>
<figure>
<img src="https://raine.dev/blog/icon-composer-mac-app-icon-size/walkingmate-icon-too-big.webp" alt="Finder window showing WalkingMate.app icon much larger than surrounding app icons like Voice Memos and Weather" loading="lazy">
<figcaption>
Why is my app's icon so damn big?
</figcaption>
</figure>
<p>Now, two months later, while trying to get an iOS app released and creating an
icon for it, I hit a validation error:</p>
<pre class="z-code"><code><span class="z-text z-plain">Invalid large app icon. The large app icon in the asset catalog in
</span><span class="z-text z-plain">"WalkingMateCompanion.app" can't be transparent or contain an alpha channel.
</span></code></pre>
<p>This led me to figure out what was causing both issues.</p>
<p>Against common wisdom, you are not meant to export the icon as PNG and put it to
your app's <code>Assets.xcassets</code>. You are in fact meant to just do a regular save
into e.g. <code>YourIcon.icon</code>, which is actually a directory, and then point in
project settings to the App Icon field to <code>YourIcon</code> (without <code>.icon</code>).</p>
<p>The <code>.icon</code> bundle is a collection of the icon's assets and a JSON file. This
all makes sense. With the raw <code>.icon</code> bundle, Xcode can package the app with an
icon appropriate for the target platform, such as a properly sized icon on
macOS.</p>
<h3 id="walkthrough"><a class="zola-anchor" href="#walkthrough" aria-label="Anchor link for: walkthrough">#</a>Walkthrough</h3>
<ol>
<li>In Icon Composer, use File > Save (not Export) and save it as "YourAppIcon"</li>
<li>What you get is actually a directory with .icon extension (e.g.,
YourAppIcon.icon)</li>
<li>Drag and drop the saved .icon file (technically a directory) into your Xcode
project and select "Copy Files to destination"</li>
<li>In your project settings > General > scroll to "App Icons and Launch Screens"</li>
<li>Find the "App Icon" field and set it to the name without the .icon extension
(e.g., just "YourAppIcon")</li>
</ol>
<figure>
<img src="https://raine.dev/blog/icon-composer-mac-app-icon-size/xcode-icon-setup.webp" alt="Xcode project settings showing WalkingMate.icon in the file navigator and App Icon field set to WalkingMate without the extension" loading="lazy">
<figcaption>
The .icon file in your Xcode project, and the App Icon field referencing it without the .icon suffix. Take note where the icon bundle is in the Project Navigator hiearachy.
</figcaption>
</figure>
<p>So it turns out that adding icons to <code>Assets.xcassets</code> is no longer the way to
do it when using Icon Composer, but this is hardly obvious.</p>
<h3 id="credits"><a class="zola-anchor" href="#credits" aria-label="Anchor link for: credits">#</a>Credits</h3>
<p>Thanks to @kdrxyz on reddit for the tip about using .icon bundles directly.</p>
Atomic commits for AI agents
2026-01-31T00:00:00+00:00
2026-01-31T00:00:00+00:00
Raine Virta
https://raine.dev/blog/atomic-commits-for-ai-agents/
<p>These days I hardly write commit messages manually anymore. The agent, with all
the context on the task, writes reasonably good commit messages in a fraction of
the time I would.</p>
<p>Where agents occasionally struggle, however, is atomic commits - commits that
contain exactly one logical change. If at any point unrelated changes are
introduced into the same file, not to mention within close proximity of each
other, the agent will have to go through unreasonable hoops to separate them, or
just lump everything into one commit.</p>
<p>This is mainly a tooling issue. Interactive git commands like <code>git add -p</code> don't
work well for agents (except in trivial cases). When asked to commit changes
separately, they may decide it's easier to start from scratch:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<img class="codeblock-icon" src="/icons/claude-color.svg" alt="Claude">
Claude
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">❯ commit the changes separately
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git diff internal/bot/listing_handler.go)
</span><span class="z-text z-plain"> ⎿ diff --git i/internal/bot/listing_handler.go ...
</span><span class="z-text z-plain"> ... +83 lines
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Let me reset and make the changes in two separate commits.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git checkout internal/bot/listing_handler.go) ← about to destroy changes
</span><span class="z-text z-plain"> ⎿ Interrupted
</span></code></pre>
</div>
<p>The agent's instinct here is to check out the file, resetting all changes, then
manually re-apply and commit them in separate steps.</p>
<h2 id="enter-git-surgeon"><a class="zola-anchor" href="#enter-git-surgeon" aria-label="Anchor link for: enter git surgeon">#</a>Enter <code>git-surgeon</code></h2>
<p>Recently I got tired of watching agents fumble with this, so I built
<a href="https://github.com/raine/git-surgeon">git-surgeon</a>. It exposes non-interactive
primitives for hunk-level git operations. Each hunk gets a stable ID that the
agent can reference to stage, unstage, discard, or commit specific changes.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<img class="codeblock-icon" src="/icons/claude-color.svg" alt="Claude">
Claude
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">❯ commit the changes separately
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ I'll commit these as separate changes using git-surgeon.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Skill(git-surgeon)
</span><span class="z-text z-plain"> ⎿ Successfully loaded skill
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git-surgeon hunks)
</span><span class="z-text z-plain"> ⎿ ac34353 internal/bot/listing_handler.go (+6 -3)
</span><span class="z-text z-plain"> 15baf94 internal/bot/listing_handler.go (+10 -2)
</span><span class="z-text z-plain"> ...
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git-surgeon commit ac34353 15baf94 -m "allow edit commands during attribute input")
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git-surgeon commit 4eefac8 bbba931 -m "add logging for attribute prompts")
</span></code></pre>
</div>
<p>For commits that already mixed concerns, there's a <code>split</code> command that breaks
them into focused commits - selecting hunks by ID, or specific lines when a hunk
itself mixes changes:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<img class="codeblock-icon" src="/icons/claude-color.svg" alt="Claude">
Claude
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">❯ split the big pagination commit that mixes concerns into multiple commits
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Skill(git-surgeon)
</span><span class="z-text z-plain"> ⎿ Successfully loaded skill
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ I'll help you split the pagination commit. Let me first look at the commit
</span><span class="z-text z-plain"> history to identify it, then examine its hunks.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git log --oneline -5)
</span><span class="z-text z-plain"> ⎿ dda5d0e add readme
</span><span class="z-text z-plain"> 945381f add pagination, logging, and active user filtering
</span><span class="z-text z-plain"> 386b2fe initial app setup
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git-surgeon hunks --commit 945381f --full)
</span><span class="z-text z-plain"> ⎿ a811273 config.py (+3 -1)
</span><span class="z-text z-plain"> 4:-LOG_LEVEL = "INFO"
</span><span class="z-text z-plain"> 5:+LOG_LEVEL = "DEBUG"
</span><span class="z-text z-plain"> 6:+LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(message)s"
</span><span class="z-text z-plain"> 7:+MAX_PAGE_SIZE = 100
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> 37562e9 models.py (+11 -2)
</span><span class="z-text z-plain"> 2:- def __init__(self, id, name, email):
</span><span class="z-text z-plain"> 3:+ def __init__(self, id, name, email, active=True):
</span><span class="z-text z-plain"> ... +12 lines
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> 8c1940f server.py (+19 -4)
</span><span class="z-text z-plain"> 2:+import logging
</span><span class="z-text z-plain"> 5:+logging.basicConfig(level="INFO")
</span><span class="z-text z-plain"> 6:+logger = logging.getLogger(__name__)
</span><span class="z-text z-plain"> ... +16 lines
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ I can see the commit mixes several concerns:
</span><span class="z-text z-plain"> 1. Logging - LOG_LEVEL, LOG_FORMAT, logging imports, logger calls
</span><span class="z-text z-plain"> 2. Pagination - MAX_PAGE_SIZE, pagination logic in list_users
</span><span class="z-text z-plain"> 3. Active user filtering - active field in User model and data
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> Let me split this into three focused commits:
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git-surgeon split 945381f \
</span><span class="z-text z-plain"> --pick a811273:4-6 8c1940f:2,5-6,34,37 -m "add logging infrastructure" \
</span><span class="z-text z-plain"> --pick 37562e9:3,7,15 8c1940f:11-13,22 -m "add active flag to users" \
</span><span class="z-text z-plain"> --rest-message "add pagination support")
</span><span class="z-text z-plain"> ⎿ committed: add logging infrastructure
</span><span class="z-text z-plain"> committed: add active flag to users
</span><span class="z-text z-plain"> committed: add pagination support
</span></code></pre>
</div>
<p>The tool is built strictly for agents: quite terse output, no interactivity,
token-efficient CLI. It ships with a skill that teaches your agent the commands,
so you load the skill and (mostly) let it figure out the rest. At this point
it's mostly an experiment and true value remains to be seen, but it does feel
satisfying seeing an agent use it.</p>
<a href="https://github.com/raine/git-surgeon" class="project-card-inline">
<div class="project-main">
<h3 class="project-name">git-surgeon</h3>
<p class="project-description">Surgical, non-interactive git hunk control for AI agents</p>
</div>
<div class="project-meta">
<span class="project-language" data-language="Rust">
<span class="language-dot"></span>
Rust
</span>
<span class="project-stars">
<svg class="star-icon" aria-hidden="true" height="16" viewBox="0 0 16 16" width="16">
<path fill="currentColor" d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path>
</svg>
15
</span>
</div>
</a>
<div class="read-next">
<a href="https://raine.dev/blog/introduction-to-workmux/" class="read-next-link">
<span class="read-next-label">Read next</span>
<span class="read-next-title">Introduction to workmux</span>
<span class="read-next-description">for parallelizing agent work across git worktrees.</span>
<svg class="read-next-arrow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
</div>
tmux tip: Add Obsidian notes to Claude Code context
2026-01-18T00:00:00+00:00
2026-01-18T00:00:00+00:00
Raine Virta
https://raine.dev/blog/obsidian-vault-claude-code/
<p>When working with <a href="https://claude.ai/code">Claude Code</a>, I often want to reference
documents from my Obsidian vault.</p>
<p>Using <a href="https://github.com/raine/tmux-file-picker">tmux-file-picker</a>, you can bind a key to open a fuzzy
finder in a popup, scoped to your vault's markdown files:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.tmux.conf
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">bind C-n display-popup -E "TMUX_FILE_PICKER_FD_FLAGS='-H --type f -e md' tmux-file-picker '/Users/raine/Library/Mobile Documents/iCloud~md~obsidian/Documents/raine'"
</span></code></pre>
</div>
<p>The <code>TMUX_FILE_PICKER_FD_FLAGS</code> environment variable overrides the default <code>fd</code>
flags, filtering to only show <code>.md</code> files. Now <kbd>prefix</kbd> +
<kbd>Ctrl-n</kbd> opens a fuzzy finder scoped to your vault's markdown files.</p>
<figure>
<img src="https://raine.dev/blog/obsidian-vault-claude-code/obsidian-notes-popup.webp" alt="Fuzzy finder popup showing Obsidian markdown files" loading="lazy">
</figure>
<p>Select one or more notes, and they're inserted into your terminal with the <code>@</code>
prefix that Claude Code recognizes as file references.</p>
<div class="read-next">
<a href="https://raine.dev/blog/tmux-file-picker-for-ai-agents/" class="read-next-link">
<span class="read-next-label">Read next</span>
<span class="read-next-title">tmux tip: Add file paths to context with fzf in Claude Code</span>
<span class="read-next-description">for more tmux-file-picker workflows.</span>
<svg class="read-next-arrow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
</div>
Dynamic tmux popup sizing across different displays
2026-01-11T00:00:00+00:00
2026-01-11T00:00:00+00:00
Raine Virta
https://raine.dev/blog/dynamic-tmux-popup-sizing/
<p>tmux's <code>display-popup</code> is hard to size when you switch between displays. Fixed
character sizes are either too small on 4K or too large on a laptop. Percentages
don't help either - 60% of a 4K monitor is massive.</p>
<p>With some help, came up with a wrapper script that uses a fancy algorithm.</p>
<pre class="z-code"><code><span class="z-text z-plain">popup = base × (screen / reference) ^ 0.5
</span></code></pre>
<p>You set your reference screen (the one you tune on) and base sizes for each
preset. On other screens, sizes scale sub-linearly - a screen twice as large
gets a popup ~1.4x bigger, not 2x.</p>
<div class="codeblock-labeled codeblock-collapsed">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
tmux-popup
</span></div>
<pre data-lang="bash" class="language-bash z-code"><code class="language-bash" data-lang="bash"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">!/usr/bin/env bash</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-set z-shell">set</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>e</span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Reference screen (MacBook) - presets are tuned for this size</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">REF_W</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">186</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">REF_H</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">43</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Scaling factor (0.0-1.0): lower = more "sticky", higher = more linear</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">SCALE</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">0.5</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Size presets: base_w, max_w, base_h, max_h</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-storage z-modifier z-shell">declare</span> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>A</span> <span class="z-variable z-other z-readwrite z-assignment z-shell">PRESETS</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-punctuation z-section z-parens z-begin z-shell">(</span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"> <span class="z-punctuation z-section z-brackets z-begin z-shell">[</span>small<span class="z-punctuation z-section z-brackets z-end z-shell">]</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>70 130 18 35<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"> <span class="z-punctuation z-section z-brackets z-begin z-shell">[</span>medium<span class="z-punctuation z-section z-brackets z-end z-shell">]</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>110 200 30 50<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"> <span class="z-punctuation z-section z-brackets z-begin z-shell">[</span>large<span class="z-punctuation z-section z-brackets z-end z-shell">]</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>150 260 40 65<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-punctuation z-section z-parens z-end z-shell">)</span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">size</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>medium<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-while z-shell">while</span> <span class="z-meta z-function-call z-arguments z-shell"><span class="z-support z-function z-double-brace z-begin z-shell">[[</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-language z-shell">#</span></span> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>gt</span> 0 <span class="z-support z-function z-double-brace z-end z-shell">]]</span></span><span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-keyword z-control z-loop z-do z-shell">do</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-conditional z-case z-shell"><span class="z-keyword z-control z-conditional z-case z-shell">case</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">1</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-keyword z-control z-in z-shell">in</span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-shell"> </span><span class="z-meta z-conditional z-case z-clause z-patterns z-shell">--size<span class="z-meta z-conditional z-case z-shell"><span class="z-keyword z-control z-conditional z-patterns z-end z-shell">)</span></span></span><span class="z-meta z-conditional z-case z-clause z-commands z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> <span class="z-variable z-other z-readwrite z-assignment z-shell">size</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">2</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-shift z-shell">shift</span></span><span class="z-meta z-function-call z-arguments z-shell"> 2</span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> </span><span class="z-meta z-conditional z-case z-clause z-commands z-shell"><span class="z-punctuation z-terminator z-case z-clause z-shell">;;</span></span><span class="z-meta z-conditional z-case z-clause z-patterns z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-patterns z-shell"> </span><span class="z-meta z-conditional z-case z-clause z-patterns z-shell">--<span class="z-meta z-conditional z-case z-clause z-patterns z-shell"><span class="z-keyword z-control z-conditional z-patterns z-end z-shell">)</span></span></span><span class="z-meta z-conditional z-case z-clause z-commands z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-shift z-shell">shift</span></span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> <span class="z-keyword z-control z-flow z-break z-shell">break</span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> </span><span class="z-meta z-conditional z-case z-clause z-commands z-shell"><span class="z-punctuation z-terminator z-case z-clause z-shell">;;</span></span><span class="z-meta z-conditional z-case z-clause z-patterns z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-patterns z-shell"> </span><span class="z-meta z-conditional z-case z-clause z-patterns z-shell"><span class="z-keyword z-operator z-regexp z-quantifier z-shell">*</span><span class="z-meta z-conditional z-case z-clause z-patterns z-shell"><span class="z-keyword z-control z-conditional z-patterns z-end z-shell">)</span></span></span><span class="z-meta z-conditional z-case z-clause z-commands z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> <span class="z-keyword z-control z-flow z-break z-shell">break</span>
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-commands z-shell"> </span><span class="z-meta z-conditional z-case z-clause z-commands z-shell"><span class="z-punctuation z-terminator z-case z-clause z-shell">;;</span></span><span class="z-meta z-conditional z-case z-clause z-patterns z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-conditional z-case z-clause z-patterns z-shell"> <span class="z-keyword z-control z-conditional z-end z-shell">esac</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-loop z-end z-shell">done</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-conditional z-if z-shell">if</span> <span class="z-meta z-function-call z-arguments z-shell"><span class="z-support z-function z-double-brace z-begin z-shell">[[</span> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>z</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-expansion z-parameter z-begin z-shell">{</span></span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-variable z-other z-readwrite z-shell">PRESETS<span class="z-punctuation z-section z-braces z-begin z-shell">[</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">size</span></span><span class="z-punctuation z-section z-braces z-end z-shell">]</span></span></span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-section z-expansion z-parameter z-end z-shell">}</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-support z-function z-double-brace z-end z-shell">]]</span></span><span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-keyword z-control z-conditional z-then z-shell">then</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-echo z-shell">echo</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>Unknown size: <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">size</span></span> (use small, medium, large)<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-keyword z-operator z-assignment z-redirection z-shell">>&</span><span class="z-constant z-numeric z-integer z-decimal z-file-descriptor z-shell">2</span></span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-exit z-shell">exit</span></span><span class="z-meta z-function-call z-arguments z-shell"> 1</span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-conditional z-end z-shell">fi</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-read z-shell">read</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>r</span> base_w max_w base_h max_h <span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-expansion z-parameter z-begin z-shell">{</span></span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-variable z-other z-readwrite z-shell">PRESETS<span class="z-punctuation z-section z-braces z-begin z-shell">[</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">size</span></span><span class="z-punctuation z-section z-braces z-end z-shell">]</span></span></span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-section z-expansion z-parameter z-end z-shell">}</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-read z-shell">read</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>r</span> w h <span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-parens z-begin z-shell">(</span><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">tmux</span></span><span class="z-meta z-function-call z-arguments z-shell"> display<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>#{window_width} #{window_height}<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span><span class="z-punctuation z-section z-parens z-end z-shell">)</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-read z-shell">read</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell">-</span>r</span> pw ph <span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-keyword z-operator z-assignment z-redirection z-shell"><</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-parens z-begin z-shell">(</span><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">awk</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> w=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">w</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> h=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">h</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> bw=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">base_w</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> bh=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">base_h</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> mw=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">max_w</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> mh=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">max_h</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> rw=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">REF_W</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> rh=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">REF_H</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>v</span> s=<span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">SCALE</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>BEGIN {
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> pw = int(bw * ((w / rw) ^ s) + 0.5)
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> if (pw > mw) pw = mw
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> if (pw > w - 2) pw = w - 2
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell">
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> ph = int(bh * ((h / rh) ^ s) + 0.5)
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> if (ph > mh) ph = mh
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> if (ph > h - 2) ph = h - 2
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell">
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> print pw, ph
</span></span></span></span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-quoted z-single z-shell"> }<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span><span class="z-punctuation z-section z-parens z-end z-shell">)</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-exec z-shell">exec</span></span><span class="z-meta z-function-call z-arguments z-shell"> tmux display-popup -w <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">pw</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> -h <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">ph</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> -E <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-language z-shell">@</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span></code></pre>
<button class="codeblock-expand">Show all</button>
</div>
<p>Usage in <code>.tmux.conf</code>:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-j</span> run-shell <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>tmux-popup --size medium workmux<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-f</span> run-shell <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>tmux-popup workmux<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span></code></pre>
<p>To tune, first get your terminal size with
<code>tmux display -p '#{window_width} #{window_height}'</code>, set <code>REF_W</code>/<code>REF_H</code> to
match, then adjust the base values until they look right. The <code>SCALE</code> factor
controls how much sizes grow on larger screens.</p>
<p>Will keep experimenting with this but for now it feels good.</p>
<div class="read-next">
<a href="https://raine.dev/blog/my-tmux-setup/" class="read-next-link">
<span class="read-next-label">Read next</span>
<span class="read-next-title">My setup, and why tmux workflow is better than ever</span>
<span class="read-next-description">Keybindings, popup tools, and workflows for a terminal-centric setup.</span>
<svg class="read-next-arrow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
</div>
My setup, and why tmux workflow is better than ever
2026-01-01T00:00:00+00:00
2026-01-01T00:00:00+00:00
Raine Virta
https://raine.dev/blog/my-tmux-setup/
<figure>
<img src="https://raine.dev/blog/my-tmux-setup/tmux-circa-2011.avif" alt="tmux, circa 2011" loading="lazy">
<figcaption>
tmux, circa 2011.
</figcaption>
</figure>
<p>Over the years, tmux has become an essential part of my terminal-centric
workflow. For many, tmux is merely a <code>screen</code> replacement to be used on remote
servers, but for me it's closer to a tiling window manager for the terminal.
Briefly, what I think makes tmux so useful:</p>
<ol>
<li>
<p>I work on many projects at a time, and recently, with many AI agents at a
time. tmux allows isolating each project inside its own <em>session</em>, and within
a project, each task or branch in its own <em>window</em>. Switching between them is
a keypress away.</p>
</li>
<li>
<p>It's programmable. It can be scripted and extended to fit one's workflow. For
example, I have a tool that allows opening recent directories (based on
<a href="https://github.com/ajeetdsouza/zoxide">zoxide</a>) into a new tmux session with sensible window layout based on the
project.</p>
</li>
<li>
<p>It's composable. One of my favorite tmux features is <code>display-popup</code>, which
opens a floating terminal that can run anything. Combine it with fzf and you
have <a href="https://raine.dev/blog/tmux-file-picker-for-ai-agents/">a file picker</a>. Small tools are wired together into
something IDE-like.</p>
</li>
</ol>
<figure>
<img src="https://raine.dev/blog/my-tmux-setup/tmux-overview.webp" alt="tmux with three panes showing neovim, an AI agent, and the status bar with multiple windows" loading="lazy">
<figcaption>
My current tmux setup. Running an AI agent, neovim, and a shell.
</figcaption>
</figure>
<aside class="aside-box">
<div class="aside-header"><svg class="aside-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg> tmux terminology</div>
<div class="aside-content">
<p>tmux organizes work in a hierarchy:
<strong>sessions</strong> contain <strong>windows</strong>, which contain <strong>panes</strong>. A session for me is
typically one project. Windows are like tabs within a session. Panes are splits
within a window.</p>
</div>
</aside>
<p>Now, let me walk through the setup. I'll try to cover the most relevant parts.
Installation instructions for tmux can be found
<a href="https://github.com/tmux/tmux/wiki/Installing">elsewhere</a>.</p>
<h2 id="terminal"><a class="zola-anchor" href="#terminal" aria-label="Anchor link for: terminal">#</a>Terminal</h2>
<p>Just briefly about terminal choice. tmux itself is fairly terminal-agnostic, but
some keybindings I prefer require terminal-level configuration.</p>
<p>I currently use <a href="https://alacritty.org/">Alacritty</a>. It's GPU-accelerated and fast, though most terminal
emulators are these days. I used iTerm2 for the longest time, but switched a few
years ago. Main advantage of Alacritty over iTerm2 is being able to configure
every aspect through a single TOML file, which is auto-reloaded automatically as
you make changes. This makes it easy to version control and to make changes with
AI tools. Good luck asking an AI agent to add the <kbd>Cmd</kbd>+<kbd>1-9</kbd>
mapping covered below through iTerm2's GUI settings.</p>
<p>I also like that Alacritty can run without any window chrome, giving a
minimalistic fullscreen terminal.</p>
<h2 id="core-settings"><a class="zola-anchor" href="#core-settings" aria-label="Anchor link for: core settings">#</a>Core settings</h2>
<p>The first thing most people change is the prefix key. The prefix key is
basically how you tell tmux "the next key is a tmux command, not input for the
terminal." The default <kbd>C-b</kbd> is hard to reach. For reasons I can't
remember anymore, I've landed on <kbd>C-f</kbd>.</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">unbind</span> <span class="z-constant z-character z-tmux">C-b</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">prefix</span> <span class="z-constant z-character z-tmux">C-f</span>
</span></code></pre>
<p>A few other basic settings I consider essential:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">base-index</span> <span class="z-constant z-numeric z-tmux">1</span> <span class="z-comment z-line z-tmux"># Window numbers start at 1</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">setw</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">mode-keys</span> vi <span class="z-comment z-line z-tmux"># Vi bindings in copy mode</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -s</span> <span class="z-variable z-other z-tmux">escape-time</span> <span class="z-constant z-numeric z-tmux">0</span> <span class="z-comment z-line z-tmux"># No delay after Esc</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">detach-on-destroy</span> <span class="z-constant z-language z-tmux">off</span> <span class="z-comment z-line z-tmux"># Stay in tmux when session closes</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">renumber-windows</span> <span class="z-constant z-language z-tmux">on</span> <span class="z-comment z-line z-tmux"># No gaps after closing windows</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">mouse</span> <span class="z-constant z-language z-tmux">off</span> <span class="z-comment z-line z-tmux"># Mouse-mode interferes with native terminal selection</span>
</span></code></pre>
<p>The <code>escape-time 0</code> is critical if you use (neo)vim. Without it, there's a
noticeable delay when pressing <kbd>Escape</kbd>. The <code>detach-on-destroy off</code>
means that when a session is closed, tmux switches to another session instead of
detaching. In practice, I rarely close sessions though.</p>
<p>Mouse mode can be useful, but it has drawbacks, so I've gotten used to working
without it.</p>
<p>For terminal capabilities, I have true color and undercurl support (the wavy
underlines neovim uses for diagnostics):</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-constant z-other z-color z-tmux">default</span>-terminal <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-256color<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-option</span><span class="z-variable z-parameter z-tmux"> -sa</span> <span class="z-variable z-other z-tmux">terminal-overrides</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>,xterm-256color:RGB<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -as</span> <span class="z-variable z-other z-tmux">terminal-overrides</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>,*:Smulx=\E[4::%p1%dm<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -as</span> <span class="z-variable z-other z-tmux">terminal-overrides</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>,*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span></code></pre>
<h2 id="vim-like-splits-and-navigation"><a class="zola-anchor" href="#vim-like-splits-and-navigation" aria-label="Anchor link for: vim like splits and navigation">#</a>Vim-like splits and navigation</h2>
<p>I've configured splits to resemble vim:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> s split-window<span class="z-variable z-parameter z-tmux"> -v</span><span class="z-variable z-parameter z-tmux"> -c</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span><span class="z-variable z-other z-tmux">#{pane_current_path}</span><span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> v split-window<span class="z-variable z-parameter z-tmux"> -h</span><span class="z-variable z-parameter z-tmux"> -c</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span><span class="z-variable z-other z-tmux">#{pane_current_path}</span><span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span></code></pre>
<p>In vim, <code>:split</code> creates a horizontal division (panes stacked vertically), and
<code>:vsplit</code> creates a vertical division. With these bindings, <kbd>prefix</kbd>
<kbd>s</kbd> and <kbd>prefix</kbd> <kbd>v</kbd> behave like their vim
counterparts <kbd>C-w</kbd> <kbd>s</kbd> and <kbd>C-w</kbd> <kbd>v</kbd>.</p>
<p>The <code>-c "#{pane_current_path}"</code> ensures new panes start in the same directory. I
use the same for new windows:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> c new-window<span class="z-variable z-parameter z-tmux"> -c</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span><span class="z-variable z-other z-tmux">#{pane_current_path}</span><span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span></code></pre>
<p>For resizing, I use characters that visually suggest the direction:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> < resize-pane<span class="z-variable z-parameter z-tmux"> -L</span> <span class="z-constant z-numeric z-tmux">10</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> > resize-pane<span class="z-variable z-parameter z-tmux"> -R</span> <span class="z-constant z-numeric z-tmux">10</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> + resize-pane<span class="z-variable z-parameter z-tmux"> -D</span> <span class="z-constant z-numeric z-tmux">10</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> - resize-pane<span class="z-variable z-parameter z-tmux"> -U</span> <span class="z-constant z-numeric z-tmux">10</span>
</span></code></pre>
<p>One of the most useful built-in bindings is <kbd>prefix</kbd> <kbd>z</kbd>,
which zooms the current pane to fill the entire window. Press it again to
restore the layout. Great when you need to focus on one pane temporarily.</p>
<p>I also bind global keys (no prefix needed) to reorder windows with
<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>←</kbd>/<kbd>→</kbd>. The <code>-d</code> flag keeps
focus on the moved window.</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind-key</span><span class="z-variable z-parameter z-tmux"> -n</span> <span class="z-constant z-character z-tmux">C-S-Left</span> swap-window<span class="z-variable z-parameter z-tmux"> -d</span><span class="z-variable z-parameter z-tmux"> -t</span> -<span class="z-constant z-numeric z-tmux">1</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind-key</span><span class="z-variable z-parameter z-tmux"> -n</span> <span class="z-constant z-character z-tmux">C-S-Right</span> swap-window<span class="z-variable z-parameter z-tmux"> -d</span><span class="z-variable z-parameter z-tmux"> -t</span> +<span class="z-constant z-numeric z-tmux">1</span>
</span></code></pre>
<p>I also use <kbd>Cmd</kbd>+<kbd>1-9</kbd> for switching windows, similarly to
browser tabs. This is not a tmux setting; it's an Alacritty keybinding that
sends the prefix followed by the window number:</p>
<aside class="aside-box">
<div class="aside-header"><svg class="aside-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg> Terminal keybindings</div>
<div class="aside-content">
<p>Terminals deal in text, not key
events. <kbd>Ctrl</kbd> combinations work because they map to control characters
baked into ASCII. <kbd>Cmd</kbd> and <kbd>Super</kbd> keys came later and have
no such mapping, so your terminal emulator has to intercept them and send escape
sequences instead.</p>
</div>
</aside>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
alacritty.toml
</span></div>
<pre data-lang="toml" class="language-toml z-code"><code class="language-toml" data-lang="toml"><span class="z-source z-toml"><span class="z-punctuation z-definition z-table z-array z-begin z-toml">[[</span><span class="z-meta z-tag z-table z-array z-toml"><span class="z-entity z-name z-table z-toml">keyboard</span><span class="z-punctuation z-separator z-table z-toml">.</span><span class="z-entity z-name z-table z-toml">bindings</span></span><span class="z-punctuation z-definition z-table z-array z-end z-toml">]]</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">chars</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span><span class="z-constant z-character z-escape z-toml">\u0006</span>1<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span> <span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> C-f 1</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">key</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>Key1<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">mods</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml">
</span><span class="z-source z-toml"><span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> ... repeated for 2-9</span>
</span></code></pre>
</div>
<p>The <code>\u0006</code> is the ASCII code for <kbd>Ctrl</kbd>+<kbd>F</kbd> (my prefix). So
<kbd>Cmd</kbd>+<kbd>3</kbd> sends <kbd>C-f</kbd> <kbd>3</kbd>, which tmux
interprets as "switch to window 3".</p>
<p>Similarly, <kbd>Cmd</kbd>+<kbd>h</kbd>/<kbd>j</kbd>/<kbd>k</kbd>/<kbd>l</kbd>
switches panes. The Alacritty binding sends prefix + arrow key:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
alacritty.toml
</span></div>
<pre data-lang="toml" class="language-toml z-code"><code class="language-toml" data-lang="toml"><span class="z-source z-toml"><span class="z-punctuation z-definition z-table z-array z-begin z-toml">[[</span><span class="z-meta z-tag z-table z-array z-toml"><span class="z-entity z-name z-table z-toml">keyboard</span><span class="z-punctuation z-separator z-table z-toml">.</span><span class="z-entity z-name z-table z-toml">bindings</span></span><span class="z-punctuation z-definition z-table z-array z-end z-toml">]]</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">chars</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span><span class="z-constant z-character z-escape z-toml">\u0006</span><span class="z-constant z-character z-escape z-toml">\u001B</span>[D<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span> <span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> C-f Left</span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">key</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>H<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml"><span class="z-meta z-tag z-key z-toml"><span class="z-entity z-name z-tag z-toml">mods</span></span> <span class="z-punctuation z-definition z-key-value z-toml">=</span> <span class="z-string z-quoted z-double z-basic z-toml"><span class="z-punctuation z-definition z-string z-begin z-toml">"</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">"</span></span>
</span><span class="z-source z-toml">
</span><span class="z-source z-toml"><span class="z-comment z-line z-number-sign z-toml"><span class="z-punctuation z-definition z-comment z-toml">#</span> J -> Down, K -> Up, L -> Right</span>
</span></code></pre>
</div>
<p>On macOS, <kbd>Cmd</kbd>+<kbd>h</kbd> is intercepted by the system to hide the
window. See
<a href="https://raine.dev/blog/tmux-navigate-panes-home-row/#hide-window-a-small-macos-annoyance">how to work around it</a>.</p>
<h2 id="appearance"><a class="zola-anchor" href="#appearance" aria-label="Anchor link for: appearance">#</a>Appearance</h2>
<p>When working with multiple panes, it helps to see at a glance which one is
active. I use slightly different background colors:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-window-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">window-style</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>bg=#101010<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-window-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">window-active-style</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>bg=#151515<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">pane-border-style</span> fg=<span class="z-constant z-other z-color z-tmux">colour238</span>,bg=<span class="z-comment z-line z-tmux">#101010</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">pane-active-border-style</span> fg=<span class="z-constant z-other z-color z-tmux">colour113</span>,bg=<span class="z-comment z-line z-tmux">#151515</span>
</span></code></pre>
<p>The difference between <code>#101010</code> and <code>#151515</code> is subtle. Inactive panes are
just a bit darker. The active pane also gets a green border. It's enough to
notice without being too distracting.</p>
<p>My status bar style is fairly pedestrian compared to others I've seen. I care
most that it's clear which window is active.</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-bg <span class="z-constant z-other z-color z-tmux">colour233</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-fg <span class="z-constant z-other z-color z-tmux">white</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-left <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span><span class="z-constant z-other z-format z-tmux">#[fg=green]</span>#S <span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-right <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span><span class="z-constant z-other z-format z-tmux">#[fg=green]</span>%d.%m. %H:%M<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-window-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">window-status-current-style</span> fg=<span class="z-constant z-other z-color z-tmux">black</span>,bg=<span class="z-constant z-other z-color z-tmux">green</span>
</span></code></pre>
<figure>
<img src="https://raine.dev/blog/my-tmux-setup/tmux-status-bar.webp" alt="tmux status bar showing session name, windows, and time" loading="lazy">
<figcaption>
Session name on the left, windows in the middle, date and time on the right.
</figcaption>
</figure>
<h2 id="copying-text"><a class="zola-anchor" href="#copying-text" aria-label="Anchor link for: copying text">#</a>Copying text</h2>
<p>tmux has a copy mode (<kbd>prefix</kbd> <kbd>[</kbd>) with vi-style navigation.
I use it when I need to scroll back through history, but for copying visible
text I find it's easier to copy with the terminal's rectangular selection.</p>
<p>Regular selection would span the entire terminal width, grabbing text from
adjacent panes. Rectangular selection lets me draw a box around exactly the text
I want from a single pane. In Alacritty, holding
<kbd>Ctrl</kbd>+<kbd>Shift</kbd> while selecting enables this. Most terminals
have a similar option. The downside is it doesn't work well with wrapped text.</p>
<p>With mouse mode enabled, the copying experience is probably better.</p>
<p>I'm also working on an experimental tool, <a href="https://github.com/raine/tmux-snaglord">tmux-snaglord</a>, that makes copying
program output easier.</p>
<h2 id="popup-tools"><a class="zola-anchor" href="#popup-tools" aria-label="Anchor link for: popup tools">#</a>Popup tools</h2>
<p>This is where things get a bit more interesting. As mentioned, tmux's
<code>display-popup</code> command opens a floating terminal window that can run any
command. When the command exits, the popup closes. Combined with for example
fzf, this pattern is incredibly powerful.</p>
<h3 id="session-switching"><a class="zola-anchor" href="#session-switching" aria-label="Anchor link for: session switching">#</a>Session switching</h3>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-j</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">15</span><span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">30</span><span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-session-switcher<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span></code></pre>
<p><a href="https://github.com/raine/tmux-tools#tmux-session-switcher">tmux-session-switcher</a> opens an fzf interface listing all sessions. I can type
to filter, and selecting one switches to it. Since sessions are sorted by
recency, hitting <kbd>C-j</kbd> <kbd>Enter</kbd> without typing anything
switches to the previous session.</p>
<p>An alternative to this is the built-in <code>choose-session</code> command.</p>
<figure>
<img src="https://raine.dev/blog/my-tmux-setup/tmux-session-switcher.webp" alt="tmux session switcher popup showing a list of sessions" width="300" loading="lazy">
<figcaption>
Session switcher with fzf.
</figcaption>
</figure>
<h3 id="opening-projects-as-a-window"><a class="zola-anchor" href="#opening-projects-as-a-window" aria-label="Anchor link for: opening projects as a window">#</a>Opening projects as a window</h3>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-o</span> display-popup<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-zoxide-window<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span></code></pre>
<p>I use <a href="https://github.com/ajeetdsouza/zoxide">zoxide</a> to track the directories I visit so that I can easily <code>cd</code> into
them. This binding shows a fzf list of recent directories, and selecting one
opens it in a new tmux window, with the name of the directory as the name of the
window. Occasionally useful.</p>
<h3 id="file-picker"><a class="zola-anchor" href="#file-picker" aria-label="Anchor link for: file picker">#</a>File picker</h3>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-f</span> display-popup<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-file-picker<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span></code></pre>
<p>I wrote about <a href="https://raine.dev/blog/tmux-file-picker-for-ai-agents/">tmux-file-picker</a> in a separate post. It's an fzf-based file
picker that inserts the selected path into the active pane. An absolute
must-have when working with Claude Code or other AI coding TUI programs.</p>
<h2 id="session-management"><a class="zola-anchor" href="#session-management" aria-label="Anchor link for: session management">#</a>Session management</h2>
<p>I generally keep a tmux session for each project. Switching between them is
instant, and each session preserves its own editor state, dev servers, and shell
sessions. To start a new session, I use <a href="https://github.com/raine/tmux-bro">tmux-bro</a>:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-t</span> display-popup<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-bro<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span></code></pre>
<p>It shows an fzf list of recent directories (via zoxide), and selecting one
creates a new session (or switches to it if one already exists).</p>
<p>For other session operations:</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> S command-prompt <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>rename-session %%<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> N new-session
</span></code></pre>
<h2 id="parallel-ai-agents"><a class="zola-anchor" href="#parallel-ai-agents" aria-label="Anchor link for: parallel ai agents">#</a>Parallel AI agents</h2>
<p>With the emergence of AI coding tools, it's become practical to have multiple
agents working on different tasks at the same time. One might be adding a
feature while another fixes a bug.</p>
<p>The problem is that when they share the same directory, they step on each
other's changes - broken builds, type errors from half-finished work, and diffs
that mix unrelated changes.</p>
<p>The solution is git worktrees, which provide separate working directories for
each task. I use <a href="https://workmux.raine.dev/">workmux</a> to pair them with tmux windows: it creates a worktree
and opens it in a new window. This pairing of one window per task feels very
natural. I can switch between windows to check on agents, review their changes,
or provide feedback.</p>
<figure>
<img src="https://raine.dev/blog/my-tmux-setup/window-list-agents.webp" alt="Tmux window list showing agent status indicators" loading="lazy">
<figcaption>
When agents are running, their status shows up in your tmux window list
</figcaption>
</figure>
<p>When a task is done, I merge the branch, delete the worktree, and close the
window. I wrote more about it in
<a href="https://raine.dev/blog/introduction-to-workmux/">a separate post</a>.</p>
<h2 id="plugins"><a class="zola-anchor" href="#plugins" aria-label="Anchor link for: plugins">#</a>Plugins</h2>
<p><a href="https://github.com/tmux-plugins/tpm">TPM</a> handles plugin management. The only plugin I actively use is
<a href="https://github.com/tmux-plugins/tmux-logging">tmux-logging</a>, which lets me save a pane's entire scrollback history to a file.
It's occasionally useful.</p>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-keyword z-other z-tmux">@plugin</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>tmux-plugins/tpm<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-keyword z-other z-tmux">@plugin</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>tmux-plugins/tmux-logging<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span></code></pre>
<h2 id="keybindings-summary"><a class="zola-anchor" href="#keybindings-summary" aria-label="Anchor link for: keybindings summary">#</a>Keybindings summary</h2>
<table><thead><tr><th>Key</th><th>Action</th></tr></thead><tbody>
<tr><td><strong>Splits & windows</strong></td><td></td></tr>
<tr><td><kbd>prefix</kbd> <kbd>s</kbd></td><td>Horizontal split</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>v</kbd></td><td>Vertical split</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>c</kbd></td><td>New window</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>x</kbd></td><td>Kill pane</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>&</kbd></td><td>Kill window</td></tr>
<tr><td><strong>Pane resizing</strong></td><td></td></tr>
<tr><td><kbd>prefix</kbd> <kbd><</kbd></td><td>Resize left</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>></kbd></td><td>Resize right</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>+</kbd></td><td>Resize down</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>-</kbd></td><td>Resize up</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>z</kbd></td><td>Zoom pane (toggle)</td></tr>
<tr><td><strong>Navigation</strong></td><td></td></tr>
<tr><td><kbd>Cmd</kbd>+<kbd>1-9</kbd></td><td>Switch to window N</td></tr>
<tr><td><kbd>Cmd</kbd>+<kbd>h</kbd>/<kbd>j</kbd>/<kbd>k</kbd>/<kbd>l</kbd></td><td>Switch panes</td></tr>
<tr><td><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>←</kbd>/<kbd>→</kbd></td><td>Reorder windows</td></tr>
<tr><td><strong>Popup tools</strong></td><td></td></tr>
<tr><td><kbd>prefix</kbd> <kbd>C-j</kbd></td><td>Session switcher</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>C-o</kbd></td><td>Zoxide window opener</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>C-f</kbd></td><td>File picker</td></tr>
<tr><td><strong>Sessions</strong></td><td></td></tr>
<tr><td><kbd>prefix</kbd> <kbd>C-t</kbd></td><td>Open/switch project</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>S</kbd></td><td>Rename session</td></tr>
<tr><td><kbd>prefix</kbd> <kbd>N</kbd></td><td>New session</td></tr>
</tbody></table>
<p>I'll keep this post updated as my setup evolves.</p>
<h2 id="full-configuration"><a class="zola-anchor" href="#full-configuration" aria-label="Anchor link for: full configuration">#</a>Full configuration</h2>
<div class="codeblock-labeled codeblock-collapsed">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.tmux.conf
</span></div>
<pre data-lang="tmux" class="language-tmux z-code"><code class="language-tmux" data-lang="tmux"><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># CORE SETTINGS</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Prefix configuration</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">unbind</span> <span class="z-constant z-character z-tmux">C-b</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">prefix</span> <span class="z-constant z-character z-tmux">C-f</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind-key</span> a send-<span class="z-variable z-other z-tmux">prefix</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># General behavior</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">base-index</span> <span class="z-constant z-numeric z-tmux">1</span> <span class="z-comment z-line z-tmux"># Start window indexing at 1</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">renumber-windows</span> <span class="z-constant z-language z-tmux">on</span> <span class="z-comment z-line z-tmux"># Renumber windows when one is closed</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">detach-on-destroy</span> <span class="z-constant z-language z-tmux">off</span> <span class="z-comment z-line z-tmux"># Don't detach when killing a session</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">mouse</span> <span class="z-constant z-language z-tmux">off</span> <span class="z-comment z-line z-tmux"># Disable mouse</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -s</span> <span class="z-variable z-other z-tmux">escape-time</span> <span class="z-constant z-numeric z-tmux">0</span> <span class="z-comment z-line z-tmux"># No delay for escape key press</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">display-time</span> <span class="z-constant z-numeric z-tmux">4000</span> <span class="z-comment z-line z-tmux"># Duration of status messages</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">focus-events</span> <span class="z-constant z-language z-tmux">on</span> <span class="z-comment z-line z-tmux"># Focus events enabled for terminals that support it</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">setw</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">mode-keys</span> vi <span class="z-comment z-line z-tmux"># Vi key bindings in copy mode</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">setw</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">aggressive-resize</span> <span class="z-constant z-language z-tmux">on</span> <span class="z-comment z-line z-tmux"># Aggressive resize for shared sessions</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> main-pane-width <span class="z-constant z-numeric z-tmux">110</span> <span class="z-comment z-line z-tmux"># Main pane width for specific layouts</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># TERMINAL & COLOR SUPPORT</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-constant z-other z-color z-tmux">default</span>-terminal <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-256color<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-option</span><span class="z-variable z-parameter z-tmux"> -sa</span> <span class="z-variable z-other z-tmux">terminal-overrides</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>,xterm-256color:RGB<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Undercurl & Underscore colors support</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -as</span> <span class="z-variable z-other z-tmux">terminal-overrides</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>,*:Smulx=\E[4::%p1%dm<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -as</span> <span class="z-variable z-other z-tmux">terminal-overrides</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>,*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># CLIPBOARD</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">set-clipboard</span> <span class="z-constant z-language z-tmux">on</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># APPEARANCE</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Status Bar</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-bg <span class="z-constant z-other z-color z-tmux">colour233</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-fg <span class="z-constant z-other z-color z-tmux">white</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-left <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span><span class="z-constant z-other z-format z-tmux">#[fg=green]</span>#S <span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-right <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span><span class="z-constant z-other z-format z-tmux">#[fg=green]</span>%d.%m. %H:%M<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-left-length <span class="z-constant z-numeric z-tmux">15</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">status</span>-right-length <span class="z-constant z-numeric z-tmux">15</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Window Status</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-window-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">window-status-current-style</span> fg=<span class="z-constant z-other z-color z-tmux">black</span>,bg=<span class="z-constant z-other z-color z-tmux">green</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Window Styles (Dim inactive windows)</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-window-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">window-style</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>bg=#101010<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set-window-option</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">window-active-style</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>bg=#151515<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Pane Borders</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">pane-border-style</span> fg=<span class="z-constant z-other z-color z-tmux">colour238</span>,bg=<span class="z-comment z-line z-tmux">#101010</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-variable z-other z-tmux">pane-active-border-style</span> fg=<span class="z-constant z-other z-color z-tmux">colour113</span>,bg=<span class="z-comment z-line z-tmux">#151515</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># KEY BINDINGS</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Config Reload</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">unbind</span> r
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> r source-file ~/.tmux.conf
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Window Management (Split like Vim)</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> s split-window<span class="z-variable z-parameter z-tmux"> -v</span><span class="z-variable z-parameter z-tmux"> -c</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span><span class="z-variable z-other z-tmux">#{pane_current_path}</span><span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> v split-window<span class="z-variable z-parameter z-tmux"> -h</span><span class="z-variable z-parameter z-tmux"> -c</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span><span class="z-variable z-other z-tmux">#{pane_current_path}</span><span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> c new-window<span class="z-variable z-parameter z-tmux"> -c</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span><span class="z-variable z-other z-tmux">#{pane_current_path}</span><span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Navigation</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span><span class="z-variable z-parameter z-tmux"> -n</span> F11 previous-window
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span><span class="z-variable z-parameter z-tmux"> -n</span> F12 next-window
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> m choose-tree
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> k choose-session
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Swap Windows</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind-key</span><span class="z-variable z-parameter z-tmux"> -n</span> <span class="z-constant z-character z-tmux">C-S-Left</span> swap-window<span class="z-variable z-parameter z-tmux"> -d</span><span class="z-variable z-parameter z-tmux"> -t</span> -<span class="z-constant z-numeric z-tmux">1</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind-key</span><span class="z-variable z-parameter z-tmux"> -n</span> <span class="z-constant z-character z-tmux">C-S-Right</span> swap-window<span class="z-variable z-parameter z-tmux"> -d</span><span class="z-variable z-parameter z-tmux"> -t</span> +<span class="z-constant z-numeric z-tmux">1</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Copy Mode (Vim-like)</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span><span class="z-variable z-parameter z-tmux"> -T</span> copy-mode-vi v send-keys<span class="z-variable z-parameter z-tmux"> -X</span> begin-selection
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span><span class="z-variable z-parameter z-tmux"> -T</span> copy-mode-vi <span class="z-constant z-character z-tmux">C-v</span> send-keys<span class="z-variable z-parameter z-tmux"> -X</span> begin-selection \; <span class="z-keyword z-other z-tmux">send-keys</span><span class="z-variable z-parameter z-tmux"> -X</span> rectangle-toggle
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span><span class="z-variable z-parameter z-tmux"> -T</span> copy-mode-vi y send-keys<span class="z-variable z-parameter z-tmux"> -X</span> copy-pipe-and-cancel <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>pbcopy<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Pane Resizing (Vim-like)</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> < resize-pane<span class="z-variable z-parameter z-tmux"> -L</span> <span class="z-constant z-numeric z-tmux">10</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> > resize-pane<span class="z-variable z-parameter z-tmux"> -R</span> <span class="z-constant z-numeric z-tmux">10</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> + resize-pane<span class="z-variable z-parameter z-tmux"> -D</span> <span class="z-constant z-numeric z-tmux">10</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> - resize-pane<span class="z-variable z-parameter z-tmux"> -U</span> <span class="z-constant z-numeric z-tmux">10</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Session Management</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">unbind</span> S
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> S command-prompt <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>rename-session %%<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> N new-session
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Create new session from current window</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> B command-prompt<span class="z-variable z-parameter z-tmux"> -p</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>New session name:<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>new-session -d -s '%%'; move-window -t '%%:'; switch-client -t '%%'<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># CUSTOM POPUPS & SCRIPTS</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-j</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">20</span>%<span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">20</span>%<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-session-switcher<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-o</span> display-popup<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-zoxide-window<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-t</span> display-popup<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-bro<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># File Pickers</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-f</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-file-picker<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-r</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-file-picker -d<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-g</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-file-picker -g<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-d</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-file-picker --zoxide --dir-only<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-z</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">60</span>%<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-file-picker --zoxide<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Custom Tools (Snaglord)</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">bind</span> <span class="z-constant z-character z-tmux">C-p</span> display-popup<span class="z-variable z-parameter z-tmux"> -h</span> <span class="z-constant z-numeric z-tmux">70</span>%<span class="z-variable z-parameter z-tmux"> -w</span> <span class="z-constant z-numeric z-tmux">70</span>%<span class="z-variable z-parameter z-tmux"> -E</span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">"</span>tmux-snaglord<span class="z-punctuation z-definition z-string z-end z-tmux">"</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># PLUGINS</span>
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># ==============================================================================</span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-keyword z-other z-tmux">@plugin</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>tmux-plugins/tpm<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">set</span><span class="z-variable z-parameter z-tmux"> -g</span> <span class="z-keyword z-other z-tmux">@plugin</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>tmux-plugins/tmux-logging<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span><span class="z-source z-tmux">
</span><span class="z-source z-tmux"><span class="z-comment z-line z-tmux"># Initialize TMUX plugin manager (keep at the very bottom)</span>
</span><span class="z-source z-tmux"><span class="z-keyword z-other z-tmux">run</span> <span class="z-string z-quoted z-single z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">'</span>~/.tmux/plugins/tpm/tpm<span class="z-punctuation z-definition z-string z-end z-tmux">'</span></span>
</span></code></pre>
<button class="codeblock-expand">Show all</button>
</div>
Using git worktrees to parallelize AI coding
2025-12-29T00:00:00+00:00
2025-12-29T00:00:00+00:00
Raine Virta
https://raine.dev/blog/git-worktrees-parallel-agents/
<p>As a continuation of the <a href="https://raine.dev/blog/introduction-to-workmux/">earlier post</a> that introduced <a href="https://workmux.raine.dev/">workmux</a>, here's
a useful pattern for delegating tasks to worktree agents.</p>
<h2 id="the-workflow-in-a-nutshell"><a class="zola-anchor" href="#the-workflow-in-a-nutshell" aria-label="Anchor link for: the workflow in a nutshell">#</a>The workflow in a nutshell</h2>
<p>The workflow uses a main agent on the main branch to brainstorm and dispatch
tasks, while worktree agents run in separate worktrees.</p>
<ol>
<li>Brainstorm tasks with the main agent</li>
<li>Refine the items, by yourself or with the help of the agent</li>
<li>Delegate tasks to worktree agents</li>
<li>Review changes and merge into main branch as they finish</li>
</ol>
<p>The steps don't have to happen in strict sequence. You can dispatch one task,
refine the next while it runs, and review changes as they come in.</p>
<h2 id="step-1-come-up-with-tasks"><a class="zola-anchor" href="#step-1-come-up-with-tasks" aria-label="Anchor link for: step 1 come up with tasks">#</a>Step 1: Come up with tasks</h2>
<p>The first step is to come up with things to do. The tasks can be anything:
features, bug fixes, general improvement ideas or refactoring suggestions etc.
As an example, I'll ask the agent for improvement ideas for a project I happen
to be working on, but you could just as easily use a to-do list file as a source
for the tasks.</p>
<p>I work with Claude Code in my main tmux window, which acts as a coordinator that
will later delegate tasks to worktree agents. This agent stays on the main
branch, brainstorms ideas, writes prompts, and spawns worker agents in their own
worktrees.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<img class="codeblock-icon" src="/icons/claude-color.svg" alt="Claude">
Claude
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">> Ask Gemini for ideas on how to improve the bot. Write the ideas to a markdown
</span><span class="z-text z-plain"> file with as much detail as possible.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ I'll first explore the codebase to understand what this bot does, then consult
</span><span class="z-text z-plain"> Gemini with relevant context.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Explore(Explore telegram-tori-bot codebase)
</span><span class="z-text z-plain"> ⎿ Done (29 tool uses · 48.6k tokens · 57s)
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Now I have a good understanding of the codebase. Let me consult Gemini with
</span><span class="z-text z-plain"> the key files for improvement ideas.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ consult-llm - consult_llm (MCP)(prompt: "I have a Telegram bot that...",
</span><span class="z-text z-plain"> model: "gemini-3-pro-preview", ...)
</span><span class="z-text z-plain"> ⎿ Here is a comprehensive analysis of your Telegram bot codebase with
</span><span class="z-text z-plain"> actionable improvement suggestions.
</span><span class="z-text z-plain"> … +186 lines (ctrl+o to expand)
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Excellent! I received comprehensive improvement ideas from Gemini. Let me
</span><span class="z-text z-plain"> write these to a detailed markdown file.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Write(IMPROVEMENT_IDEAS.md)
</span><span class="z-text z-plain"> ⎿ Wrote 730 lines to IMPROVEMENT_IDEAS.md
</span><span class="z-text z-plain"> # Telegram Tori Bot - Improvement Ideas
</span><span class="z-text z-plain"> *Generated via Gemini analysis on 2025-12-29*
</span><span class="z-text z-plain"> This document contains comprehensive improvement suggestions for the
</span><span class="z-text z-plain"> Telegram Tori bot, organized by priority and category.
</span><span class="z-text z-plain"> ---
</span><span class="z-text z-plain"> ## Table of Contents
</span><span class="z-text z-plain"> … +720 lines (ctrl+o to expand)
</span></code></pre>
</div>
<p>I like to occasionally use <code>gemini-3-pro-preview</code> through <a href="https://github.com/raine/consult-llm-mcp">consult-llm-mcp</a> to
see what Gemini comes up with.</p>
<h2 id="step-2-refine-the-tasks"><a class="zola-anchor" href="#step-2-refine-the-tasks" aria-label="Anchor link for: step 2 refine the tasks">#</a>Step 2: Refine the tasks</h2>
<p>Gemini came up with a nice list of items to do, but I don't necessarily want to
implement all of them, or they might require some adjustments. The advantage of
a markdown file as an intermediate step, as opposed to just holding the items in
the agent's context, is that you can freely edit it in the editor. However,
either way works, so this step is optional.</p>
<figure>
<img src="https://raine.dev/blog/git-worktrees-parallel-agents/ideas-markdown-editor.webp" alt="Markdown file with improvement ideas open in editor" loading="lazy">
<figcaption>
Reviewing and editing the generated suggestions
</figcaption>
</figure>
<h2 id="step-3-delegate-to-an-agent-in-a-worktree"><a class="zola-anchor" href="#step-3-delegate-to-an-agent-in-a-worktree" aria-label="Anchor link for: step 3 delegate to an agent in a worktree">#</a>Step 3: Delegate to an agent in a worktree</h2>
<p>As mentioned in the <a href="https://raine.dev/blog/introduction-to-workmux/">introductory post</a>, the advantage of worktrees in
agent parallelization is that the changes done won't conflict with other agents.
Agents will only see type errors, test failures, or lint issues caused by their
own changes, and reviewing their work is also much easier.</p>
<p>From Gemini's suggestions, I want to implement the natural language editing
feature, which lets users edit draft listings with messages like "Set price to
80€". (What a great idea by the way. This avoids the need to build a separate
bot slash command for every conceivable action.)</p>
<p>Instructing the agent to create a worktree for a task with workmux requires some
prompt glue, so I have a <a href="https://code.claude.com/docs/en/slash-commands#custom-slash-commands">custom command</a> that makes worktree
creation easy.</p>
<div class="codeblock-labeled codeblock-collapsed">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
~/.claude/commands/worktree.md
</span></div>
<pre data-lang="markdown" class="language-markdown z-code"><code class="language-markdown" data-lang="markdown"><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Launch one or more tasks in new git worktrees using workmux.
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Tasks: $ARGUMENTS
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-2 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">##</span> </span><span class="z-markup z-heading z-2 z-markdown"><span class="z-entity z-name z-section z-markdown">Instructions</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Note: The tasks above may reference something discussed earlier in the
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">conversation (e.g., "do option 2", "implement the fix we discussed"). Include
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">all relevant context from the conversation in each prompt you write.
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">If tasks reference a markdown file (e.g., a plan or spec), re-read the file to
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">ensure you have the latest version before writing prompts.
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">For each task:
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-bullet z-markdown">1<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"> <span class="z-meta z-paragraph z-list z-markdown">Generate a short, descriptive worktree name (2-4 words, kebab-case)
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">2<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Write a detailed implementation prompt to a temp file
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">3<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Run <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>workmux add <worktree-name> -b -P <temp-file><span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span> to create the worktree
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"></span><span class="z-meta z-paragraph z-markdown">The prompt file should:
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"> <span class="z-meta z-paragraph z-list z-markdown">Include the full task description
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Use RELATIVE paths only (never absolute paths, since each worktree has its own
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown">root directory)
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Be specific about what the agent should accomplish
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Include instruction to review the changes with Gemini after completing the
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown">task (use <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>mcp__consult-llm__consult_llm<span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span> with model "gemini-3-pro-preview",
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown">include relevant files and git_diff)
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span>
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"></span><span class="z-meta z-block-level z-markdown"><span class="z-markup z-heading z-2 z-markdown"><span class="z-punctuation z-definition z-heading z-begin z-markdown">##</span> </span><span class="z-markup z-heading z-2 z-markdown"><span class="z-entity z-name z-section z-markdown">Workflow</span><span class="z-meta z-whitespace z-newline z-markdown">
</span></span></span></span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Write ALL temp files first, THEN run all workmux commands.
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Step 1 - Write all prompt files (in parallel):
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-meta z-code-fence z-definition z-begin z-shell-script z-markdown-gfm"><span class="z-punctuation z-definition z-raw z-code-fence z-begin z-markdown">```</span><span class="z-constant z-other z-language-name z-markdown">bash</span>
</span><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"></span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">tmpfile</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-parens z-begin z-shell">(</span><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">mktemp</span></span><span class="z-punctuation z-section z-parens z-end z-shell">)</span></span>.md</span>
</span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cat</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-keyword z-operator z-assignment z-redirection z-shell">></span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">tmpfile</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-string z-unquoted z-heredoc z-shell"><span class="z-keyword z-operator z-assignment z-redirection z-shell"><<</span> <span class="z-punctuation z-definition z-string z-begin z-shell">'</span><span class="z-keyword z-control z-heredoc-token z-shell">EOF</span><span class="z-punctuation z-definition z-string z-end z-shell">'</span></span><span class="z-string z-unquoted z-heredoc z-shell">
</span></span></span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-unquoted z-heredoc z-shell">Implement feature X...
</span></span></span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-string z-unquoted z-heredoc z-shell"><span class="z-keyword z-control z-heredoc-token z-shell">EOF</span></span></span>
</span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-echo z-shell">echo</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">tmpfile</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Note the path for step 2</span><span class="z-comment z-line z-number-sign z-shell">
</span></span></span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"></span></span><span class="z-meta z-code-fence z-definition z-end z-shell-script z-markdown-gfm"><span class="z-punctuation z-definition z-raw z-code-fence z-end z-markdown">```</span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Step 2 - After ALL files are written, run workmux commands (in parallel):
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-meta z-code-fence z-definition z-begin z-shell-script z-markdown-gfm"><span class="z-punctuation z-definition z-raw z-code-fence z-begin z-markdown">```</span><span class="z-constant z-other z-language-name z-markdown">bash</span>
</span><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"></span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">workmux</span></span><span class="z-meta z-function-call z-arguments z-shell"> add feature-x<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>b</span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>P</span> /tmp/tmp.abc123.md</span>
</span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">workmux</span></span><span class="z-meta z-function-call z-arguments z-shell"> add feature-y<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>b</span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>P</span> /tmp/tmp.def456.md</span>
</span></span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-raw z-code-fence z-shell-script z-markdown-gfm"><span class="z-source z-shell z-bash"></span></span><span class="z-meta z-code-fence z-definition z-end z-shell-script z-markdown-gfm"><span class="z-punctuation z-definition z-raw z-code-fence z-end z-markdown">```</span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">After creating the worktrees, inform the user which branches were created.
</span></span></code></pre>
<button class="codeblock-expand">Show all</button>
</div>
<p>This command instructs the agent to create a new worktree with workmux, passing
the referenced task as a prompt to the agent that starts within the created
worktree and its tmux window.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<img class="codeblock-icon" src="/icons/claude-color.svg" alt="Claude">
Claude
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">> /worktree Implement natural language editing
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(tmpfile=$(mktemp).md && cat > "$tmpfile" << 'EOF'…)
</span><span class="z-text z-plain"> ⎿ /tmp/tmp.43Qi9kRtoS.md
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(workmux add natural-language-edit -b -P /tmp/tmp.43Qi9kRtoS.md)
</span><span class="z-text z-plain"> ⎿ Running setup commands...
</span><span class="z-text z-plain"> ✓ Setup complete
</span><span class="z-text z-plain"> ✓ Successfully created worktree and tmux window for 'natural-language-edit'
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Created worktree natural-language-edit on branch natural-language-edit.
</span><span class="z-text z-plain"> The agent is now implementing the natural language editing feature.
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">> /worktree Fix the listing_handler.go race condition
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(tmpfile=$(mktemp).md && cat > "$tmpfile" << 'EOF'…)
</span><span class="z-text z-plain"> ⎿ /tmp/tmp.JNGvRIz4NC.md
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(workmux add fix-race-condition -b -P /tmp/tmp.JNGvRIz4NC.md)
</span><span class="z-text z-plain"> ⎿ Running setup commands...
</span><span class="z-text z-plain"> ✓ Setup complete
</span><span class="z-text z-plain"> ✓ Successfully created worktree and tmux window for 'fix-race-condition'
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Created worktree fix-race-condition on branch fix-race-condition.
</span><span class="z-text z-plain"> The agent is now working on fixing the nil draft race condition.
</span></code></pre>
</div>
<aside class="aside-box">
<div class="aside-header"><svg class="aside-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg> What's workmux add doing?</div>
<div class="aside-content">
<p><code>workmux add natural-language-edit -b -P /tmp/tmp.xxx.md</code> creates a worktree and
tmux window named <code>natural-language-edit</code>. The <code>-b</code> flag runs it in the
background (stays in current window), and <code>-P</code> passes the temp file as a prompt
to the agent.</p>
</div>
</aside>
<figure>
<img src="https://raine.dev/blog/git-worktrees-parallel-agents/worktree-agent-started.webp" alt="Terminal showing agent working in the natural-language-edit worktree" loading="lazy">
<figcaption>
The agent started working on the task in its own window and worktree
</figcaption>
</figure>
<p>Because every worktree has its own tmux window with the agent process running,
you can interact with the agent as needed, or start reviewing the changes as
they come in.</p>
<figure>
<img src="https://raine.dev/blog/git-worktrees-parallel-agents/window-list-agents.webp" alt="Tmux window list showing one agent completed, one in progress" loading="lazy">
<figcaption>
One agent finished, another still working
</figcaption>
</figure>
<h2 id="step-4-review-changes-and-merge"><a class="zola-anchor" href="#step-4-review-changes-and-merge" aria-label="Anchor link for: step 4 review changes and merge">#</a>Step 4: Review changes and merge</h2>
<p>Once an agent finishes, you can switch to its window and review the changes like
you normally would with <code>git diff</code> or other tools. I usually skim the diff in
Neovim and request changes as needed. For the natural language editing feature,
it's useful to also run the dev server in another pane to test the changes
manually.</p>
<p>You may have noticed the <code>/worktree</code> command asks agents to review the changes
before finishing. I find it extremely useful to do a round of review with AI,
and especially Gemini, before looking at the diff myself.</p>
<figure>
<img src="https://raine.dev/blog/git-worktrees-parallel-agents/reviewing-diff-neovim.webp" alt="Reviewing the diff in Neovim" loading="lazy">
<figcaption>
Reviewing the agent's changes in Neovim
</figcaption>
</figure>
<p>When happy with the changes, commit (or have the agent do it), then run
<code>workmux merge --rebase</code> to rebase on main, merge, and clean up the worktree.</p>
<p>To minimize conflicts, I try to organize work so that parallel agents work on
separate areas of the codebase. For example, better avoid having one agent
refactor a module while another adds features to it. However, if there are
conflicts from other agents' merged branches, the worktree's agent can usually
resolve them (see <a href="https://raine.dev/blog/resolve-conflicts-with-claude/">earlier post</a>).</p>
<a href="https://workmux.raine.dev/" class="project-card-inline">
<div class="project-main">
<h3 class="project-name">workmux</h3>
<p class="project-description">git worktrees + tmux windows for zero-friction parallel dev</p>
</div>
<div class="project-meta">
<span class="project-language" data-language="Rust">
<span class="language-dot"></span>
Rust
</span>
<span class="project-stars">
<svg class="star-icon" aria-hidden="true" height="16" viewBox="0 0 16 16" width="16">
<path fill="currentColor" d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path>
</svg>
756
</span>
</div>
</a>
<div class="read-next">
<a href="https://raine.dev/blog/my-tmux-setup/" class="read-next-link">
<span class="read-next-label">Read next</span>
<span class="read-next-title">My setup, and why tmux workflow is better than ever</span>
<span class="read-next-description">for more on the tmux workflow these tools are built around.</span>
<svg class="read-next-arrow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
</div>
Resolve merge conflicts with Claude Code
2025-12-26T00:00:00+00:00
2025-12-26T00:00:00+00:00
Raine Virta
https://raine.dev/blog/resolve-conflicts-with-claude/
<p>Merge conflicts are an unavoidable part of software development when working in
a team, or even solo, especially when running
<a href="https://raine.dev/blog/introduction-to-workmux/">multiple agents in parallel</a>. Even if
you do your best to organize work for agents so that parallel tasks don't touch
the same area of the codebase, conflicts will invariably occur at some point.</p>
<p>Fortunately, the agent you built the feature with can probably resolve the
conflicts for you. If you still have the same session open, the agent should
have good context on how to resolve the diffs in a way that preserves the new
feature. What it might be missing though is context on the changes in the base
branch that caused the conflicts.</p>
<p>Prompting Claude Code to simply resolve the conflicts might be enough for it to
do sufficient groundwork, but to make the process more reliable I've recently
set up the following <code>/rebase</code> <a href="https://code.claude.com/docs/en/skills">skill</a> to streamline rebasing current
branch on the <code>main</code> branch. It specifically instructs Claude to understand the
intent behind the changes in the target branch before resolving conflicts, which
appears to help.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
~/.claude/skills/rebase/SKILL.md
</span></div>
<pre data-lang="markdown" class="language-markdown z-code"><code class="language-markdown" data-lang="markdown"><span class="z-text z-html z-markdown"><span class="z-meta z-block-level z-markdown"><span class="z-meta z-separator z-thematic-break z-markdown"><span class="z-punctuation z-definition z-thematic-break z-markdown">---</span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">name: rebase
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">description: Rebase the current branch.
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">allowed-tools: Bash
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown"><span class="z-markup z-heading z-2 z-setext z-markdown"><span class="z-punctuation z-definition z-heading z-setext z-markdown">---</span></span></span>
</span><span class="z-text z-html z-markdown">
</span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Rebase the current branch.
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Arguments: $ARGUMENTS
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">Behavior:
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"> <span class="z-meta z-paragraph z-list z-markdown">No arguments: rebase on local main
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> "origin": fetch origin, rebase on origin/main
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> "origin/branch": fetch origin, rebase on origin/branch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> "branch": rebase on local branch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"></span><span class="z-meta z-paragraph z-markdown">Steps:
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-bullet z-markdown">1<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"> <span class="z-meta z-paragraph z-list z-markdown">Check for uncommitted changes:
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"> </span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Run <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>git status --porcelain<span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"> </span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> If there are changes, run <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>git stash push -m "rebase-temp"<span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"> </span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Remember to pop the stash after rebase completes
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">2<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Parse arguments:
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"> </span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> No args → target is "main", no fetch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"> </span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Contains "/" (e.g., "origin/develop") → split into remote and branch, fetch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown">remote, target is remote/branch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"> </span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Just "origin" → fetch origin, target is "origin/main"
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"> </span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Anything else → target is that branch name, no fetch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">3<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> If fetching, run: <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>git fetch <remote><span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">4<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Run: <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>git rebase <target><span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">5<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> If conflicts occur, handle them carefully (see below)
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">6<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> Continue until rebase is complete
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-bullet z-markdown">7<span class="z-punctuation z-definition z-list_item z-markdown">.</span></span><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> If changes were stashed in step 1, run <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>git stash pop<span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-numbered z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-numbered z-markdown"></span><span class="z-meta z-paragraph z-markdown">Handling conflicts:
</span></span><span class="z-text z-html z-markdown"><span class="z-meta z-paragraph z-markdown">
</span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"> <span class="z-meta z-paragraph z-list z-markdown">BEFORE resolving any conflict, understand what changes were made to each
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown">conflicting file in the target branch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> For each conflicting file, run <span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>git log -p -n 3 <target> -- <file><span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span> to see
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown">recent changes to that file in the target branch
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> The goal is to preserve BOTH the changes from the target branch AND our
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown">branch's changes
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> After resolving each conflict, stage the file and continue with
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> </span><span class="z-meta z-paragraph z-list z-markdown"><span class="z-markup z-raw z-inline z-markdown"><span class="z-punctuation z-definition z-raw z-begin z-markdown">`</span>git rebase --continue<span class="z-punctuation z-definition z-raw z-end z-markdown">`</span></span>
</span></span></span><span class="z-text z-html z-markdown"><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"></span></span><span class="z-markup z-list z-unnumbered z-bullet z-markdown"><span class="z-punctuation z-definition z-list_item z-markdown">-</span></span><span class="z-markup z-list z-unnumbered z-markdown"><span class="z-meta z-paragraph z-list z-markdown"> If a conflict is too complex or unclear, ask for guidance before proceeding
</span></span></span></code></pre>
</div>
<p>To rebase on <code>main</code>, run it with <code>/rebase</code>, or <code>/rebase origin</code> to also fetch
and rebase on <code>origin/main</code>.</p>
<p>Here's what it looks like in practice when rebasing onto a branch that has
refactored the codebase:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<img class="codeblock-icon" src="/icons/claude-color.svg" alt="Claude">
Claude
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">> /rebase tori-login
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git rebase tori-login)
</span><span class="z-text z-plain"> ⎿ Error: Exit code 1
</span><span class="z-text z-plain"> error: could not apply 88adb7e... add llm-based category auto-selection
</span><span class="z-text z-plain"> hint: Resolve all conflicts manually, mark them as resolved with
</span><span class="z-text z-plain"> hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
</span><span class="z-text z-plain"> …
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ I see there's a significant conflict. The tori-login branch has refactored
</span><span class="z-text z-plain"> bot.go to use handlers (AuthHandler, ListingHandler), while my commit still
</span><span class="z-text z-plain"> uses the old structure. Let me understand the changes in the target branch
</span><span class="z-text z-plain"> first:
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ Bash(git log -p -n 3 tori-login -- bot.go | head -200)
</span><span class="z-text z-plain"> ⎿ commit 67c20e028872a455fb057e35b2a635503a690107
</span><span class="z-text z-plain"> Author: Raine Virta <[email protected]>
</span><span class="z-text z-plain"> Date: Sun Dec 28 19:21:25 2025 +0200
</span><span class="z-text z-plain"> …
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">⏺ My commit needs to:
</span><span class="z-text z-plain"> 1. Use llm.Analyzer instead of vision.Analyzer
</span><span class="z-text z-plain"> 2. Add /osasto command to handle category re-selection
</span><span class="z-text z-plain"> 3. Add category auto-selection using LLM
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"> The conflicts are:
</span><span class="z-text z-plain"> 1. Import section - need to use llm instead of vision
</span><span class="z-text z-plain"> 2. Bot struct - need to use llm.Analyzer
</span><span class="z-text z-plain"> …
</span></code></pre>
</div>
<p>The key behavior here is that Claude examines the target branch's changes with
<code>git log -p</code> before attempting to resolve conflicts, giving it context on the
refactoring that occurred.</p>
workmux: git worktrees + tmux for parallel AI agents
2025-12-25T00:00:00+00:00
2025-12-25T00:00:00+00:00
Raine Virta
https://raine.dev/blog/introduction-to-workmux/
<p>Recently, with AI-assisted coding gaining momentum, a long-standing and not that
well-known git feature is finding new relevance. The feature is <a href="https://git-scm.com/docs/git-worktree">git
worktrees</a>, the ability to have multiple branches checked out
simultaneously, each in its own directory.</p>
<p>Running multiple agents in parallel is becoming increasingly common, and agents
benefit from the isolation worktrees provide. If agents share the same working
tree, they quickly run into each other's type errors and broken builds. Separate
worktrees also make reviewing their changes easier, because you see the diff for
a single unit of work. Even outside of agents, worktrees reduce pain from
context-switching. No need to stash/commit, reinstall dependencies, or lose
editor state.</p>
<p>I've been curious about worktrees since before LLM agents, but I've always
struggled with the low-level tooling the vanilla <code>git worktree</code> CLI provides.
And despite having tried worktree wrapper CLIs that make the use of worktrees
more ergonomic, I haven't found them to quite fit my tmux-based workflow.</p>
<aside class="aside-box">
<div class="aside-header"><svg class="aside-icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg> What's tmux?</div>
<div class="aside-content">
<p><a href="https://github.com/tmux/tmux">tmux</a> is a terminal multiplexer - sort of like a
window manager, but for the terminal. You can organize work into sessions, each
with multiple windows and panes, and switch between them instantly.</p>
</div>
</aside>
<p>This led to <a href="https://workmux.raine.dev/">workmux</a>, a utility that ties worktrees with tmux windows
and streamlines the flow from starting a new feature in a worktree, to merging
it.</p>
<p>This has turned out to be the right level of abstraction for my terminal-centric
workflow. While tools like <a href="https://www.vibekanban.com/">Vibe Kanban</a> and <a href="https://github.com/smtg-ai/claude-squad">Claude
Squad</a> offer alternative UIs, workmux fits naturally into existing
tmux workflows. Each worktree maps to a tmux window, where it runs the agent and
any other commands you've configured. Workmux also shows agent status in the
window list, so you can tell at a glance which agents are working or done.</p>
<p>Here's what it looks like:</p>
<figure>
<div class="video-container">
<video
src="https://raine.dev/blog/introduction-to-workmux/demo.mp4"
poster="https://raine.dev/blog/introduction-to-workmux/demo-poster.jpg"
controls
preload="metadata"
></video>
<button type="button" class="video-play-button" aria-label="Play video"></button>
</div>
</figure>
<h2 id="getting-started"><a class="zola-anchor" href="#getting-started" aria-label="Anchor link for: getting started">#</a>Getting started</h2>
<p>Install via Homebrew or Cargo:</p>
<pre data-lang="bash" class="language-bash z-code"><code class="language-bash" data-lang="bash"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">brew</span></span><span class="z-meta z-function-call z-arguments z-shell"> install raine/workmux/workmux</span>
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> or</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cargo</span></span><span class="z-meta z-function-call z-arguments z-shell"> install workmux</span>
</span></code></pre>
<p>The core workflow is two commands:</p>
<pre data-lang="bash" class="language-bash z-code"><code class="language-bash" data-lang="bash"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Create a worktree and tmux window</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">workmux</span></span><span class="z-meta z-function-call z-arguments z-shell"> add feature-name</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> When done, merge to main and clean up</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">workmux</span></span><span class="z-meta z-function-call z-arguments z-shell"> merge</span>
</span></code></pre>
<p>The <code>add</code> command creates the worktree, opens a tmux window, and switches you to
it. <code>merge</code> merges your branch into main and removes everything: worktree,
window, and the local branch. This is the most basic workflow, but <code>add</code>
supports many useful options, like checking out a pull request with <code>--pr</code> or
generating the branch name from a given prompt with <code>--auto-name</code>. Check the
<a href="https://github.com/raine/workmux#workmux-add-branch-name">command documentation</a>
for the complete list.</p>
<h2 id="configuration"><a class="zola-anchor" href="#configuration" aria-label="Anchor link for: configuration">#</a>Configuration</h2>
<p>The tool works out of the box with sensible defaults, but you can customize it
with a <code>.workmux.yaml</code> in your project:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.workmux.yaml
</span></div>
<pre data-lang="yaml" class="language-yaml z-code"><code class="language-yaml" data-lang="yaml"><span class="z-source z-yaml"><span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">panes</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">command</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><agent></span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">focus</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">command</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-unquoted z-plain z-out z-yaml">npm install && npm run dev</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">split</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-unquoted z-plain z-out z-yaml">horizontal</span>
</span><span class="z-source z-yaml">
</span><span class="z-source z-yaml"><span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">files</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">symlink</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml">.turbo</span> <span class="z-comment z-line z-number-sign z-yaml"><span class="z-punctuation z-definition z-comment z-line z-number-sign z-yaml">#</span> share build cache across worktrees
</span></span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">copy</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml">.env</span>
</span></code></pre>
</div>
<p>The <code><agent></code> placeholder expands to whatever agent you're using (<code>claude</code>,
<code>codex</code>, <code>gemini</code>, etc.). File symlinks let you share caches across worktrees,
and copies handle config files that need to be per-worktree.</p>
<figure>
<img src="https://raine.dev/blog/introduction-to-workmux/agent-status.webp" alt="tmux window list showing agent status icons" loading="lazy">
<figcaption>
When agents are running, their status shows up in your tmux window list.
</figcaption>
</figure>
<p>With this setup, the tmux window list becomes a dashboard. You can dispatch
tasks to agents in separate worktrees. A quick glance shows who's thinking, who
needs input, and who's ready for review.</p>
<p>After a few weeks of dogfooding, workmux has changed my standard git workflow.
Now, instead of plain branches, I tend to go for a dedicated worktree for each
task, be it a feature or reviewing someone else's code. The mental model of
having a tmux window per task feels very natural. Each one is isolated, with its
own terminal state, editor session and a running dev server.</p>
<p>The <a href="https://raine.dev/blog/git-worktrees-parallel-agents/">next post</a> explores more advanced agentic workflows, like
how to brainstorm tasks with a main agent and delegate them to worktree agents.
In the meantime, feel free to check out <a href="https://workmux.raine.dev/">workmux</a>.</p>
<a href="https://workmux.raine.dev/" class="project-card-inline">
<div class="project-main">
<h3 class="project-name">workmux</h3>
<p class="project-description">git worktrees + tmux windows for zero-friction parallel dev</p>
</div>
<div class="project-meta">
<span class="project-language" data-language="Rust">
<span class="language-dot"></span>
Rust
</span>
<span class="project-stars">
<svg class="star-icon" aria-hidden="true" height="16" viewBox="0 0 16 16" width="16">
<path fill="currentColor" d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path>
</svg>
756
</span>
</div>
</a>
<div class="read-next">
<a href="https://raine.dev/blog/git-worktrees-parallel-agents/" class="read-next-link">
<span class="read-next-label">Read next</span>
<span class="read-next-title">Using git worktrees to parallelize AI coding</span>
<span class="read-next-description">a workflow for delegating tasks to parallel worktree agents.</span>
<svg class="read-next-arrow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
</div>
tmux tip: Add file paths to context with fzf in Claude Code
2025-12-21T00:00:00+00:00
2025-12-21T00:00:00+00:00
Raine Virta
https://raine.dev/blog/tmux-file-picker-for-ai-agents/
<p>One of the killer features in <code>tmux</code> is the floating popup window. This enables
all kinds of interesting workflows, because with a mapping of your choice, you
can spawn any process in a floating window on top of your panes. This process
will inherit the current pane's working directory, has all the tmux APIs etc. at
its disposal, so all kinds of cool things are possible.</p>
<p>Over a year ago, when I was still using <a href="http://aider.chat/">Aider</a> and suffering from its
clunky file path autocomplete, I
<a href="https://github.com/Aider-AI/aider/issues/2156#issuecomment-2439919513">got the idea</a>
of combining tmux popups with <a href="https://github.com/junegunn/fzf"><code>fzf</code></a>. The basic idea is that you launch
<code>fzf</code> in a popup that filters through files in your current working directory.
You can select one or more files, and the selected files are inserted into the
pane that invoked the tool, each prefixed with the <code>@</code> symbol as is customary in
AI terminal UI apps.</p>
<figure>
<div class="video-container">
<video
src="https://raine.dev/blog/tmux-file-picker-for-ai-agents/demo.mp4"
muted
playsinline
controls
preload="metadata"
></video>
<button type="button" class="video-play-button" aria-label="Play video"></button>
</div>
</figure>
<p>I've since packaged this and a few other file/directory picking related
workflows under a single command-line tool called
<a href="https://github.com/raine/tmux-file-picker"><code>tmux-file-picker</code></a>.</p>
<p>You can find the installation instructions in the <a href="https://github.com/raine/tmux-file-picker#readme">README</a>. For now,
I'll cover some of my favorite workflows and the related configuration.</p>
<h2 id="workflows"><a class="zola-anchor" href="#workflows" aria-label="Anchor link for: workflows">#</a>Workflows</h2>
<h3 id="just-pick-some-files-in-the-current-directory"><a class="zola-anchor" href="#just-pick-some-files-in-the-current-directory" aria-label="Anchor link for: just pick some files in the current directory">#</a>Just pick some files in the current directory</h3>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.tmux.conf
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">bind C-f display-popup -E "tmux-file-picker"
</span></code></pre>
</div>
<p>This is the most basic workflow covered above and shown in the demo. The bind
makes it so that pressing <kbd>prefix</kbd> (<kbd>Ctrl-b</kbd> by default) +
<kbd>Ctrl-f</kbd> launches <code>tmux-file-picker</code> in a tmux popup.</p>
<h3 id="pick-directories-in-the-current-directory"><a class="zola-anchor" href="#pick-directories-in-the-current-directory" aria-label="Anchor link for: pick directories in the current directory">#</a>Pick directories in the current directory</h3>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.tmux.conf
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">bind C-r display-popup -E "tmux-file-picker -d"
</span></code></pre>
</div>
<p>With <code>-d</code>, it opens a fzf that filters directories in the current repository.
This is sometimes useful when you want to point the agent to some directory but
not a specific file.</p>
<h3 id="pick-recently-accessed-directories-or-files"><a class="zola-anchor" href="#pick-recently-accessed-directories-or-files" aria-label="Anchor link for: pick recently accessed directories or files">#</a>Pick recently accessed directories or files</h3>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.tmux.conf
</span></div>
<pre class="z-code"><code><span class="z-text z-plain">bind C-z display-popup -E "tmux-file-picker --zoxide"
</span><span class="z-text z-plain">bind C-d display-popup -E "tmux-file-picker --zoxide --dir-only"
</span></code></pre>
</div>
<p>A recent addition is that the tool integrates with
<a href="https://github.com/ajeetdsouza/zoxide"><code>zoxide</code></a>, which allows filtering
through recently accessed directories. With <code>--zoxide</code>, you are presented with a
two-step flow, where the tool first prompts to filter through directories, and
upon selection, a second fuzzy-finder is invoked where files in that directory
are shown. I've come to really like this one, because I very often tell agents
to refer to some configuration etc. in other projects.</p>
<p>Hope any tmux users out there find this useful. To me, this nicely illustrates
the power of terminal composability. A popup, a fuzzy finder, and a few lines of
config solve a problem that might otherwise require a complex plugin ecosystem
in a GUI app.</p>
<p>For another although still work in progress tool that also uses tmux popups,
check out: <a href="https://github.com/raine/tmux-snaglord">tmux-snaglord</a></p>
<div class="read-next">
<a href="https://raine.dev/blog/my-tmux-setup/" class="read-next-link">
<span class="read-next-label">Read next</span>
<span class="read-next-title">My setup, and why tmux workflow is better than ever</span>
<span class="read-next-description">for the full context of how I use tmux for development.</span>
<svg class="read-next-arrow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
</div>
My experience with Zola static site engine
2025-12-20T00:00:00+00:00
2025-12-20T00:00:00+00:00
Raine Virta
https://raine.dev/blog/zola-static-site-experience/
<p>I recently converted <a href="https://walkingmate.zendit.fi/">WalkingMate's website</a>
from plain HTML to use <a href="https://www.getzola.org/">Zola</a> which is a static site
engine written in Rust. As the number of pages grew from just one, the lack of
templating started to be a problem for development experience. Duplicating the
navigation and footer in every page is annoying because they can start to
diverge by accident. I chose Zola for no particular reason, but the fact that
it's written in Rust appeals to me, as that generally signals performance and
modern DX.</p>
<p>Overall, my experience with Zola has been positive, and I've since also built
this site with Zola. I like the first-class markdown support with TOML
frontmatter for creating content, and it seems flexible enough to serve various
of use cases. For example, in the WalkingMate website, I have a YAML file that
contains the full changelog of the app:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
data/changelog.yaml
</span></div>
<pre data-lang="yaml" class="language-yaml z-code"><code class="language-yaml" data-lang="yaml"><span class="z-source z-yaml"><span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">version</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-quoted z-single z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">'</span>1.2.0<span class="z-punctuation z-definition z-string z-end z-yaml">'</span></span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">date</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-unquoted z-plain z-out z-yaml">December 19, 2025</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">changes</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-quoted z-double z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">"</span>Added step calibration wizard - walk for 60 seconds while tapping
</span></span><span class="z-source z-yaml"><span class="z-string z-quoted z-double z-yaml"> spacebar to find your treadmill's correction factor<span class="z-punctuation z-definition z-string z-end z-yaml">"</span></span>
</span><span class="z-source z-yaml"> <span class="z-punctuation z-definition z-block z-sequence z-item z-yaml">-</span> <span class="z-string z-quoted z-single z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">'</span>Fixed workouts being lost when treadmill disconnects mid-session (now
</span></span><span class="z-source z-yaml"><span class="z-string z-quoted z-single z-yaml"> auto-saves after 60s or can be saved manually)<span class="z-punctuation z-definition z-string z-end z-yaml">'</span></span>
</span><span class="z-source z-yaml"><span class="z-comment z-line z-number-sign z-yaml"><span class="z-punctuation z-definition z-comment z-line z-number-sign z-yaml">#</span> ...
</span></span></code></pre>
</div>
<p>I can use this same YAML to show the full list of changes in a What's New page,
but also to render just the latest changelog item on the frontpage:</p>
<pre data-lang="html" class="language-html z-code"><code class="language-html" data-lang="html"><span class="z-text z-html z-basic"><span class="z-comment z-block z-html"><span class="z-punctuation z-definition z-comment z-begin z-html"><!--</span> Front page <span class="z-punctuation z-definition z-comment z-end z-html">--></span></span>
</span><span class="z-text z-html z-basic"><span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">whats-new-content</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> {% set changelog = load_data(path="data/changelog.yaml") %} {% set latest =
</span><span class="z-text z-html z-basic"> changelog | first %}
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">whats-new-item</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">whats-new-header</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-any z-html">h3</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>Version {{ latest.version }}<span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-block z-any z-html">h3</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">whats-new-date</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>{{ latest.date }}<span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-any z-html">span</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-block z-any z-html">ul</span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">whats-new-changes</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> {% for change in latest.changes %}
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-inline z-any z-html">li</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>{{ change | markdown(inline=true) | safe }}<span class="z-meta z-tag z-inline z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-any z-html">li</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> {% endfor %}
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-block z-any z-html">ul</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"> <span class="z-meta z-tag z-inline z-a z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"><</span><span class="z-entity z-name z-tag z-inline z-a z-html">a</span> <span class="z-meta z-attribute-with-value z-html"><span class="z-entity z-other z-attribute-name z-html">href</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span>/changelog<span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span> <span class="z-meta z-attribute-with-value z-class z-html"><span class="z-entity z-other z-attribute-name z-class z-html">class</span><span class="z-punctuation z-separator z-key-value z-html">=</span><span class="z-string z-quoted z-double z-html"><span class="z-punctuation z-definition z-string z-begin z-html">"</span></span><span class="z-string z-quoted z-double z-html"><span class="z-meta z-class-name z-html">whats-new-link</span><span class="z-punctuation z-definition z-string z-end z-html">"</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>View all updates <span class="z-constant z-character z-entity z-named z-html"><span class="z-punctuation z-definition z-entity z-html">&</span>rarr<span class="z-punctuation z-terminator z-entity z-html">;</span></span><span class="z-meta z-tag z-inline z-a z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-inline z-a z-html">a</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span><span class="z-text z-html z-basic"><span class="z-meta z-tag z-block z-any z-html"><span class="z-punctuation z-definition z-tag z-begin z-html"></</span><span class="z-entity z-name z-tag z-block z-any z-html">div</span><span class="z-punctuation z-definition z-tag z-end z-html">></span></span>
</span></code></pre>
<figure>
<img src="https://raine.dev/blog/zola-static-site-experience/walkingmate-changelog-frontpage.webp" alt="WalkingMate changelog rendered on the frontpage" loading="lazy">
<figcaption>
The changelog YAML rendered as a What's New section on the frontpage.
</figcaption>
</figure>
<p>However, it's not without its rough edges, although, I've not found that many.</p>
<h2 id="zola-it-s-not-ai-native-lol"><a class="zola-anchor" href="#zola-it-s-not-ai-native-lol" aria-label="Anchor link for: zola it s not ai native lol">#</a>Zola: It's not AI native, lol</h2>
<p>As I use Claude Code quite heavily (outside of writing these blog posts), I
quickly noticed that Zola's file watcher was not picking up edits done by
Claude. A quick AI consultation revealed the issue might be that Claude does its
edits atomically, which means that it creates a new file containing the edit,
and then renames it to original location, replacing the original file. This
method avoids corruption if the write fails, but many file watchers apparently
don't detect the rename as a change to the original file.</p>
<p>The best solution I could find for Claude Code specifically, is to add a
<a href="https://docs.anthropic.com/en/docs/claude-code/hooks">hook</a>, that touches the
file with a change whenever Claude changes it.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.claude/settings.json
</span></div>
<pre data-lang="json" class="language-json z-code"><code class="language-json" data-lang="json"><span class="z-source z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>hooks<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>PostToolUse<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>matcher<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>Write|Edit<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>hooks<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>type<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>command<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>command<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>.claude/hooks/touch-zola-files<span class="z-punctuation z-definition z-string z-end z-json">"</span></span>
</span></span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></code></pre>
</div>
<p>This hook would run every time Claude finishes using the <code>Write</code> or <code>Edit</code>
tools, executing the following <code>.claude/hooks/touch-zola-files</code> script.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
.claude/hooks/touch-zola-files
</span></div>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">!/usr/bin/env bash</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-set z-shell">set</span></span><span class="z-meta z-function-call z-arguments z-shell"> -euo pipefail</span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Trigger Zola's livereload after Claude writes files.</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Zola doesn't detect atomic writes (temp file + rename) and ignores touch</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> if content is unchanged. Appending a Zola comment forces a content change</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> that triggers rebuild but renders as nothing in HTML.</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">file_path</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-meta z-group z-expansion z-command z-parens z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-punctuation z-section z-parens z-begin z-shell">(</span><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">jq</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>r</span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>.tool_input.file_path<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span><span class="z-punctuation z-section z-parens z-end z-shell">)</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-conditional z-if z-shell">if</span> <span class="z-meta z-function-call z-arguments z-shell"><span class="z-support z-function z-double-brace z-begin z-shell">[[</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">file_path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-keyword z-operator z-logical z-shell">=~</span> <span class="z-meta z-regexp z-shell">/(content|templates|sass|static)/</span> <span class="z-support z-function z-double-brace z-end z-shell">]]</span></span> <span class="z-keyword z-operator z-logical z-and z-shell">&&</span> <span class="z-meta z-function-call z-arguments z-shell"><span class="z-support z-function z-double-brace z-begin z-shell">[[</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">file_path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-keyword z-operator z-logical z-shell">=~</span> <span class="z-meta z-regexp z-shell"><span class="z-constant z-character z-escape z-shell">\.</span>(md|html|toml|css|js|scss)$</span> <span class="z-support z-function z-double-brace z-end z-shell">]]</span></span><span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-keyword z-control z-conditional z-then z-shell">then</span>
</span><span class="z-source z-shell z-bash"> <span class="z-variable z-other z-readwrite z-assignment z-shell">token</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-support z-function z-echo z-shell">echo</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">token</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-keyword z-operator z-assignment z-redirection z-shell">>></span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">file_path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">perl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>i</span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>pe</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>s/\Q<span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">token</span></span>\E\n?//g<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span><span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">file_path</span></span><span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-keyword z-control z-conditional z-end z-shell">fi</span>
</span></code></pre>
</div>
<p>So far this method has worked flawlessly, and Zola is picking up changes done by
Claude.</p>
<h2 id="not-cache-busting-out-of-the-box"><a class="zola-anchor" href="#not-cache-busting-out-of-the-box" aria-label="Anchor link for: not cache busting out of the box">#</a>Not cache busting out of the box</h2>
<p>When I deployed the new <a href="/apps">Mac Apps</a> page, and had a look at the live page
at <a href="https://raine.dev/apps">https://raine.dev/apps</a>, the styles were broken.
Hitting force refresh fixed it. That signals mainly one thing, which is that the
browser had cached the old styles and visiting the newly deployed site did not
invalidate that cache. The most common way to fix this kind of problem is to use
asset hashing for site resources such as CSS and JavaScript.</p>
<p>Fortunately, Zola has support for this through the
<a href="https://www.getzola.org/documentation/templates/overview/#get-url">get_url</a>
function's <code>cachebust</code> parameter:</p>
<pre data-lang="diff" class="language-diff z-code"><code class="language-diff" data-lang="diff"><span class="z-source z-diff"><span class="z-markup z-deleted z-diff"><span class="z-punctuation z-definition z-deleted z-diff">-</span> <link rel="stylesheet" href="{{ get_url(path="main.css") }}">
</span></span><span class="z-source z-diff"><span class="z-markup z-deleted z-diff"><span class="z-punctuation z-definition z-deleted z-diff">-</span> <link rel="stylesheet" href="{{ get_url(path="syntax-light.css") }}" id="syntax-light">
</span></span><span class="z-source z-diff"><span class="z-markup z-deleted z-diff"><span class="z-punctuation z-definition z-deleted z-diff">-</span> <link rel="stylesheet" href="{{ get_url(path="syntax-dark.css") }}" id="syntax-dark" disabled>
</span></span><span class="z-source z-diff"><span class="z-markup z-inserted z-diff"><span class="z-punctuation z-definition z-inserted z-diff">+</span> <link rel="stylesheet" href="{{ get_url(path="main.css", cachebust=true) }}">
</span></span><span class="z-source z-diff"><span class="z-markup z-inserted z-diff"><span class="z-punctuation z-definition z-inserted z-diff">+</span> <link rel="stylesheet" href="{{ get_url(path="syntax-light.css", cachebust=true) }}" id="syntax-light">
</span></span><span class="z-source z-diff"><span class="z-markup z-inserted z-diff"><span class="z-punctuation z-definition z-inserted z-diff">+</span> <link rel="stylesheet" href="{{ get_url(path="syntax-dark.css", cachebust=true) }}" id="syntax-dark" disabled>
</span></span></code></pre>
<p>Now instead <code>/main.css</code>, the assets are getting served as for example
<code>/main.css?h=498d9c65a03173df1709</code>, and browsers are guaranteed a fresh copy
after deploying.</p>
Making Keychron K3 Pro behave like Apple Magic Keyboard
2025-12-15T00:00:00+00:00
2025-12-15T00:00:00+00:00
Raine Virta
https://raine.dev/blog/keychron-k3-pro-apple-magic-keyboard/
<p>My keyboard setup lately has been that I use Apple Magic Keyboard for work on
the Mac, and Keychron K3 Pro for gaming on the PC. Just recently, a key fell off
from the Apple keyboard and it had to be sent in for a repair which will likely
take at least 2 weeks. For this reason, I'm now trying to make the Keychron as
similar to the Apple Keyboard as possible to reuse muscle memory. Previously,
I've also used Keychron K3 v2 with Mac, which had needed some
<a href="https://karabiner-elements.pqrs.org/">Karabiner Elements</a> hacks to make it work
the way I want.</p>
<p>Lack of Touch ID aside, the biggest difference between the two is that Keychron
has a <code>fn</code> key where Magic keyboard has an Option key. I have a great many
combinations that I'm used to activating through the Option key, which I quickly
noticed when switching over to K3 as they did not work through the <code>fn</code> key.
Critically, I also noticed that my old Karabiner mappings to make the K3 Pro
work did not work either. The reason is that while K2 v3 sends an actual <code>fn</code>
key when pressing the key, the default behavior in K3 Pro is to switch to
another layer (Momentary Layer, as it's called) which contains mappings related
to the keyboard internals, such as controlling RGB or checking battery level. In
the default Mac mode, this <code>fn</code> also activates the top row to behave as function
keys.</p>
<figure>
<img src="https://raine.dev/blog/keychron-k3-pro-apple-magic-keyboard/keychron-vs-magic-keyboard.avif" alt="Fn key on Keychron K3 Pro next to Option key on Magic Keyboard" loading="lazy">
<figcaption>
The Fn key on K3 Pro sits where the Option key is on Magic Keyboard.
</figcaption>
</figure>
<p>The great thing about K3 Pro however which I've started to appreciate just now
is the QMK support. This allows you to override any key, seemingly in almost any
way possible, by going to the Keychron launcher app at
<a href="https://launcher.keychron.com/">launcher.keychron.com</a>.</p>
<figure>
<img src="https://raine.dev/blog/keychron-k3-pro-apple-magic-keyboard/keychron-launcher-ui.webp" alt="Keychron Launcher web app showing keyboard layout" loading="lazy">
<figcaption>
Keychron Launcher allows deep key remapping via QMK.
</figcaption>
</figure>
<p>An interesting thing the configurator tool revealed about the keyboard is that
the Mac/Windows switch at the back of the keyboard actually just controls the
layer the keyboard is in by default. So even if in your usage you don't switch
between Mac and Windows devices, you could use this toggle to switch between for
example gaming and work setups, assuming you configure the layers appropriately.</p>
<figure>
<img src="https://raine.dev/blog/keychron-k3-pro-apple-magic-keyboard/mac-win-switch.avif" alt="Mac/Windows toggle switch on K3 Pro" loading="lazy">
<figcaption>
The Mac/Windows toggle actually just switches between keyboard layers.
</figcaption>
</figure>
<p>With the power of QMK, solving my problem of <code>fn</code> being in the place of the
Option key is luckily really simple: Change the <code>fn</code> key to send the
<code>ROpt</code>/<code>RALT</code> keycode. This makes it so that it acts as a modifier the exact
same way as the Option key on the Apple keyboard. Also, since I don't use the
right control, I changed it to the original <code>fn</code> behavior of <code>MO(1)</code> (<code>MO</code> for
momentary), which triggers the aforementioned layer switch. This approach is way
better than the one I had with K3 v2, because I don't have to add Karabiner
modifications for each <code>fn</code> modifier using combination separately.</p>
<figure>
<img src="https://raine.dev/blog/keychron-k3-pro-apple-magic-keyboard/launcher-key-remapping.webp" alt="Key remapping configuration showing Fn changed to RALT" loading="lazy">
<figcaption>
Remapping Fn to ROpt and Right Control to MO(1).
</figcaption>
</figure>
<p>With this, aside from the keyboard layout working correctly, one of my favorite
Karabiner modifications also works:</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
~/.config/karabiner/karabiner.json
</span></div>
<pre data-lang="json" class="language-json z-code"><code class="language-json" data-lang="json"><span class="z-source z-json"><span class="z-comment z-line z-double-slash z-js"><span class="z-punctuation z-definition z-comment z-json">//</span> profiles[].complex_modifications.rules[]
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>description<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>Option+[ and Option+] to back and forward<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>manipulators<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>from<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>open_bracket<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>mandatory<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>right_option<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>to<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>tab<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>left_control<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-sequence z-json">,</span> <span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>left_shift<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-punctuation z-section z-sequence z-end z-json">]</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>type<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>basic<span class="z-punctuation z-definition z-string z-end z-json">"</span></span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-sequence z-json">,</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>from<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>close_bracket<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>mandatory<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>right_option<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>to<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>tab<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>left_control<span class="z-punctuation z-definition z-string z-end z-json">"</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-punctuation z-section z-sequence z-end z-json">]</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>type<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>basic<span class="z-punctuation z-definition z-string z-end z-json">"</span></span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></code></pre>
</div>
<p>This mapping allows switching between tabs with <code>Option + [ / ]</code>.</p>
<p>Still, there's another quirk I encountered regarding the keys left of <code>1</code> and
<code>Z</code> respectively, when switching between Keychron and the internal keyboard of
Macbook. Even though both keyboards have ISO physical layouts, they require
different "Virtual Keyboard Type" setting in Karabiner. With the Keychron, I
needed ISO, but with the internal keyboard, I needed ANSI. When the setting was
wrong, these two keys behaved as if swapped.</p>
<p>The solution is to set the virtual keyboard type to ISO (which works for
Keychron), and add Simple Modifications in Karabiner for the Apple keyboards
that swap the two keys:</p>
<ul>
<li><code>grave_accent_and_tilde</code> → <code>non_us_backslash</code></li>
<li><code>non_us_backslash</code> → <code>grave_accent_and_tilde</code></li>
</ul>
<p>This way both keyboards can use the same Karabiner profile with ISO selected as
the keyboard type, and you don't need to swap between two profiles depending on
which keyboard is in use.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
~/.config/karabiner/karabiner.json
</span></div>
<pre data-lang="json" class="language-json z-code"><code class="language-json" data-lang="json"><span class="z-source z-json"><span class="z-comment z-line z-double-slash z-js"><span class="z-punctuation z-definition z-comment z-json">//</span> profiles[].devices[]
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"> <span class="z-comment z-line z-double-slash z-js"><span class="z-punctuation z-definition z-comment z-json">//</span> External Magic Keyboard
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>identifiers<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>is_keyboard<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-constant z-language z-json">true</span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>product_id<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-constant z-numeric z-integer z-decimal z-json">666</span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>vendor_id<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-constant z-numeric z-integer z-decimal z-json">76</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>simple_modifications<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>from<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>to<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-sequence z-json">,</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>from<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>to<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></code></pre>
</div>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
~/.config/karabiner/karabiner.json
</span></div>
<pre data-lang="json" class="language-json z-code"><code class="language-json" data-lang="json"><span class="z-source z-json"><span class="z-comment z-line z-double-slash z-js"><span class="z-punctuation z-definition z-comment z-json">//</span> profiles[].devices[]
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"> <span class="z-comment z-line z-double-slash z-js"><span class="z-punctuation z-definition z-comment z-json">//</span> Internal keyboard
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>identifiers<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>is_keyboard<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-constant z-language z-json">true</span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>simple_modifications<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>from<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>to<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-sequence z-json">,</span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>from<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-separator z-mapping z-pair z-json">,</span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>to<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-punctuation z-section z-sequence z-begin z-json">[</span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-section z-mapping z-begin z-json">{</span> </span><span class="z-meta z-mapping z-key z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">"</span></span></span><span class="z-meta z-mapping z-json"><span class="z-punctuation z-separator z-mapping z-key-value z-json">:</span> </span><span class="z-meta z-mapping z-value z-json"><span class="z-string z-quoted z-double z-json"><span class="z-punctuation z-definition z-string z-begin z-json">"</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">"</span></span> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span><span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"><span class="z-meta z-mapping z-value z-json"> <span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-meta z-sequence z-json"> <span class="z-punctuation z-section z-sequence z-end z-json">]</span></span>
</span></span><span class="z-source z-json"><span class="z-meta z-mapping z-value z-json"><span class="z-punctuation z-section z-mapping z-end z-json">}</span></span>
</span></code></pre>
</div>
<p>In summary, if you're switching between Apple Magic Keyboard and Keychron K3
Pro, remapping the <code>fn</code> key to <code>ROpt</code> via the Keychron Launcher makes the
transition seamless from a muscle memory point of view. Having become familiar
with QMK during this, I'm sure I won't buy another keyboard without it. With
QMK, you can be pretty confident any mapping issue can be solved on firmware
level.</p>
Connecting to a wireless network on Ubuntu Server 21.04 and Raspberry Pi 4
2021-05-22T00:00:00+00:00
2021-05-22T00:00:00+00:00
Raine Virta
https://raine.dev/blog/ubuntu-server-wifi-raspberry-pi/
<p>Problems solved here:</p>
<ul>
<li>Connecting to a wireless network on Ubuntu Server 21.04 and Raspberry Pi 4</li>
<li>Prefer 5 GHz WiFi when 2.4 GHz exists with the same SSID</li>
</ul>
<hr />
<p>Add the wifi network configuration to <code>/etc/netplan/50-cloud-init.yaml</code>. Looks like this file is generated by netplan somehow and there's probably a better way but this persists through restarts so it's good enough for now.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">$</span></span><span class="z-meta z-function-call z-arguments z-shell"> sudo vim /etc/netplan/50-cloud-init.yaml</span>
</span></code></pre>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
/etc/netplan/50-cloud-init.yaml
</span></div>
<pre data-lang="yaml" class="language-yaml z-code"><code class="language-yaml" data-lang="yaml"><span class="z-source z-yaml"><span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">network</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">ethernets</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">eth0</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">dhcp4</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">optional</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">version</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-numeric z-integer z-decimal z-yaml">2</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">wifis</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">wlan0</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">optional</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">dhcp4</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">access-points</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-quoted z-single z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">'</span>{{WIFI NAME}}<span class="z-punctuation z-definition z-string z-end z-yaml">'</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">password</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-quoted z-single z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">'</span>{{WIFI PASSWORD}}<span class="z-punctuation z-definition z-string z-end z-yaml">'</span></span>
</span></code></pre>
</div>
<p>Raspberry Pi should connect to the network after restarting.</p>
<p>Next problem: It is preferring the 2.4 GHz wifi over 5 GHz. Or at least it was for me.</p>
<p>We need to hardcode the basic service set identifier (BSSID) of 5 GHz network in the above configuration.</p>
<p>Find out the BSSID for the 5 GHz network:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Will not return results without sudo</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">$</span></span><span class="z-meta z-function-call z-arguments z-shell"> sudo iwlist wlan0 scan</span>
</span></code></pre>
<p>In the output, look for an entry like this for the 5 Ghz network:</p>
<pre class="z-code"><code><span class="z-text z-plain">Cell 01 - Address: 2C:FD:A1:DD:D3:34
</span><span class="z-text z-plain"> Channel:36
</span><span class="z-text z-plain"> Frequency:5.18 GHz (Channel 36)
</span><span class="z-text z-plain"> Quality=56/70 Signal level=-54 dBm
</span><span class="z-text z-plain"> Encryption key:on
</span><span class="z-text z-plain"> ESSID:"{{WIFI NAME}}"
</span></code></pre>
<p>The thing after <code>Address:</code> is the BSSID.</p>
<p>Add it to <code>/etc/netplan/50-cloud-init.yaml</code> on same indentation level as <code>password</code>.</p>
<div class="codeblock-labeled">
<div class="codeblock-header"><span class="codeblock-file">
<svg class="codeblock-icon" viewBox="4 2 16 20" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
/etc/netplan/50-cloud-init.yaml
</span></div>
<pre data-lang="yaml" class="language-yaml z-code"><code class="language-yaml" data-lang="yaml"><span class="z-source z-yaml"><span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">network</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">ethernets</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">eth0</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">dhcp4</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">optional</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">version</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-numeric z-integer z-decimal z-yaml">2</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">wifis</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">wlan0</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">optional</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">dhcp4</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-constant z-language z-boolean z-yaml">true</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">access-points</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-quoted z-single z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">'</span>{{WIFI NAME}}<span class="z-punctuation z-definition z-string z-end z-yaml">'</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">password</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-quoted z-single z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">'</span>{{WIFI PASSWORD}}<span class="z-punctuation z-definition z-string z-end z-yaml">'</span></span>
</span><span class="z-source z-yaml"> <span class="z-string z-unquoted z-plain z-out z-yaml"><span class="z-entity z-name z-tag z-yaml">bssid</span></span><span class="z-punctuation z-separator z-key-value z-mapping z-yaml">:</span> <span class="z-string z-quoted z-single z-yaml"><span class="z-punctuation z-definition z-string z-begin z-yaml">'</span>2C:FD:A1:DD:D3:34<span class="z-punctuation z-definition z-string z-end z-yaml">'</span></span>
</span></code></pre>
</div>
<p>Done. Restart and Raspberry Pi should connect to the 5 Ghz network.</p>
Connect to Beats headphones from command-line
2018-01-02T00:00:00+00:00
2018-01-02T00:00:00+00:00
Raine Virta
https://raine.dev/blog/connect-beats-headphones-command-line/
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell">!/usr/bin/env bash</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> Requirements:</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash"><span class="z-comment z-line z-number-sign z-shell"><span class="z-punctuation z-definition z-comment z-begin z-shell">#</span></span><span class="z-comment z-line z-number-sign z-shell"> - Add https://github.com/lapfelix/BluetoothConnector/ to $PATH</span><span class="z-comment z-line z-number-sign z-shell">
</span></span><span class="z-source z-shell z-bash">
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">mac</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-meta z-function-call z-shell"></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-meta z-group z-expansion z-command z-backticks z-shell"><span class="z-punctuation z-section z-group z-begin z-shell">`</span><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">BluetoothDeviceConnector</span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">grep</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>Beats Studio³<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">awk</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>{print $1}<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span><span class="z-punctuation z-section z-group z-end z-shell">`</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">BluetoothDeviceConnector</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">mac</span></span></span>
</span></code></pre>
Switch browser tabs with scroll wheel in Mac OS X using Karabiner
2016-01-25T00:00:00+00:00
2016-01-25T00:00:00+00:00
Raine Virta
https://raine.dev/blog/switch-browser-tabs-scroll-wheel-karabiner/
<p>I have a Kensington Slimblade trackball and wanted an accessible way to switch
tabs if my left hand is resting around home row and the right hand is on the
trackball.</p>
<table><thead><tr><th>Hotkey</th><th>Action</th></tr></thead><tbody>
<tr><td><kbd>⌘ Scroll Wheel Up</kbd></td><td>Switch to tab on the left</td></tr>
<tr><td><kbd>⌘ Scroll Wheel Down</kbd></td><td>Switch to tab on the right</td></tr>
</tbody></table>
<p>Here's the configuration that needs to be added in
<a href="https://pqrs.org/osx/karabiner/">Karabiner's</a> <code>private.xml</code>:</p>
<pre data-lang="xml" class="language-xml z-code"><code class="language-xml" data-lang="xml"><span class="z-text z-xml"><span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"><</span><span class="z-entity z-name z-tag z-localname z-xml">item</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"> <span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"><</span><span class="z-entity z-name z-tag z-localname z-xml">name</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>Scroll Wheel to Switch Tabs Back and Forward. (Command+{, Command+})<span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"></</span><span class="z-entity z-name z-tag z-localname z-xml">name</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"> <span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"><</span><span class="z-entity z-name z-tag z-localname z-xml">identifier</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>private.app_google_chrome_scroll_wheel_to_switch_tabs_back_forward<span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"></</span><span class="z-entity z-name z-tag z-localname z-xml">identifier</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"> <span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"><</span><span class="z-entity z-name z-tag z-localname z-xml">only</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>GOOGLE_CHROME<span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"></</span><span class="z-entity z-name z-tag z-localname z-xml">only</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"> <span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"><</span><span class="z-entity z-name z-tag z-localname z-xml">autogen</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"> __ScrollWheelToKey__ ScrollWheel::UP, ModifierFlag::COMMAND_L,
</span><span class="z-text z-xml"> KeyCode::BRACKET_LEFT, ModifierFlag::COMMAND_R | ModifierFlag::SHIFT_R
</span><span class="z-text z-xml"> <span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"></</span><span class="z-entity z-name z-tag z-localname z-xml">autogen</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"> <span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"><</span><span class="z-entity z-name z-tag z-localname z-xml">autogen</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"> __ScrollWheelToKey__ ScrollWheel::DOWN, ModifierFlag::COMMAND_L,
</span><span class="z-text z-xml"> KeyCode::BRACKET_RIGHT, ModifierFlag::COMMAND_R | ModifierFlag::SHIFT_R
</span><span class="z-text z-xml"> <span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"></</span><span class="z-entity z-name z-tag z-localname z-xml">autogen</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span><span class="z-text z-xml"><span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml"></</span><span class="z-entity z-name z-tag z-localname z-xml">item</span><span class="z-punctuation z-definition z-tag z-end z-xml">></span></span>
</span></code></pre>
Tutorial to ramda-cli, a jq-like JSON processor for command-line
2015-09-27T00:00:00+00:00
2015-09-27T00:00:00+00:00
Raine Virta
https://raine.dev/blog/ramda-cli-tutorial/
<p>In this tutorial we'll use ramda-cli with <a href="https://developer.github.com/v3/repos/#list-user-repositories">GitHub's Repos API</a> to
get a list of <a href="https://github.com/jeresig"><strong>@jeresig's</strong></a> most starred repos.</p>
<p><a href="https://github.com/raine/ramda-cli">ramda-cli</a> is a command-line tool for processing JSON using
functional pipelines. As the name suggests, its utility comes from
<a href="https://github.com/ramda/ramda">Ramda</a> and <a href="http://ramdajs.com/docs">the wide array of functions</a> it provides for
operating on lists and collections of objects. It also employs
<a href="http://livescript.net">LiveScript</a> for its terse and powerful syntax.</p>
<p>On the way, there's a gentle introduction to some functional programming
concepts such as currying and function composition. We'll build a pipeline of
functions that takes a list of repos and returns the ten most starred repos in
descending order as a list of <code>{name, stargazers_count}</code> objects. Finally, we'll
print the result as table.</p>
<p>Copy-pasting this into a shell session will make all the examples runnable:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">npm</span></span><span class="z-meta z-function-call z-arguments z-shell"> install<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>g</span> ramda-cli</span>
</span><span class="z-source z-shell z-bash"><span class="z-variable z-other z-readwrite z-assignment z-shell">url</span><span class="z-keyword z-operator z-assignment z-shell">=</span><span class="z-string z-unquoted z-shell">https://api.github.com/users/jeresig/repos<span class="z-constant z-character z-escape z-shell">\?</span>per_page<span class="z-constant z-character z-escape z-shell">\=</span>100</span>
</span></code></pre>
<hr />
<h3 id="fetch-the-data-with-curl"><a class="zola-anchor" href="#fetch-the-data-with-curl" aria-label="Anchor link for: fetch the data with curl">#</a>fetch the data with curl</h3>
<p>Let's first use <code>curl</code> to get the list of repos in JSON format and pipe it to
<code>R identity -p</code> to get an idea of what we're working with.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> https://api.github.com/users/jeresig/repos<span class="z-constant z-character z-escape z-shell">\?</span>per_page<span class="z-constant z-character z-escape z-shell">\=</span>100</span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"> identity<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span></span>
</span></code></pre>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">id</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">3549786</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">full_name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>jeresig/apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></span></span></code></pre>
<p>As in programming, in ramda-cli data is manipulated by applying a function to
the data. The result will by default be written to standard output in JSON
format.</p>
<p>Since <a href="http://ramdajs.com/docs/#identity"><code>identity</code></a> stands for a function that
simply returns its argument, our command will pipe the JSON payload unchanged
through to stdout in a more readable (<code>-p</code> is for pretty) format.</p>
<blockquote>
<p><strong>Reader exercise:</strong> Replace <code>identity</code> with a function that would return the
number of repos.</p>
</blockquote>
<h3 id="pluck-the-names"><a class="zola-anchor" href="#pluck-the-names" aria-label="Anchor link for: pluck the names">#</a>pluck the names</h3>
<p><strong>@jeresig</strong> has a lot of repos and the API returns a ton of info we don't care
about, so we'll go ahead and see how the output could be reduced to just a list
of the names of those repos.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>pluck \name<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span></span>
</span></code></pre>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>babel<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>brooklynjs.github.io<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>casperjs<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></code></pre>
<p>For those unfamiliar with Ramda's curried API or LiveScript, this will require
some explaining.</p>
<p>In LiveScript, as in CoffeeScript, parentheses are optional when calling a
function. Backslash preceding a word is sugar for string. Therefore,
<code>pluck \name</code> compiles into <code>pluck('name')</code> in JavaScript.</p>
<blockquote>
<p><code>pluck :: String -> {*} -> [*]</code> Returns a new list by plucking the same named
property off all objects in the list supplied.</p>
</blockquote>
<p><a href="http://ramdajs.com/docs/#pluck"><code>pluck</code></a> is a function that for a given key and
a list of objects, returns a list of values corresponding to that key from all
the objects in the list. Ramda's functions are all <a href="http://fr.umio.us/favoring-curry/">by design
curried</a>, so we can <em>partially apply</em> <code>pluck</code> with just the key
we want, <code>'name'</code>, and thus get back a function that will be waiting for the
second argument, a list of objects.</p>
<p>Since curl gives us a <em>list</em> of repos, it's a great match for a function that is
waiting for a list objects to get the properties from.</p>
<h3 id="filter-out-forks"><a class="zola-anchor" href="#filter-out-forks" aria-label="Anchor link for: filter out forks">#</a>filter out forks</h3>
<p>Looks like the output contains repos that are forks. Not a big deal but for the
sake of example we could get just the repos that are originally by <strong>@jeresig</strong>.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>pluck \name<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span></span>
</span></code></pre>
<p>Here, <a href="http://ramdajs.com/docs/#whereEq"><code>where-eq</code></a> set up a with a spec object
(<code>{ fork: true }</code>) creates a predicate function to be used with
<a href="http://ramdajs.com/docs/#filter"><code>filter</code></a>. <code>filter</code> is now waiting for the
second argument that curl will provide, a list of repos to filter.</p>
<p>Notice that two independent pieces of code are now passed to <code>R</code>. What happens
here is our program is still evaluates into a single function, but it's composed
under the hood by ramda-cli from the given functions in order from left to
right. Therefore, what we just did is equivalent to explicitly using
<a href="http://ramdajs.com/docs/#pipe"><code>R.pipe</code></a> for function composition:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>pipe( filter(where-eq({ fork: false })), pluck("name") )<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span></code></pre>
<p>The list is first filtered, then <code>name</code> property is plucked from each object. In
this way we can build a pipeline of operations to be applied on our data in a
specific order.</p>
<h3 id="get-the-stars-also"><a class="zola-anchor" href="#get-the-stars-also" aria-label="Anchor link for: get the stars also">#</a>get the stars also</h3>
<p>Now that we have a list of repo names that are not forks, we can make the output
more interesting by grabbing also the number of stars.</p>
<p>Instead of <code>pluck</code> we need an operation that picks specific fields from a list
of objects. In Ramda, there's a function called
<a href="http://ramdajs.com/docs/#project"><code>project</code></a> for just that.</p>
<blockquote>
<p><code>project :: [k] → [{k: v}] → [{k: v}]</code> Reasonable analog to SQL select
statement.</p>
</blockquote>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span></code></pre>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>datacook<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">2</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>deepleap<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>dromaeo<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">63</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></code></pre>
<h3 id="sort-by-stars"><a class="zola-anchor" href="#sort-by-stars" aria-label="Anchor link for: sort by stars">#</a>sort by stars</h3>
<p>Before we can make the output more visually appealing, we have a few steps to
add in our pipeline. First, sorting by <code>stargazers_count</code> in descending order.</p>
<blockquote>
<p><code>sortBy :: (a → String) → [a] → [a]</code> Sorts the list according to a key
generated by the supplied function.</p>
</blockquote>
<p><a href="http://ramdajs.com/docs/#sortBy"><code>sortBy</code></a> together with
<a href="http://ramdajs.com/docs/#prop"><code>prop</code></a> sorts a list of objects according to the
field given to <code>prop</code>.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>sort-by prop \stargazers_count<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span></code></pre>
<p>Finally, we apply <a href="http://ramdajs.com/docs/#reverse"><code>reverse</code></a> to get the most
starred projects first and limit the list to first 10 items with
<a href="http://ramdajs.com/docs/#take"><code>take</code></a>.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>p</span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>sort-by prop \stargazers_count<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> reverse <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>take 10<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span></code></pre>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>processing-js<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1682</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>node-stream-playground<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">311</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>fireunit<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">228</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>env-js<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">stargazers_count</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">205</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></code></pre>
<h3 id="render-as-table"><a class="zola-anchor" href="#render-as-table" aria-label="Anchor link for: render as table">#</a>render as table</h3>
<p>Good, now that the data is getting transformed into a shape that has the info we
want, it can be presented in a more readable format.</p>
<p>Using the <code>--output-type table</code> option, a list of objects may be printed as a
table in such way that the objects' keys become the table headers. It's
convenient because all we need is an uniform list of objects to get a pretty
table. So we'll do just that. Remove the <code>-p</code> flag from before and slap
<code>-o table</code> at the end.</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>sort-by prop \stargazers_count<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"> reverse <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>take 15<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-punctuation z-separator z-continuation z-line z-shell">\
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> table</span>
</span></code></pre>
<pre class="z-code"><code><span class="z-text z-plain">┌────────────────────────┬──────────────────┐
</span><span class="z-text z-plain">│ name │ stargazers_count │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ processing-js │ 1684 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ node-stream-playground │ 311 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ fireunit │ 228 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ env-js │ 205 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ trie-js │ 172 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ pulley │ 171 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ retweet │ 72 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ dromaeo │ 63 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ stack-scraper │ 46 │
</span><span class="z-text z-plain">├────────────────────────┼──────────────────┤
</span><span class="z-text z-plain">│ jquery-workshop │ 38 │
</span><span class="z-text z-plain">└────────────────────────┴──────────────────┘
</span></code></pre>
<blockquote>
<p><strong>Reader exercise:</strong> Add a URL column so that the projects can be viewed in
browser.</p>
</blockquote>
<hr />
<p>This concludes the tutorial. If you're new to Ramda and want to learn more,
check out the <a href="https://github.com/ramda/ramda/wiki#articles">list of articles</a> in the wiki. For
ramda-cli, <a href="https://github.com/raine/ramda-cli">the README</a> provides helpful information and examples.</p>
<p>Thanks to <a href="https://github.com/buzzdecafe">buzzdecafe</a> for providing feedback on
this article.</p>
<hr />
<h2 id="bonus-section-run-from-a-file"><a class="zola-anchor" href="#bonus-section-run-from-a-file" aria-label="Anchor link for: bonus section run from a file">#</a>bonus section: run from a file</h2>
<p>As the pipeline grows, it becomes increasingly more manageable option to write
the pipeline in a separate script file. For this, ramda-cli provides the
<code>--file</code> option:</p>
<pre class="z-code"><code><span class="z-text z-plain">-f, --file String read a function from a js/ls file instead of args; useful for
</span><span class="z-text z-plain"> larger scripts
</span></code></pre>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-comment z-line z-double-slash z-ts"><span class="z-punctuation z-definition z-comment z-ts">//</span></span><span class="z-comment z-line z-double-slash z-ts"> most-starred.js</span>
</span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-storage z-type z-ts">var</span> <span class="z-meta z-var-single-variable z-expr z-ts"><span class="z-meta z-definition z-variable z-ts"><span class="z-variable z-other z-constant z-ts">R</span></span> </span><span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-meta z-function-call z-ts"><span class="z-support z-function z-ts">require</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>ramda<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-meta z-brace z-round z-ts">)</span></span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-storage z-type z-ts">var</span> <span class="z-meta z-var-single-variable z-expr z-ts"><span class="z-meta z-definition z-variable z-ts"><span class="z-variable z-other z-readwrite z-ts">isNotFork</span></span> </span><span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-entity z-name z-function z-ts">whereEq</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">fork</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-language z-boolean z-false z-ts">false</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-meta z-brace z-round z-ts">)</span></span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span><span class="z-source z-ts">
</span><span class="z-source z-ts"><span class="z-support z-type z-object z-module z-ts">module</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-support z-type z-object z-module z-ts">exports</span> <span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-entity z-name z-function z-ts">pipe</span></span><span class="z-meta z-brace z-round z-ts">(</span>
</span><span class="z-source z-ts"> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-entity z-name z-function z-ts">filter</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-variable z-other z-readwrite z-ts">isNotFork</span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span><span class="z-source z-ts"> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-entity z-name z-function z-ts">project</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>name<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>stargazers_count<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-meta z-brace z-square z-ts">]</span></span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span><span class="z-source z-ts"> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-entity z-name z-function z-ts">sortBy</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-entity z-name z-function z-ts">prop</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>stargazers_count<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span><span class="z-source z-ts"> <span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-variable z-other z-property z-ts">reverse</span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span><span class="z-source z-ts"> <span class="z-meta z-function-call z-ts"><span class="z-variable z-other z-constant z-object z-ts">R</span><span class="z-punctuation z-accessor z-ts">.</span><span class="z-entity z-name z-function z-ts">take</span></span><span class="z-meta z-brace z-round z-ts">(</span><span class="z-constant z-numeric z-decimal z-ts">10</span><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span><span class="z-source z-ts"><span class="z-meta z-brace z-round z-ts">)</span><span class="z-punctuation z-terminator z-statement z-ts">;</span>
</span></code></pre>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">curl</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>s</span> <span class="z-meta z-group z-expansion z-parameter z-shell"><span class="z-punctuation z-definition z-variable z-shell">$</span><span class="z-variable z-other z-readwrite z-shell">url</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>f</span> most-starred.js<span class="z-variable z-parameter z-option z-shell"><span class="z-punctuation z-definition z-parameter z-shell"> -</span>o</span> table</span>
</span></code></pre>
<h2 id="further-reading"><a class="zola-anchor" href="#further-reading" aria-label="Anchor link for: further reading">#</a>further reading</h2>
<ul>
<li><a href="https://github.com/raine/ramda-cli">ramda-cli on GitHub</a></li>
<li><a href="https://github.com/ramda/ramda/wiki#articles">Articles on Ramda wiki</a></li>
<li><a href="https://raine.dev/blog/essential-livescript-for-ramda-cli/">Essential LiveScript for ramda-cli</a></li>
<li><a href="http://livescript.net/blog/functional-programming-in-javascript-using-livescript-and-prelude-ls.html">Functional Programming in JavaScript using LiveScript and prelude.ls</a></li>
<li><a href="http://learnxinyminutes.com/docs/livescript/">Learn LiveScript in Y Minutes</a></li>
</ul>
Essential LiveScript for ramda-cli
2015-09-22T00:00:00+00:00
2015-09-22T00:00:00+00:00
Raine Virta
https://raine.dev/blog/essential-livescript-for-ramda-cli/
<p><a href="http://livescript.net">LiveScript</a> is a language that compiles into JavaScript. It allows
for writing code with minimal boilerplate and is especially suitable for
functional style.</p>
<p>With <a href="https://github.com/raine/ramda-cli">ramda-cli</a>, snippets of code are written directly on the
command line:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">cat</span></span><span class="z-meta z-function-call z-arguments z-shell"> people.json</span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>filter (.name is /bob/i)<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>pluck \name<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-support z-function z-test z-begin z-shell">[</span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>Bobbie Huel<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> , <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>Bobby Ryan<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span>, <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>Dr. Bobby Schiller<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span> <span class="z-support z-function z-test z-end z-shell">]</span></span>
</span></code></pre>
<p>For this reason, LiveScript interface makes sense.</p>
<p>In this document I'll quickly go through the most practical features from the
perspective of ramda-cli.</p>
<h2 id="invoking-a-function"><a class="zola-anchor" href="#invoking-a-function" aria-label="Anchor link for: invoking a function">#</a>Invoking a function</h2>
<p>Functions can be called without parentheses if passing arguments.</p>
<p>Nested calls wrap to the end of line as such:</p>
<p><code>console.log to-upper \foo</code> → <code>console.log(toUpper('foo'))</code></p>
<p>Without arguments, use a bang call: <code>do-stuff!</code></p>
<h2 id="basic-syntactic-sugar"><a class="zola-anchor" href="#basic-syntactic-sugar" aria-label="Anchor link for: basic syntactic sugar">#</a>Basic syntactic sugar</h2>
<p>Backslash string sugar is ideal for short identifiers:</p>
<p><code>\foo</code> → <code>'foo'</code></p>
<p><code><[ foo bar xyz ]></code> → <code>[ 'foo', 'bar', 'xyz' ]</code></p>
<p>String interpolation with hash inside double quotes:</p>
<p><code>"#first #last"</code> → <code>first + " " + last</code></p>
<p><code>"#{obj.foo} #{obj.bar}"</code> → <code>obj.foo + " " + obj.bar</code></p>
<p>Properties can be accessed without brackets:</p>
<p><code>arr.0</code> → <code>arr[0]</code></p>
<p><code>obj.'foo'</code> → <code>obj.foo</code></p>
<h2 id="objects"><a class="zola-anchor" href="#objects" aria-label="Anchor link for: objects">#</a>Objects</h2>
<p>Braces are optional.</p>
<p><code>console.log milk: 1, cheese: 2</code> → <code>console.log({ milk: 1, cheese: 2 })</code></p>
<h4 id="example"><a class="zola-anchor" href="#example" aria-label="Anchor link for: example">#</a>Example</h4>
<pre class="z-code"><code><span class="z-text z-plain"># :: {k: v} → {k: v}
</span><span class="z-text z-plain">inc-count = evolve count: add 1 # evolve({ count: add(1) })
</span><span class="z-text z-plain">inc-count count: 1 # => { count: 2 }
</span></code></pre>
<blockquote>
<p><a href="http://ramdajs.com/docs/#evolve"><code>evolve :: {k: (v -> v)} -> {k: v} -> {k: v}</code></a></p>
</blockquote>
<h2 id="defining-a-function"><a class="zola-anchor" href="#defining-a-function" aria-label="Anchor link for: defining a function">#</a>Defining a function</h2>
<p><code>identity = (x) -> x</code> → <code>var identity = function(x) { return x }</code></p>
<p>A function implicitly returns the last statement.</p>
<p><code>(str) -> str.to-upper-case!</code> → <code>function(str) { return str.toUpperCase() }</code></p>
<p>You can omit the argument list and refer to the first argument as <code>it</code>.</p>
<p><code>filter -> it % 2 is 0</code> → <code>filter(function(it) { return it % 2 === 0 })</code></p>
<h4 id="example-1"><a class="zola-anchor" href="#example-1" aria-label="Anchor link for: example 1">#</a>Example</h4>
<pre class="z-code"><code><span class="z-text z-plain">add = (x, y) -> x + y # var add = function(x, y) { return x + y }
</span><span class="z-text z-plain">add 1 2 # => 3
</span></code></pre>
<h2 id="access-and-function-call-shorthand"><a class="zola-anchor" href="#access-and-function-call-shorthand" aria-label="Anchor link for: access and function call shorthand">#</a>Access and function call shorthand</h2>
<p><code>(.length)</code> → <code>(it) -> it.length</code> → <code>function(it) { return it.length }</code></p>
<h4 id="examples"><a class="zola-anchor" href="#examples" aria-label="Anchor link for: examples">#</a>Examples</h4>
<pre class="z-code"><code><span class="z-text z-plain">map (.length), <[ expire syncrisis reindicating ]> # => [ 6, 9, 12 ]
</span><span class="z-text z-plain">find (.0 is \f), <[ foo bar xyz ]> # => 'foo'
</span></code></pre>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-support z-function z-echo z-shell">echo</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>{"hello": "world"}<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span> <span class="z-keyword z-operator z-logical z-pipe z-shell">|</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">R</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>.hello<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span></span>
</span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell"><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">"</span>world<span class="z-punctuation z-definition z-string z-end z-shell">"</span></span></span></span>
</span></code></pre>
<p>The above works because <code>'.hello'</code> evaluates like this:</p>
<pre data-lang="sh" class="language-sh z-code"><code class="language-sh" data-lang="sh"><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">ramda-cli:main</span></span><span class="z-meta z-function-call z-arguments z-shell"> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">'</span>(.hello)<span class="z-punctuation z-definition z-string z-end z-shell">'</span></span> +1ms input code</span>
</span><span class="z-source z-shell z-bash"> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">ramda-cli:main</span></span>
</span><span class="z-source z-shell z-bash"><span class="z-punctuation z-definition z-compound z-begin z-shell">(</span><span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">function</span></span><span class="z-meta z-function-call z-arguments z-shell">(it</span><span class="z-punctuation z-definition z-compound z-end z-shell">)</span><span class="z-meta z-function-call z-arguments z-shell"><span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-begin z-shell">{</span>
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-meta z-group z-expansion z-brace z-shell"> return it.hello;
</span></span></span><span class="z-source z-shell z-bash"><span class="z-meta z-function-call z-arguments z-shell"><span class="z-meta z-group z-expansion z-brace z-shell"><span class="z-punctuation z-section z-expansion z-brace z-end z-shell">}</span></span></span><span class="z-meta z-function-call z-shell"></span>)<span class="z-keyword z-operator z-logical z-continue z-shell">;</span> <span class="z-meta z-function-call z-shell"><span class="z-variable z-function z-shell">+29ms</span></span><span class="z-meta z-function-call z-arguments z-shell"> compiled code</span>
</span></code></pre>
<p>Normally you would need to wrap it into parentheses like <code>(.hello)</code>, however,
ramda-cli wraps every function argument to parentheses automatically. This is a
case where using a LS idiom is superior to using a Ramda function. To get a
property value of an object, Ramda provides <code>prop('hello')</code>.</p>
<h2 id="interlude"><a class="zola-anchor" href="#interlude" aria-label="Anchor link for: interlude">#</a>Interlude</h2>
<p>These all do the same thing, with decreasing level of verbosity.</p>
<pre class="z-code"><code><span class="z-text z-plain">list = [\foo, \hello, \bar]
</span><span class="z-text z-plain">list.filter (str) -> str.length is 5
</span><span class="z-text z-plain">list.filter -> it.length is 5
</span><span class="z-text z-plain">list.filter (.length is 5)
</span><span class="z-text z-plain"># => [ 'hello' ]
</span></code></pre>
<h2 id="sections"><a class="zola-anchor" href="#sections" aria-label="Anchor link for: sections">#</a>Sections</h2>
<p>You can use operators as functions.</p>
<pre class="z-code"><code><span class="z-text z-plain">map (+ '!') <[ foo bar ]> # => [ 'foo!', 'bar!' ]
</span><span class="z-text z-plain">reduce (+), 0, [1, 2, 3] # => 6
</span><span class="z-text z-plain">filter (!= null), [1, null, 2] # => [ 1, 2 ]
</span></code></pre>
<h2 id="function-composition-operators"><a class="zola-anchor" href="#function-composition-operators" aria-label="Anchor link for: function composition operators">#</a>Function composition operators</h2>
<ul>
<li><code>(f << g) x</code> is <code>compose(f, g)(x)</code> is <code>f(g(x))</code></li>
<li><code>(f >> g) x</code> is <code>pipe(f, g)(x)</code> is <code>g(f(x))</code></li>
<li>The <code>f . g</code> behaves the same way as <code><<</code>.</li>
</ul>
<h4 id="example-2"><a class="zola-anchor" href="#example-2" aria-label="Anchor link for: example 2">#</a>Example</h4>
<pre class="z-code"><code><span class="z-text z-plain">fn = map head >> inc # map(pipe(head, toUpper))
</span><span class="z-text z-plain">fn [[1, 2], [3, 4]] # => [ 2, 4 ]
</span></code></pre>
<h1 id="further-reading"><a class="zola-anchor" href="#further-reading" aria-label="Anchor link for: further reading">#</a>Further reading</h1>
<p>There's a lot more to LiveScript, but this will get you started with
<a href="https://github.com/raine/ramda-cli">ramda-cli</a>. For more, check out these articles. You might wonder why
settle with transpiling ES6.</p>
<ul>
<li><a href="http://livescript.net/blog/functional-programming-in-javascript-using-livescript-and-prelude-ls.html">Functional Programming in JavaScript using LiveScript and prelude.ls</a></li>
<li><a href="http://gkz.github.io/LiveScript/blog/functional-programming-in-javascript-using-livescript-part-2.html">Functional Programming in JavaScript using LiveScript - Part 2</a></li>
<li><a href="http://learnxinyminutes.com/docs/livescript/">Learn LiveScript in Y Minutes</a></li>
<li><a href="http://livescript.net/blog/ten-reasons-to-switch-from-coffeescript.html">10 Reasons to Switch from CoffeeScript to LiveScript</a></li>
</ul>
<p>See also:</p>
<ul>
<li><a href="https://raine.dev/blog/ramda-cli-tutorial/">Tutorial to ramda-cli, a jq-like JSON processor for command-line</a></li>
<li><a href="https://github.com/raine/ramda-cli/wiki#ramda-vs-livescript">Ramda vs. LiveScript</a></li>
</ul>
Parsing and rendering tori.fi categories with Acorn, archy and Ramda
2015-03-20T00:00:00+00:00
2015-03-20T00:00:00+00:00
Raine Virta
https://raine.dev/blog/parsing-tori-categories/
<p><a href="http://tori.fi">Tori.fi</a>, a Finnish online marketplace based on
<a href="http://www.blocket.se/">blocket.se</a>, has a category selection input that
dynamically reveals sub-categories as user selects them.</p>
<p>It works like this:</p>
<p><img src="https://raine.dev/blog/parsing-tori-categories/category-widget.gif" alt="" /></p>
<p>I was curious about the data structure beneath this widget and how it would look
like if the categories were rendered into a tree-like format.</p>
<p>The source lies in the file <a href="http://static.tori.net/templates/common/arrays_v2.js"><code>arrays_v2.js</code></a> <a href="https://gist.github.com/anonymous/56afd6c1b8f6a9614c1d#file-arrays_v2-js-L18">[gist]</a> and
contains all kinds of variables to localize and configure the site, including
the <code>category_list</code> object that is hooked up to the category selection. The
object follows a schema where the keys identify categories by their numeric ids,
and the values are metadata for their respective category, for example the
parent's id.</p>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-storage z-type z-ts">var</span> <span class="z-meta z-var-single-variable z-expr z-ts"><span class="z-meta z-definition z-variable z-ts"><span class="z-variable z-other z-readwrite z-ts">category_list</span></span> </span><span class="z-keyword z-operator z-assignment z-ts">=</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-constant z-numeric z-decimal z-ts">1000</span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>name<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>ASUNNOT JA TONTIT<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>name_en<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>level<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">0</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-constant z-numeric z-decimal z-ts">1010</span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>name<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Asunnot<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>name_en<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Apartments & Houses<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>level<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>parent<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1000</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>leaf<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></span></span><span class="z-source z-ts"><span class="z-meta z-var z-expr z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"></span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span>
</span></code></pre>
<p>After a quick research, I found the tools to accomplish this task:</p>
<ul>
<li><a href="https://github.com/marijnh/acorn">Acorn</a>, for parsing JS into an abstract syntax tree</li>
<li><a href="https://github.com/substack/node-archy">archy</a>, a node library by substack for rendering nested hierachies</li>
</ul>
<p>Also, we'll be using <a href="http://livescript.net">LiveScript</a> and <a href="http://ramdajs.com">Ramda</a> as base tools
in this endeavor.</p>
<p>Now the problem could be split into two parts:</p>
<ol>
<li>Parsing the code with acorn and finding the <code>category_list</code> node in the AST
output</li>
<li>Transforming the output into something that archy understands</li>
</ol>
<p>Let's tackle the parsing problem first.</p>
<h3 id="parsing"><a class="zola-anchor" href="#parsing" aria-label="Anchor link for: parsing">#</a>Parsing</h3>
<p>Parsing the file into AST with acorn is simple:</p>
<pre class="z-code"><code><span class="z-text z-plain">ast = acorn.parse fs.read-file-sync 'arrays_v2.js'
</span></code></pre>
<p>A bit more trouble is navigating the
<a href="https://gist.githubusercontent.com/anonymous/a2be7b5e2cb83533260b/raw/ast.json">massive AST output</a>
acorn produces for this file. Luckily, in its <code>dist/walk</code> module acorn provides
a utility function called <code>findNodeAt</code> that we can use.</p>
<pre class="z-code"><code><span class="z-text z-plain">findNodeAt(node, start, end, test, base, state)
</span></code></pre>
<p>It takes a <code>node</code>, i.e. our <code>ast</code> from before, and a predicate function <code>test</code>
that determines if a node is of interest. The rest of the arguments can be used
for more elaborate searches but are not needed in this case.</p>
<p>Let's write a function <code>find-category-list-props</code> that takes an <code>ast</code> and
returns the properties of the <code>category_list</code> variable.</p>
<pre class="z-code"><code><span class="z-text z-plain">find-category-list-props = (ast) ->
</span><span class="z-text z-plain"> walk.find-node-at ast, null, null, is-category-list
</span><span class="z-text z-plain"> |> (.node.declarations.0.init.properties)
</span></code></pre>
<p>The predicate for matching the <code>category_list</code> variable is as follows:</p>
<pre class="z-code"><code><span class="z-text z-plain">is-category-list = (node-type, node) ->
</span><span class="z-text z-plain"> node.kind is \var and
</span><span class="z-text z-plain"> node.declarations?.0.id.name is \category_list
</span></code></pre>
<p>Essentially, it walks through the AST, looking for a variable definition with
the name <code>category_list</code>. Finally, it picks the first declaration of the <code>var</code>
and its properties.</p>
<p>The output is list of objects that looks like this:</p>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32300</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">key</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span>
</span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32300</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1000</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">raw </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>1000<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">type </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">end </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32304</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span>
</span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32307</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">properties</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32310</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">key </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32310</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>name<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">type</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">end</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32316</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32319</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>ASUNNOT JA TONTIT<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">type</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">end</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32338</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">kind </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>init<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">type </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Property<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">end </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32338</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32341</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">key </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32341</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>name_en<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">type</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">end</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32350</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value </span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">start</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32353</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">value</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">type</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">end</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">32359</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></span></span></span></span></span></span></span></code></pre>
<p>Cool. We got the data, but as it is, it's not yet very easy to work with. Next,
we'll come up with an operation that will transform above into something more
manageable, a simple list of objects:</p>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">id</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1000</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>ASUNNOT JA TONTIT<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name_en</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">level</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">0</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">id</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1010</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Asunnot<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">name_en</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Apartments & Houses<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">level</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">parent</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1000</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">leaf</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-constant z-numeric z-decimal z-ts">1</span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></code></pre>
<pre class="z-code"><code><span class="z-text z-plain"># :: Object → Object
</span><span class="z-text z-plain">prop-to-obj = -> (it.key.value): it.value.value
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"># :: [Object] → Object
</span><span class="z-text z-plain">props-to-obj = (R.map prop-to-obj) >> R.merge-all
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"># :: [Object] → [Object]
</span><span class="z-text z-plain">get-simple-props = map ->
</span><span class="z-text z-plain"> props = props-to-obj it.value.properties
</span><span class="z-text z-plain"> R.merge id: it.key.value, props
</span></code></pre>
<p>Using <a href="https://github.com/raine/treis"><code>treis</code></a>, we may illustrate what each of these functions does when
<code>get-simple-props</code> is given a single object in a list as input.</p>
<img src="treis-diagram.png" width="513" height="402">
<p>We are done with the first part of the problem. The set of operations we've
built can be expressed as <code>parse</code> function composed of all the steps:</p>
<pre class="z-code"><code><span class="z-text z-plain"># :: FilePath -> [Object]
</span><span class="z-text z-plain">parse = R.pipe do
</span><span class="z-text z-plain"> fs.read-file-sync
</span><span class="z-text z-plain"> acorn.parse
</span><span class="z-text z-plain"> find-category-list-props
</span><span class="z-text z-plain"> get-simple-props
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">render = ???
</span></code></pre>
<h3 id="rendering"><a class="zola-anchor" href="#rendering" aria-label="Anchor link for: rendering">#</a>Rendering</h3>
<p><a href="https://github.com/substack/node-archy"><code>archy</code></a> is a library for printing nested hierarchies with unicode
pipes. You might be familiar with this style of output from <code>npm</code>.</p>
<blockquote>
<p><code>archy(obj, prefix='', opts={})</code></p>
</blockquote>
<p>As input, archy takes a tree-like structure of
<code>{ label: String, nodes: [Object] }</code></p>
<pre class="z-code"><code><span class="z-text z-plain">archy do
</span><span class="z-text z-plain"> label: \foo
</span><span class="z-text z-plain"> nodes: [
</span><span class="z-text z-plain"> label: \bar
</span><span class="z-text z-plain"> nodes: [
</span><span class="z-text z-plain"> label: \xyz
</span><span class="z-text z-plain"> nodes: []
</span><span class="z-text z-plain"> ]
</span><span class="z-text z-plain"> ]
</span></code></pre>
<pre class="z-code"><code><span class="z-text z-plain">foo
</span><span class="z-text z-plain">└─┬ bar
</span><span class="z-text z-plain"> └── xyz
</span></code></pre>
<p>We need something to make our flat list of objects a tree that adheres to
archy's API. The function will be called <code>category-list-to-tree</code>.</p>
<pre class="z-code"><code><span class="z-text z-plain">category-list-to-tree = (list) ->
</span><span class="z-text z-plain"> root = 'CATEGORIES'
</span><span class="z-text z-plain"> recurse-category = (cat) ->
</span><span class="z-text z-plain"> label: cat?.'name_en' or root
</span><span class="z-text z-plain"> nodes: R.map recurse-category, R.filter do
</span><span class="z-text z-plain"> R.where-eq do
</span><span class="z-text z-plain"> level : cat?.level + 1 or 0
</span><span class="z-text z-plain"> parent : cat?.id or void
</span><span class="z-text z-plain"> , list
</span><span class="z-text z-plain"> recurse-category null
</span></code></pre>
<p>The function recursively builds a tree. The first call to <code>recurse-category</code>
with <code>null</code> as <code>cat</code> simply looks for root level (<code>level: 0</code>) categories without
a parent in the list. Next, for each of those we do the same operation and now
instead look for categories of the respective category's level incremented by 1
and its id as <code>parent</code>.</p>
<p>The output looks like:</p>
<pre data-lang="js" class="language-js z-code"><code class="language-js" data-lang="js"><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-entity z-name z-label z-ts">label</span><span class="z-punctuation z-separator z-label z-ts">:</span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>CATEGORIES<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"> <span class="z-entity z-name z-label z-ts">nodes</span><span class="z-punctuation z-separator z-label z-ts">:</span>
</span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Apartments & Houses<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Holidayhouses<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Land & agriculture<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Garage and storage room<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Overseas apartments<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span> <span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>VEHICLES<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span>
</span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Cars<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Car parts & accessories<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-support z-class z-builtin z-ts">Object</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Caravans<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Caravans accessories<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Moto<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-support z-class z-builtin z-ts">Object</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Moto parts & accessories<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-support z-class z-builtin z-ts">Object</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Work machinery and equipment<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-support z-class z-builtin z-ts">Object</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Boats<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-support z-class z-builtin z-ts">Object</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span></span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"><span class="z-meta z-objectliteral z-ts"><span class="z-meta z-object z-member z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-objectliteral z-ts"><span class="z-punctuation z-definition z-block z-ts">{</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">label</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span> <span class="z-string z-quoted z-single z-ts"><span class="z-punctuation z-definition z-string z-begin z-ts">'</span>Boatparts<span class="z-punctuation z-definition z-string z-end z-ts">'</span></span></span><span class="z-punctuation z-separator z-comma z-ts">,</span> <span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts">nodes</span></span><span class="z-meta z-object z-member z-ts"><span class="z-meta z-object-literal z-key z-ts"><span class="z-punctuation z-separator z-key-value z-ts">:</span></span><span class="z-meta z-array z-literal z-ts"> <span class="z-meta z-brace z-square z-ts">[</span><span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span> <span class="z-meta z-brace z-square z-ts">]</span></span> </span><span class="z-punctuation z-definition z-block z-ts">}</span></span><span class="z-punctuation z-separator z-comma z-ts">,</span>
</span></span></span><span class="z-source z-ts"><span class="z-meta z-block z-ts"><span class="z-meta z-array z-literal z-ts"> <span class="z-keyword z-operator z-spread z-ts">...</span>
</span></span></span></code></pre>
<p>Finally we can put all of the pieces together and run the data through archy and
print it to stdout.</p>
<pre class="z-code"><code><span class="z-text z-plain"># :: FilePath -> [Object]
</span><span class="z-text z-plain">parse = R.pipe do
</span><span class="z-text z-plain"> fs.read-file-sync
</span><span class="z-text z-plain"> acorn.parse
</span><span class="z-text z-plain"> find-category-list-props
</span><span class="z-text z-plain"> get-simple-props
</span><span class="z-text z-plain">
</span><span class="z-text z-plain"># note: >> is LiveScript syntax for R.pipe
</span><span class="z-text z-plain">render = category-list-to-tree >> archy
</span><span class="z-text z-plain">
</span><span class="z-text z-plain">parse-and-render = parse >> render
</span><span class="z-text z-plain">parse-and-render 'arrays_v2.js'
</span><span class="z-text z-plain">|> console.log
</span></code></pre>
<h3 id="output"><a class="zola-anchor" href="#output" aria-label="Anchor link for: output">#</a>Output</h3>
<pre class="z-code"><code><span class="z-text z-plain">CATEGORIES
</span><span class="z-text z-plain">├─┬ REAL
</span><span class="z-text z-plain">│ ├── Apartments & Houses
</span><span class="z-text z-plain">│ ├── Holidayhouses
</span><span class="z-text z-plain">│ ├── Land & agriculture
</span><span class="z-text z-plain">│ ├── Garage and storage room
</span><span class="z-text z-plain">│ └── Overseas apartments
</span><span class="z-text z-plain">├─┬ VEHICLES
</span><span class="z-text z-plain">│ ├── Cars
</span><span class="z-text z-plain">│ ├─┬ Car parts & accessories
</span><span class="z-text z-plain">│ │ ├── Autostereos & accessories
</span><span class="z-text z-plain">│ │ ├── Car spare parts
</span><span class="z-text z-plain">│ │ ├── Roof racks and boxes
</span><span class="z-text z-plain">│ │ ├── Trailers
</span><span class="z-text z-plain">│ │ ├── Tyres & bands
</span><span class="z-text z-plain">│ │ └── Other accessories
</span><span class="z-text z-plain">│ ├── Caravans
</span><span class="z-text z-plain">│ ├── Caravans accessories
</span><span class="z-text z-plain">│ ├─┬ Moto
</span><span class="z-text z-plain">│ │ ├── Motorcycles
</span><span class="z-text z-plain">│ │ ├── Mopeds/Vespas
</span><span class="z-text z-plain">│ │ ├── Motocars
</span><span class="z-text z-plain">│ │ ├── Snowmobiles
</span><span class="z-text z-plain">│ │ └── Fourwheels
</span><span class="z-text z-plain">│ ├─┬ Moto parts & accessories
</span><span class="z-text z-plain">│ │ ├── Suits, shoes and helmets
</span><span class="z-text z-plain">│ │ ├── Wheels
</span><span class="z-text z-plain">│ │ └── Other accesories & parts
</span><span class="z-text z-plain">│ ├─┬ Work machinery and equipment
</span><span class="z-text z-plain">│ │ ├── Transport equipment
</span><span class="z-text z-plain">│ │ ├── Machinery
</span><span class="z-text z-plain">│ │ ├── Woods and farming machines
</span><span class="z-text z-plain">│ │ ├── Excavation machinery
</span><span class="z-text z-plain">│ │ └── Other machinery
</span><span class="z-text z-plain">│ ├─┬ Boats
</span><span class="z-text z-plain">│ │ ├── Sailing boat
</span><span class="z-text z-plain">│ │ ├── Motor boat
</span><span class="z-text z-plain">│ │ ├── Rubber and Ribboat
</span><span class="z-text z-plain">│ │ ├── Jolle and rowingboat
</span><span class="z-text z-plain">│ │ ├── Kajak and canoe
</span><span class="z-text z-plain">│ │ ├── Waterscooter
</span><span class="z-text z-plain">│ │ └── Other water vehicles
</span><span class="z-text z-plain">│ └── Boatparts
</span><span class="z-text z-plain">├─┬ HOME & PERSONAL
</span><span class="z-text z-plain">│ ├─┬ Electric home appliances
</span><span class="z-text z-plain">│ │ ├── Dishwashers
</span><span class="z-text z-plain">│ │ ├── Fridges and freezers
</span><span class="z-text z-plain">│ │ ├── Ovens and microwaves
</span><span class="z-text z-plain">│ │ ├── Washing and drying machines
</span><span class="z-text z-plain">│ │ ├── Vacuum cleaners and cleaning
</span><span class="z-text z-plain">│ │ └── Other home appliances
</span><span class="z-text z-plain">│ ├── Kitchen accessories and dishes
</span><span class="z-text z-plain">│ ├─┬ Interior & furnitures
</span><span class="z-text z-plain">│ │ ├── Antique & art
</span><span class="z-text z-plain">│ │ ├── Shelves & keeping
</span><span class="z-text z-plain">│ │ ├── Carpets & textiles
</span><span class="z-text z-plain">│ │ ├── Tables & chairs
</span><span class="z-text z-plain">│ │ ├── Sofas & armchairs
</span><span class="z-text z-plain">│ │ ├── Beds & bedroom
</span><span class="z-text z-plain">│ │ ├── Lights
</span><span class="z-text z-plain">│ │ ├── Paintings
</span><span class="z-text z-plain">│ │ ├── Decoration
</span><span class="z-text z-plain">│ │ └── Other interior
</span><span class="z-text z-plain">│ ├─┬ Garden & yard
</span><span class="z-text z-plain">│ │ ├── Garden furniture & grills
</span><span class="z-text z-plain">│ │ ├── Lawn movers & machines
</span><span class="z-text z-plain">│ │ ├── Plants & seeds
</span><span class="z-text z-plain">│ │ ├── Pots, rocks & decorations
</span><span class="z-text z-plain">│ │ └── Other garden & yard
</span><span class="z-text z-plain">│ ├── Clothing & Shoes
</span><span class="z-text z-plain">│ ├─┬ Accessories and watches
</span><span class="z-text z-plain">│ │ ├── Clocks and jewelry
</span><span class="z-text z-plain">│ │ ├── Bags and hats
</span><span class="z-text z-plain">│ │ └── Other clothes
</span><span class="z-text z-plain">│ ├── Childrens clothes and shoes
</span><span class="z-text z-plain">│ ├─┬ Childrens accessories and toys
</span><span class="z-text z-plain">│ │ ├── Safety seats
</span><span class="z-text z-plain">│ │ ├── Children furniture
</span><span class="z-text z-plain">│ │ ├── Baby carriage
</span><span class="z-text z-plain">│ │ ├── Toys and games
</span><span class="z-text z-plain">│ │ ├── Children accessories
</span><span class="z-text z-plain">│ │ └── Others
</span><span class="z-text z-plain">│ └─┬ Constructing and renovations
</span><span class="z-text z-plain">│ ├── Bathroom, WC and sauna
</span><span class="z-text z-plain">│ ├── Electronics
</span><span class="z-text z-plain">│ ├── Tools, ladders and equipments
</span><span class="z-text z-plain">│ ├── Heaters and fireplaces
</span><span class="z-text z-plain">│ ├── Kitchen
</span><span class="z-text z-plain">│ ├── Insulation and roofs
</span><span class="z-text z-plain">│ ├── HVAC and pipes
</span><span class="z-text z-plain">│ ├── Windows, doors and floors
</span><span class="z-text z-plain">│ └── Other constructing and renovations
</span><span class="z-text z-plain">├─┬ SPORT & HOBBY
</span><span class="z-text z-plain">│ ├─┬ Sports
</span><span class="z-text z-plain">│ │ ├── Ice hockey and skating
</span><span class="z-text z-plain">│ │ ├── Skiing and snowboarding
</span><span class="z-text z-plain">│ │ ├── Soccer
</span><span class="z-text z-plain">│ │ ├── Rollerskating and skateboarding
</span><span class="z-text z-plain">│ │ ├── Martial arts
</span><span class="z-text z-plain">│ │ ├── Swimming and diving
</span><span class="z-text z-plain">│ │ ├── Running and jogging
</span><span class="z-text z-plain">│ │ ├── Golf
</span><span class="z-text z-plain">│ │ ├── Gym & Fitness
</span><span class="z-text z-plain">│ │ ├── Ball games
</span><span class="z-text z-plain">│ │ ├── Outdoors & Camping
</span><span class="z-text z-plain">│ │ └── Other sports
</span><span class="z-text z-plain">│ ├─┬ Biking and accessories
</span><span class="z-text z-plain">│ │ ├── Racer Bikes
</span><span class="z-text z-plain">│ │ ├── Mountain bikes
</span><span class="z-text z-plain">│ │ ├── Childrens bikes
</span><span class="z-text z-plain">│ │ ├── Other bikes
</span><span class="z-text z-plain">│ │ └── Bike accessories and helmets
</span><span class="z-text z-plain">│ ├─┬ Music and instruments
</span><span class="z-text z-plain">│ │ ├── Guitars, basses and amplifiers
</span><span class="z-text z-plain">│ │ ├── Pianos, organs and keyboards
</span><span class="z-text z-plain">│ │ ├── Drums
</span><span class="z-text z-plain">│ │ ├── Music CD, DVD and records
</span><span class="z-text z-plain">│ │ └── Other music and instruments
</span><span class="z-text z-plain">│ ├── Hunting
</span><span class="z-text z-plain">│ ├── Fishing
</span><span class="z-text z-plain">│ ├── Films
</span><span class="z-text z-plain">│ ├─┬ Books and magazines
</span><span class="z-text z-plain">│ │ ├── Hobby books
</span><span class="z-text z-plain">│ │ ├── Written books
</span><span class="z-text z-plain">│ │ ├── Childrens books
</span><span class="z-text z-plain">│ │ ├── Comics
</span><span class="z-text z-plain">│ │ ├── Studying books
</span><span class="z-text z-plain">│ │ ├── Magazines
</span><span class="z-text z-plain">│ │ └── Other books
</span><span class="z-text z-plain">│ ├─┬ Pets
</span><span class="z-text z-plain">│ │ ├── Cats
</span><span class="z-text z-plain">│ │ ├── Dogs
</span><span class="z-text z-plain">│ │ ├── Fish and aquariums
</span><span class="z-text z-plain">│ │ ├── Rodents
</span><span class="z-text z-plain">│ │ ├── Other animals
</span><span class="z-text z-plain">│ │ ├── Cats and dogs accessories
</span><span class="z-text z-plain">│ │ └── Animal parts
</span><span class="z-text z-plain">│ ├─┬ Horses and horsesports
</span><span class="z-text z-plain">│ │ ├── Saddles and accessories
</span><span class="z-text z-plain">│ │ ├── Horses and ponies
</span><span class="z-text z-plain">│ │ ├── Trailers and transports
</span><span class="z-text z-plain">│ │ └── Other utilities and accessories
</span><span class="z-text z-plain">│ ├── Travel and Tickets
</span><span class="z-text z-plain">│ ├─┬ Collecting
</span><span class="z-text z-plain">│ │ ├── Tableware
</span><span class="z-text z-plain">│ │ ├── Coins and medals
</span><span class="z-text z-plain">│ │ └── Other collecting
</span><span class="z-text z-plain">│ ├── Handiwork
</span><span class="z-text z-plain">│ ├─┬ Photography
</span><span class="z-text z-plain">│ │ ├── Cameras
</span><span class="z-text z-plain">│ │ ├── Lenses
</span><span class="z-text z-plain">│ │ ├── Photographing accessories
</span><span class="z-text z-plain">│ │ └── Other photography
</span><span class="z-text z-plain">│ └── Other sports and hobbies
</span><span class="z-text z-plain">├─┬ ELECTRONICS
</span><span class="z-text z-plain">│ ├── Phones
</span><span class="z-text z-plain">│ ├─┬ TV/Audio/Video/Cameras
</span><span class="z-text z-plain">│ │ ├── Television
</span><span class="z-text z-plain">│ │ ├── Digiboxes
</span><span class="z-text z-plain">│ │ ├── Audio and musicplayers
</span><span class="z-text z-plain">│ │ ├── Hometheathers, and DVD devices
</span><span class="z-text z-plain">│ │ ├── Consoles and playing
</span><span class="z-text z-plain">│ │ └── Other consumer electronics
</span><span class="z-text z-plain">│ └─┬ Computers & accessories
</span><span class="z-text z-plain">│ ├── Tablets
</span><span class="z-text z-plain">│ ├── Laptop
</span><span class="z-text z-plain">│ ├── Desktop computers
</span><span class="z-text z-plain">│ ├── Computer accessories
</span><span class="z-text z-plain">│ ├── Components
</span><span class="z-text z-plain">│ ├── Networks components
</span><span class="z-text z-plain">│ ├── Computer programs
</span><span class="z-text z-plain">│ └── Other computers & accessoriers
</span><span class="z-text z-plain">├─┬ BUSINESS & JOBS
</span><span class="z-text z-plain">│ ├── Jobs available
</span><span class="z-text z-plain">│ ├── CV
</span><span class="z-text z-plain">│ ├── Services
</span><span class="z-text z-plain">│ ├── Farming
</span><span class="z-text z-plain">│ ├── Construction services
</span><span class="z-text z-plain">│ └── Companies & shops
</span><span class="z-text z-plain">└─┬ OTHERS
</span><span class="z-text z-plain"> └── Others
</span></code></pre>
<p>I've shared the code on GitHub at
<a href="https://github.com/raine/parse-tori-categories"><code>raine/parse-tori-categories</code></a>.</p>