<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>zatsepin.dev / essays</title><link>https://zatsepin.dev/essays/</link><description>Essays and long-form writing</description><language>en</language><lastBuildDate>Mon, 17 Aug 2026 14:25:26 GMT</lastBuildDate><item><title>Build my own Skitch in 2026 #1</title><link>https://zatsepin.dev/essays/4ujepfen8t06183?lang=en</link><guid isPermaLink="true">https://zatsepin.dev/essays/4ujepfen8t06183?lang=en</guid><pubDate>Mon, 17 Aug 2026 11:56:28 GMT</pubDate><description><![CDATA[Yura Zatsepin recreates Evernote&apos;s magical image search locally using open-source AI models (semantic embeddings and color clustering) without cloud services, proving modern tech makes it cheap and easy.]]></description><content:encoded><![CDATA[<article no-shadow="" class="prose max-w-none prose-stone mx-auto drop-cap"><div class="markdown-styles" data-hash="1pd92os"><style>:host{display:block;position:relative;contain:content;}:host([hidden]){display:none;}</style><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11/styles/github.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11/styles/github-dark.min.css" media="(prefers-color-scheme:dark)"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0/dist/katex.min.css"></div><div class="markdown-body" data-hash="bzqtc6"><p><strong>Fifteen Years Ago, I Became an Evernote User</strong></p>
<p>Fifteen years ago, I became an Evernote user. A happy time, when my thoughts were all about startups, Silicon Valley, and technology. And although today's Obsidian evangelists are absolutely certain that they are the first to create a second brain, allow me to say: I was building my second brain in the 2010s using Evernote and Moleskine. And Evernote's OCR search through images and documents was absolutely magical. It's no coincidence, apparently, that before that, engineers had been working on OCR solutions for the US government.</p>
<p>This article is about how you can build a local search for images and photos that will work without large corporate services, without LLM/SaaS, and without sending personal data outside.</p>
<p><strong>What Evernote Meant to Me</strong></p>
<p>Of course, as is the case now, behind a fairly simple but good product there must be something more. An idea or distribution. After all, it's a note-taking service. For me, it was the idea that overseas, a bearded Russian guy, Stepan Pachikov, a tech guy, took it, built it, and made some crazy money from it. I used it, recommended it to friends, participated in his hackathons, and even won prizes there.</p>
<p>Of all of Evernote's features, only three were the most valuable in my opinion. But it was precisely these that kept me as a user until 2022 — the year I abandoned SaaS services from large companies. More than ten years of subscription...</p>
<p>So, what were these anchor features:</p>
<ol>
<li><p><strong>Search through documents, images, and photos</strong></p>
<p>An incredibly useful thing. It's clear that with proper tag and folder organization, you can find any, even the oldest, note or document just by navigating through some system. But if you add very accurate search to the system, the process becomes much more pleasant. I had many medical documents that were very easy to find by keywords present in the image or scan. And I didn't have to manually perform preliminary indexing and organization just for the sake of organization. Just adding one tag — and the OCR search did its job. Yes, at that time the search was indeed just OCR. But we'll come back to this.</p>
</li>
<li><p><strong>Alarm on notes</strong></p>
<p>I had several successful use cases: the alarms would go off a couple of times a year and helped me not miss the right moment, bringing back the context of what I needed to do. Now I use email for this.</p>
</li>
<li><p><strong>Skitch (screenshot annotator)</strong></p>
<p>In my profession, I need to communicate with other people, showing certain elements of a product and leaving comments on them. Similar services existed and still exist. The Linux alternative still can't draw normal arrows.</p>
</li>
</ol>
<p>Recently, while browsing through screenshots on my PC in search of one specific image, I thought how cool it would be to have search like in Evernote. And yes, I know that there are and have been many similar solutions. Not only commercial products but also open-source ones — just search on GitHub. But all of them, as always, are "not the same," "the old one was better." Although, of course, it wasn't actually better; the search in Evernote worked on the OCR principle. Obviously, since the company still exists, I dare assume that it has been improved to hybrid search. That is, a combination of OCR (search by text recognized from an image) and semantic search, i.e., search by embeddings obtained from the image using some model.
We'll be talking about small models that can be run on ordinary user hardware and have a very limited focus on what they do.</p>
<h4 id="what-do-i-want-from-image-search">What Do I Want from Image Search?</h4>
<p>If an image shows a cat or a metal grate, I would like to be able to write that query and get similar images. I would really like to search for images by color palette. You might ask why, but it's incredibly convenient. In my use cases, when I remember the design of a service clearly, searching for "profile form" might not help, but "purple color + profile form" will find the result. I want to search using not only English text but also Russian in queries. So that I can write "metal grate" or "металлический люк" and get similar results. Of course, the search should also account for text on the image if there's a lot of it, as well as the meaning of that text. For example, I took a screenshot of a YouTube recipe for pizza dough, which was written with a marker on a board. The text in the image contains the words water, flour, and salt. Ideally, I would want to find it by the query "pizza recipe" or just "recipe." Also, the search should be fast. Okay, I'm willing to wait for long indexing and preliminary analysis of the image database, but the search itself should be near-instantaneous. So, is it possible to do this using modern models without using external services and APIs?</p>
<h4 id="step-1-semantic-search">Step 1. Semantic Search.</h4>
<p>Or search by image embeddings and the embedding obtained from the user's text query.
To solve this, we need a multimodal embedding model, preferably with an MIT/Apache license. And there is one: Nomic Embed Vision. Like any trendy transformer-based model with a BERT architecture. The beauty of this model for our engineering task is that text and images as input produce vectors from the same space. We can search for text by image, image by image, or image by text. A downside is that Russian language queries don't work optimally, but we'll fix that too, later.</p>
<p>Let's figure out how I'm going to test the quality of the models adopted in the search pipeline. I'll do it using a qualitative method — whether I like it or not — on my local folder with screenshots. There are about 500 of them in total. The global goal is to feed my entire knowledge base, notes, screenshots, and references, but for now, we'll limit ourselves to searching through screenshots. I had a list of images that I wanted to find in my screenshot folder. And the search should find them. No complex benchmarks or methodology, I just need to enjoy the search.
So, we take Rust, take the ONNX Nomic Vision models, and see what happens.<img src="https://zatsepin.dev/api/files/pbc_pages/4ujepfen8t06183/lubo_1785192719485729924_08y66ee37h.png" alt=""></p>
<p>The very first result worked perfectly. Of course, it was a coincidence, but a fortunate one. I had a screenshot from a YouTube video on how to weld a manhole cover in a parking lot so that the management company could bury a pump there. Finding this image was instantaneous, having only Semantic search with a single model at hand.
The embeddings themselves can be stored in any database with vector support and search capabilities. SQLite has a plugin, and there's also LanceDB. They are ideal because they don't have excessive complexity in support and interaction. Remember, SQLite is installed on practically every smartphone in the world and is an absolutely bulletproof database.</p>
<p>The only problem was with queries in Russian. I tackled this head-on and selected a small model for translating between Russian and English.
<a href="https://huggingface.co/Teradata/opus-mt_tiny_rus-eng">https://huggingface.co/Teradata/opus-mt_tiny_rus-eng</a>
Queries started working significantly better. Of course, we lose some quality with each such step. If the model were already multilingual, it would be better, but we're taking the simple path. Training a similar model would require not only a stack of 16+ H100/H200 GPUs but also extensive work with datasets, which is unacceptable for my task. However, with modest effort (by lengthening the model pipeline), the target result improved significantly, and it became possible to search for a metallic object using the word "метал" (metal) as well as "metal."</p>
<h4 id="step-2-search-by-colors">Step 2. Search by Colors.</h4>
<p>This is my long-standing dream of how I would like to search through my library of image references. And colors fit perfectly into a vector form. To implement this search, we need to do two things. First, extract dominant colors or a kind of palette from the image. Second, encode this color palette as a vector. Then we can simply search for similar vectors, exactly as I did before with embeddings from the Nomic Vision model. To extract the palette, you can use different algorithms, but as I like to approach such tasks, I start with the simplest solutions that can yield a significantly good result in ML. There's k-means clustering, and that's what I used. There are several good libraries for Rust; I used <a href="https://crates.io/crates/kmeans_colors">https://crates.io/crates/kmeans_colors</a>.
For each image, k-means extracts 5 dominant colors in the Lab color space, concatenates them into a single flat vector, and indexes them in LanceDB. When a user searches simultaneously by text and color, the system first performs a semantic search (vector or hybrid), and then re-ranks the results based on a combined score. In this score, semantic relevance is primary, and color proximity is a refining factor.</p>
<p>Let's test the color search.</p>
<p><img src="https://zatsepin.dev/api/files/pbc_pages/4ujepfen8t06183/screenshot_from_2026_07_25_21_09_38_glrt2nl8i6.png" alt=""></p>
<p>Bam! It works, of course, in combination with semantic search. For example, I want to search for login UX screenshots. And I remember that the site I'm looking for is orange or red.</p>
<p><img src="https://zatsepin.dev/api/files/pbc_pages/4ujepfen8t06183/screenshot_from_2026_07_25_21_12_07_nlpwqda8rc.png" alt=""></p>
<p>Works like a charm. Exactly what I wanted. In the my next youtube video, I'll talk about what else can be added to this pipeline to make it even better. Let me remind you that the search already works absolutely magically, and we haven't even done OCR yet — we haven't recognized the text on the images and added it to so-called hybrid search.</p>
<p>More than 20 years have passed since work on Evernote began. If at the beginning of this SaaS journey, image search technology was indeed a formidable weapon against competitors, over time the technology has become very cheap to develop and implement. Yes, of course, Tesseract OCR became open-source decades ago too. But it's like heaven and earth compared to what we can now run locally on a user's machine and with what level of quality.</p>
<p>We are supposedly supposed to be entering an era of thousands and thousands of new applications and services that can solve old problems much more simply, quickly, and better. LLMs seem to have solved the software problem and left only the problem of the idea. But where are these thousands of new startups?</p>
<p>P.S.</p>
<p>You can try the result by downloading the application via the <a href="https://zatsepin.dev/lubo">link</a>. The application is currently only available for macOS, Linux and Windows. Battle tested only on Linux yet.. I am continuing to work on it right now and don't yet see what will become of it. For now, a working idea has emerged: to create my own Skitch. But with entirely local models for search and image processing. If you're interested, you can subscribe at <a href="https://zatsepin.dev/subscribe">https://zatsepin.dev/subscribe</a> or on YouTube at <a href="https://www.youtube.com/@yurizatsepin">https://www.youtube.com/@yurizatsepin</a></p>
</div>
      <script type="text/markdown">
        
**Fifteen Years Ago, I Became an Evernote User**

Fifteen years ago, I became an Evernote user. A happy time, when my thoughts were all about startups, Silicon Valley, and technology. And although today's Obsidian evangelists are absolutely certain that they are the first to create a second brain, allow me to say: I was building my second brain in the 2010s using Evernote and Moleskine. And Evernote's OCR search through images and documents was absolutely magical. It's no coincidence, apparently, that before that, engineers had been working on OCR solutions for the US government.

This article is about how you can build a local search for images and photos that will work without large corporate services, without LLM/SaaS, and without sending personal data outside.

**What Evernote Meant to Me**

Of course, as is the case now, behind a fairly simple but good product there must be something more. An idea or distribution. After all, it's a note-taking service. For me, it was the idea that overseas, a bearded Russian guy, Stepan Pachikov, a tech guy, took it, built it, and made some crazy money from it. I used it, recommended it to friends, participated in his hackathons, and even won prizes there.

Of all of Evernote's features, only three were the most valuable in my opinion. But it was precisely these that kept me as a user until 2022 — the year I abandoned SaaS services from large companies. More than ten years of subscription...

So, what were these anchor features:

1.  **Search through documents, images, and photos**
    
    An incredibly useful thing. It's clear that with proper tag and folder organization, you can find any, even the oldest, note or document just by navigating through some system. But if you add very accurate search to the system, the process becomes much more pleasant. I had many medical documents that were very easy to find by keywords present in the image or scan. And I didn't have to manually perform preliminary indexing and organization just for the sake of organization. Just adding one tag — and the OCR search did its job. Yes, at that time the search was indeed just OCR. But we'll come back to this.
    
2.  **Alarm on notes**
    
    I had several successful use cases: the alarms would go off a couple of times a year and helped me not miss the right moment, bringing back the context of what I needed to do. Now I use email for this.
    
3.  **Skitch (screenshot annotator)**
    
    In my profession, I need to communicate with other people, showing certain elements of a product and leaving comments on them. Similar services existed and still exist. The Linux alternative still can't draw normal arrows.

Recently, while browsing through screenshots on my PC in search of one specific image, I thought how cool it would be to have search like in Evernote. And yes, I know that there are and have been many similar solutions. Not only commercial products but also open-source ones — just search on GitHub. But all of them, as always, are "not the same," "the old one was better." Although, of course, it wasn't actually better; the search in Evernote worked on the OCR principle. Obviously, since the company still exists, I dare assume that it has been improved to hybrid search. That is, a combination of OCR (search by text recognized from an image) and semantic search, i.e., search by embeddings obtained from the image using some model.
We'll be talking about small models that can be run on ordinary user hardware and have a very limited focus on what they do.

#### What Do I Want from Image Search?

If an image shows a cat or a metal grate, I would like to be able to write that query and get similar images. I would really like to search for images by color palette. You might ask why, but it's incredibly convenient. In my use cases, when I remember the design of a service clearly, searching for "profile form" might not help, but "purple color + profile form" will find the result. I want to search using not only English text but also Russian in queries. So that I can write "metal grate" or "металлический люк" and get similar results. Of course, the search should also account for text on the image if there's a lot of it, as well as the meaning of that text. For example, I took a screenshot of a YouTube recipe for pizza dough, which was written with a marker on a board. The text in the image contains the words water, flour, and salt. Ideally, I would want to find it by the query "pizza recipe" or just "recipe." Also, the search should be fast. Okay, I'm willing to wait for long indexing and preliminary analysis of the image database, but the search itself should be near-instantaneous. So, is it possible to do this using modern models without using external services and APIs?

#### Step 1. Semantic Search.
Or search by image embeddings and the embedding obtained from the user's text query.
To solve this, we need a multimodal embedding model, preferably with an MIT/Apache license. And there is one: Nomic Embed Vision. Like any trendy transformer-based model with a BERT architecture. The beauty of this model for our engineering task is that text and images as input produce vectors from the same space. We can search for text by image, image by image, or image by text. A downside is that Russian language queries don't work optimally, but we'll fix that too, later.

Let's figure out how I'm going to test the quality of the models adopted in the search pipeline. I'll do it using a qualitative method — whether I like it or not — on my local folder with screenshots. There are about 500 of them in total. The global goal is to feed my entire knowledge base, notes, screenshots, and references, but for now, we'll limit ourselves to searching through screenshots. I had a list of images that I wanted to find in my screenshot folder. And the search should find them. No complex benchmarks or methodology, I just need to enjoy the search.
So, we take Rust, take the ONNX Nomic Vision models, and see what happens.![](https://zatsepin.dev/api/files/pbc_pages/4ujepfen8t06183/lubo_1785192719485729924_08y66ee37h.png)


The very first result worked perfectly. Of course, it was a coincidence, but a fortunate one. I had a screenshot from a YouTube video on how to weld a manhole cover in a parking lot so that the management company could bury a pump there. Finding this image was instantaneous, having only Semantic search with a single model at hand.
The embeddings themselves can be stored in any database with vector support and search capabilities. SQLite has a plugin, and there's also LanceDB. They are ideal because they don't have excessive complexity in support and interaction. Remember, SQLite is installed on practically every smartphone in the world and is an absolutely bulletproof database.

The only problem was with queries in Russian. I tackled this head-on and selected a small model for translating between Russian and English.
https://huggingface.co/Teradata/opus-mt_tiny_rus-eng
Queries started working significantly better. Of course, we lose some quality with each such step. If the model were already multilingual, it would be better, but we're taking the simple path. Training a similar model would require not only a stack of 16+ H100/H200 GPUs but also extensive work with datasets, which is unacceptable for my task. However, with modest effort (by lengthening the model pipeline), the target result improved significantly, and it became possible to search for a metallic object using the word "метал" (metal) as well as "metal."

#### Step 2. Search by Colors.

This is my long-standing dream of how I would like to search through my library of image references. And colors fit perfectly into a vector form. To implement this search, we need to do two things. First, extract dominant colors or a kind of palette from the image. Second, encode this color palette as a vector. Then we can simply search for similar vectors, exactly as I did before with embeddings from the Nomic Vision model. To extract the palette, you can use different algorithms, but as I like to approach such tasks, I start with the simplest solutions that can yield a significantly good result in ML. There's k-means clustering, and that's what I used. There are several good libraries for Rust; I used https://crates.io/crates/kmeans_colors.
For each image, k-means extracts 5 dominant colors in the Lab color space, concatenates them into a single flat vector, and indexes them in LanceDB. When a user searches simultaneously by text and color, the system first performs a semantic search (vector or hybrid), and then re-ranks the results based on a combined score. In this score, semantic relevance is primary, and color proximity is a refining factor.

Let's test the color search.


![](https://zatsepin.dev/api/files/pbc_pages/4ujepfen8t06183/screenshot_from_2026_07_25_21_09_38_glrt2nl8i6.png)

Bam! It works, of course, in combination with semantic search. For example, I want to search for login UX screenshots. And I remember that the site I'm looking for is orange or red.

![](https://zatsepin.dev/api/files/pbc_pages/4ujepfen8t06183/screenshot_from_2026_07_25_21_12_07_nlpwqda8rc.png)

Works like a charm. Exactly what I wanted. In the my next youtube video, I'll talk about what else can be added to this pipeline to make it even better. Let me remind you that the search already works absolutely magically, and we haven't even done OCR yet — we haven't recognized the text on the images and added it to so-called hybrid search.

More than 20 years have passed since work on Evernote began. If at the beginning of this SaaS journey, image search technology was indeed a formidable weapon against competitors, over time the technology has become very cheap to develop and implement. Yes, of course, Tesseract OCR became open-source decades ago too. But it's like heaven and earth compared to what we can now run locally on a user's machine and with what level of quality.

We are supposedly supposed to be entering an era of thousands and thousands of new applications and services that can solve old problems much more simply, quickly, and better. LLMs seem to have solved the software problem and left only the problem of the idea. But where are these thousands of new startups?

P.S.

You can try the result by downloading the application via the [link](https://zatsepin.dev/lubo). The application is currently only available for macOS, Linux and Windows. Battle tested only on Linux yet.. I am continuing to work on it right now and don't yet see what will become of it. For now, a working idea has emerged: to create my own Skitch. But with entirely local models for search and image processing. If you're interested, you can subscribe at https://zatsepin.dev/subscribe or on YouTube at https://www.youtube.com/@yurizatsepin


      </script>
    </article>]]></content:encoded></item><item><title>How to Run a GPU Runtime for Google Collab Using Terraform</title><link>https://zatsepin.dev/essays/0ipfipxovlxbu5m?lang=en</link><guid isPermaLink="true">https://zatsepin.dev/essays/0ipfipxovlxbu5m?lang=en</guid><pubDate>Wed, 25 Feb 2026 15:32:32 GMT</pubDate><description><![CDATA[How to Connect an External GPU to Google Colab.

Using OpenTofu: Automate Jupyter Notebook Deployment on a Remote Server with Port Forwarding and Access Setup.
]]></description><content:encoded><![CDATA[]]></content:encoded></item><item><title>JJ: Why Git Is Outdated for AI-Assisted Development</title><link>https://zatsepin.dev/essays/lz7ogvv1zowmcqw?lang=en</link><guid isPermaLink="true">https://zatsepin.dev/essays/lz7ogvv1zowmcqw?lang=en</guid><pubDate>Wed, 18 Feb 2026 20:58:01 GMT</pubDate><description><![CDATA[JJ: Why Git Is Outdated for AI-Assisted Development — An introduction to Jujutsu (JJ), a version control system that sits on top of Git, designed to solve Git&apos;s usability problems and provide better protection against AI agent mishaps. Covers core concepts like undo/redo operations, commit management, and practical examples of splitting, squashing, and editing commits, while maintaining full Git compatibility.]]></description><content:encoded><![CDATA[<article class="prose max-w-none prose-stone mx-auto drop-cap"><div class=markdown-styles data-hash=1pd92os><style>:host {
        display: block;
        position: relative;
        contain: content;
      }
      :host([hidden]) {
        display: none;
      }</style><link href=https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css rel=stylesheet><link href=https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11/styles/github.min.css rel=stylesheet><link href=https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11/styles/github-dark.min.css media=(prefers-color-scheme:dark) rel=stylesheet><link href=https://cdn.jsdelivr.net/npm/katex@0/dist/katex.min.css rel=stylesheet></div><div class=markdown-body data-hash=69s942><p>Imagine this: your Claude Code or Cursor AI agent deletes a file or starts rewriting it from scratch. The file contents haven't been saved in a git commit yet—it's still in an unstaged state. Everything is gone. All you can do is hope the AI agent can reconstruct something from memory and checkpoints, but these are workarounds at best—and you might miss the moment.<p>When I switched from pure Git to JJ on top of Git, this scenario became impossible. I became much more confident about my code while working with AI agents. Let me show you why JJ will improve your life as a programmer and how to integrate it into your workflow without breaking your team's existing processes.<h2 id=why-git-doesnt-handle-parallel-development>Why Git Doesn't Handle Parallel Development</h2><p>Git is fundamentally outdated for systems where code is being written in parallel with you on your machine.<p>Git's paradigm is simple: you collect some changes locally, put them in a box, label that box with a hash identifier, and send it through a pipeline of boxes. That box seems immutable to you—you'd rather not touch it again, except maybe to read what was done in it. If something happened before you assembled the box, you just write it again. Usually you haven't gone far, and what could go wrong anyway?<p>In gaming terms, git commits are like manual saves you make by hand. But you don't have autosave.<h3 id=the-underlying-tension>The Underlying Tension</h3><p>For years, this concept worked brilliantly. Though we must admit, Git's interface for all its possibilities has always been quite complex. The ongoing struggle with how commit history looks across different companies suggests something might not be optimal in this system.<p><strong>JJ (Jujutsu)</strong> is designed to solve Git's usability problems while maintaining full compatibility with existing repositories and workflows.<p>JJ is a version control system that sits on top of Git, providing a much more intuitive interface and powerful features for managing your code history—especially when working with AI agents.<p>Let me show you three core concepts that make JJ powerful:<ul><li>JJ log operations: undo and redo<li>Working copy as a commit<li>Advanced operations: squash, split, edit</ul><h3 id=part-1-jj-operations-with-undoredo>Part 1: JJ Operations with Undo/Redo</h3><p>Let's set up your first JJ project:<pre><code class="hljs language-sh"><span class=hljs-built_in>mkdir</span> jj-step-0 && <span class=hljs-built_in>cd</span> jj-step-0 && jj git init
<span class=hljs-built_in>touch</span> README.md
<span class=hljs-built_in>echo</span> <span class=hljs-string>"first try"</span> >> README.md
jj diff
</code></pre><details><summary>Screenshot (jj diff)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/first_try_jj_diff_vlpobuic1g.png></p></details><p>If you run the command <code>jj log</code>, you'll see that you already have a Change ID and Commit ID. On the left is the Change ID, on the right is the Commit ID.<p>Change ID is a permanent identifier of the logical task you're working on. Unlike a Git hash (which in jj is called Commit ID), Change ID doesn't change when you edit files, change descriptions, etc. Change ID never changes—even if you do <code>amend</code>, <code>rebase</code>, or edit a commit message. The main commands <code>jj edit</code>, <code>jj describe</code> are performed on the selected Change ID.<p>Change ID is a random 16-byte number, represented as a string of 12 characters from z-k. This makes it look unique and easy to read.<p>In jj, the working directory is always a "live" commit. Any change to files immediately becomes part of the current revision. The working copy is marked with the <code>@</code> symbol in the output of <code>jj log</code>. The commit hash will change with each edit you make in the project. That's why it's not so important during work.<div class="markdown-alert markdown-alert-tip"><p class=markdown-alert-title><svg class="octicon octicon-light-bulb mr-2" viewbox="0 0 16 16" aria-hidden=true height=16 width=16><path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"></path></svg>Tip<ul><li>Add a description to this commit (via jj describe).<li>Create the next empty commit on top (jj new) to "close" the current one.<li>Check the status of files to make sure everything is tracked (jj status).</ul></div><pre><code class="hljs language-sh">jj commit -m <span class=hljs-string>"First"</span>
jj
</code></pre><details><summary>Screenshot (jj first commit)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/jj_first_commit_28kazk42lv.png></p></details><p>Our first commit is created. And at this point there are no changes in the new working directory, so it's time to start writing a feature with the help of an AI agent.<pre><code class="hljs language-sh"><span class=hljs-built_in>echo</span> <span class=hljs-string>"Cool staff"</span> >> README.md
<span class=hljs-built_in>touch</span> new-file.md && <span class=hljs-built_in>echo</span> <span class=hljs-string>"One more thing"</span> >> new-file.md
jj diff
</code></pre><details><summary>Screenshot (jj diff with changes)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/cool_staff_one_more_thing_juljg6ooid.png></p></details><p>Something terrible happens: your AI agent decides to rewrite new-file.md from scratch and deletes the original content, replacing it with something strange.<p>Let's simulate what the AI did:<pre><code class="hljs language-sh"><span class=hljs-built_in>rm</span> new-file.md
<span class=hljs-built_in>touch</span> new-file.md
<span class=hljs-built_in>echo</span> <span class=hljs-string>"Bad staff"</span> >> new-file.md
</code></pre><details><summary>Screenshot (bad changes by AI)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/bad_staff_by_ai_kdnpl9b48r.png></p></details><p>What now? We're not going to beg the AI to restore our work. Instead, let's see what JJ has saved:<pre><code class="hljs language-sh">jj evolog --color=always -p
</code></pre><details><summary>Screenshot (jj evolog)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/evolog_jj_q2n79dmph9.png></p></details><p>Good! JJ maintains a history of all changes in the working commit (there's no unstaged here), as well as a history of all operations you've performed. Each modification, deletion, or addition is a separate operation with its own hash. Now let's undo what the AI did:<pre><code class="hljs language-sh">jj undo
jj diff
</code></pre><details><summary>Screenshot (jj undo)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/undo_jj_3fqbfdc06o.png></p></details><p>Done. And our "Cool staff" is back in place. If we realize the AI was right, we can go back with:<pre><code class="hljs language-sh">jj redo
</code></pre><p><strong>Important</strong>: <code>undo</code> and <code>redo</code> operate on operations, not file changes. If you made a commit and realized you need to add something else, just use <code>jj undo</code>.<h4 id=key-jj-commands-for-operations>Key JJ Commands for Operations</h4><table><thead><tr><th>Task<th>Command<tbody><tr><td>See how a change evolved<td><code>jj evolog -p</code><tr><td>Undo last operation<td><code>jj undo</code><tr><td>Redo after undo<td><code>jj redo</code><tr><td>View operation history<td><code>jj op log</code><tr><td>Restore specific state<td><code>jj op restore &lt;operation-id></code><tr><td>Compare two versions<td><code>jj op diff --from &lt;a> --to &lt;b> --git</code></table><h3 id=part-2-managing-commits-and-history>Part 2: Managing Commits and History</h3><p>Now let's explore how JJ simplifies managing commit history and messages. Let's start a new project:<pre><code class="hljs language-sh"><span class=hljs-built_in>cd</span> .. && <span class=hljs-built_in>mkdir</span> jj-step-1 && <span class=hljs-built_in>cd</span> jj-step-1 && jj git init
</code></pre><p>Let's work on three features in the project:<pre><code class="hljs language-sh"><span class=hljs-built_in>touch</span> file.md
<span class=hljs-built_in>echo</span> <span class=hljs-string>"Feat 1"</span> >> file.md
<span class=hljs-built_in>echo</span> <span class=hljs-string>"Feature 2"</span> >> file.md
jj commit -m <span class=hljs-string>"Feat 1 & 2"</span>

<span class=hljs-built_in>touch</span> file2.md
<span class=hljs-built_in>echo</span> <span class=hljs-string>"Feature 3"</span> >> file2.md
jj commit -m <span class=hljs-string>"Feat 3"</span>
</code></pre><details><summary>Screenshot (feat 3)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/feat_3_rqscdaqsxo.png></p></details><p>Wait, you realize you should be writing "Feature" everywhere, not "Feat" in your code. The new company rules require it—did you miss that? We need to go back and fix the first commit. With JJ this will be incredibly simple.<pre><code class="hljs language-sh">jj edit r
</code></pre><details><summary>Screenshot (jj edit r)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/edit_r_sqojhoqcr3.png></p></details><p>Notice we're using just the first letter <code>r</code> instead of the full Change ID value. JJ uses "minimally sufficient" commit references—just enough characters to uniquely identify a revision without collisions in your current project context. As your project grows, more characters will be needed.<p>Now we're "inside" the first commit. We can modify file.md as needed and change the commit message with:<pre><code class="hljs language-sh">jj describe
</code></pre><details><summary>Screenshot (jj describe)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/jj_describe_j1fs5fjmfo.png></p></details><h4 id=splitting-commits>Splitting Commits</h4><p>Since we're here, let's split the first commit into two separate commits—one for each feature. Well, you shouldn't do different tasks in one commit. This is incredibly simple with:<pre><code class="hljs language-sh">jj <span class=hljs-built_in>split</span>
</code></pre><p>Press 'F' to unfold all changes, select "Feature 1" and confirm with 'c'. Set your commit message to "Feature 1", save, and set another message for the second commit. You should now have a clean history.<details><summary>Screenshot (jj split)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/jj_split_ydc0pevu7q.png></p></details><details><summary>Screenshot (jj split success)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/jj_split_success_z9rh7vsqkv.png></p></details><p>Return to our latest commit with the third feature:<pre><code class="hljs language-sh">jj edit u
</code></pre><p>And start working on a fourth feature:<pre><code class="hljs language-sh">jj new -m <span class=hljs-string>"WIP 4 feat"</span>
<span class=hljs-built_in>touch</span> README.md && <span class=hljs-built_in>echo</span> <span class=hljs-string>"Blazing fast project"</span> >> README.md
</code></pre><p>JJ has a concept where you don't need to use the commit command explicitly. You can start work by preserving the old commit with a description, and begin work on something new with a description like "WIP 4 feat". Later, at any moment, you can rename the commit description and start a new one. This concept explicitly tells you what work you've started and what you're currently doing.<p>Wait—README.md should probably be part of the first commit. That's not right! But there's no problem, just squash your current work into the first commit and continue:<pre><code class="hljs language-sh">jj squash --to r
</code></pre><details><summary>Screenshot (jj squash)</summary> <p><img alt src=/api/files/pbc_pages/lz7ogvv1zowmcqw/jj_squash_74lwzuhimq.png></p></details><p>Edit the commit message and you're done!<hr><h2 id=conclusion>Conclusion</h2><p>We've practiced with JJ and studied its powerful and user-friendly interface for Git. The beauty is that <strong>you can use it without changing your team's processes</strong>—JJ works on top of regular Git.<p>You can maintain cleaner history and worry much less about AI accidentally damaging your local work. The confidence this brings is invaluable in our anxious world.<p>Of course, I can't promise a seamless transition. Mastering tools like JJ takes a long time, and you might hit moments where pure Git seems easier. Honestly, I accepted JJ only on my second attempt at using it. Like trying olives for the first time, it might not appeal to you immediately—but if you keep going, everything will work out just fine.<p>I haven't covered everything JJ can do, but I hope this was helpful and that you'll feel inspired to try it and make your work even better.<hr><h2 id=subscribe>Subscribe</h2><p>Subscribe to my channel at <a href=https://zatsepin.dev/subscribe>https://zatsepin.dev/subscribe</a> for access to my exclusive content, project source code, and much more.<h2 id=references>References</h2><ul><li><a href=https://docs.jj-vcs.dev/>https://docs.jj-vcs.dev/</a><li><a href=https://jj-for-everyone.github.io/>https://jj-for-everyone.github.io/</a><li><a href=https://steveklabnik.github.io/jujutsu-tutorial>https://steveklabnik.github.io/jujutsu-tutorial</a></ul></div></article>]]></content:encoded></item><item><title>Snow Trail Shader in Bevy Engine (WGSL)</title><link>https://zatsepin.dev/essays/g46wdri4o5qwt3h?lang=en</link><guid isPermaLink="true">https://zatsepin.dev/essays/g46wdri4o5qwt3h?lang=en</guid><pubDate>Fri, 13 Feb 2026 16:54:15 GMT</pubDate><description><![CDATA[A 3D interactive snow trails demo built with Bevy engine featuring character physics, dynamic lighting, and RTS-style camera controls.]]></description><content:encoded><![CDATA[]]></content:encoded></item></channel></rss>