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">&quot;</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">&quot;</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">&quot;</span>H<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</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">&quot;</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">&quot;</span>J<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</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">&quot;</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">&quot;</span>K<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</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">&quot;</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">&quot;</span>L<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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> ~&#x2F;.config&#x2F;ghostty&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;tmux-navigate-panes-home-row&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;icon-composer-mac-app-icon-size&#x2F;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">&quot;WalkingMateCompanion.app&quot; can&#39;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 &gt; 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 &gt; General &gt; 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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;icon-composer-mac-app-icon-size&#x2F;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&#39;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 &quot;allow edit commands during attribute input&quot;) </span><span class="z-text z-plain"> </span><span class="z-text z-plain">⏺ Bash(git-surgeon commit 4eefac8 bbba931 -m &quot;add logging for attribute prompts&quot;) </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&#39;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 = &quot;INFO&quot; </span><span class="z-text z-plain"> 5:+LOG_LEVEL = &quot;DEBUG&quot; </span><span class="z-text z-plain"> 6:+LOG_FORMAT = &quot;%(asctime)s %(name)s %(levelname)s %(message)s&quot; </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=&quot;INFO&quot;) </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 &quot;add logging infrastructure&quot; \ </span><span class="z-text z-plain"> --pick 37562e9:3,7,15 8c1940f:11-13,22 -m &quot;add active flag to users&quot; \ </span><span class="z-text z-plain"> --rest-message &quot;add pagination support&quot;) </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:&#x2F;&#x2F;github.com&#x2F;raine&#x2F;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 &quot;TMUX_FILE_PICKER_FD_FLAGS=&#39;-H --type f -e md&#39; tmux-file-picker &#39;/Users/raine/Library/Mobile Documents/iCloud~md~obsidian/Documents/raine&#39;&quot; </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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;obsidian-vault-claude-code&#x2F;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 &quot;sticky&quot;, 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">&quot;</span>70 130 18 35<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&quot;</span>110 200 30 50<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&quot;</span>150 260 40 65<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&quot;</span>medium<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</span></span> <span class="z-keyword z-operator z-assignment z-redirection z-shell">&gt;&amp;</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">&lt;</span><span class="z-keyword z-operator z-assignment z-redirection z-shell">&lt;</span><span class="z-keyword z-operator z-assignment z-redirection z-shell">&lt;</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</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">&quot;</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">&lt;</span><span class="z-keyword z-operator z-assignment z-redirection z-shell">&lt;</span><span class="z-keyword z-operator z-assignment z-redirection z-shell">&lt;</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</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">&#39;</span>#{window_width} #{window_height}<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&quot;</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">&lt;</span><span class="z-keyword z-operator z-assignment z-redirection z-shell">&lt;</span><span class="z-keyword z-operator z-assignment z-redirection z-shell">&lt;</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&#39;</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 &gt; 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 &gt; 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 &gt; 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 &gt; 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">&#39;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&#39;</span>tmux-popup --size medium workmux<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>tmux-popup workmux<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;my-tmux-setup&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;my-tmux-setup&#x2F;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">&quot;</span>tmux-256color<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&#39;</span>,xterm-256color:RGB<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>,*:Smulx=\E[4::%p1%dm<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>,*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&amp;%d::%p1%{255}%&amp;%d%;m<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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> &lt; 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> &gt; 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">&quot;</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">&quot;</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">&quot;</span>Key1<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</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">&quot;</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">&quot;</span>H<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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">&quot;</span>Command<span class="z-punctuation z-definition z-string z-end z-toml">&quot;</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 -&gt; Down, K -&gt; Up, L -&gt; 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">&#39;</span>bg=#101010<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>bg=#151515<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</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">&#39;</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">&#39;</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">&#39;</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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;my-tmux-setup&#x2F;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">&quot;</span>tmux-session-switcher<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;my-tmux-setup&#x2F;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">&quot;</span>tmux-zoxide-window<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-file-picker<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-bro<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&#39;</span>rename-session %%<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;my-tmux-setup&#x2F;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">&#39;</span>tmux-plugins/tpm<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>tmux-plugins/tmux-logging<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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 &amp; 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>&amp;</kbd></td><td>Kill window</td></tr> <tr><td><strong>Pane resizing</strong></td><td></td></tr> <tr><td><kbd>prefix</kbd> <kbd>&lt;</kbd></td><td>Resize left</td></tr> <tr><td><kbd>prefix</kbd> <kbd>&gt;</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&#39;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 &amp; 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">&quot;</span>tmux-256color<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&#39;</span>,xterm-256color:RGB<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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 &amp; 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">&#39;</span>,*:Smulx=\E[4::%p1%dm<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>,*:Setulc=\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&amp;%d::%p1%{255}%&amp;%d%;m<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</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">&#39;</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">&#39;</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">&#39;</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">&#39;</span>bg=#101010<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>bg=#151515<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</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">&quot;</span>pbcopy<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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> &lt; 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> &gt; 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">&#39;</span>rename-session %%<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&quot;</span>New session name:<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</span></span> <span class="z-string z-quoted z-double z-tmux"><span class="z-punctuation z-definition z-string z-begin z-tmux">&quot;</span>new-session -d -s &#39;%%&#39;; move-window -t &#39;%%:&#39;; switch-client -t &#39;%%&#39;<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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 &amp; 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">&quot;</span>tmux-session-switcher<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-zoxide-window<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-bro<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-file-picker<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-file-picker -d<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-file-picker -g<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-file-picker --zoxide --dir-only<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-file-picker --zoxide<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&quot;</span>tmux-snaglord<span class="z-punctuation z-definition z-string z-end z-tmux">&quot;</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">&#39;</span>tmux-plugins/tpm<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>tmux-plugins/tmux-logging<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&#39;</span>~/.tmux/plugins/tpm/tpm<span class="z-punctuation z-definition z-string z-end z-tmux">&#39;</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">&gt; 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&#39;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: &quot;I have a Telegram bot that...&quot;, </span><span class="z-text z-plain"> model: &quot;gemini-3-pro-preview&quot;, ...) </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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;git-worktrees-parallel-agents&#x2F;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> ~&#x2F;.claude&#x2F;commands&#x2F;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., &quot;do option 2&quot;, &quot;implement the fix we discussed&quot;). 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 &lt;worktree-name&gt; -b -P &lt;temp-file&gt;<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 &quot;gemini-3-pro-preview&quot;, </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">&gt;</span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</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">&quot;</span></span> <span class="z-string z-unquoted z-heredoc z-shell"><span class="z-keyword z-operator z-assignment z-redirection z-shell">&lt;&lt;</span> <span class="z-punctuation z-definition z-string z-begin z-shell">&#39;</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">&#39;</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">&quot;</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">&quot;</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">&gt; /worktree Implement natural language editing </span><span class="z-text z-plain"> </span><span class="z-text z-plain">⏺ Bash(tmpfile=$(mktemp).md &amp;&amp; cat &gt; &quot;$tmpfile&quot; &lt;&lt; &#39;EOF&#39;…) </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 &#39;natural-language-edit&#39; </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">&gt; /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 &amp;&amp; cat &gt; &quot;$tmpfile&quot; &lt;&lt; &#39;EOF&#39;…) </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 &#39;fix-race-condition&#39; </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&#x27;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;git-worktrees-parallel-agents&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;git-worktrees-parallel-agents&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;git-worktrees-parallel-agents&#x2F;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:&#x2F;&#x2F;workmux.raine.dev&#x2F;" 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> ~&#x2F;.claude&#x2F;skills&#x2F;rebase&#x2F;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"> &quot;origin&quot;: 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"> &quot;origin/branch&quot;: 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"> &quot;branch&quot;: 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 &quot;rebase-temp&quot;<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 &quot;main&quot;, 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 &quot;/&quot; (e.g., &quot;origin/develop&quot;) → 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 &quot;origin&quot; → fetch origin, target is &quot;origin/main&quot; </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 &lt;remote&gt;<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 &lt;target&gt;<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 &lt;target&gt; -- &lt;file&gt;<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&#39;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">&gt; /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: &quot;git add/rm &lt;conflicted_files&gt;&quot;, then run &quot;git rebase --continue&quot;. </span><span class="z-text z-plain"> … </span><span class="z-text z-plain"> </span><span class="z-text z-plain">⏺ I see there&#39;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 &lt;[email protected]&gt; </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&#x27;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;introduction-to-workmux&#x2F;demo.mp4" poster="https:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;introduction-to-workmux&#x2F;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">&lt;agent&gt;</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 &amp;&amp; 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>&lt;agent&gt;</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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;introduction-to-workmux&#x2F;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:&#x2F;&#x2F;workmux.raine.dev&#x2F;" 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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;tmux-file-picker-for-ai-agents&#x2F;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 &quot;tmux-file-picker&quot; </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 &quot;tmux-file-picker -d&quot; </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 &quot;tmux-file-picker --zoxide&quot; </span><span class="z-text z-plain">bind C-d display-popup -E &quot;tmux-file-picker --zoxide --dir-only&quot; </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&#x2F;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">&#39;</span>1.2.0<span class="z-punctuation z-definition z-string z-end z-yaml">&#39;</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">&quot;</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&#39;s correction factor<span class="z-punctuation z-definition z-string z-end z-yaml">&quot;</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">&#39;</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">&#39;</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">&lt;!--</span> Front page <span class="z-punctuation z-definition z-comment z-end z-html">--&gt;</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">&lt;</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">&quot;</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">&quot;</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">&gt;</span></span> </span><span class="z-text z-html z-basic"> {% set changelog = load_data(path=&quot;data/changelog.yaml&quot;) %} {% 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">&lt;</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">&quot;</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">&quot;</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">&gt;</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">&lt;</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">&quot;</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">&quot;</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">&gt;</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">&lt;</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">&gt;</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">&lt;/</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">&gt;</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">&lt;</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">&quot;</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">&quot;</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">&gt;</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">&lt;/</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">&gt;</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">&lt;/</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">&gt;</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">&lt;</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">&quot;</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">&quot;</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">&gt;</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">&lt;</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">&gt;</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">&lt;/</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">&gt;</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">&lt;/</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">&gt;</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">&lt;/</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">&gt;</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">&lt;</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">&quot;</span>/changelog<span class="z-punctuation z-definition z-string z-end z-html">&quot;</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">&quot;</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">&quot;</span></span></span><span class="z-punctuation z-definition z-tag z-end z-html">&gt;</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">&amp;</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">&lt;/</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">&gt;</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">&lt;/</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">&gt;</span></span> </span></code></pre> <figure> <img src="https:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;zola-static-site-experience&#x2F;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&#x2F;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">&quot;</span>hooks<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>PostToolUse<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>matcher<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>Write|Edit<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>hooks<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>type<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>command<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>command<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>.claude/hooks/touch-zola-files<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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&#x2F;hooks&#x2F;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&#39;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&#39;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">&#39;</span>.tool_input.file_path<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&quot;</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">&quot;</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">&amp;&amp;</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">&quot;</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">&quot;</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">&quot;</span><span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&quot;</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">&quot;</span></span> <span class="z-keyword z-operator z-assignment z-redirection z-shell">&gt;&gt;</span><span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</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">&quot;</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">&quot;</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">&quot;</span></span> <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</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">&quot;</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> &lt;link rel=&quot;stylesheet&quot; href=&quot;{​{ get_url(path=&quot;main.css&quot;) }​}&quot;&gt; </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> &lt;link rel=&quot;stylesheet&quot; href=&quot;{​{ get_url(path=&quot;syntax-light.css&quot;) }​}&quot; id=&quot;syntax-light&quot;&gt; </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> &lt;link rel=&quot;stylesheet&quot; href=&quot;{​{ get_url(path=&quot;syntax-dark.css&quot;) }​}&quot; id=&quot;syntax-dark&quot; disabled&gt; </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> &lt;link rel=&quot;stylesheet&quot; href=&quot;{​{ get_url(path=&quot;main.css&quot;, cachebust=true) }​}&quot;&gt; </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> &lt;link rel=&quot;stylesheet&quot; href=&quot;{​{ get_url(path=&quot;syntax-light.css&quot;, cachebust=true) }​}&quot; id=&quot;syntax-light&quot;&gt; </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> &lt;link rel=&quot;stylesheet&quot; href=&quot;{​{ get_url(path=&quot;syntax-dark.css&quot;, cachebust=true) }​}&quot; id=&quot;syntax-dark&quot; disabled&gt; </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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;keychron-k3-pro-apple-magic-keyboard&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;keychron-k3-pro-apple-magic-keyboard&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;keychron-k3-pro-apple-magic-keyboard&#x2F;mac-win-switch.avif" alt="Mac&#x2F;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:&#x2F;&#x2F;raine.dev&#x2F;blog&#x2F;keychron-k3-pro-apple-magic-keyboard&#x2F;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> ~&#x2F;.config&#x2F;karabiner&#x2F;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">&quot;</span>description<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>Option+[ and Option+] to back and forward<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>manipulators<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>from<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>open_bracket<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>mandatory<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>right_option<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>to<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>tab<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>left_control<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>left_shift<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>type<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>basic<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>from<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>close_bracket<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>mandatory<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>right_option<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>to<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>tab<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>modifiers<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>left_control<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>type<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>basic<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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> ~&#x2F;.config&#x2F;karabiner&#x2F;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">&quot;</span>identifiers<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>is_keyboard<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>product_id<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>vendor_id<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>simple_modifications<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>from<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>to<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>from<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>to<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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> ~&#x2F;.config&#x2F;karabiner&#x2F;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">&quot;</span>identifiers<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>is_keyboard<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>simple_modifications<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>from<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>to<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>from<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>non_us_backslash<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>to<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>key_code<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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">&quot;</span>grave_accent_and_tilde<span class="z-punctuation z-definition z-string z-end z-json">&quot;</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> &#x2F;etc&#x2F;netplan&#x2F;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">&#39;</span>{{WIFI NAME}}<span class="z-punctuation z-definition z-string z-end z-yaml">&#39;</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">&#39;</span>{{WIFI PASSWORD}}<span class="z-punctuation z-definition z-string z-end z-yaml">&#39;</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:&quot;{{WIFI NAME}}&quot; </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> &#x2F;etc&#x2F;netplan&#x2F;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">&#39;</span>{{WIFI NAME}}<span class="z-punctuation z-definition z-string z-end z-yaml">&#39;</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">&#39;</span>{{WIFI PASSWORD}}<span class="z-punctuation z-definition z-string z-end z-yaml">&#39;</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">&#39;</span>2C:FD:A1:DD:D3:34<span class="z-punctuation z-definition z-string z-end z-yaml">&#39;</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">&#39;</span>Beats Studio³<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>{print $1}<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&lt;</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">&gt;</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">&lt;</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">&gt;</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">&lt;/</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">&gt;</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">&lt;</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">&gt;</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">&lt;/</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">&gt;</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">&lt;</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">&gt;</span></span>GOOGLE_CHROME<span class="z-meta z-tag z-xml"><span class="z-punctuation z-definition z-tag z-begin z-xml">&lt;/</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">&gt;</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">&lt;</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">&gt;</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">&lt;/</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">&gt;</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">&lt;</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">&gt;</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">&lt;/</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">&gt;</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">&lt;/</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">&gt;</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">&#39;</span>apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>jeresig/apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>pluck \name<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>babel<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>brooklynjs.github.io<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>casperjs<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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 -&gt; {*} -&gt; [*]</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">&#39;</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</span></span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&#39;</span>pluck \name<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>pipe( filter(where-eq({ fork: false })), pluck(&quot;name&quot;) )<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</span></span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&#39;</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>apples2artworks<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>datacook<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>deepleap<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>dromaeo<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>sort-by prop \stargazers_count<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>sort-by prop \stargazers_count<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>take 10<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>processing-js<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>node-stream-playground<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>fireunit<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>env-js<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>filter where-eq fork: false<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>project [\name \stargazers_count]<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>sort-by prop \stargazers_count<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>take 15<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>ramda<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>name<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>stargazers_count<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>stargazers_count<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>filter (.name is /bob/i)<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</span></span> <span class="z-string z-quoted z-single z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&#39;</span>pluck \name<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&quot;</span>Bobbie Huel<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</span></span> , <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</span>Bobby Ryan<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</span></span>, <span class="z-string z-quoted z-double z-shell"><span class="z-punctuation z-definition z-string z-begin z-shell">&quot;</span>Dr. Bobby Schiller<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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>&lt;[ foo bar xyz ]&gt;</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 # =&gt; { count: 2 } </span></code></pre> <blockquote> <p><a href="http://ramdajs.com/docs/#evolve"><code>evolve :: {k: (v -&gt; v)} -&gt; {k: v} -&gt; {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) -&gt; x</code> → <code>var identity = function(x) { return x }</code></p> <p>A function implicitly returns the last statement.</p> <p><code>(str) -&gt; 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 -&gt; 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) -&gt; x + y # var add = function(x, y) { return x + y } </span><span class="z-text z-plain">add 1 2 # =&gt; 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) -&gt; 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), &lt;[ expire syncrisis reindicating ]&gt; # =&gt; [ 6, 9, 12 ] </span><span class="z-text z-plain">find (.0 is \f), &lt;[ foo bar xyz ]&gt; # =&gt; &#39;foo&#39; </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">&#39;</span>{&quot;hello&quot;: &quot;world&quot;}<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&#39;</span>.hello<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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">&quot;</span>world<span class="z-punctuation z-definition z-string z-end z-shell">&quot;</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">&#39;</span>(.hello)<span class="z-punctuation z-definition z-string z-end z-shell">&#39;</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) -&gt; str.length is 5 </span><span class="z-text z-plain">list.filter -&gt; it.length is 5 </span><span class="z-text z-plain">list.filter (.length is 5) </span><span class="z-text z-plain"># =&gt; [ &#39;hello&#39; ] </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 (+ &#39;!&#39;) &lt;[ foo bar ]&gt; # =&gt; [ &#39;foo!&#39;, &#39;bar!&#39; ] </span><span class="z-text z-plain">reduce (+), 0, [1, 2, 3] # =&gt; 6 </span><span class="z-text z-plain">filter (!= null), [1, null, 2] # =&gt; [ 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 &lt;&lt; g) x</code> is <code>compose(f, g)(x)</code> is <code>f(g(x))</code></li> <li><code>(f &gt;&gt; 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>&lt;&lt;</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 &gt;&gt; inc # map(pipe(head, toUpper)) </span><span class="z-text z-plain">fn [[1, 2], [3, 4]] # =&gt; [ 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">&#39;</span>name<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>ASUNNOT JA TONTIT<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>name_en<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>level<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>name<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Asunnot<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>name_en<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Apartments &amp; Houses<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>level<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>parent<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>leaf<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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 &#39;arrays_v2.js&#39; </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) -&gt; </span><span class="z-text z-plain"> walk.find-node-at ast, null, null, is-category-list </span><span class="z-text z-plain"> |&gt; (.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) -&gt; </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">&#39;</span>1000<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>name<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>ASUNNOT JA TONTIT<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>init<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Property<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>name_en<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Literal<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>ASUNNOT JA TONTIT<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Asunnot<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Apartments &amp; Houses<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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 = -&gt; (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) &gt;&gt; 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 -&gt; </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 -&gt; [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) -&gt; </span><span class="z-text z-plain"> root = &#39;CATEGORIES&#39; </span><span class="z-text z-plain"> recurse-category = (cat) -&gt; </span><span class="z-text z-plain"> label: cat?.&#39;name_en&#39; 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">&#39;</span>CATEGORIES<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>REAL<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Apartments &amp; Houses<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Holidayhouses<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Land &amp; agriculture<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Garage and storage room<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Overseas apartments<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>VEHICLES<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Cars<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Car parts &amp; accessories<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Caravans<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Caravans accessories<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Moto<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Moto parts &amp; accessories<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Work machinery and equipment<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Boats<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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">&#39;</span>Boatparts<span class="z-punctuation z-definition z-string z-end z-ts">&#39;</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 -&gt; [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: &gt;&gt; is LiveScript syntax for R.pipe </span><span class="z-text z-plain">render = category-list-to-tree &gt;&gt; archy </span><span class="z-text z-plain"> </span><span class="z-text z-plain">parse-and-render = parse &gt;&gt; render </span><span class="z-text z-plain">parse-and-render &#39;arrays_v2.js&#39; </span><span class="z-text z-plain">|&gt; 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 &amp; Houses </span><span class="z-text z-plain">│ ├── Holidayhouses </span><span class="z-text z-plain">│ ├── Land &amp; 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 &amp; accessories </span><span class="z-text z-plain">│ │ ├── Autostereos &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; furnitures </span><span class="z-text z-plain">│ │ ├── Antique &amp; art </span><span class="z-text z-plain">│ │ ├── Shelves &amp; keeping </span><span class="z-text z-plain">│ │ ├── Carpets &amp; textiles </span><span class="z-text z-plain">│ │ ├── Tables &amp; chairs </span><span class="z-text z-plain">│ │ ├── Sofas &amp; armchairs </span><span class="z-text z-plain">│ │ ├── Beds &amp; 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 &amp; yard </span><span class="z-text z-plain">│ │ ├── Garden furniture &amp; grills </span><span class="z-text z-plain">│ │ ├── Lawn movers &amp; machines </span><span class="z-text z-plain">│ │ ├── Plants &amp; seeds </span><span class="z-text z-plain">│ │ ├── Pots, rocks &amp; decorations </span><span class="z-text z-plain">│ │ └── Other garden &amp; yard </span><span class="z-text z-plain">│ ├── Clothing &amp; 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 &amp; 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 &amp; Fitness </span><span class="z-text z-plain">│ │ ├── Ball games </span><span class="z-text z-plain">│ │ ├── Outdoors &amp; 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 &amp; 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 &amp; accessoriers </span><span class="z-text z-plain">├─┬ BUSINESS &amp; 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 &amp; 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>