<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kamil</title>
    <description>The latest articles on DEV Community by Kamil (@kameleon21).</description>
    <link>https://dev.to/kameleon21</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1103771%2Ff68e6304-ba6f-4841-a4bf-45833367cafd.JPG</url>
      <title>DEV Community: Kamil</title>
      <link>https://dev.to/kameleon21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kameleon21"/>
    <language>en</language>
    <item>
      <title>Who Benefits Most From AI?</title>
      <dc:creator>Kamil</dc:creator>
      <pubDate>Tue, 11 Aug 2026 17:09:00 +0000</pubDate>
      <link>https://dev.to/kameleon21/who-benefits-most-from-ai-213m</link>
      <guid>https://dev.to/kameleon21/who-benefits-most-from-ai-213m</guid>
      <description>&lt;p&gt;I recently came across a post that started a chain of thought for me. The claim was that senior developers — people with many years in the industry — gain the most from AI tools, while their junior colleagues have far more to lose than to gain by leaning on them.&lt;/p&gt;

&lt;p&gt;I've been thinking about it since.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: I'm by no means a senior in the tech space. I value and understand that knowledge gained through experience and hardship has tremendous value and shouldn't be overlooked. But being on the other side of that line is probably what makes the question worth asking out loud.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter I — Use It or Lose It
&lt;/h2&gt;

&lt;p&gt;Let me start with the obvious part: what we don't use atrophies eventually. This is true across fields and industries and has stood the test of time. Muscles, languages, mental arithmetic, sight-reading music. Why would tech be different?&lt;/p&gt;

&lt;p&gt;The version of the argument I keep hearing goes something like this. Experienced developers have seen enough issues and bugs to spot when AI is suggesting something that won't work. A junior raises their first PR and gets thirty comments asking them to fix things they didn't know were problems. That kind of foresight can't be bought or simulated — it's paid for in incidents, bad deploys, and reviews that stung a bit.&lt;/p&gt;

&lt;p&gt;So far, so good. But there's a hole in it, and it's the same hole "use it or lose it" always has.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If atrophy is real, it applies to everyone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The senior's advantage isn't a permanent trait. It's accumulated capital. It was built by reading a lot of code closely, sitting with bugs longer than was comfortable, and being wrong enough to develop taste. If that same person spends the next three years reviewing generated code at speed instead of writing and debugging it themselves, that capital doesn't sit there gathering interest. It depreciates like anything else.&lt;/p&gt;

&lt;p&gt;Which changes the question. It's not "who benefits most?" It's "who can afford this, and for how long?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter II — What Juniors Actually Lose
&lt;/h2&gt;

&lt;p&gt;I don't think the risk for junior developers is that they'll know less. You can look things up. The risk is more specific, and it's worth naming precisely.&lt;/p&gt;

&lt;p&gt;What juniors are missing isn't knowledge. It's an &lt;strong&gt;evaluation function&lt;/strong&gt; — the ability to look at a plausible-looking solution and tell whether it's actually right.&lt;br&gt;
That's the whole game with these tools. The output is fluent and confident whether or not it's correct. A senior reads a suggestion and somethings stand out: that error handling is wrong, that query is going to hurt at scale, that's not how this library actually behaves. A junior reads the same suggestion and it looks... fine. It looks like code. It compiles.&lt;/p&gt;

&lt;p&gt;And here's the uncomfortable part: the evaluation function gets built by doing exactly the work the tool removes. You learn what bad code feels like by writing bad code and then living with it. You develop a sense for where bugs hide by hunting one badly for two days before finding it.&lt;/p&gt;

&lt;p&gt;There's a term for this in learning research — &lt;em&gt;desirable difficulty&lt;/em&gt;. Struggle that feels inefficient in the moment is often the thing that makes the learning stick. AI is extremely good at removing precisely that kind of friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter III — Replacing the Attempt vs. Reviewing It
&lt;/h2&gt;

&lt;p&gt;I don't want to land on "juniors shouldn't use AI," though, because I don't believe it and it isn't what actually happens.&lt;/p&gt;

&lt;p&gt;Juniors were never learning through pure suffering anyway. They were learning through &lt;strong&gt;suffering with feedback&lt;/strong&gt; — a senior on the PR, someone pairing with them, a review that explained the &lt;em&gt;why&lt;/em&gt;. The struggle mattered because someone was there afterwards to tell them what the struggle meant.&lt;/p&gt;

&lt;p&gt;So the useful line isn't AI vs. no AI. It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the tool replace my attempt, or respond to it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ask it to write the function before you've thought about the problem, and you've outsourced the thinking. Write your version first, &lt;em&gt;then&lt;/em&gt; ask what's wrong with it, and you've gained a reviewer with infinite patience. Same tool, opposite outcomes. The order of operations is the whole thing.&lt;/p&gt;

&lt;p&gt;This is also where the point I keep coming back to lives. Working with AI genuinely helps when you're rubber-ducking through a problem, or when you want to ask every question you have without worrying that you sound like you don't know what you're doing. That's real, and I don't think people who've never been the most junior person in the room appreciate how real it is.&lt;/p&gt;

&lt;p&gt;But it comes with a cost worth naming. A junior's questions are signals. They tell the team where the gaps are, what onboarding missed, who's stuck this week. When all of those questions go to a chat window instead, the team loses that signal — and the junior loses a hundred small chances to build a relationship with someone who could actually mentor them.&lt;/p&gt;

&lt;p&gt;Ask the model. Then still ask the person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter IV — Which Skills Are Safe to Lose?
&lt;/h2&gt;

&lt;p&gt;Every abstraction we've adopted has cost us a skill, and we mostly decided it was worth it. Very few of us write assembly by hand. Most of us couldn't manage memory manually under pressure. Compilers, IDEs, garbage collection, Stack Overflow — each arrived with someone predicting the rot of a generation of programmers.&lt;/p&gt;

&lt;p&gt;Sometimes they were right, and it didn't matter.&lt;/p&gt;

&lt;p&gt;So maybe the better question isn't whether AI causes skill atrophy. It obviously does; everything does. The question is which skills are &lt;strong&gt;load-bearing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My rough attempt at the line:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probably safe to outsource&lt;/strong&gt; — syntax recall, boilerplate, the argument order of a standard library function, the flags for a CLI you touch twice a year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not safe to outsource&lt;/strong&gt; — telling whether code does what you think it does. Holding a system in your head. Debugging something nobody has written a blog post about. Knowing which question to ask.&lt;/p&gt;

&lt;p&gt;The first list is lookup. The second is judgment. Tools have always been fine at replacing lookup. This is the first one that convincingly &lt;em&gt;imitates&lt;/em&gt; judgment, which is what makes it different, and what makes it worth being deliberate about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter V — So, Who Benefits Most?
&lt;/h2&gt;

&lt;p&gt;I started this with someone else's claim: seniors gain the most, juniors have the most to lose. Four chapters later I'm not sure it's a question about seniority at all. I think it's a question about what you're spending, and whether you've ever looked at the price.&lt;/p&gt;

&lt;p&gt;I don't have an answer for you. I'm not convinced the answer transfers between people anyway. But these are the questions I've ended up asking myself, and I'd rather hand them over than pretend I've resolved them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If your judgement is capital, when did you last add to it?&lt;/strong&gt; Not spend it — add to it. Reviewing generated code at speed draws down the reserve. What's been topping it back up?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you reach for the tool, is it replacing your attempt or responding to it?&lt;/strong&gt; Be honest about the order of operations. Most of us know which one we did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which of the skills you're shedding are lookup, and which are judgement?&lt;/strong&gt; And would you notice the difference before the moment you actually needed it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the last thing you did the hard way on purpose?&lt;/strong&gt; If nothing comes to mind quickly, that's information.&lt;/p&gt;

&lt;p&gt;And the one sitting underneath all of them: what are you optimising for? Shipping this week, or still being worth something in five years? Both are legitimate. Plenty of people have good reasons to choose the first — deadlines are real, rent is real. But it's a trade, and trades go badly when you don't know you're making one.&lt;/p&gt;

&lt;p&gt;So — who benefits most from AI?&lt;/p&gt;

&lt;p&gt;My best guess is: whoever knows what it's costing them and has decided the price is worth paying.&lt;/p&gt;

&lt;p&gt;Which one is that for you?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>development</category>
      <category>discuss</category>
    </item>
    <item>
      <title>One keystroke to a project: building a tmux session launcher with fzf</title>
      <dc:creator>Kamil</dc:creator>
      <pubDate>Sun, 02 Aug 2026 15:19:27 +0000</pubDate>
      <link>https://dev.to/kameleon21/one-keystroke-to-a-project-building-a-tmux-session-launcher-with-fzf-198n</link>
      <guid>https://dev.to/kameleon21/one-keystroke-to-a-project-building-a-tmux-session-launcher-with-fzf-198n</guid>
      <description>&lt;p&gt;I hit &lt;code&gt;Ctrl-F&lt;/code&gt; more than any other key combination on this machine.&lt;/p&gt;

&lt;p&gt;It runs a shell function called &lt;code&gt;fts&lt;/code&gt; — "find tmux session," which is not a good name but it's four years too late to change it. I press it, a fuzzy finder opens listing every project directory I have, I type a few characters, and I'm sitting in a tmux session for that project with the panes already laid out. If the session already existed, I'm back in it exactly where I left off. If what I typed doesn't exist yet, it offers to create it.&lt;/p&gt;

&lt;p&gt;Somebody watched me do this over a screen share recently and asked what was going on. So: here's the whole thing, the four tools it's built on, and a breakdown of every part that isn't obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you'll end up with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One keystroke from anywhere to any project&lt;/li&gt;
&lt;li&gt;Fuzzy search across every repo you own, with a live directory tree preview&lt;/li&gt;
&lt;li&gt;Type a name that doesn't exist → it offers to scaffold and place it&lt;/li&gt;
&lt;li&gt;Never accidentally start a second tmux session for a project you already have open&lt;/li&gt;
&lt;li&gt;The same window/pane layout in every project, every time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Before this, starting work looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/repo/work/some-project-i-half-remember-the-name-of
tmux new-session &lt;span class="nt"&gt;-s&lt;/span&gt; some-project
&lt;span class="c"&gt;# split some panes, badly, slightly differently each time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three or four commands, one of which needed me to remember a path. None of it hard. All of it friction at exactly the wrong moment — the moment you've decided to start something, which is the moment you're most likely to get distracted instead.&lt;/p&gt;

&lt;p&gt;I'd also collected tmux sessions named &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt; and &lt;code&gt;some-project-2&lt;/code&gt;, because I kept starting new ones instead of attaching to the one already running.&lt;/p&gt;

&lt;p&gt;So the goal wasn't really speed. It was making the right thing the automatic thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Four tools plus zsh. All four are worth having on their own, and three of them are things you'll reach for daily once installed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Version I'm on&lt;/th&gt;
&lt;th&gt;What it does here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/tmux/tmux" rel="noopener noreferrer"&gt;&lt;strong&gt;tmux&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3.6a&lt;/td&gt;
&lt;td&gt;The terminal multiplexer. Holds the sessions, windows and panes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/junegunn/fzf" rel="noopener noreferrer"&gt;&lt;strong&gt;fzf&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.67.0&lt;/td&gt;
&lt;td&gt;The fuzzy finder. Every picker in this function is fzf.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sharkdp/fd" rel="noopener noreferrer"&gt;&lt;strong&gt;fd&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;10.3.0&lt;/td&gt;
&lt;td&gt;A faster, friendlier &lt;code&gt;find&lt;/code&gt;. Generates the list of directories.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/eza-community/eza" rel="noopener noreferrer"&gt;&lt;strong&gt;eza&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;0.23.4&lt;/td&gt;
&lt;td&gt;A modern &lt;code&gt;ls&lt;/code&gt;. Draws the tree preview inside fzf.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;zsh&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.9&lt;/td&gt;
&lt;td&gt;The shell. &lt;code&gt;read -q&lt;/code&gt; and the &lt;code&gt;zle&lt;/code&gt; keybinding are zsh-specific.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;macOS (Homebrew):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;tmux fzf fd eza
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Debian / Ubuntu:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;tmux fzf fd-find
&lt;span class="c"&gt;# eza needs its own repo on older releases — see eza-community/eza#installation&lt;/span&gt;
&lt;span class="c"&gt;# and note the fd binary is called `fdfind` on Debian; symlink or alias it to `fd`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Arch:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; tmux fzf fd eza
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why these, specifically
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;fd over &lt;code&gt;find&lt;/code&gt;.&lt;/strong&gt; The important part isn't speed, it's that &lt;code&gt;fd&lt;/code&gt; respects &lt;code&gt;.gitignore&lt;/code&gt; by default. Without that, the picker list is 90% &lt;code&gt;node_modules&lt;/code&gt; and the whole thing is unusable. You can do the same with &lt;code&gt;find&lt;/code&gt; and a pile of &lt;code&gt;-prune&lt;/code&gt; flags, but you'll write them once and never touch them again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;eza over &lt;code&gt;tree&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;eza --tree --level=2 --color=always --icons&lt;/code&gt; is one command that gives a colourised, icon'd, gitignore-aware two-level tree. &lt;code&gt;tree&lt;/code&gt; can get close but not with the same flags-to-output ratio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;fzf.&lt;/strong&gt; No substitute. &lt;code&gt;--print-query&lt;/code&gt; in particular — covered below — is what makes the create-if-missing flow possible at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tmux ≥ 1.8&lt;/strong&gt; for the commands used here. Anything from the last decade is fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bash instead of zsh?
&lt;/h3&gt;

&lt;p&gt;The function is mostly POSIX-ish, but three bits are zsh:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;read -q "confirm?Proceed? [y/N] "&lt;/code&gt; → in bash: &lt;code&gt;read -n 1 -p "Proceed? [y/N] " confirm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;"$fzf_out"&lt;/code&gt; (herestring) → bash has this too, you're fine&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;zle&lt;/code&gt; widget at the end → bash equivalent is &lt;code&gt;bind -x '"\C-f": fts'&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else ports directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full function
&lt;/h2&gt;

&lt;p&gt;Copy-paste this into your &lt;code&gt;~/.zshrc&lt;/code&gt;. The only thing you need to change is &lt;code&gt;FTS_ROOT&lt;/code&gt; — mine is &lt;code&gt;~/repo&lt;/code&gt;, with projects grouped one level deeper into &lt;code&gt;work&lt;/code&gt;, &lt;code&gt;learning&lt;/code&gt;, &lt;code&gt;college&lt;/code&gt;, &lt;code&gt;openSource&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ---- fts: find tmux session ----&lt;/span&gt;
&lt;span class="nv"&gt;FTS_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FTS_ROOT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="p"&gt;/repo&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

fts&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local dir &lt;/span&gt;query selection fzf_out

  &lt;span class="c"&gt;# --print-query: line 1 = typed query, line 2 = selected match&lt;/span&gt;
  &lt;span class="nv"&gt;fzf_out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;fd &lt;span class="nt"&gt;--type&lt;/span&gt; d &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FTS_ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null | fzf &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--height&lt;/span&gt; 40% &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--print-query&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Directory: "&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s1"&gt;'eza --tree --level=2 --color=always --icons --ignore-glob "node_modules" {}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--preview-window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;right:50%&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1p'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fzf_out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'2p'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fzf_out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$selection&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$selection&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# No match, but something was typed → treat it as a new project name&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;sanitized
    &lt;span class="nv"&gt;sanitized&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-cs&lt;/span&gt; &lt;span class="s1"&gt;'a-zA-Z0-9_-'&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^-//;s/-$//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sanitized&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;return

    &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;parent
    &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;fd &lt;span class="nt"&gt;--type&lt;/span&gt; d &lt;span class="nt"&gt;--max-depth&lt;/span&gt; 1 &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FTS_ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null | fzf &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--height&lt;/span&gt; 30% &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Create '&lt;/span&gt;&lt;span class="nv"&gt;$sanitized&lt;/span&gt;&lt;span class="s2"&gt;' inside: "&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$parent&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;return

    &lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;%/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$sanitized&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Creating &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"confirm?Proceed? [y/N] "&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;echo
    mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    return&lt;/span&gt;  &lt;span class="c"&gt;# escaped out — do nothing&lt;/span&gt;
  &lt;span class="k"&gt;fi

  &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;session_name
  &lt;span class="nv"&gt;session_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;' ./:'&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="c"&gt;# Already running? Attach and stop.&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;tmux has-session &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMUX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;tmux switch-client &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:main"&lt;/span&gt;
    &lt;span class="k"&gt;else
      &lt;/span&gt;tmux attach-session &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:main"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    return
  fi&lt;/span&gt;

  &lt;span class="c"&gt;# Window 1 (main): single pane&lt;/span&gt;
  tmux new-session &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; main

  &lt;span class="c"&gt;# Window 2 (dev): big left pane, two stacked on the right&lt;/span&gt;
  tmux new-window   &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;       &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; dev
  tmux split-window &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:dev"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  tmux split-window &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:dev"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-pane&lt;/span&gt;  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:dev"&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt;

  tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-window&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:main"&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMUX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;tmux switch-client &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;tmux attach-session &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# ---- bind it to Ctrl-F ----&lt;/span&gt;
fts-widget&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;BUFFER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"fts"&lt;/span&gt;
  zle accept-line
&lt;span class="o"&gt;}&lt;/span&gt;
zle &lt;span class="nt"&gt;-N&lt;/span&gt; fts-widget
bindkey &lt;span class="s1"&gt;'^F'&lt;/span&gt; fts-widget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;source ~/.zshrc&lt;/code&gt; and hit &lt;code&gt;Ctrl-F&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The rest of this post is what each part is doing and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: the picker, and the &lt;code&gt;--print-query&lt;/code&gt; trick
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;fzf_out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;fd &lt;span class="nt"&gt;--type&lt;/span&gt; d &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FTS_ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null | fzf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--height&lt;/span&gt; 40% &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--print-query&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Directory: "&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--preview&lt;/span&gt; &lt;span class="s1"&gt;'eza --tree --level=2 --color=always --icons --ignore-glob "node_modules" {}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--preview-window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;right:50%&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flag by flag:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--height 40%&lt;/code&gt; — fzf renders in the bottom 40% of the terminal instead of taking the whole screen. Your scrollback stays visible, which makes it feel like a prompt rather than a mode you've entered.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--reverse&lt;/code&gt; — list grows downward from the prompt at the top. Personal preference; try both.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--prompt="Directory: "&lt;/code&gt; — labels what you're picking. Matters more once you have several fzf-driven tools and they all look identical.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--preview&lt;/code&gt; — runs a command on the highlighted line. &lt;code&gt;{}&lt;/code&gt; is substituted with that line.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--preview-window=right:50%&lt;/code&gt; — where the preview goes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The preview was a late addition and it changed how the tool feels. When you've got three directories with similar names, two levels of tree tells you instantly which one is the real project and which one you abandoned in March.&lt;/p&gt;

&lt;p&gt;Now the important flag. &lt;strong&gt;&lt;code&gt;--print-query&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By default fzf prints what you selected. With &lt;code&gt;--print-query&lt;/code&gt;, it prints two lines: what you &lt;em&gt;typed&lt;/em&gt;, then what you &lt;em&gt;selected&lt;/em&gt;. If your query matched nothing, line two is empty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1p'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fzf_out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;      &lt;span class="c"&gt;# line 1&lt;/span&gt;
&lt;span class="nv"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'2p'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$fzf_out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;# line 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole trick. An empty line two isn't a failure — it's a different intent. You didn't fail to find a project, you named one that doesn't exist yet. The picker becomes a creator with no second command and no flag, and I never have to decide which mode I'm in before I start typing.&lt;/p&gt;

&lt;p&gt;Three branches follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt;   &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$selection&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;  &lt;span class="c"&gt;# picked something → open it&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;     &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;  &lt;span class="c"&gt;# typed something unmatched → create it&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;                              &lt;span class="c"&gt;# nothing at all → do nothing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That third branch matters as much as the others. Hitting &lt;code&gt;Esc&lt;/code&gt; has to be completely free. A tool you reach for reflexively needs a zero-cost way to back out, or you stop reaching for it reflexively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: creating a project, carefully
&lt;/h2&gt;

&lt;p&gt;This is the only place &lt;code&gt;fts&lt;/code&gt; touches the filesystem, so it's the only place it asks permission.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;sanitized&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-cs&lt;/span&gt; &lt;span class="s1"&gt;'a-zA-Z0-9_-'&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^-//;s/-$//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tr -cs 'a-zA-Z0-9_-' '-'&lt;/code&gt; reads as: take the set of alphanumerics, hyphen and underscore; &lt;code&gt;-c&lt;/code&gt; &lt;strong&gt;complements&lt;/strong&gt; it (so we're now matching everything &lt;em&gt;not&lt;/em&gt; in that set); replace those with &lt;code&gt;-&lt;/code&gt;; &lt;code&gt;-s&lt;/code&gt; &lt;strong&gt;squeezes&lt;/strong&gt; runs of the replacement into one.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;my new API client!!&lt;/code&gt; becomes &lt;code&gt;my-new-API-client-&lt;/code&gt;, and the &lt;code&gt;sed&lt;/code&gt; trims the leading/trailing hyphen → &lt;code&gt;my-new-API-client&lt;/code&gt;. It's there because I type project names in prose and don't want to think about casing or spaces.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;fd &lt;span class="nt"&gt;--type&lt;/span&gt; d &lt;span class="nt"&gt;--max-depth&lt;/span&gt; 1 &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FTS_ROOT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null | fzf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--height&lt;/span&gt; 30% &lt;span class="nt"&gt;--reverse&lt;/span&gt; &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Create '&lt;/span&gt;&lt;span class="nv"&gt;$sanitized&lt;/span&gt;&lt;span class="s2"&gt;' inside: "&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A second fzf, this time &lt;code&gt;--max-depth 1&lt;/code&gt;, to pick which top-level bucket it lands in. That's my taxonomy, but the idea generalises: a new directory needs a home, and picking one from a list beats typing a path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"confirm?Proceed? [y/N] "&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;read -q&lt;/code&gt; is zsh's single-keypress read — it returns 0 only on &lt;code&gt;y&lt;/code&gt;/&lt;code&gt;Y&lt;/code&gt;, and the text after the &lt;code&gt;?&lt;/code&gt; is the prompt. It's the last gate before anything is written. I'd keep it even though it costs a keystroke every time, because a fuzzy finder that silently creates directories on a typo is one you learn to distrust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: naming the session
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;session_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;' ./:'&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tmux session names can't contain &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;:&lt;/code&gt; — those are its own separators for windows and panes, so &lt;code&gt;mysession:dev&lt;/code&gt; already means something. Spaces are legal but painful to type in &lt;code&gt;-t&lt;/code&gt; targets. All three get mapped to &lt;code&gt;_&lt;/code&gt;, and &lt;code&gt;tr -s '_'&lt;/code&gt; squeezes runs down to one so &lt;code&gt;my..project&lt;/code&gt; doesn't become &lt;code&gt;my__project&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: the check that stops duplicate sessions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;tmux has-session &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMUX&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;tmux switch-client &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:main"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;tmux attach-session &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:main"&lt;/span&gt;
  &lt;span class="k"&gt;fi
  return
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the highest-value block in the function. &lt;code&gt;has-session&lt;/code&gt; exits 0 if a session by that name exists — check first, attach if it's there, build only if it isn't. &lt;code&gt;fts&lt;/code&gt; on a project I opened this morning is a two-keystroke return to exactly where I was. &lt;code&gt;fts&lt;/code&gt; on a fresh one builds it. Same command either way, and I never have to know which case I'm in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;$TMUX&lt;/code&gt; gotcha.&lt;/strong&gt; &lt;code&gt;$TMUX&lt;/code&gt; is set whenever you're inside a tmux session. If you are and you call &lt;code&gt;attach-session&lt;/code&gt;, you get &lt;strong&gt;nested tmux&lt;/strong&gt; — a session rendered inside a session, two status bars, and a prefix key that now goes to the wrong one. What you actually want from inside tmux is &lt;code&gt;switch-client&lt;/code&gt;, which moves your existing client to a different session.&lt;/p&gt;

&lt;p&gt;Every tmux helper script I've written has needed this branch eventually. If you write your own, put it in from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: building the layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux new-session &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; main

tmux new-window   &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;       &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; dev
tmux split-window &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:dev"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
tmux split-window &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:dev"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-pane&lt;/span&gt;  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:dev"&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt;

tmux &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="nt"&gt;-window&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two flags carry most of the weight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt; creates the session &lt;strong&gt;detached&lt;/strong&gt;, so all the setup happens off-screen. Without it you'd watch the panes get built.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c "$dir"&lt;/code&gt; sets the working directory for the session and every window/pane created with it. Without this everything starts in &lt;code&gt;$HOME&lt;/code&gt; and you &lt;code&gt;cd&lt;/code&gt; four times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The targeting syntax is &lt;code&gt;session:window&lt;/code&gt; — &lt;code&gt;"$session_name:dev"&lt;/code&gt; means "the window called &lt;code&gt;dev&lt;/code&gt; in this session". &lt;code&gt;split-window -h&lt;/code&gt; splits horizontally (side by side), &lt;code&gt;-v&lt;/code&gt; vertically (stacked). &lt;code&gt;select-pane -L&lt;/code&gt; moves focus to the pane on the &lt;strong&gt;L&lt;/strong&gt;eft.&lt;/p&gt;

&lt;p&gt;What you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─ main ────────────────┐   ┌─ dev ──────────┬──────────┐
│                       │   │                │  server  │
│      (NeoVim)         │   │   (editor /    ├──────────┤
│                       │   │    scratch)    │   logs   │
└───────────────────────┘   └────────────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;main&lt;/strong&gt; is one full-width pane — editor, or an agent, or whatever I'm actually doing. &lt;strong&gt;dev&lt;/strong&gt; is a big left pane with two stacked on the right: dev server top, tests or logs bottom.&lt;/p&gt;

&lt;p&gt;The last &lt;code&gt;select-window&lt;/code&gt; returns to &lt;code&gt;main&lt;/code&gt;, so &lt;code&gt;dev&lt;/code&gt; sits pre-arranged and waiting the first time I switch to it rather than needing setup mid-flow.&lt;/p&gt;

&lt;p&gt;I'm not proposing this layout — it's just the one I converged on. The value isn't the arrangement, it's that it's the &lt;em&gt;same&lt;/em&gt; arrangement every time, so my hands know where things are without looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: binding it to a key
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;fts&lt;/code&gt; still meant typing &lt;code&gt;fts&lt;/code&gt;. Three characters, but three characters at the shell prompt, competing with whatever I was half-typing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fts-widget&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;BUFFER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"fts"&lt;/span&gt;
  zle accept-line
&lt;span class="o"&gt;}&lt;/span&gt;
zle &lt;span class="nt"&gt;-N&lt;/span&gt; fts-widget
bindkey &lt;span class="s1"&gt;'^F'&lt;/span&gt; fts-widget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;zle&lt;/strong&gt;, the Zsh Line Editor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BUFFER&lt;/code&gt; is the current command line as a string. Assigning to it replaces whatever you'd typed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zle accept-line&lt;/code&gt; presses Enter for you.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zle -N fts-widget&lt;/code&gt; registers the function as a widget so it can be bound.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bindkey '^F' fts-widget&lt;/code&gt; maps &lt;code&gt;Ctrl-F&lt;/code&gt; to it. &lt;code&gt;^F&lt;/code&gt; is zsh notation for Ctrl-F.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Ctrl-F&lt;/code&gt; normally moves the cursor forward one character in emacs mode. I never used it — right arrow exists. Check what you're overwriting with &lt;code&gt;bindkey | grep '\^F'&lt;/code&gt; before you take a binding, but &lt;em&gt;do&lt;/em&gt; take one. The difference between a three-character command and a keystroke is the difference between a tool you use and a tool you use without thinking about it, and only the second kind actually changes anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bash version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;bind&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s1"&gt;'"\C-f": fts'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Nothing shows up in the picker."&lt;/strong&gt; &lt;code&gt;fd&lt;/code&gt; respects &lt;code&gt;.gitignore&lt;/code&gt; and skips hidden directories. If your projects live somewhere ignored, add &lt;code&gt;--hidden --no-ignore&lt;/code&gt; — you'll want a &lt;code&gt;--exclude .git&lt;/code&gt; alongside it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"sessions have weird truncated names."&lt;/strong&gt; &lt;code&gt;basename&lt;/code&gt; on a path with a trailing slash returns something you didn't expect. &lt;code&gt;${dir%/}&lt;/code&gt; strips it; add that if you're feeding paths in from elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"I get two status bars."&lt;/strong&gt; That's nested tmux — see the &lt;code&gt;$TMUX&lt;/code&gt; check in step 4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"&lt;code&gt;read -q&lt;/code&gt; : command not found."&lt;/strong&gt; You're in bash. Use &lt;code&gt;read -n 1 -p "Proceed? [y/N] " confirm&lt;/code&gt; and test &lt;code&gt;[[ $confirm == [yY] ]]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The preview is blank."&lt;/strong&gt; &lt;code&gt;eza&lt;/code&gt; isn't installed or isn't on &lt;code&gt;PATH&lt;/code&gt; inside fzf's subshell. Test with &lt;code&gt;eza --tree --level=2 .&lt;/code&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it yours
&lt;/h2&gt;

&lt;p&gt;The two things most worth changing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The root.&lt;/strong&gt; Set &lt;code&gt;FTS_ROOT&lt;/code&gt; in your &lt;code&gt;.zshrc&lt;/code&gt; before the function, or export it per-machine. If your projects aren't under a single tree, feed fzf multiple &lt;code&gt;fd&lt;/code&gt; calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt; fd &lt;span class="nt"&gt;--type&lt;/span&gt; d &lt;span class="nb"&gt;.&lt;/span&gt; ~/repo&lt;span class="p"&gt;;&lt;/span&gt; fd &lt;span class="nt"&gt;--type&lt;/span&gt; d &lt;span class="nt"&gt;--max-depth&lt;/span&gt; 2 &lt;span class="nb"&gt;.&lt;/span&gt; ~/work&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; | fzf ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The layout.&lt;/strong&gt; Everything in step 5 is just tmux commands. Want three windows? Add another &lt;code&gt;new-window&lt;/code&gt;. Want a server running the moment the session exists?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tmux send-keys &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$session_name&lt;/span&gt;&lt;span class="s2"&gt;:dev.{top-right}"&lt;/span&gt; &lt;span class="s1"&gt;'bun dev'&lt;/span&gt; C-m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;C-m&lt;/code&gt; is Enter. Note the &lt;code&gt;{top-right}&lt;/code&gt; rather than a pane number — tmux accepts positional pane targets, and they're worth using here because numeric indices depend on whether you've set &lt;code&gt;pane-base-index 1&lt;/code&gt; in your &lt;code&gt;tmux.conf&lt;/code&gt;. I have, so &lt;code&gt;dev.1&lt;/code&gt; on my machine is the left pane and on a default install it's the top right. Positional targets don't care.&lt;/p&gt;

&lt;p&gt;I don't actually do this — different projects have different start commands and I'd rather type it than have the wrong one fire — but it's one line if you want it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd still change
&lt;/h2&gt;

&lt;p&gt;Some honesty, since it's easy to write a post like this and imply the thing is finished:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session names collide on basename.&lt;/strong&gt; &lt;code&gt;~/repo/work/api&lt;/code&gt; and &lt;code&gt;~/repo/learning/api&lt;/code&gt; both want to be &lt;code&gt;api&lt;/code&gt;, and the second silently attaches to the first. I've been lucky rather than careful. The fix is to derive the name from the path relative to &lt;code&gt;FTS_ROOT&lt;/code&gt; — &lt;code&gt;work_api&lt;/code&gt;, &lt;code&gt;learning_api&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't know about git worktrees.&lt;/strong&gt; They show up as ordinary directories, so you can end up with a session per worktree and nothing in the name telling you which branch you're in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every project gets the same two windows.&lt;/strong&gt; A Go CLI and a Next.js app get identical treatment. A &lt;code&gt;.tmux-layout&lt;/code&gt; file it checked for would be the obvious next step; I keep not writing it because the generic layout has been fine.&lt;/p&gt;

&lt;p&gt;None of these have cost me enough to fix, which is worth stating plainly. This is a tool that's been good enough for a long time, not one that's been polished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;If you don't want &lt;code&gt;fts&lt;/code&gt; specifically, take the shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Find the friction that isn't hard, just repeated.&lt;/strong&gt; Mine was four commands and a remembered path. It never registered as a problem because no single instance was one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the right thing automatic, not just fast.&lt;/strong&gt; The &lt;code&gt;has-session&lt;/code&gt; check is the most valuable line here and it has nothing to do with speed — it makes the correct behaviour the one that takes no thought.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle the miss.&lt;/strong&gt; &lt;code&gt;--print-query&lt;/code&gt; turned "you typed something that doesn't exist" from an error into a feature. Plenty of tools have a branch like that sitting unused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind it.&lt;/strong&gt; An unbound tool is one you have to decide to use.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole thing lives in my &lt;code&gt;.zshrc&lt;/code&gt; between an alias block and a function that opens the current repo's PR page in a browser. No tests, no repo, no README. Best return-on-effort code I've written this year, and it took an afternoon.&lt;/p&gt;

&lt;p&gt;Not everything has to be a project.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you build a version of this, I'd genuinely like to see it — especially the layout section, since that's the part everyone does differently.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>workstations</category>
      <category>tooling</category>
      <category>development</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Did I really build this?</title>
      <dc:creator>Kamil</dc:creator>
      <pubDate>Sat, 01 Aug 2026 17:14:37 +0000</pubDate>
      <link>https://dev.to/kameleon21/did-i-really-build-this-1c4p</link>
      <guid>https://dev.to/kameleon21/did-i-really-build-this-1c4p</guid>
      <description>&lt;p&gt;Lately I've been thinking about the things we build with AI. And I keep landing on the same question. Did I really build this? Or did I just foresee it being built?&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 1 — Confusion
&lt;/h2&gt;

&lt;p&gt;The steps are always the same. I get an idea, or I see something online and think "I bet I could make that, or at least put my own spin on it." New project, Neovim, agent pane on the side.&lt;/p&gt;

&lt;p&gt;And then I sit there.&lt;/p&gt;

&lt;p&gt;I stare at the empty screen and the blinking cursor for a while, and eventually I give in and explain the idea to Claude Code, and watch it try to build something out of a prompt I hadn't really thought through yet.&lt;/p&gt;

&lt;p&gt;While it Ponders and Ruminates, I get this vague feeling. Am I the creator of this project? Can I say it's mine? I'm in the passenger seat doing the directing, sure. Making the calls on what goes in and what stays out. But at what point do I stop being the creator, or even the orchestrator, and start being the approver of a machine's output?&lt;/p&gt;

&lt;p&gt;Some of you will say that's just the nature of coding with AI now. Less about writing code, more about understanding the system behind it. Fair enough. An agent writes more code in a minute than I could in thirty and I'm not going to sit here and argue with that.&lt;/p&gt;

&lt;p&gt;I started learning to code at a funny time. I was working my way through The Odin Project, getting to grips with web development, and ChatGPT was released at the end of that same year. The ground shifted while I was still figuring out the basics.&lt;/p&gt;

&lt;p&gt;I remember being away on holiday, four hours into a flight with no internet, working on a Christmas-themed programming assignment. I spent the whole flight trying to simulate the mechanics of falling snowballs and get the engine working under the hood, while also questioning why our professor had us learning Java in BlueJ of all things. Still haven't figured that one out.&lt;/p&gt;

&lt;p&gt;I remember that project to this day. The frustration and the satisfaction in equal measure. Five years later I can still recall the exact feeling of getting it to behave the way I wanted, and how completely mine it felt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 2 — Connection
&lt;/h2&gt;

&lt;p&gt;That flight wouldn't be a problem now. It would be a Wi-Fi purchase.&lt;/p&gt;

&lt;p&gt;I've shipped more in the last year than in the two or three before it combined. Things that used to sit in a "someday" note get built on a Saturday. And most of them work. I want to be clear that this isn't a complaint about quality, because the code is usually fine. Sometimes it's better than what I'd have written on my own.&lt;/p&gt;

&lt;p&gt;But I notice I don't feel much about them.&lt;/p&gt;

&lt;p&gt;For a while I assumed that was about understanding. That if I read the diffs properly, if I could hold the whole system in my head the way I used to, the feeling would come back. So I tried it. Reviewed one project line by line like I was being paid to do it, and by the end I understood it completely.&lt;/p&gt;

&lt;p&gt;It still felt like something I'd found rather than something I'd made.&lt;/p&gt;

&lt;p&gt;So it wasn't understanding I was missing. It was connection to the thing. And the connection came from the hours.&lt;/p&gt;

&lt;p&gt;I keep coming back to that phrase, hours in the saddle. Pilots log them. Riders log them. Nobody logs how much of the manual they've read, because reading the manual isn't the thing. The hours are the thing. You put the time in and slowly you stop thinking about the machine and start thinking through it.&lt;/p&gt;

&lt;p&gt;That's what those four hours on the plane bought me. Not knowledge about falling snowballs, which honestly I could have got off one page of documentation. A feel for that specific program. By the end of the flight I knew where everything was because I'd put it there. I knew which part was held together with tape because I was the one who taped it. When something broke I didn't really debug it so much as reach for it, the way you reach for a light switch in your own house in the dark.&lt;/p&gt;

&lt;p&gt;I don't have that with the projects I've built this year. I know where the files are because I looked. I know how the auth flow works because I read it back. It's all correct and none of it is in my hands.&lt;/p&gt;

&lt;p&gt;And it isn't credit I'm after, or authorship, nothing that grand. It's just the ordinary familiarity that comes from spending long enough with something that it stops being separate from you.&lt;/p&gt;

&lt;p&gt;You can be handed an understanding of a system. Nobody can hand you a feel for one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 3 — Now what
&lt;/h2&gt;

&lt;p&gt;I don't have a good ending for this, which is part of why I wanted to write it down.&lt;/p&gt;

&lt;p&gt;The obvious answer is to go back to doing it all by hand, and I don't buy that. I've seen what I get done now compared to three years ago and I'm not giving that up to feel more authentic about it. The hours weren't precious in themselves either. Plenty of them went on things I'd happily never do again.&lt;/p&gt;

&lt;p&gt;So I've been trying things instead. They're all the same question asked in different ways: what do I keep for myself.&lt;/p&gt;

&lt;p&gt;Writing the first version myself and letting the agent take it from there, on the theory that the early hours are the ones that stick. Picking one part of each project, the part I actually care about, and refusing to hand it over. Going slower on the things I intend to keep and fast on the things I don't. Going back to something I don't recognise and rewriting a piece of it by hand just to get dirty in it, even though the code was fine as it was.&lt;/p&gt;

&lt;p&gt;Some of that helps. Though I can't tell yet whether it helps because it works, or because it makes me feel better, and I'm suspicious of anything I can't tell that about.&lt;/p&gt;

&lt;p&gt;Maybe the feeling doesn't come back in the same form. Maybe it moves somewhere else, into problems rather than programs. That would be fine, I think. I'd just like to know.&lt;/p&gt;

&lt;p&gt;For now, the honest answer to did I really build this is: some of them, yes. Some of them I watched get built.&lt;/p&gt;

&lt;p&gt;I'm still working out how to tell which is which before I start, rather than a month after I've finished.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>writing</category>
    </item>
  </channel>
</rss>
