<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US" xmlns:yt="http://www.youtube.com/xml/schemas/2015" xmlns:media="http://search.yahoo.com/mrss/">
  <title>tonsky.me</title>
  <subtitle>Nikita Prokopov’s blog</subtitle>
  <link rel="self" href="https://tonsky.me/atom.xml" type="application/atom+xml" />
  <link rel="alternate" href="https://tonsky.me/" type="text/html" />
  <link rel="next" href="https://tonsky.me/atom.xml?page=37" />
  <id>https://tonsky.me/</id>
  <updated>2026-03-04T00:44:10Z</updated>
  <author>
    <name>Nikita Prokopov</name>
    <email>niki@tonsky.me</email>
  </author>
  <entry>
    <title>Claude is an Electron App because we’ve lost native</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/blog/fall-of-native/" />
    <id>https://tonsky.me/blog/fall-of-native/</id>
    <published>2026-03-03T00:00:00Z</published>
    <updated>2026-03-04T00:44:10Z</updated>
    <summary type="html"><![CDATA[
Article argues that Claude is not an Electron app not because LLMs can’t do it, but because there are no advantages left for native
]]></summary>
    <content type="html"><![CDATA[
<p>In <a href="https://www.dbreunig.com/2026/02/21/why-is-claude-an-electron-app.html">“Why is Claude an Electron App?”</a> Drew Breunig wonders:</p>
<blockquote>
  <p>Claude spent $20k on an agent swarm implementing (kinda) a C-compiler in Rust, but desktop Claude is an Electron app.</p>
  <p>If code is free, why aren’t all apps native?</p>
</blockquote>
<p>And then argues that the answer is that LLMs are not good enough yet. They can do 90% of the work, so there’s still a substantial amount of manual polish, and thus, increased costs.</p>
<p>But I think that’s not the real reason. The real reason is: native has nothing to offer.</p>
<p>API-wise, native apps lost to web apps a long time ago. Native APIs are terrible to use, and OS vendors use everything in their power to make you not want to develop native apps for their platform. That explains the rise of Electron before LLM times, but it’s also a problem that LLMs solve now: if that was a real barrier to developing native apps, it doesn’t exist anymore.</p>
<p>Then there’re looks and consistency. Some time ago, maybe in the late 90s and 2000s, native was ahead. It used to look good, it was consistent, and it all actually worked: the more apps used native look and feel, the better user experience was across apps (which we used to call programs). </p>
<p>These days, though, native is as bad as the web, if not worse. Consistency is basically out the window. Anything can look like anything, buttons have no borders, contrast doesn’t exist, and neither do conventions. Apple, for example, seems to place traffic lights and corner radius by vibes rather than by any measurable guidelines.</p>
<figure>
<img src="https://tonsky.me/blog/fall-of-native/radii@2x.webp" loading="lazy"><figcaption><a href="https://x.com/vaxryy/status/1977175437382930662">Maybe the server should round the corners?</a></figcaption></figure>
<p>Looks could be good, but they also can be bad, and then you are stuck with platform-consistent, but generally bad UI (Liquid Glass ahem). It changes too often, too: the app you made today will look out of place next year, when Apple decides to change look and feel yet again. There’s no native look anymore.</p>
<figure>
<img src="https://tonsky.me/blog/fall-of-native/run@2x.webp" loading="lazy"><figcaption><a href="https://grumpy.website/1723">Computer UIs also degrade over time</a></figcaption></figure>
<p>Theoretically, native apps can integrate with OS on a deeper level. This sounds nice, but what does that mean in practice? There are almost no good interoperable file formats; everything is locked inside individual apps, most services moved to the web, and OSes dropped the ball for making a good shared baseline. You can integrate with OS-provided calendar, but you can’t do it with web calendar. Well, you can, of course, but it’s easier on the web; native doesn’t help with it at all.</p>
<figure>
<img src="https://tonsky.me/blog/fall-of-native/calendar@2x.webp" loading="lazy"><figcaption><a href="https://grumpy.website/1747">Web pages only lead to more web pages</a></figcaption></figure>
<p>Finally, the last hope of people longing for native is performance. They feel that native apps will be faster. Well, they can, but it doesn’t mean they will. Web apps can be faster, too, but in practice, nobody cares. There’s no technical reason why <a href="https://tonsky.me/blog/js-bloat/">Slack needs to load 80 MiB</a> just to show 10 channel names and 3 messages on a screen. The web is not the problem here! It’s a choice to be bad. What makes you think it’ll be different once the company decides to move to native?</p>
<p>Don’t get me wrong: writing this brings me no joy. I don’t think web is a solution either. I just remember good times when native did a better-than-average job, and we were all better for using it, and it saddens me that these times have passed.</p>
<p>I just don’t think that kidding ourselves that the only problem with software is Electron and it all will be butterflies and unicorns once we rewrite Slack in SwiftUI is not productive. The real problem is a lack of care. And the slop; you can build it with any stack.</p>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>Podcast: На Маке нет никаких шкафов @ Думаем дальше</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/talks/#2026-01-15" />
    <id>https://tonsky.me/talks/#2026-01-15</id>
    <published>2026-01-15T00:00:00Z</published>
    <updated>2026-01-15T00:00:00Z</updated>
    <content type="html"><![CDATA[
<p><img src="https://tonsky.me/talks/covers/Думаем дальше.png">    <audio controls="" preload="none">
      <source src="https://tonsky.me/talks/content/2026-01-15 Думаем дальше.mp3" type="audio/mpeg">
    </audio>
</img></p>
<p>С Ильей Бирманом провожаем Алана Дая, вспоминая, в чём состоят достижения Мака, Джобса и ХИГа (но и Винду добрым словом тоже вспоминаем).</p>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>It’s hard to justify Tahoe icons</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/blog/tahoe-icons/" />
    <id>https://tonsky.me/blog/tahoe-icons/</id>
    <published>2026-01-05T00:00:00Z</published>
    <updated>2026-03-04T00:44:10Z</updated>
    <summary type="html"><![CDATA[
Looking at the first principles of icon design—and how Apple failed to apply all of them in macOS Tahoe
]]></summary>
    <content type="html"><![CDATA[
<p><em>Translations: <a href="https://nptr.cc/posts/2026-01/tonsky-tahoe/">Chinese 1</a> <a href="https://sspai.com/post/105394">Chinese 2</a> <a href="https://habr.com/ru/companies/ruvds/articles/985818/">Russian</a></em></p>
<p>I was reading Macintosh Human Interface Guidelines <a href="https://dn721903.ca.archive.org/0/items/apple-hig/Macintosh_HIG_1992.pdf">from 1992</a> and found this nice illustration:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/hig_icons@2x.webp" loading="lazy"></figure>
<p>accompanied by explanation:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/hig_quote@2x.webp" loading="lazy"></figure>
<p>Fast forward to 2025. Apple releases macOS Tahoe. Main attraction? Adding unpleasant, distracting, illegible, messy, cluttered, confusing, frustrating icons (their words, not mine!) to every menu item:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/sequoia_tahoe_textedit@2x.webp" loading="lazy"><figcaption>Sequoia → Tahoe</figcaption></figure>
<p>It’s bad. But why exactly is it bad? Let’s delve into it!</p>
<p>Disclaimer: screenshots are a mix from macOS 26.1 and 26.2, taken   from stock Apple apps only that come pre-installed with the system. No system settings were modified.</p>
<h1 id="icons-should-differentiate">Icons should differentiate</h1>
<p>The main function of an icon is to help you find what you are looking for faster.</p>
<p>Perhaps counter-intuitively, adding an icon to everything is exactly the wrong thing to do. To stand out, things need to be different. But if everything has an icon, nothing stands out.</p>
<p>The same applies to color: black-and-white icons look clean, but they don’t help you find things faster!</p>
<p>Microsoft used to know this:</p>
<figure>
<a href="https://tonsky.me"><img src="https://tonsky.me/blog/tahoe-icons/word@2x.webp" class="noround" loading="lazy"></a></figure>
<p>Look how much faster you can find Save or Share in the right variant:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_cleanup@2x.webp" loading="lazy"></figure>
<p>It also looks cleaner. Less cluttered.</p>
<p>A colored version would be even better (clearer separation of text from icon, faster to find):</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_cleanup_color@2x.webp" loading="lazy"></figure>
<p>I know you won’t like how it looks. I don’t like it either. These icons are hard to work with. You’ll have to actually design for color to look nice. But the principle stands: it is way easier to use.</p>
<h1 id="consistency-between-apps">Consistency between apps</h1>
<p>If you want icons to work, they need to be <em>consistent</em>. I need to be able to learn what to look for.</p>
<p>For example, I see a “Cut” command and <img class="inline" src="https://tonsky.me/blog/tahoe-icons/scissors.svg"> next to it. Okay, I think. Next time I’m looking for “Cut,” I might save some time and start looking for <img class="inline" src="https://tonsky.me/blog/tahoe-icons/scissors.svg"> instead.</p>
<p>How is Tahoe doing on that front? I present to you: Fifty Shades of “New”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_new@2x.webp" loading="lazy"></figure>
<p>I even collected them all together, so the absurdity of the situation is more obvious.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/icons_new@2x.webp" loading="lazy"></figure>
<p>Granted, some of them are different operations, so they have different icons. I guess creating a smart folder is different from creating a journal entry. But this?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_new_object@2x.webp" loading="lazy"></figure>
<p>Or this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/icons_new_smart_folder@2x.webp" loading="lazy"></figure>
<p>Or this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/icons_new_window@2x.webp" loading="lazy"></figure>
<p>There is no excuse.</p>
<p>Same deal with open:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_open@2x.webp" loading="lazy"></figure>
<p>Save:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_save@2x.webp" loading="lazy"></figure>
<p>Yes. One of them is a checkmark. And they can’t even agree on the direction of an arrow!</p>
<p>Close:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_close@2x.webp" loading="lazy"></figure>
<p>Find (which is sometimes called Search, and sometimes Filter):</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_find@2x.webp" loading="lazy"></figure>
<p>Delete (from Cut-Copy-Paste-Delete fame):</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_delete@2x.webp" loading="lazy"></figure>
<p>Minimize window.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/menu_minimize@2x.webp" loading="lazy"></figure>
<p>These are not some obscure, unique operations. These are OS basics, these are foundational. Every app has them, and they are always in the same place. They shouldn’t look different!</p>
<h1 id="consistency-inside-the-same-app">Consistency inside the same app</h1>
<p>Icons are also used in toolbars. Conceptually, operations in a toolbar are identical to operations called through the menu, and thus should use the same icons. That’s the simplest case to implement: inside the same app, often on the same screen. How hard can it be to stay consistent?</p>
<p>Preview:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/preview@2x.webp" loading="lazy"></figure>
<p>Photos: same <img class="inline" src="https://tonsky.me/blog/tahoe-icons/info_circle.svg"> and <img class="inline" src="https://tonsky.me/blog/tahoe-icons/info.svg"> mismatch, but reversed ¯\_(ツ)_/¯</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/photos@2x.webp" loading="lazy"></figure>
<p>Maps and others often use different symbols for zoom:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/consistency_maps@2x.webp" loading="lazy"></figure>
<h1 id="icon-reuse">Icon reuse</h1>
<p>Another cardinal sin is to use the same icon for different actions. Imagine: I have learned that <img class="inline" src="https://tonsky.me/blog/tahoe-icons/square_and_pencil.svg"> means “New”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/new_note@2x.webp" loading="lazy"></figure>
<p>Then I open an app and see <img class="inline" src="https://tonsky.me/blog/tahoe-icons/square_and_pencil.svg">. “Cool”, I think, “I already know what it means”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/edit_address@2x.webp" loading="lazy"></figure>
<p>Gotcha!</p>
<p>You’d think: okay, <img class="inline" src="https://tonsky.me/blog/tahoe-icons/eye.svg"> means quick look:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/quick_look@2x.webp" loading="lazy"></figure>
<p>Sometimes, sure. Some other times, <img class="inline" src="https://tonsky.me/blog/tahoe-icons/eye.svg"> means “Show completed”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/show_completed@2x.webp" loading="lazy"></figure>
<p>Sometimes <img class="inline" src="https://tonsky.me/blog/tahoe-icons/square_and_arrow_down.svg"> is “Import”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/import@2x.webp" loading="lazy"></figure>
<p>Sometimes <img class="inline" src="https://tonsky.me/blog/tahoe-icons/square_and_arrow_down.svg"> is “Updates”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/update@2x.webp" loading="lazy"></figure>
<p>Same as with consistency, icon reuse doesn’t only happen between apps. Sometimes you see <img class="inline" src="https://tonsky.me/blog/tahoe-icons/rectangle_pencil_ellipsis.svg"> in a toolbar:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/form_filling_toolbar@2x.webp" loading="lazy"></figure>
<p>Then go to the menu <em>in the same app</em> and see <img class="inline" src="https://tonsky.me/blog/tahoe-icons/rectangle_pencil_ellipsis.svg"> means something else:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/autofill@2x.webp" loading="lazy"></figure>
<p>Sometimes identical icons meet in the same menu.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/save_export@2x.webp" loading="lazy"></figure>
<p>Sometimes next to each other.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/passwords@2x.webp" loading="lazy"></figure>
<p>Sometimes they put an entire barrage of identical icons in a row:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/photos_export@2x.webp" loading="lazy"></figure>
<p>This doesn’t help anyone. No user will find a menu item faster or will understand the function better if all icons are the same.</p>
<p>The worst case of icon reuse so far has been the Photos app:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/photos_copy@2x.webp" loading="lazy"></figure>
<p>It feels like the person tasked with choosing a unique icon for every menu item just ran out of ideas.</p>
<p>Understandable.</p>
<h1 id="too-much-nuance">Too much nuance</h1>
<p>When looking at icons, we usually allow for slight differences in execution. That lets us, for example, understand that these <em>technically different</em> road signs mean the same thing:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/pedestrians.webp" loading="lazy"></figure>
<p>Same applies for icons: if you draw an arrow going out of the box in one place and also an arrow and the box but at a slightly different angle, or with different stroke width, or make one filled, we will understand them as meaning the same thing.</p>
<p>Like, <img class="inline" src="https://tonsky.me/blog/tahoe-icons/info_circle.svg"> is supposed to mean something else from <img class="inline" src="https://tonsky.me/blog/tahoe-icons/info_circle_fill.svg">? Come on!</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_i@2x.webp" loading="lazy"></figure>
<p>Or two letters A that only slightly differ in the font size:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_font_size@2x.webp" loading="lazy"></figure>
<p>A pencil is “Rename” but a slightly thicker pencil is “Highlight”?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_pencil@2x.webp" loading="lazy"></figure>
<p>Arrows that use different diagonals?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_actual_size@2x.webp" loading="lazy"></figure>
<p>Three dots occupying ⅔ of space vs three dots occupying everything. Seriously?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_sidebar@2x.webp" loading="lazy"></figure>
<p>Slightly darker dots?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_quality@2x.webp" loading="lazy"></figure>
<p>The sheet of paper that changes meaning depending on if its corner is folded or if there are lines inside?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_sheet@2x.webp" loading="lazy"></figure>
<p>But the final boss are arrows. They are all different:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_arrows@2x.webp" loading="lazy"></figure>
<p>Supposedly, a user must become an expert at noticing how squished the circle is, if it starts top to right or bottom to right, and how far the arrow’s end goes.</p>
<p>Do I care? Honestly, no. I could’ve given it a shot, maybe, if Apple applied these consistently. But Apple considers <img class="inline" src="https://tonsky.me/blog/tahoe-icons/square_and_pencil.svg"> and <img class="inline" src="https://tonsky.me/blog/tahoe-icons/plus.svg"> to mean the same thing in one place, and expects me to notice minute details like this in another?</p>
<p>Sorry, I can’t trust you. Not after everything I’ve seen.</p>
<h1 id="detalization">Detalization</h1>
<p>Icons are supposed to be easily recognizable from a distance. Every icon designer knows: small details are no-go. You can have them sometimes, maybe, for aesthetic purposes, but you can’t <em>rely</em> on them.</p>
<p>And icons in Tahoe menus are <em>tiny</em>. Most of them fit in a 12×12 pixel square (actual resolution is 24×24 because of Retina), and because many of them are not square, one dimension is usually even less than 12.</p>
<p>It’s not a lot of space to work with! Even Windows 95 had 16×16 icons. If we take the typical DPI of that era at 72 dots per inch, we get a physical icon size of 0.22 inches (5.6 mm). On a modern MacBook Pro with 254 DPI, Tahoe’s 24×24 icons are 0.09 inches (2.4 mm). Sure, 24 is bigger than 16, but in reality, these icons’ area is 4 times as small!</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/dpi_comparison@2x.webp" loading="lazy"><figcaption>Simulated physical size comparison between 16×16 at 72 DPI (left) and 24×24 at 254 DPI (right)</figcaption></figure>
<p>So when I see this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_zoom@2x.webp" loading="lazy"></figure>
<p>I struggle. I can tell they are different. But I definitely struggle to tell what’s being drawn.</p>
<p>Even zoomed in 20×, it’s still a mess:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_zoomed@2x.webp" loading="lazy"></figure>
<p>Or here. These are three different icons:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_lists@2x.webp" loading="lazy"></figure>
<p>Am I supposed to tell plus sign from sparkle here?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_sparkle@2x.webp" loading="lazy"></figure>
<p>Some of these lines are half the pixel thicker than the other lines, and that’s supposed to be the main point:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_redact@2x.webp" loading="lazy"></figure>
<p>Is this supposed to be an arrow?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_original@2x.webp" loading="lazy"></figure>
<p>A paintbrush?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_paste@2x.webp" loading="lazy"></figure>
<p>Look, a tiny camera.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_screenshot@2x.webp" loading="lazy"></figure>
<p>It even got an even tinier viewfinder, which you can almost see if you zoom in 20×:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_screenshot_zoomed@2x.webp" loading="lazy"></figure>
<p>Or here. There is a box, inside that box is a circle, and inside it is a tiny letter <code>i</code> with a total height of 2 pixels:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_properties@2x.webp" loading="lazy"></figure>
<p>Don’t see it?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_properties_zoomed@2x.webp" loading="lazy"></figure>
<p>I don’t. But it’s there...</p>
<p>And this is a window! It even has traffic lights! How adorable:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_window@2x.webp" loading="lazy"></figure>
<p>Remember: these are retina pixels, ¼ of a real pixel. Steve Jobs himself claimed they were invisible.</p>
<blockquote>
  <p>It turns out there’s a magic number right around 300 pixels per inch, that when you hold something around to 10 to 12 inches away from your eyes, is the limit of the human retina to differentiate the pixels.</p>
</blockquote>
<p>And yet, Tahoe icons rely on you being able to see them.</p>
<h1 id="pixel-grid">Pixel grid</h1>
<p>When you have so little space to work with, every pixel matters. You can make a good icon, but you have to choose your pixels very carefully.</p>
<p>For Tahoe icons, Apple decided to use vector fonts instead of good old-fashioned bitmaps. It saves Apple resources—draw once, use everywhere. Any size, any display resolution, any font width.</p>
<p>But there’re downsides: fonts are hard to position vertically, their size <a href="https://tonsky.me/blog/font-size/">doesn’t map directly to pixels</a>, stroke width doesn’t map 1-to-1 to pixel grid, etc. So, they work everywhere, but they also look blurry and mediocre everywhere:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/details_clean_up@2x.webp" loading="lazy"><figcaption>Tahoe icon (left) and its pixel-aligned version (right).</figcaption></figure>
<p>They certainly start to work better once you give them more pixels.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/ipad_comparison@2x.webp" loading="lazy"><figcaption>iPad OS 26 vs macOS 26</figcaption></figure>
<p>or make graphics simpler. But the combination of small details and tiny icon size is deadly. So, until Apple releases MacBooks with 380+ DPI, unfortunately, we still have to care about the pixel grid.</p>
<h1 id="confusing-metaphors">Confusing metaphors</h1>
<p>Icons might serve another function: to help users understand the meaning of the command.</p>
<p>For example, once you know the context (move window), these icons explain what’s going on faster than words:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/window@2x.webp" loading="lazy"></figure>
<p>But for this to work, the user must understand what’s drawn on the icon. It must be a familiar object with a clear translation to computer action (like Trash can → Delete), a widely used symbol, or an easy-to-understand diagram. HIG:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/hig_metaphor@2x.webp" loading="lazy"></figure>
<p>A rookie mistake would be to misrepresent the object. For example, this is how selection looks like:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_selection@2x.webp" loading="lazy"></figure>
<p>But its icon looks like this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_select@2x.webp" loading="lazy"></figure>
<p>Honestly, I’ve been writing this essay for a week, and I still have zero ideas why it looks like that. There’s an object that looks like this, but it’s a text block in Freeform/Preview:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_text_block@2x.webp" loading="lazy"></figure>
<p>It’s called <code>character.textbox</code> in SF Symbols:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/character_textbox@2x.webp" loading="lazy"></figure>
<p>Why did it become a metaphor for “Select all”? My best guess is it’s a mistake.</p>
<p>Another place uses text selection from iOS as a metaphor. On a Mac!</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_text_selection@2x.webp" loading="lazy"></figure>
<p>Some concepts have obvious or well-established metaphors. In that case, it’s a mistake not to use them. For example, bookmarks: <img class="inline" src="https://tonsky.me/blog/tahoe-icons/bookmark.svg">. Apple, for some reason, went with a book:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_bookmarks@2x.webp" loading="lazy"></figure>
<p>Sometimes you already have an interface element and can use it for an icon. However, try not to confuse your users. Dots in a rectangle look like password input, not permissions:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_permissions@2x.webp" loading="lazy"></figure>
<p>Icon here says “Check” but the action is “Uncheck”.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_mark_incomplete@2x.webp" loading="lazy"></figure>
<p>Terrible mistake: icon doesn’t help, it actively confuses the user.</p>
<p>It’s also tempting to construct a two-level icon: an object and some sort of indicator. Like, a checkbox and a cross, meaning “Delete checkbox”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_mark_unchecked@2x.webp" loading="lazy"></figure>
<p>Or a user and a checkmark, like “Check the user”:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_manage@2x.webp" loading="lazy"></figure>
<p>Unfortunately, constructs like this rarely work. Users don’t build sentences from building blocks you provide; they have no desire to solve these puzzles.</p>
<p>Finding metaphors is hard. Nouns are easier than verbs, and menu items are mostly verbs. How does open look? Like an arrow pointing to the top right? Why?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_open@2x.webp" loading="lazy"></figure>
<p>I’m not saying there’s an obvious metaphor for “Open” Apple missed. There isn’t. But that’s the point: if you can’t find a good metaphor, using no icon is better than using a bad, confusing, or nonsensical icon.</p>
<p>There’s a game I like to play to test the quality of the metaphor. Remove the labels and try to guess the meaning. Give it a try:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_guess@2x.webp" loading="lazy"></figure>
<p>It’s delusional to think that there’s a good icon for every action if you think hard enough. There isn’t. It’s a lost battle from the start. No amount of money or “management decisions” is going to change that. The problems are 100% self-inflicted.</p>
<p>All this being said, I gotta give Apple credit where credit is due. When they are good at choosing metaphors, they are good:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_up_down@2x.webp" loading="lazy"></figure>
<h1 id="symmetrical-actions">Symmetrical actions</h1>
<p>A special case of a confusing metaphor is using different metaphors for actions that are direct opposites of one another. Like Undo/Redo, Open/Close, Left/Right.</p>
<p>It’s good when their icons use the same metaphor:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/symmetry_import_export_right@2x.webp" loading="lazy"></figure>
<p>Because it saves you time and cognitive resources. Learn one, get another one for free.</p>
<p>Because of that, it’s a mistake not to use common metaphors for related actions:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/symmetry_select@2x.webp" loading="lazy"></figure>
<p>Or here:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/symmetry_clipboard@2x.webp" loading="lazy"></figure>
<p>Another mistake is to create symmetry where there is none. “Back” and “See all”?</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/symmetry_app_store@2x.webp" loading="lazy"></figure>
<p>Some menus in Tahoe make both mistakes. E.g. lack of symmetry between Show/Hide and false symmetry between completed/subtasks:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/symmetry_eye@2x.webp" loading="lazy"></figure>
<p>Import not mirrored by Export but by Share:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/symmetry_import_export@2x.webp" loading="lazy"></figure>
<h1 id="text-in-icons">Text in icons</h1>
<p>HIG again:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/hig_text_icons@2x.webp" loading="lazy"></figure>
<p>Authors of HIG are arguing against including text as a part of an icon. So something like this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/metaphor_select@2x.webp" loading="lazy"></figure>
<p>or this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/similar_i@2x.webp" loading="lazy"></figure>
<p>would not fly in 1992.</p>
<p>I agree, but Tahoe has more serious problems: icons consisting <em>only</em> of text. Like this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_font@2x.webp" loading="lazy"></figure>
<p>It’s unclear where “metaphorical, abstract icon text that is not supposed to be read literally” ends and actual text starts. They use the same font, the same color, so how am I supposed to differentiate? Icons just get in a way: A...Complete? AaFont? What does it mean?</p>
<p>I can maybe understand <img class="inline" src="https://tonsky.me/blog/tahoe-icons/textformat_characters_dottedunderline.svg"> and <img class="inline" src="https://tonsky.me/blog/tahoe-icons/a_ellipsis.svg">. Dots are supposed to represent something. I can imagine thinking that led to <img class="inline" src="https://tonsky.me/blog/tahoe-icons/aa.svg">. But <img class="inline" src="https://tonsky.me/blog/tahoe-icons/textformat_characters.svg">? No decorations. No effects. Just plain Abc. Really?</p>
<h1 id="text-transformations">Text transformations</h1>
<p>One might think that using icons to illustrate text transformations is a better idea.</p>
<p>Like, you look at this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_transformations@2x.webp" loading="lazy"></figure>
<p>or this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_size@2x.webp" loading="lazy"></figure>
<p>or this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_styles@2x.webp" loading="lazy"></figure>
<p>and just from the icon alone understand what will happen with the text. Icon <em>illustrates</em> the action.</p>
<p>Also, BIU are well-established in word processing, so all upside?</p>
<p>Not exactly. The problem is the same—text icon looks like text, not icon. Plus, these icons are <em>excessive</em>. What’s the point of taking the first letter and repeating it? The word “Bold” already starts with a letter “B”, it reads just as easily, so why double it? Look at it again:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_styles@2x.webp" loading="lazy"></figure>
<p>It’s also repeated once more as a shortcut...</p>
<p>There is a better way to design this menu:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_styles_inline@2x.webp" loading="lazy"></figure>
<p>And it was known to Apple for at least 33 years.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/hig_style@2x.webp" loading="lazy"></figure>
<h1 id="system-elements-in-icons">System elements in icons</h1>
<p>Operating system, of course, uses some visual elements for its own purposes. Like window controls, resize handles, cursors, shortcuts, etc. It would be a mistake to use those in icons.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/hig_standard_elements@2x.webp" loading="lazy"></figure>
<p>Unfortunately, Apple fell into this trap, too. They reused arrows.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_arrow@2x.webp" loading="lazy"></figure>
<p>Key shortcuts:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_encoding@2x.webp" loading="lazy"></figure>
<p>HIG has an entire section on ellipsis specifically and how dangerous it is to use it anywhere else in the menu.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/hig_ellipsis@2x.webp" loading="lazy"></figure>
<p>And this exact problem is in Tahoe, too.</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/text_ellipsis@2x.webp" loading="lazy"></figure>
<h1 id="icons-break-scanning">Icons break scanning</h1>
<p>Without icons, you can just scan the menu from top to bottom, reading only the first letters. Because they all align:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/align_sequoia@2x.webp" loading="lazy"><figcaption>macOS Sequoia</figcaption></figure>
<p>In Tahoe, though, some menu items have icons, some don’t, and they are aligned differently:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/align_tahoe@2x.webp" loading="lazy"></figure>
<p>Some items can have both checkmarks <em>and</em> icons, or have only one of them, or have neither, so we get situations like this:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/align_holes@2x.webp" loading="lazy"></figure>
<p>Ugh.</p>
<h1 id="special-mention">Special mention</h1>
<p>This menu deserves its own category:</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/writing_direction@2x.webp" loading="lazy"></figure>
<p>Same icon for different actions. Missing the obvious metaphor. Somehow making the first one slightly smaller than the second and third. Congratulations! It got it all.</p>
<h1 id="is-hig-still-relevant">Is HIG still relevant?</h1>
<p>I’ve been mentioning HIG a lot, and you might be wondering: is an interface manual from 1992 still relevant today? Haven’t computers changed so much that entirely new principles, designs, and idioms apply?</p>
<p>Yes and no. Of course, advice on how to adapt your icons to black-and-white displays is obsolete. But the principles—as long as they are good principles—still apply, because they are based on how humans work, not how computers work.</p>
<p>Humans don’t get a new release every year. Our memory doesn’t double. Our eyesight doesn’t become sharper. Attention works the same way it always has. Visual recognition, motor skills—all of this is exactly as it was in 1992.</p>
<p>So yeah, until we get a direct chip-to-brain interface, HIG will stay relevant.</p>
<h1 id="conclusion">Conclusion</h1>
<p>In my opinion, Apple took on an impossible task: to add an icon to every menu item. There are just not enough good metaphors to do something like that.</p>
<p>But even if there were, the premise itself is questionable: if everything has an icon, it doesn’t mean users will find what they are looking for faster.</p>
<p>And even if the premise was solid, I still wish I could say: they did the best they could, given the goal. But that’s not true either: they did a poor job consistently applying the metaphors and designing the icons themselves.</p>
<p>I hope this article would be helpful in avoiding common mistakes in icon design, which Apple managed to collect all in one OS release. I love computers, I love interfaces, I love visual communication. It makes me sad seeing perfectly good knowledge already accessible 30 years ago being completely ignored or thrown away today.</p>
<p>On the upside: it’s not that hard anymore to design better than Apple! Let’s drink to that. Happy New year!</p>
<figure>
<img src="https://tonsky.me/blog/tahoe-icons/smiley@2x.webp" loading="lazy"><figcaption>From SF Symbols: a smiley face calling somebody on the phone</figcaption></figure>
<h1 id="notes">Notes</h1>
<p>During review of this post I was made familiar with <a href="https://blog.jim-nielsen.com/2025/icons-in-menus/">Jim Nielsen’s article</a>, which hits a lot of the same points as I do. I take that as a sign there’s some common truth behind our reasoning.</p>
<p>Also note: Safari → File menu got worse since 26.0. Used to have only 4 icons, now it’s 18!</p>
<p>Thanks Kevin, Ryan, and Nicki for reading drafts of this post.</p>
<h1 id="upd-notable-mentions">UPD: Notable mentions</h1>
<ul>
  <li><a href="https://simonwillison.net/2026/Jan/5/its-hard-to-justify-tahoe-icons/">Simon Willison</a></li>
  <li><a href="https://daringfireball.net/linked/2026/01/05/hard-to-justify-tahoe-icons">John Gruber</a></li>
  <li><a href="https://x.com/jasonfried/status/2008632347034566694">Jason Fried</a></li>
  <li><a href="https://mjtsai.com/blog/2025/12/10/icons-in-menus-everywhere/#icons-in-menus-everywhere-update-2026-01-05">Michael Tsai</a></li>
  <li><a href="https://www.ben-evans.com/pausdjfap/2026/1/13/11-january">Ben Evans</a></li>
  <li><a href="https://tldr.tech/design/2026-01-07">TLDR Design</a></li>
  <li><a href="https://9to5mac.com/2026/01/06/macos-tahoe-icons-do-exactly-what-apple-said-designers-should-never-do/">9TO5Mac</a></li>
  <li><a href="https://pxlnv.com/linklog/tahoe-icons-inconsistent-confusing-illegible/">Pixel Envy</a></li>
  <li><a href="https://bytes.dev/archives/452">bytes.dev</a></li>
</ul>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>Statistics made simple</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/blog/clj-simple-stats/" />
    <id>https://tonsky.me/blog/clj-simple-stats/</id>
    <published>2025-12-15T00:00:00Z</published>
    <updated>2026-03-04T00:44:10Z</updated>
    <summary type="html"><![CDATA[
Announcing a simple statistics library for Clojure web servers
]]></summary>
    <content type="html"><![CDATA[
<p>I have a weird relationship with statistics: on one hand, I try not to look at it too often. Maybe once or twice a year. It’s because analytics is not actionable: what difference does it make if a thousand people saw my article or ten thousand?</p>
<p>I mean, sure, you might try to guess people’s tastes and only write about what’s popular, but that will destroy your soul pretty quickly.</p>
<p>On the other hand, I feel nervous when something is not accounted for, recorded, or saved for future reference. I might not need it now, but what if ten years later I change my mind?</p>
<p>Seeing your readers also helps to know you are not writing into the void. So I really don’t need much, something very basic: the number of readers per day/per article, maybe, would be enough.</p>
<p>Final piece of the puzzle: I self-host my web projects, and I use an old-fashioned web server instead of delegating that task to Nginx.</p>
<p>Static sites are popular and for a good reason: they are fast, lightweight, and fulfil their function. I, on the other hand, might have an unfinished gestalt or two: I want to feel the full power of the computer when serving my web pages, to be able to do fun stuff that is beyond static pages. I need that freedom that comes with a full programming language at your disposal. I want to program my own web server (in Clojure, sorry everybody else).</p>
<h1 id="existing-options">Existing options</h1>
<p>All this led me on a quest for a statistics solution that would uniquely fit my needs. Google Analytics was out: bloated, not privacy-friendly, terrible UX, Google is evil, etc.</p>
<figure>
<img src="https://tonsky.me/blog/clj-simple-stats/ga@2x.webp" loading="lazy"><figcaption>What is going on?</figcaption></figure>
<p>Some other JS solution might’ve been possible, but still questionable: SaaS? Paid? Will they be around in 10 years? Self-host? Are their cookies GDPR-compliant? How to count RSS feeds?</p>
<p>Nginx has access logs, so I tried server-side statistics that feed off those (namely, Goatcounter). Easy to set up, but then I needed to create domains for them, manage accounts, monitor the process, and it wasn’t even performant enough on my server/request volume!</p>
<h1 id="my-solution">My solution</h1>
<p>So I ended up building my own. You are welcome to join, if your constraints are similar to mine. This is how it looks:</p>
<figure>
<img src="https://tonsky.me/blog/clj-simple-stats/screenshot@2x.webp" loading="lazy"></figure>
<p>It’s pretty basic, but does a few things that were important to me.</p>
<h2 id="setup">Setup</h2>
<p>Extremely easy to set up. And I mean it as a feature.</p>
<p>Just add our middleware to your Ring stack and get everything automatically: collecting and reporting.</p>
<pre><code>(def app
  (-&gt; routes
    ...
    (ring.middleware.params/wrap-params)
    (ring.middleware.cookies/wrap-cookies)
    ...
    (clj-simple-stats.core/wrap-stats))) ;; &lt;-- just add this</code></pre>
<p>It’s zero setup in the best sense: nothing to configure, nothing to monitor, minimal dependency. It starts to work immediately and doesn’t ask anything from you, ever.</p>
<p>See, you already have your web server, why not reuse all the setup you did for it anyway?</p>
<h2 id="request-types">Request types</h2>
<p>We distinguish between request types. In my case, I am only interested in live people, so I count them separately from RSS feed requests, favicon requests, redirects, wrong URLs, and bots. Bots are particularly active these days. Gotta get that AI training data from somewhere.</p>
<p>RSS feeds are live people in a sense, so extra work was done to count them properly. Same reader requesting <code>feed.xml</code> 100 times in a day will only count as one request.</p>
<p>Hosted RSS readers often report user count in User-Agent, like this:</p>
<pre><code>Feedly/1.0 (+http://www.feedly.com/fetcher.html; 457 subscribers; like FeedFetcher-Google)

Mozilla/5.0 (compatible; BazQux/2.4; +https://bazqux.com/fetcher; 6 subscribers)

Feedbin feed-id:1373711 - 142 subscribers</code></pre>
<p>My personal respect and thank you to everybody on this list. I see you.</p>
<figure>
<img src="https://tonsky.me/blog/clj-simple-stats/readers@2x.webp" loading="lazy"></figure>
<h2 id="graphs">Graphs</h2>
<p>Visualization is important, and so is choosing the correct graph type. This is wrong:</p>
<figure>
<img src="https://tonsky.me/blog/clj-simple-stats/goat@2x.webp" loading="lazy"></figure>
<p>Continuous line suggests interpolation. It reads like between 1 visit at 5am and 11 visits at 6am there were points with 2, 3, 5, 9 visits in between. Maybe 5.5 visits even! That is not the case.</p>
<p>This is how a semantically correct version of that graph should look:</p>
<figure>
<img src="https://tonsky.me/blog/clj-simple-stats/graph@2x.webp" loading="lazy"></figure>
<p>Some attention was also paid to having reasonable labels on axes. You won’t see something like 117, 234, 10875. We always choose round numbers appropriate to the scale: 100, 200, 500, 1K etc.</p>
<p>Goes without saying that all graphs have the same vertical scale and syncrhonized horizontal scroll.</p>
<h2 id="insights">Insights</h2>
<p>We don’t offer much (as I don’t need much), but you can narrow reports down by page, query, referrer, user agent, and any date slice.</p>
<h2 id="not-implemented-yet">Not implemented (yet)</h2>
<p>It would be nice to have some insights into “What was this spike caused by?”</p>
<p>Some basic breakdown by country would be nice. I do have IP addresses (for what they are worth), but I need a way to package GeoIP into some reasonable size (under 1 Mb, preferably; some loss of resolution is okay).</p>
<p>Finally, one thing I am really interested in is “Who wrote about me?” I do have referrers, only question is how to separate signal from noise.</p>
<p>Performance. DuckDB is a sport: it compresses data and runs column queries, so storing extra columns per row doesn’t affect query performance. Still, each dashboard hit is a query across the entire database, which at this moment (~3 years of data) sits around 600 MiB. I definitely need to look into building some pre-calculated aggregates.</p>
<p>One day.</p>
<h2 id="how-to-get">How to get</h2>
<p>Head to <a href="https://github.com/tonsky/clj-simple-stats">github.com/tonsky/clj-simple-stats</a> and follow the instructions:</p>
<figure>
<a href="https://github.com/tonsky/clj-simple-stats"><img src="https://tonsky.me/blog/clj-simple-stats/banner@2x.webp" loading="lazy"></a></figure>
<p>Let me know what you think! Is it usable to you? What could be improved?</p>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>How to get hired in 2025</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/blog/hiring-ai/" />
    <id>https://tonsky.me/blog/hiring-ai/</id>
    <published>2025-11-26T00:00:00Z</published>
    <updated>2026-03-04T00:44:10Z</updated>
    <summary type="html"><![CDATA[
A collection of red flags in software engineers' test assignments
]]></summary>
    <content type="html"><![CDATA[
<img src="https://tonsky.me/blog/hiring-ai/cover.webp"><p>It’s 2025 and you are applying for a software engineer position. They give you a test assignment. You complete it yourself, send it over, and get rejected. Why?</p>
<p>Because it looked like AI.</p>
<p>Unfortunately, it’s 2025, AI is spreading like glitter in a kindergarten, and it’s really easy to mistake hard human labor for soulless, uninspired machine slop.</p>
<p>Following are the main <em>red flags</em> in test assignments that should be <strong>avoided</strong>:</p>
<ul>
  <li>The assignment was read and understood in full.</li>
  <li>All parts are implemented.</li>
  <li>Industry-standard tools and frameworks are used.</li>
  <li>The code is split into small, readable functions.</li>
  <li>Variables have descriptive names.</li>
  <li>Complex parts have comments.</li>
  <li>Errors are handled, error messages are easy to follow.</li>
  <li>Source files are organized reasonably.</li>
  <li>The web interface looks nice.</li>
  <li>There are tests.</li>
</ul>
<p>Avoid these AI giveaways and spread the word!</p>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>Logo: Clojure+</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/design/#2025-11-clojure-plus" />
    <id>https://tonsky.me/design/#2025-11-clojure-plus</id>
    <published>2025-11-18T00:00:00Z</published>
    <updated>2025-11-18T00:00:00Z</updated>
    <content type="html"><![CDATA[
<img src="https://tonsky.me/design/images/2025-11-clojure-plus.svg" style="aspect-ratio: 1450/750; " width="1000" height="517"><br>
<a href='https://github.com/tonsky/clojure-plus'>Clojure+</a> is a project to improve Clojure stdlib.
]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>Needy programs</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/blog/needy-programs/" />
    <id>https://tonsky.me/blog/needy-programs/</id>
    <published>2025-11-13T00:00:00Z</published>
    <updated>2026-03-04T00:44:10Z</updated>
    <summary type="html"><![CDATA[
We used to use software; now software started to use us
]]></summary>
    <content type="html"><![CDATA[
<p>If you’ve been around, you might’ve noticed that our relationships with programs have changed.</p>
<p>Older programs were all about what you need: you can do this, that, whatever you want, just let me know. You were in control, you were giving orders, and programs obeyed.</p>
<p>But recently (a decade, more or less), this relationship has subtly changed. Newer programs (which are called apps now, yes, I know) started to want things from you.</p>
<h1 id="accounts">Accounts</h1>
<p>The most obvious example is user accounts. In most cases, I, as a user, don’t need an account. Yet programs keep insisting that I, not them, “need” one.</p>
<p>I don’t. I have more accounts already than a population of a small town. This is something <em>you</em> want, not me.</p>
<figure>
  <iframe width="720" height="405" src="https://www.youtube-nocookie.com/embed/8UGO3EG5DC8?start=169" frameborder="0" allow="autoplay; encrypted-media; picture-in-picture" allowfullscreen=""></iframe>
<figcaption>The only correct reaction to an account screen</figcaption></figure>
<p>And even if you give up and create one, they will never leave you alone: they’ll ask for 2FA, then for password rotation, then will log you out for no good reason. You’ll never see the end of it either way.</p>
<figure>
  <video autoplay="" muted="" loop="" preload="auto" playsinline="" controls="">
    <source src="https://tonsky.me/blog/needy-programs/bd1bb9c8b386f422.mp4" type="video/mp4">
  </video>
</figure>
<p>This got so bad that when a program doesn’t ask you to create an account, it feels <em>refreshing</em>.</p>
<p>“Okay, but accounts are still needed to sync stuff between machines.”</p>
<p>Wrong. Syncthing is a secure, multi-machine distributed app and yet <a href="https://tonsky.me/blog/syncthing/">doesn’t need an account</a>.</p>
<p>“Okay, but you still need an account if you pay for a subscription?”</p>
<p><a href="https://mullvad.net/">Mullvad VPN</a> accepts payments and yet didn’t ask me for my email.</p>
<p>How come these apps can go without an account, but your code editor and your terminal can’t?</p>
<h1 id="updates">Updates</h1>
<p>Every program has an update mechanism now. Everybody is checking for updates all the time. Some notoriously bad ones lock you out until you update. You get notified a few seconds after a new version is available.</p>
<p>And yet: do we, users, really need these updates? Did we ask for them?</p>
<p>I’ve been running barebone Nvidia drivers without their bloated desktop app (partly because it asks for an account, lol).</p>
<p>As a result, there’s nobody to notify me about new drivers. And you know what? It’s been fine. I could forget to update for months, and still everything works. It’s the most relaxing I’ve felt in a while.</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/update_pnpm.webp" loading="lazy"><figcaption>Even terminal programs bother you with updates now.</figcaption></figure>
<p>There has been a new major release of Syncthing in August. How did I learn about it? By accident; a friend told me. And you know what? I’m happy with that. If I upgrade, nothing in my life will change. It works just fine now. So do I really <em>need</em> an update? Is it <em>my</em> need?</p>
<p>It’s simple, really. If I need an update, I will know it: I’ll encounter a bug or a lack of functionality. Then I’ll go and update.</p>
<p>Until then, politely fuck off.</p>
<h1 id="notifications">Notifications</h1>
<p>Notifications are the ultimate example of neediness: a program, a mechanical, lifeless thing, an unanimate object, is bothering its master about something the master didn’t ask for. Hey, who is more important here, a human or a machine?</p>
<p>Notifications are like email: to-do items that are forced on you by another party. Hey, it’s not my job to dismiss your notifications!</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/notifications_idea.webp" loading="lazy"><figcaption>I just downloaded this and already have three notifications to dismiss.</figcaption></figure>
<p>Sure, there are good notifications. Sometimes users need to be notified about something they care about, like the end of a long-running process.</p>
<p>But the general pattern is so badly abused that it’s hard to justify it now. You can make a case that giving a toddler a gun can help it protect itself. But much worse things will probably happen much sooner.</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/notifications_market.webp" loading="lazy"><figcaption>These fucking dots.</figcaption></figure>
<p>There’s no good reason why, e.g. code editor needs a notification system. What’s there to notify about? Updates? Sublime Text has no notifications. And you know what? It works just fine. I never felt underinformed while using it.</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/chrome@2x.jpeg" loading="lazy"><figcaption>The ultimate example: account, update, and notification</figcaption></figure>
<h1 id="onboarding">Onboarding</h1>
<p>The company needs to announce a new feature and makes a popup window about it.</p>
<p>Read this again: The company. Needs. It’s not even about the user. Never has been.</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/onboarding_calendar@2x.webp" loading="lazy"><figcaption>What’s new in Calendar? I don’t know, 13th month?</figcaption></figure>
<p>Did I ask about Copilot? No. The company wants me to use it. Not me:</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/onboarding_copilot@2x.png" loading="lazy"></figure>
<p>Do I care about Figma Make? Not really, no.</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/onboarding_figma.png" loading="lazy"></figure>
<p>Yet I still know about it, against my will.</p>
<h1 id="to-sum-it-up">To sum it up</h1>
<p>I’ve read somewhere (sorry, lost the link):</p>
<blockquote>
  <p><code>ls</code> never asks you to create an account or to update.</p>
</blockquote>
<p>I agree. <code>ls</code> is a good program. <code>ls</code> is a tool. It does what I need it to do and stays quiet otherwise. I use it; it doesn’t use me. That’s a good, healthy relationship.</p>
<p>At the other end of the spectrum, we have services. Programs that constantly update. Programs that have news, that “keep you informed”. Programs that need something from you all the time. Programs that update Terms of Service just to remind you of themselves.</p>
<figure>
<img src="https://tonsky.me/blog/needy-programs/terms_bsky.webp" loading="lazy"></figure>
<p>Programs that have their own agenda and that are trying to make it yours, too. Programs that want you to think about them. Programs that think they are entitled to a part of your attention. “Pick me” programs.</p>
<p>And you know what? Fuck these programs. Give me back my computer.</p>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>I am sorry, but everyone is getting syntax highlighting wrong</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/blog/syntax-highlighting/" />
    <id>https://tonsky.me/blog/syntax-highlighting/</id>
    <published>2025-10-15T00:00:00Z</published>
    <updated>2026-03-04T00:44:10Z</updated>
    <summary type="html"><![CDATA[
Applying human ergonomics and design principles to syntax highlighting
]]></summary>
    <content type="html"><![CDATA[
<img src="https://tonsky.me/blog/syntax-highlighting/cover.webp"><p><em>Translations: <a href="https://habr.com/ru/articles/957024/">Russian</a></em></p>
<p>Syntax highlighting is a tool. It can help you read code faster. Find things quicker. Orient yourself in a large file.</p>
<p>Like any tool, it can be used correctly or incorrectly. Let’s see how to use syntax highlighting to help you work.</p>
<h1 id="christmas-lights-diarrhea">Christmas Lights Diarrhea</h1>
<p>Most color themes have a unique bright color for literally everything: one for variables, another for language keywords, constants, punctuation, functions, classes, calls, comments, etc.</p>
<p>Sometimes it gets so bad one can’t see the base text color: everything is highlighted. What’s the base text color here?</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/diarrhea.webp" loading="lazy"></figure>
<p>The problem with that is, if everything is highlighted, nothing stands out. Your eye adapts and considers it a new norm: everything is bright and shiny, and instead of getting separated, it all blends together.</p>
<p>Here’s a quick test. Try to find the function definition here:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/definitions_bad.webp" loading="lazy"></figure>
<p>and here:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/definitions_good.webp" loading="lazy"></figure>
<p>See what I mean?</p>
<p>So yeah, unfortunately, you can’t just highlight everything. You have to make decisions: what is more important, what is less. What should stand out, what shouldn’t.</p>
<p>Highlighting everything is like assigning “top priority” to every task in Linear. It only works if most of the tasks have lesser priorities.</p>
<p class="loud">If everything is highlighted, nothing is highlighted.</p>
<h1 id="enough-colors-to-remember">Enough colors to remember</h1>
<p>There are two main use-cases you want your color theme to address:</p>
<ol start="1">
  <li>Look at something and tell what it is by its color (you can tell by reading text, yes, but why do you need syntax highlighting then?)</li>
  <li>Search for something. You want to know what to look for (which color).</li>
</ol>
<p>1 is a direct index lookup: color → type of thing.</p>
<p>2 is a reverse lookup: type of thing → color.</p>
<p>Truth is, most people don’t do these lookups at all. They might think they do, but in reality, they don’t.</p>
<p>Let me illustrate. Before:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/change_before.webp" loading="lazy"></figure>
<p>After:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/change_after.webp" loading="lazy"></figure>
<p>Can you see it? I misspelled <code>return</code> for <code>retunr</code> and its color switched from red to purple.</p>
<p>I can’t.</p>
<p>Here’s another test. Close your eyes (not yet! Finish this sentence first) and try to remember what color your color theme uses for class names?</p>
<p>Can you?</p>
<p>If the answer for both questions is “no”, then your color theme is <em>not functional</em>. It might give you comfort (as in—I feel safe. If it’s highlighted, it’s probably code) but you can’t use it as a tool. It doesn’t <em>help</em> you.</p>
<p>What’s the solution? Have an absolute minimum of colors. So little that they all fit in your head at once. For example, my color theme, Alabaster, only uses four:</p>
<ul>
  <li>Green for strings</li>
  <li>Purple for constants</li>
  <li>Yellow for comments</li>
  <li>Light blue for top-level definitions</li>
</ul>
<p>That’s it! And I was able to type it all from memory, too. This minimalism allows me to actually do lookups: if I’m looking for a string, I know it will be green. If I’m looking at something yellow, I know it’s a comment.</p>
<p class="loud">Limit the number of different colors to what you can remember.</p>
<p>If you swap green and purple in my editor, it’ll be a catastrophe. If somebody swapped colors in yours, would you even notice?</p>
<h1 id="what-should-you-highlight">What should you highlight?</h1>
<p>Something there isn’t a lot of. Remember—we want highlights to stand out. That’s why I don’t highlight variables or function calls—they are everywhere, your code is probably 75% variable names and function calls.</p>
<p>I do highlight constants (numbers, strings). These are usually used more sparingly and often are reference points—a lot of logic paths start from constants.</p>
<p>Top-level definitions are another good idea. They give you an idea of a structure quickly.</p>
<p>Punctuation: it helps to separate names from syntax a little bit, and you care about names first, especially when quickly scanning code.</p>
<p>Please, please don’t highlight language keywords. <code>class</code>, <code>function</code>, <code>if</code>, <code>else</code>stuff like this. You rarely look for them: “where’s that if” is a valid question, but you will be looking not at the <code>if</code> the keyword, but at the condition after it. The condition is the important, distinguishing part. The keyword is not.</p>
<p class="loud">Highlight names and constants. Grey out punctuation. Don’t highlight language keywords.</p>
<h1 id="comments-are-important">Comments are important</h1>
<p>The tradition of using grey for comments comes from the times when people were paid by line. If you have something like</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/javadoc.webp" loading="lazy"></figure>
<p>of course you would want to grey it out! This is bullshit text that doesn’t add anything and was written to be ignored.</p>
<p>But for good comments, the situation is opposite. Good comments ADD to the code. They explain something that couldn’t be expressed directly. They are <em>important</em>.</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/yellow_comments.webp" loading="lazy"></figure>
<p>So here’s another controversial idea:</p>
<p class="loud">Comments should be highlighted, not hidden away.</p>
<p>Use bold colors, draw attention to them. Don’t shy away. If somebody took the time to tell you something, then you want to read it.</p>
<h1 id="two-types-of-comments">Two types of comments</h1>
<p>Another secret nobody is talking about is that there are two types of comments:</p>
<ol start="1">
  <li>Explanations</li>
  <li>Disabled code</li>
</ol>
<p>Most languages don’t distinguish between those, so there’s not much you can do syntax-wise. Sometimes there’s a convention (e.g. <code>--</code> vs <code>/* */</code> in SQL), then use it!</p>
<p>Here’s a real example from Clojure codebase that makes perfect use of two types of comments:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/two_types_of_comments.webp" loading="lazy"><figcaption>Disabled code is gray, explanation is bright yellow</figcaption></figure>
<h1 id="light-or-dark">Light or dark?</h1>
<p>Per statistics, 70% of developers prefer dark themes. Being in the other 30%, that question always puzzled me. Why?</p>
<p>And I think I have an answer. Here’s a typical dark theme:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/vscode_default_dark@2x.webp" loading="lazy"></figure>
<p>and here’s a light one:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/vscode_default_light@2x.webp" loading="lazy"></figure>
<p>On the latter one, colors are way less vibrant. Here, I picked them out for you:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/vscode_colors@2x.png" loading="lazy"><figcaption>Notice how many colors there are. No one can remember that many.</figcaption></figure>
<p>This is because dark colors are in general less distinguishable and more muddy. Look at Hue scale as we move brightness down:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/brightness_hue@2x.webp" loading="lazy"></figure>
<p>Basically, in the dark part of the spectrum, you just get fewer colors to play with. There’s no “dark yellow” or good-looking “dark teal”.</p>
<p>Nothing can be done here. There are no magic colors hiding somewhere that have both good contrast on a white background and look good at the same time. By choosing a light theme, you are dooming yourself to a very limited, bad-looking, barely distinguishable set of dark colors.</p>
<p>So it makes sense. Dark themes do look better. Or rather: light ones can’t look good. Science ¯\_(ツ)_/¯</p>
<p>But!</p>
<p>But.</p>
<p>There is one trick you can do, that I don’t see a lot of. Use background colors! Compare:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/bg_highlight@2x.png" loading="lazy"></figure>
<p>The first one has nice colors, but the contrast is too low: letters become hard to read.</p>
<p>The second one has good contrast, but you can barely see colors.</p>
<p>The last one has <em>both</em>: high contrast and clean, vibrant colors. Lighter colors are readable even on a white background since they fill a lot more area. Text is the same brightness as in the second example, yet it gives the impression of clearer color. It’s all upside, really.</p>
<p>UI designers know about this trick for a while, but I rarely see it applied in code editors:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/badge.png" loading="lazy"></figure>
<p>If your editor supports choosing background color, give it a try. It might open light themes for you.</p>
<h1 id="bold-and-italics">Bold and italics</h1>
<p>Don’t use. This goes into the same category as too many colors. It’s just another way to highlight something, and you don’t need too many, because you can’t highlight everything.</p>
<p>In theory, you might try to <em>replace</em> colors with typography. Would that work? I don’t know. I haven’t seen any examples.</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/typography.png" loading="lazy"><figcaption>Using italics and bold instead of colors</figcaption></figure>
<h1 id="myth-of-number-based-perfection">Myth of number-based perfection</h1>
<p>Some themes pay too much attention to be scientifically uniform. Like, all colors have the same exact lightness, and hues are distributed evenly on a circle.</p>
<p>This could be nice (to know if you have OCD), but in practice, it doesn’t work as well as it sounds:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/balanced.png" loading="lazy"><figcaption>OkLab l=0.7473 c=0.1253 h=0, 45, 90, 135, 180, 225, 270, 315</figcaption></figure>
<p>The idea of highlighting is to make things stand out. If you make all colors the same lightness and chroma, they will look very similar to each other, and it’ll be hard to tell them apart.</p>
<p>Our eyes are way more sensitive to differences in lightness than in color, and we should use it, not try to negate it.</p>
<h1 id="lets-design-a-color-theme-together">Let’s design a color theme together</h1>
<p>Let’s apply these principles step by step and see where it leads us. We start with the theme from the start of this post:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi00.png" loading="lazy"></figure>
<p>First, let’s remove highlighting from language keywords and re-introduce base text color:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi10.png" loading="lazy"></figure>
<p>Next, we remove color from variable usage:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi20.png" loading="lazy"></figure>
<p>and from function/method invocation:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi30.png" loading="lazy"></figure>
<p>The thinking is that your code is mostly references to variables and method invocation. If we highlight those, we’ll have to highlight more than 75% of your code.</p>
<p>Notice that we’ve kept variable declarations. These are not as ubiquitous and help you quickly answer a common question: where does thing thing come from?</p>
<p>Next, let’s tone down punctuation:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi40.png" loading="lazy"></figure>
<p>I prefer to dim it a little bit because it helps names stand out more. Names alone can give you the general idea of what’s going on, and the exact configuration of brackets is rarely equally important.</p>
<p>But you might roll with base color punctuation, too:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi40_alt.png" loading="lazy"></figure>
<p>Okay, getting close. Let’s highlight comments:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi50.png" loading="lazy"></figure>
<p>We don’t use red here because you usually need it for squiggly lines and errors.</p>
<p>This is still one color too many, so I unify numbers and strings to both use green:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi60.png" loading="lazy"></figure>
<p>Finally, let’s rotate colors a bit. We want to respect nesting logic, so function declarations should be brighter (yellow) than variable declarations (blue).</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi70.png" loading="lazy"></figure>
<p>Compare with what we started:</p>
<figure>
<img src="https://tonsky.me/blog/syntax-highlighting/dyi00.png" loading="lazy"></figure>
<p>In my opinion, we got a much more workable color theme: it’s easier on the eyes and helps you find stuff faster.</p>
<h1 id="shameless-plug-time">Shameless plug time</h1>
<p>I’ve been applying these principles for <a href="https://github.com/tonsky/vscode-theme-alabaster/commit/5c840f5fb57e5cd0dce93ac8c450495bdb0a2658">about 8 years now</a>.</p>
<p>I call this theme Alabaster and I’ve built it a couple of times for the editors I used:</p>
<ul>
  <li><a href="https://github.com/tonsky/vscode-theme-alabaster">VS Code</a></li>
  <li><a href="https://github.com/tonsky/intellij-alabaster">JetBrains IDEs</a></li>
  <li><a href="https://github.com/tonsky/sublime-scheme-alabaster">Sublime Text</a> (<a href="https://github.com/tonsky/clojure-sublimed/?tab=readme-ov-file#color-scheme">twice</a>)</li>
</ul>
<p>It’s also been ported to many other editors and terminals; the most complete list is <a href="https://github.com/tonsky/sublime-scheme-alabaster?tab=readme-ov-file#variations-1">probably here</a>. If your editor is not on the list, try searching for it by name—it might be built-in already! I always wondered where these color themes come from, and now I became an author of one (and I still don’t know).</p>
<p>Feel free to use Alabaster as is or build your own theme using the principles outlined in the article—either is fine by me.</p>
<p>As for the principles themselves, they worked out fantastically for me. I’ve never wanted to go back, and just one look at any “traditional” color theme gives me a scare now.</p>
<p>I <em>suspect</em> that the only reason we don’t see more restrained color themes is that people never really thought about it. Well, this is your wake-up call. I hope this will inspire people to use color more deliberately and to change the default way we build and use color themes.</p>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
  <entry>
    <title>Talk: Почему компьютеры не умеют считать? @ Podlodka</title>
    <link rel="alternate" type="text/html" href="https://tonsky.me/talks/#2025-09-01" />
    <id>https://tonsky.me/talks/#2025-09-01</id>
    <published>2025-09-01T00:00:00Z</published>
    <updated>2025-09-01T00:00:00Z</updated>
    <content type="html"><![CDATA[
<p>  <video poster="https://tonsky.me/talks/content/2025-09-01 Podlodka.webp" preload="none" controls="">
    <source src="https://tonsky.me/talks/content/2025-09-01 Podlodka.mp4" type="video/mp4">
  </video>
</p>
<p>Как компьютеры представляют числа – от int и float до NaN, BigInt, decimals и комплексных. Прошлись по всему числовому зоопарку: обсудили, зачем нужны разные типы, где они подводят, и почему 0.1 + 0.2 ≠ 0.3 – не баг, а особенность.</p>

]]></content>
    <author>
      <name>Nikita Prokopov</name>
      <email>niki@tonsky.me</email>
    </author>
  </entry>
</feed>
