<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yyzTools</title>
    <description>The latest articles on DEV Community by yyzTools (@jearry).</description>
    <link>https://dev.to/jearry</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4087854%2F6e862585-d7ef-4a3f-aace-1c7b111228c5.png</url>
      <title>DEV Community: yyzTools</title>
      <link>https://dev.to/jearry</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jearry"/>
    <language>en</language>
    <item>
      <title>v1.1.0: Mouse Gestures, a One-Click Desktop Tidy-Up, and Your Own LLM for Translation</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Sun, 04 Oct 2026 08:34:25 +0000</pubDate>
      <link>https://dev.to/jearry/v110-mouse-gestures-a-one-click-desktop-tidy-up-and-your-own-llm-for-translation-1p9o</link>
      <guid>https://dev.to/jearry/v110-mouse-gestures-a-one-click-desktop-tidy-up-and-your-own-llm-for-translation-1p9o</guid>
      <description>&lt;p&gt;&lt;em&gt;Release notes for &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt; v1.1.0.2000 (Sep 25, 2026), plus the design argument behind the headline feature: the command palette owns the keyboard — mouse gestures give the mouse the same deal. One trigger, one grammar.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flcgzvy3mv8wsqs54gkue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flcgzvy3mv8wsqs54gkue.png" alt="mouse gestures" width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Five days after 1.0.9 shipped space-bar file preview, v1.1.0.2000 is out. The headline is mouse gestures; the quieter updates (custom LLM translation, a one-click desktop organizer, OCR quality-of-life) are the ones I suspect daily users will feel most. Full list first, then the design argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in 1.1.0.2000
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mouse gestures&lt;/strong&gt; — hold the trigger button, draw a stroke, an action runs: simulated hotkeys, window control (minimize/maximize/close), volume, launch an app, switch tasks, web search. On by default, off in Settings → Mouse Gestures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dock: one-click desktop organize&lt;/strong&gt; — the dock gained a button that sweeps scattered desktop icons into dock categories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom LLM translation engine&lt;/strong&gt; — Settings → Translate → Translation engine → Custom LLM. Point the translator at any OpenAI-compatible endpoint: your own key, your own host, your own model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick OCR &amp;amp; Screenshot: regular hotkeys&lt;/strong&gt; — they now behave like first-class system features instead of palette-only actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick OCR: partial text copy&lt;/strong&gt; — select and copy just the lines you need from recognized text; the window can be dragged from any blank area.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OCR &amp;amp; Quick OCR: "merge sentences"&lt;/strong&gt; — an option that joins fragmented recognition output back into readable lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dock: drag items across categories&lt;/strong&gt; — plus popup polish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lightning System Optimizer: rule-handling refinements.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App usage stats: fixed&lt;/strong&gt; incorrect LockApp.exe durations after sleep/hibernate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The design argument: the mouse needed its own keyboard shortcuts
&lt;/h2&gt;

&lt;p&gt;The command palette in yyzTools has one rule: a single hotkey (Alt+Space) plus a grammar (type &lt;code&gt;f&lt;/code&gt; for files, &lt;code&gt;c&lt;/code&gt; for clipboard, &lt;code&gt;p&lt;/code&gt; for processes...). Users remember one trigger and a vocabulary, not forty hotkeys.&lt;/p&gt;

&lt;p&gt;Keyboards got that deal. The mouse never did — the mouse's "shortcuts" are pixel targets in corners (close, minimize, the taskbar's far end), each one a small aiming exercise.&lt;/p&gt;

&lt;p&gt;Gestures give the mouse the same structure: &lt;strong&gt;hold one trigger, draw one stroke.&lt;/strong&gt; Direction is the grammar. &lt;code&gt;↓&lt;/code&gt; is minimize for me, &lt;code&gt;←→&lt;/code&gt; is task switch, and the stroke takes less time than moving the cursor to a title-bar button. The action set (hotkeys, windows, volume, apps, tasks, search) deliberately mirrors the palette's — anything the keyboard grammar can express, the mouse grammar can too.&lt;/p&gt;

&lt;p&gt;Two policy choices worth stating:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;On by default, one-click off.&lt;/strong&gt; A feature that intercepts mouse buttons must be trivially removable. If you live in middle-click-drag apps (Blender, CAD), Settings → Mouse Gestures is one toggle away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strokes over letters.&lt;/strong&gt; Straight-line strokes (up/down/left/right and combos) — not drawn characters. Strokes survive sloppy input; letter recognition punishes exactly the hurried users who want the feature.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why custom LLM translation is a privacy feature, not an AI feature
&lt;/h2&gt;

&lt;p&gt;The translator has always been the one part of yyzTools that must touch the network — translating text means asking &lt;em&gt;someone&lt;/em&gt; who knows both languages. Until now, that someone was a fixed public service.&lt;/p&gt;

&lt;p&gt;Now it's whoever you say: any OpenAI-compatible endpoint. A local model behind Ollama or LM Studio, a corporate gateway, a provider you've actually read the privacy policy of. The request goes where you point it, which turns "where does my selected text go?" from a trust question into a configuration question.&lt;/p&gt;

&lt;p&gt;For an otherwise local-first suite, that's the right shape for AI features: the capability is welcome, the endpoint must be yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The desktop organizer: the desktop is a surface, not a landfill
&lt;/h2&gt;

&lt;p&gt;One-click organize exists because desktops accumulate: screenshots, downloads, that one folder you keep meaning to sort. The dock can now sweep icons into its categories in one pass — files into a files category, shortcuts into their apps — leaving the desktop usable as, well, a desktop.&lt;/p&gt;

&lt;p&gt;It pairs oddly well with the gestures release. Between "draw a stroke to minimize everything" and "one click to tidy the desktop," v1.1.0 is quietly the release about surfaces: less digging through piles, more staying where you are.&lt;/p&gt;




&lt;p&gt;As always: free, no account, no telemetry; network calls belong to features that inherently need one, and translation now goes to the endpoint &lt;em&gt;you&lt;/em&gt; choose. Windows 10/11, 12 languages, 387 command modules. Setup 175 MB, portable 166.8 MB, incremental updates after that. &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What would you bind to a stroke? I'm collecting bindings worth shipping as examples — drop yours in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>Space-Bar File Preview Everywhere: Borrowing macOS Quick Look's Best Trick for Windows</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Sat, 03 Oct 2026 15:44:26 +0000</pubDate>
      <link>https://dev.to/jearry/space-bar-file-preview-everywhere-borrowing-macos-quick-looks-best-trick-for-windows-8b9</link>
      <guid>https://dev.to/jearry/space-bar-file-preview-everywhere-borrowing-macos-quick-looks-best-trick-for-windows-8b9</guid>
      <description>&lt;p&gt;&lt;em&gt;I build &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt; — a free, local-first Windows toolkit that folds 40+ tools into one installer. The 1.0.9 release added system-level space-bar file preview, and the interesting parts are the trigger chain (borrowed with credit from QuickLook), the "one preview control, four entry points" rule, and what falls through to a hex viewer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4w6mhqzb7fu7v4q9n43i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4w6mhqzb7fu7v4q9n43i.png" alt="file preview" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;macOS users have had this since 2009: select a file, hit space, see it. Hit space again, it's gone. No app launches, no focus stolen, no ceremony. Windows never shipped an answer, and the third-party attempts all stop one step short — they preview &lt;em&gt;in Explorer&lt;/em&gt;, but the moment you find the file somewhere else (a search tool, a disk analyzer), you're back to double-clicking.&lt;/p&gt;

&lt;p&gt;yyzTools 1.0.9 (Sep 20) ships space-bar preview, and the design rule that shaped it was: &lt;strong&gt;preview has to work at every point where you meet a file&lt;/strong&gt;, or it doesn't really work anywhere. This post is about the three engineering decisions that made that rule affordable.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The trigger: listen, don't intercept
&lt;/h2&gt;

&lt;p&gt;The obvious implementation is a global keyboard hook (&lt;code&gt;SetWindowsHookEx&lt;/code&gt;) that grabs VK_SPACE when Explorer has focus. The obvious implementation is also the one that breaks scrolling: Explorer already uses space (scroll down in details view), and a hook that swallows the key to open a preview has now degraded every file manager it touches.&lt;/p&gt;

&lt;p&gt;What shipped instead is a read-only listener on RawInput:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RawInput, not a hook.&lt;/strong&gt; The preview manager registers for raw keyboard input in its own process. It &lt;em&gt;sees&lt;/em&gt; the space press; it never &lt;em&gt;consumes&lt;/em&gt; it. Explorer's native space behavior keeps working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The trigger chain idea is borrowed from QuickLook&lt;/strong&gt; (the open-source Windows one — &lt;a href="https://github.com/QL-Win/QuickLook" rel="noopener noreferrer"&gt;QL-Win/QuickLook&lt;/a&gt;, GPL). Credit where due: their space-up/space-down handling and auto-repeat suppression is documented in their source, and I followed the same approach rather than rediscovering it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A one-second suppression after typing.&lt;/strong&gt; If the focused window recently received character input (you just renamed a file, you're in the search box), space is a spacebar, not a preview toggle. The listener waits for the input context to go quiet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus and selection tracking.&lt;/strong&gt; A 500 ms poll follows the current selection in the active file manager; when focus leaves file-management surfaces, the preview hides itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On by default, off in Settings. A feature that types in file managers has to be removable in one click, or it's malware-adjacent.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Getting the selected file: one interface, seven file managers
&lt;/h2&gt;

&lt;p&gt;"Which file is selected" sounds trivial until you leave Explorer. The selection probe (&lt;code&gt;ExplorerSelection&lt;/code&gt;) speaks several dialects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explorer &amp;amp; desktop&lt;/strong&gt; — Shell COM (&lt;code&gt;IShellWindows&lt;/code&gt; / &lt;code&gt;IFolderView2&lt;/code&gt;), the honest way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directory Opus&lt;/strong&gt; — their official &lt;code&gt;dopusrt&lt;/code&gt; IPC, because DOpus is its own universe and they document the door.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything, Multi Commander, IDM, FilePilot, DeskBox&lt;/strong&gt; — each with its own probe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same interface behind all of them: &lt;code&gt;GetSelectedPath()&lt;/code&gt; or nothing. The preview doesn't care which app is in front; it cares whether &lt;em&gt;something&lt;/em&gt; answered.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. One preview control, four entry points
&lt;/h2&gt;

&lt;p&gt;This is the part I'd defend hardest. The preview window is a single &lt;code&gt;WinFilePreview&lt;/code&gt; surface with one non-negotiable behavior: &lt;strong&gt;it never steals focus.&lt;/strong&gt; It shows with &lt;code&gt;SW_SHOWNOACTIVATE&lt;/code&gt;, which is what lets the arrow keys in the file manager keep moving the selection while the preview follows along — select file, space, arrow, arrow, each one re-rendering. That follow-along loop is the entire value of Quick Look, and it dies the moment the preview takes keyboard focus.&lt;/p&gt;

&lt;p&gt;Inside, rendering is a WebView2 host with one front-end module per file type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images (jpg/png/gif/webp/svg...), zoom and rotate&lt;/li&gt;
&lt;li&gt;~57 text/code extensions with CodeMirror syntax highlighting&lt;/li&gt;
&lt;li&gt;Markdown, PDF (paginated)&lt;/li&gt;
&lt;li&gt;Office: docx/xlsx/pptx first page — no Office install needed&lt;/li&gt;
&lt;li&gt;Video (mp4/webm/mov/mkv) and audio (mp3/wav/ogg) play inline&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3D models (gltf/glb/fbx/obj/stl/ply/dae) — interactive, you can rotate them&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CAD drawings (DXF)&lt;/strong&gt; — parsed and rendered client-side&lt;/li&gt;
&lt;li&gt;Anything unrecognized falls through to a binary hex view, not a blank window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 3D/CAD line matters more than it looks: most Windows previewers don't have it, and for anyone working with game assets or mechanical drawings, "space to spin the model" is the whole feature.&lt;/p&gt;

&lt;p&gt;And the entry points: Explorer (and the third-party managers above), the built-in Lightning File Search, the Lightning Disk Space Analyzer, and the command palette's file module. Same key, same window, same behavior. The search → preview → Enter-to-open loop is where it pays off daily: you stopped opening files to check if they're the right files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;Honest limits, so nobody's surprised: archives don't list their contents (they fall to the hex view), PSD/RAW aren't rendered, odd encodings can garble, and huge videos take a second or two before the first frame. AVI/WMV/FLV depend on what WebView2's codec set will decode.&lt;/p&gt;




&lt;p&gt;yyzTools is free, no account, no telemetry — the only network calls in the suite are features that inherently need one (translation, update checks). Windows 10/11, 12 languages, 387 command modules, &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;. Questions about the RawInput chain, the DOpus IPC, or the focus-preserving window are welcome in the comments.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>software</category>
      <category>tools</category>
    </item>
    <item>
      <title>One Index, Four Tools: What Happens When Your Search Engine Becomes a Platform</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Fri, 18 Sep 2026 13:47:22 +0000</pubDate>
      <link>https://dev.to/jearry/one-index-four-tools-what-happens-when-your-search-engine-becomes-a-platform-cl7</link>
      <guid>https://dev.to/jearry/one-index-four-tools-what-happens-when-your-search-engine-becomes-a-platform-cl7</guid>
      <description>&lt;p&gt;&lt;em&gt;I build &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt; — a free, local-first Windows toolkit that folds 40+ tools (command palette, file search, clipboard history, OCR, batch processing) into one installer. The 1.0.8 release ships four new maintenance tools, and they all share one unusual design decision: none of them scan your disk. They ask an index that's already there.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3z76kwwpmm73rn9pd98l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3z76kwwpmm73rn9pd98l.png" alt="Lightning Disk Space Analyzer — 533 GB, 1,108,909 files, no scan phase" width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fys4ie742wvc8tljenfnt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fys4ie742wvc8tljenfnt.png" alt="One index, four tools — the 1.0.8 architecture" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool that was secretly an engine
&lt;/h2&gt;

&lt;p&gt;Back in 1.0.5 I wrote about replacing Everything with a homegrown search engine: read the NTFS Master File Table directly, store the metadata columnar, snapshot it to disk, load via mmap. The resident index for a million-plus files ended up at a few megabytes, and queries stayed Everything-compatible.&lt;/p&gt;

&lt;p&gt;That engine runs as a Windows service. The service holds the volume handles (reading raw &lt;code&gt;$MFT&lt;/code&gt; needs more than a standard user token), keeps the snapshots fresh from the USN journal, and answers queries over named pipes. The GUI never elevates.&lt;/p&gt;

&lt;p&gt;For a year, that service had exactly one consumer: the file search window. Which always felt a bit wasteful. A fresh MFT snapshot is effectively a &lt;em&gt;complete catalog of every file on your disk&lt;/em&gt; — the treemap above is 533 GB across 1,108,909 files, drawn before you could have finished reading a progress bar. Every disk utility that starts by walking directory trees is re-deriving information the index already has.&lt;/p&gt;

&lt;p&gt;So 1.0.8 is the release where three new tools stopped deriving it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consumer #1: the disk analyzer that never scans
&lt;/h2&gt;

&lt;p&gt;Space analyzers are famous for their progress bars. WinDirStat, TreeSize, all of them — you launch it, pick a drive, and watch a tree walk grind through a few hundred thousand directories before anything useful appears.&lt;/p&gt;

&lt;p&gt;Ours has no progress bar because there's nothing to watch. The analyzer sends a &lt;code&gt;dirsize&lt;/code&gt; query to the service and gets sizes back from the snapshot — a first pass over a volume is the time of one IPC round trip. "Drilling down" is just asking about a subdirectory, which is another round trip. The client keeps no state at all; there's nothing to invalidate when files change, because nothing was cached.&lt;/p&gt;

&lt;p&gt;The interesting UI part is a squarified treemap (Bruls et al. — the layout algorithm, not a vibe). Folder area is proportional to bytes, anything below 0.75% of the view gets merged into an "other" tile so the map stays readable. Right-click gives you recycle-bin delete, permanent delete, copy path, or open in Explorer — and the delete path validates that the target is inside the current result set before it acts, because a treemap tile is a view of data, not a file handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consumer #2: the file cleaner with a budget
&lt;/h2&gt;

&lt;p&gt;The new cleaner covers 152 rules in eight categories: system, browsers, apps, AI tool caches, containers, development artifacts, project build outputs, and a "large files" bucket that's really a &lt;code&gt;size:&amp;gt;50MB&lt;/code&gt; aggregate query grouped by extension. That last one is a good example of how rules and queries blur: a rule that would take a recursive scan minutes is, over the index, one aggregate call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5pi548eoy9wfftb3i7ww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5pi548eoy9wfftb3i7ww.png" alt="Lightning File Scan &amp;amp; Clean — 152 rules, Safe/Elevation badges, everything reviewed before anything runs" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scan reuses the service's &lt;code&gt;dirsize&lt;/code&gt;/&lt;code&gt;aggregate&lt;/code&gt; queries. No directory walk, no per-file &lt;code&gt;stat()&lt;/code&gt; storm.&lt;/p&gt;

&lt;p&gt;One design choice I want to defend: &lt;strong&gt;this cleaner deletes permanently.&lt;/strong&gt; It does not use the recycle bin, because cleaning 8 GB of build artifacts into the recycle bin doesn't clean anything — it just renames the problem. But permanent deletion needs to earn trust, so it runs behind four gates:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Whitelist roots&lt;/strong&gt; — five built-in roots plus detected project roots are never touched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protected segments&lt;/strong&gt; — name-level protections, including chat application database files (the ones that hold your message history).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reparse points are never followed or deleted&lt;/strong&gt; — a junction or symlink is a boundary, not a shortcut through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process re-check&lt;/strong&gt; — if a rule targets an app's cache, the app must not be running before its files go.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Riskier rules ship unchecked by default. The checklist has three-state semantics (parent / partial / all), the list goes read-only mid-clean, and you can stop at any time — the report logs what it stopped with, honestly, as "stopped".&lt;/p&gt;

&lt;h2&gt;
  
  
  Consumer #3: the search window grew a native UI
&lt;/h2&gt;

&lt;p&gt;The file search window itself moved from WebView2 to ImGui on DX11. Nothing against the browser engine, but a search results list wants to scroll a hundred thousand rows without caring, and Dear ImGui does that on a UI thread that sleeps in &lt;code&gt;WaitMessage&lt;/code&gt; when nothing is happening. The window now follows the system theme and language in real time — it listens for the same &lt;code&gt;ImmersiveColorSet&lt;/code&gt; broadcast Windows sends when dark mode flips.&lt;/p&gt;

&lt;p&gt;The engine underneath didn't change: bigram inverted index, USN catch-up, 5–30 MB resident. The status bar in the screenshot below is the whole pitch in one line — &lt;em&gt;search service online (live index), results: 255 (155 ms)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ga26p0e1kae8fbqp1gj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5ga26p0e1kae8fbqp1gj.png" alt="Lightning File Search on the new ImGui UI, dark theme — 255 results in 155 ms" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The plumbing nobody sees but everybody feels
&lt;/h2&gt;

&lt;p&gt;Three front ends plus the existing batch tools now hit one service over named pipes. The concurrency model matters more than it sounds: interactive searches get their own slot, and heavy queries (&lt;code&gt;dirsize&lt;/code&gt;/&lt;code&gt;aggregate&lt;/code&gt;) get three more, assigned to client processes LRU-style so a third tool degrades gracefully instead of colliding. The two groups never preempt each other — a long cleanup scan can't abort your ad-hoc search, and vice versa; within a slot, a newer request from the same client replaces its own stale one, which is exactly the "user already typed something else" case.&lt;/p&gt;

&lt;p&gt;One subtlety worth keeping: the front end can't distinguish "indexed" from "truth" — the snapshot is only as fresh as the last USN catch-up. For search that's fine. For a tool that's about to &lt;em&gt;delete&lt;/em&gt; files, it isn't, which is why the cleaner re-validates through the normal filesystem layer before anything destructive, and why the analyzer's delete path confirms the target exists where the treemap says it does.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[FileClean rule] --&amp;gt;|"aggregate query"| S[Named pipe]
    B[DiskSpace drill-down] --&amp;gt;|"dirsize query"| S
    S --&amp;gt;|"own slot per group&amp;lt;br/&amp;gt;no cross-preemption"| C[Service answers&amp;lt;br/&amp;gt;from mmap snapshot]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  What I'd take from this
&lt;/h2&gt;

&lt;p&gt;The general lesson, if there is one: &lt;strong&gt;when you own an index, every new tool has a much shorter spec.&lt;/strong&gt; The cleaner's "scan" feature is a query. The analyzer's entire engine is an IPC client. The hard part — reading NTFS fast, storing it small, keeping it fresh — was paid once, and four tools now amortize it.&lt;/p&gt;

&lt;p&gt;There's a product lesson in there too. 1.0.8 looks like "four new tools" in a changelog, but from the inside it's one capability (a resident disk catalog) finding three more users. If you're building a suite, the features that compose like this are worth more than the ones that bolt on.&lt;/p&gt;




&lt;p&gt;yyzTools 1.0.8 is free, no account, no telemetry — the analyzer and cleaner work entirely from local data, and the only network calls in the suite belong to features that inherently need one (like web translation). Windows 10/11, 12 languages, at &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;. Happy to answer questions about the treemap layout, the IPC slot design, or the safety model in the comments.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>tools</category>
    </item>
    <item>
      <title>You Can Write Desktop Tools in Plain HTML/CSS/JS — Here's the SDK That Lets You</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Mon, 07 Sep 2026 02:36:52 +0000</pubDate>
      <link>https://dev.to/jearry/you-can-write-desktop-tools-in-plain-htmlcssjs-heres-the-sdk-that-lets-you-5597</link>
      <guid>https://dev.to/jearry/you-can-write-desktop-tools-in-plain-htmlcssjs-heres-the-sdk-that-lets-you-5597</guid>
      <description>&lt;p&gt;&lt;em&gt;I build &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt; — a free, local-first Windows productivity suite (command palette, clipboard history, OCR, file preview, batch processing — 40+ tools in one install). Today's post isn't about the app itself; it's about the module SDK I just opened up, because the development model is the kind of thing frontend developers keep telling me should be impossible: **one static webpage + one JSON file = a native desktop tool window.&lt;/em&gt;* No C++. No build step. No packaging.*&lt;/p&gt;




&lt;h2&gt;
  
  
  The demo first
&lt;/h2&gt;

&lt;p&gt;Download the sample project, unzip it, and you get two folders — &lt;code&gt;Web/&lt;/code&gt; and &lt;code&gt;Modules/&lt;/code&gt;. Copy them next to &lt;code&gt;yyzTools.exe&lt;/code&gt;, restart, and hit Alt+Space. Type &lt;code&gt;sdk&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;A 760×540 native window opens. It's a full-disk file search app — instant results, Everything-style syntax (&lt;code&gt;ext:pdf size:&amp;gt;100mb&lt;/code&gt;), double-click to open files.&lt;/p&gt;

&lt;p&gt;Behind that window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Web/SDK_Sample/
  index.html       # plain DOM, data-i18n attributes
  filefinder.js    # all the logic (vanilla JS, zero deps)
  filefinder.css   # styled entirely with --zen-* variables
  i18n.js          # string dictionary (zh_cn / en)
  lib/
    zen_api.js     # the ONLY library — the native API wrapper
Modules/SDK_Sample.zenmod   # one JSON declaring the window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;package.json&lt;/code&gt;. No &lt;code&gt;node_modules&lt;/code&gt;. No bundler. It's 2026 and I'm shipping an SDK whose sample runs without a toolchain — deliberately, and the reason matters.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcl07cwvthggdiy6mkhav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcl07cwvthggdiy6mkhav.png" alt=" " width="799" height="568"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcm644ly0xfszkrrjuwr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcm644ly0xfszkrrjuwr6.png" alt=" " width="799" height="568"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How the window gets born: it's declared, not built
&lt;/h2&gt;

&lt;p&gt;The part of desktop development that scares everyone off was never business logic — it's windows. Message loops, DPI awareness, taskbar integration, hotkey registration. The SDK's answer is to make the host a data file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sdk_sample"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SDK_Sample/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;760&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"layoutType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fas fa-search"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The host reads this at startup and creates a native window with a WebView2 control pointing at your page. And because the module now exists in the app's registry, it inherits the whole suite's infrastructure for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Command palette search&lt;/strong&gt; — localized across all 12 UI languages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global hotkey binding&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dock mounting&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme integration&lt;/strong&gt; — the host injects &lt;code&gt;--zen-*&lt;/code&gt; CSS variables; your page follows the app's light/dark mode with zero &lt;code&gt;prefers-color-scheme&lt;/code&gt; media queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You write the feature. The host absorbs the decade of Win32 plumbing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1ieo8ffkeo9vnhefkoz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1ieo8ffkeo9vnhefkoz.png" alt=" " width="800" height="71"&gt;&lt;/a&gt;## Talking to the system: the ZenAPI bridge&lt;/p&gt;

&lt;p&gt;Your webpage gets system powers through a &lt;code&gt;window.Zen&lt;/code&gt; bridge, consumed via an official &lt;code&gt;ZenAPI&lt;/code&gt; wrapper class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ZenAPI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./lib/zen_api.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Full-disk search (async — runs on a background thread)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ZenAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice ext:pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ZenAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isOk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* unified error check */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Open the file / reveal in Explorer&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ZenAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ZenAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openFileLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Persist your own config (narrow-grained patch, not full rewrite)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ZenAPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;sdk_sample&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One design detail worth stealing for anyone building a JS bridge: &lt;code&gt;searchFile&lt;/code&gt; is asynchronous, and the native side implements a &lt;strong&gt;search slot&lt;/strong&gt; — a new request &lt;em&gt;displaces&lt;/em&gt; any in-flight one (the old Promise resolves empty). Combined with 300ms input debounce and a monotonically increasing sequence number on the frontend to discard out-of-order responses, rapid typing neither floods the background thread nor flashes stale results. In interactive search, the correct semantic for concurrent requests is &lt;em&gt;overwrite&lt;/em&gt;, not &lt;em&gt;queue&lt;/em&gt; — the opposite of what server-side intuition tells you.&lt;/p&gt;

&lt;p&gt;The API surface goes well beyond search: file dialogs, chunked reads for large files, clipboard, screenshot → OCR pipeline, spawning external programs, invoking other module windows. The full reference is published in 12 languages — including which interface returns &lt;code&gt;isDir&lt;/code&gt; as a 1/0 &lt;em&gt;number&lt;/em&gt; rather than a boolean, because hand-rolled JSON paths and property-tree paths serialize differently. Documenting the type traps is the difference between docs people trust and docs people tolerate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging: just open it in a browser
&lt;/h2&gt;

&lt;p&gt;This is the part that sells frontend developers. Double-click &lt;code&gt;index.html&lt;/code&gt; — it opens in your normal browser. There's no &lt;code&gt;window.Zen&lt;/code&gt; there, so &lt;code&gt;ZenAPI.call&lt;/code&gt; goes down the exception path and returns &lt;code&gt;error: -1&lt;/code&gt;, but your layout, styles, debounce, and rendering logic all work normally. Iterate the UI in the browser; hop into the real host only for native integration. The degradation path was designed in from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest security section
&lt;/h2&gt;

&lt;p&gt;The native API is fully open to module developers: file read/write/delete, process creation, window control, clipboard. &lt;strong&gt;No permission prompts, no sandbox, no undo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a trust-model decision, not an oversight. This SDK targets developers building tools &lt;em&gt;for themselves or their team&lt;/em&gt; — the deployment unit is "copy a folder", the distribution radius is people who already trust you. If an attacker can write to your modules directory, they can already drop an exe; disguising it as a &lt;code&gt;.zenmod&lt;/code&gt; gains them nothing. Sandboxing buys you nothing here and costs every module author a permissions negotiation they don't need.&lt;/p&gt;

&lt;p&gt;If I ever build a public module store, this design gets rebuilt — different trust model, different security requirements. Match the security to the trust model, not to a checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bother
&lt;/h2&gt;

&lt;p&gt;If you write web pages at all, here's the pitch: your existing skillset now produces desktop tools with a native window, global hotkeys, tray integration, and a theming system — for the cost of one folder and one JSON. The internal tool you'd otherwise ship as "open this localhost link" can be a real window in the palette.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs (quick start + full API reference): &lt;a href="https://yyztools.com/sdk.html" rel="noopener noreferrer"&gt;yyztools.com/sdk.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sample project download: same page&lt;/li&gt;
&lt;li&gt;The app: free forever, no accounts, no telemetry, Windows 10/11&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sample is a genuinely pleasant read — four files, an hour, including the debounce/race handling. If you build something with it, drop it in the comments; good ones may make it into the built-in catalog.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Local-First Tax: What It Actually Costs to Refuse the Cloud</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:03:38 +0000</pubDate>
      <link>https://dev.to/jearry/the-local-first-tax-what-it-actually-costs-to-refuse-the-cloud-bnp</link>
      <guid>https://dev.to/jearry/the-local-first-tax-what-it-actually-costs-to-refuse-the-cloud-bnp</guid>
      <description>&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt; — a Windows productivity suite (clipboard history, OCR, file search, app usage stats) that is aggressively local-first by design. No accounts, no telemetry, no cloud sync. This post isn't about the product. It's about the real costs of the stance.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The pitch vs. the tax
&lt;/h2&gt;

&lt;p&gt;"Local-first" is a compelling pitch: your data stays on your machine, no cloud dependencies, no breach exposure, works offline. It resonates with privacy-conscious users and with developers who've seen what happens when a cloud service sunsets your data.&lt;/p&gt;

&lt;p&gt;But the pitch hides real costs. After building a local-first tool for years, here's the tax I've paid—and what I'm still not sure is worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The costs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Multi-device sync is genuinely gone
&lt;/h3&gt;

&lt;p&gt;"Copy something at work, see it at home" is the cloud clipboard's killer feature. I can't offer it without violating the core principle (data leaves the machine). My users—and I—work around it by manually syncing the local storage directory with a file-sync tool.&lt;/p&gt;

&lt;p&gt;That feels like 2005. The cloud clipboard isn't popular by accident; cross-device sync is genuinely useful. Telling users "just sync the DB folder yourself" is a real regression in convenience.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Local models are heavy
&lt;/h3&gt;

&lt;p&gt;The OCR engine (RapidOCR's ONNX models) ships in the installer. The app is ~600MB. A cloud OCR tool is a 5MB app + a server. Users with small SSDs or slow connections notice.&lt;/p&gt;

&lt;p&gt;There's no way around this with local-first: a capable local model has weight. The trade is "data stays local" for "installer is large." I've made peace with it, but it's a tax.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No network effects
&lt;/h3&gt;

&lt;p&gt;Cloud tools get sticky because your data is there, your collaborators are there, your history is there. A local tool has none of that. Retention is purely "does this work well enough day to day?"—which is fragile. A user can walk away any time, taking nothing with them.&lt;/p&gt;

&lt;p&gt;For a solo dev without a marketing budget, this is a slow grind. Cloud tools compound (data lock-in); local tools don't.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The convenience/privacy framing is mostly a false binary
&lt;/h3&gt;

&lt;p&gt;"People will trade privacy for convenience" is the industry line. In my experience, users don't perceive the trade at all. They hit "allow" and use the cloud tool. The local-first stance is &lt;strong&gt;invisible until something goes wrong&lt;/strong&gt; (a breach, a policy change, a sunset), at which point it's too late—the data is already gone or compromised.&lt;/p&gt;

&lt;p&gt;You're selling insurance against a low-probability event. Most users don't buy insurance they can't see.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The user is responsible for backups
&lt;/h3&gt;

&lt;p&gt;Cloud tools hide backup responsibility. Local-first puts it on the user. I document the storage path and recommend syncing it, but I can't force it. If a user's drive dies and they didn't sync, their clipboard history is gone.&lt;/p&gt;

&lt;p&gt;This is philosophically correct ("it's your data, you own the responsibility"), but operationally it's a support burden and a UX cliff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I drew the line
&lt;/h2&gt;

&lt;p&gt;Not everything can be local. Translation, downloads, opening URLs—these are network by nature. The question is how to handle the network features honestly.&lt;/p&gt;

&lt;p&gt;My rule: &lt;strong&gt;the network feature's data destination is explicit and user-chosen.&lt;/strong&gt; Translation sends data to &lt;em&gt;the service the user picks in settings&lt;/em&gt; (DeepL, Google, etc.), not a fixed yyzTools server. The user knows where their text goes because they chose the service. No hidden relay.&lt;/p&gt;

&lt;p&gt;This is a middle path: local-first for the data that can be local (clipboard, OCR, search, stats), explicit-and-chosen for the data that can't (translation). It's not pure, but it's honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The no-account stance
&lt;/h2&gt;

&lt;p&gt;Accounts aren't just "friction." They're a privacy design decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Account → behavior tied to identity&lt;/strong&gt; → you're processing personal information, with all the compliance that implies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No account → no identity&lt;/strong&gt; → behavior can't be linked to a person → no personal information processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;yyzTools has no accounts, no telemetry, no behavioral collection. This isn't "saving a registration step"—it's a structural choice that removes the &lt;em&gt;possibility&lt;/em&gt; of identity-behavior linkage. For users in regulated environments (enterprise, healthcare, government), this is a feature, not a limitation.&lt;/p&gt;

&lt;p&gt;For personal users, it's invisible. Nobody installs a clipboard manager thinking "glad there's no account." They notice the absence only when a cloud tool's breach headlines hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it defensible?
&lt;/h2&gt;

&lt;p&gt;I keep going back and forth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The case for:&lt;/strong&gt; privacy regulations are tightening (GDPR, data residency laws). As breaches accumulate and cloud trust erodes, the local-first stance ages into relevance. The users who care are a minority, but they're loyal. The stance differentiates in a market where "free + cloud + ads" is the default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The case against:&lt;/strong&gt; 95% of users don't value it until it's too late. The convenience tax (no sync, large installer, no collaboration) is felt daily; the privacy benefit is felt never (until a breach, which probably won't happen to them). You're paying a real daily cost for an insurance most users will never collect on.&lt;/p&gt;

&lt;p&gt;I don't have a conclusion. I have a stance and a tax bill, and I'm still calculating whether the exchange is worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually want to ask
&lt;/h2&gt;

&lt;p&gt;I'd love to hear from both sides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For users who've tried local-first tools and reverted to cloud:&lt;/strong&gt; what was the breaking point? Sync? Collaboration? The lack of a web UI?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For users who run local-first and wouldn't go back:&lt;/strong&gt; what makes it worth the convenience cost for you?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For builders:&lt;/strong&gt; is local-first a defensible long-term position, or a boutique stance that 95% of users don't value? Does it only make sense in regulated industries, or is there a broader market?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The middle path&lt;/strong&gt;—local for what can be local, explicit-chosen cloud for what can't—is it meaningfully different from a cloud tool, or just a cloud tool with better labeling?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not here to sell you on local-first. I'm here because I'm honestly uncertain whether it's a defensible position or a philosophical luxury, and I'd value outside perspective.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building yyzTools — a free, local-first Windows productivity suite. No accounts, no telemetry, no cloud sync. Website: &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;. The "no sync" tax is real; I use a file-sync tool myself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>privacy</category>
      <category>software</category>
      <category>startup</category>
    </item>
    <item>
      <title>Why I Replaced Everything With a Homegrown MFT Search Engine (Memory: Hundreds of MB a Few MB)</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:21:47 +0000</pubDate>
      <link>https://dev.to/jearry/why-i-replaced-everything-with-a-homegrown-mft-search-engine-memory-hundreds-of-mb-a-few-mb-p87</link>
      <guid>https://dev.to/jearry/why-i-replaced-everything-with-a-homegrown-mft-search-engine-memory-hundreds-of-mb-a-few-mb-p87</guid>
      <description>&lt;p&gt;&lt;em&gt;I build &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt; — a free, local-first Windows productivity suite that folds 40+ tools (command palette, clipboard history, OCR, file preview, batch processing) into one installer. This post is about the 1.0.5 release, where I did something that sounds insane: I removed my dependency on Everything — the gold standard of Windows file search — and wrote my own engine.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;For about two years, file search in yyzTools was powered by Everything. And let me be clear: Everything is phenomenal. Sub-second full-disk search, a mature query syntax (&lt;code&gt;ext:pdf size:&amp;gt;100mb dm:today&lt;/code&gt;), battle-tested by millions of users. Integrating it was one of the best decisions I made early on — instant world-class search for free.&lt;/p&gt;

&lt;p&gt;But there was a catch that grew more annoying with every release: &lt;strong&gt;Everything is designed to be used standalone.&lt;/strong&gt; Its index lives in its own service process. On a machine with a lot of files, that index holds &lt;strong&gt;hundreds of MB of RAM&lt;/strong&gt;, resident from boot to shutdown.&lt;/p&gt;

&lt;p&gt;For a standalone search tool, that's a perfectly reasonable price. But yyzTools is a &lt;em&gt;resident&lt;/em&gt; suite — the whole point is that it sits quietly in your tray until you hit Alt+Space. Every MB of resident memory lowers the threshold at which users uninstall you. And search is the heaviest feature in the suite.&lt;/p&gt;

&lt;p&gt;So the conflict was structural: &lt;strong&gt;the dimension Everything optimizes least for (being embedded in someone else's memory-sensitive app) was exactly my core requirement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In 1.0.5, I replaced it. Index memory went from hundreds of MB to &lt;strong&gt;a few MB&lt;/strong&gt;. Query syntax stayed Everything-compatible. Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrong turn #1: the "official" API is a trap
&lt;/h2&gt;

&lt;p&gt;When you start researching full-disk enumeration on Windows, you find &lt;code&gt;FSCTL_ENUM_USN_DATA&lt;/code&gt;. Documented, blessed, gives you every file's FRN (file reference number) and parent FRN. Seems like the obvious choice.&lt;/p&gt;

&lt;p&gt;I implemented it. &lt;strong&gt;1.34 million files, 120 seconds to enumerate.&lt;/strong&gt; Rejected.&lt;/p&gt;

&lt;p&gt;The problem: USN records are a &lt;em&gt;change journal&lt;/em&gt;, not a catalog. They don't carry file size or timestamps — so for every single file you have to call back with &lt;code&gt;FSCTL_GET_NTFS_FILE_RECORD&lt;/code&gt; to fetch the full MFT record. A million random reads by FRN. That's not slow code; that's a wrong-shaped data channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Everything way: read $MFT directly
&lt;/h2&gt;

&lt;p&gt;Everything and WizFile don't use that API for full enumeration. They open the volume device (&lt;code&gt;\\.\C:&lt;/code&gt;, read-only) and &lt;strong&gt;read the $MFT byte stream directly&lt;/strong&gt;. The Master File Table is NTFS's census book: every file and directory is one fixed 1024-byte record, stored sequentially, containing name, size, and timestamps. Scan it linearly and you get everything in one pass of sequential IO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Parse the $DATA run list of $MFT's own record (VCN→LCN mapping)&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;ParseMftRuns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BYTE&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mftRec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int64_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;lcns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. ReadFile the volume handle in chunks, per run&lt;/span&gt;
&lt;span class="c1"&gt;// 3. Fix up each 1024-byte record, extract $FILE_NAME / $DATA attributes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details worth knowing if you go this route:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard links&lt;/strong&gt;: one MFT record can carry multiple $FILE_NAME attributes (one per link, each pointing at its own parent directory). Each one must become a separate row in your index, or link results silently go missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt;: reading the volume device needs more than a standard user token. That's why the engine runs as a separate process backed by a Windows service — the service holds the volume handle, the main app consumes queries over IPC, and the GUI never elevates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the memory went: columnar storage
&lt;/h2&gt;

&lt;p&gt;The full-enumeration part was honestly the easier half. The interesting question was: you now have 1.34 million rows of metadata. How do you hold them in RAM for &lt;strong&gt;a few MB&lt;/strong&gt; instead of a hundred-plus?&lt;/p&gt;

&lt;p&gt;The naive layout is an array of objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FileEntry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;frn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parentFrn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// 24 B&lt;/span&gt;
    &lt;span class="kt"&gt;int64_t&lt;/span&gt;  &lt;span class="n"&gt;mtime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// 8 B&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// 32 B + heap allocation per file&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isDir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                     &lt;span class="c1"&gt;// 1 B + 7 B padding&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Padding, per-string heap allocations, allocator headers, terrible cache locality. That's your hundred-plus MB right there — not because anyone wrote bad code, but because &lt;strong&gt;the layout itself is that size&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The engine stores rows columnar instead — separate tight arrays, one per attribute, plus an interned name pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;frns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// full 64-bit FRNs (sequence&amp;lt;&amp;lt;48 | index)&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mtimes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Unix seconds — 1970..2106 fits in 4 bytes&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nameOffs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// offset into the blob&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nameLens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="n"&gt;isDirs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;     &lt;span class="n"&gt;nameBlob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// UTF-8 pool, no NUL terminators, deduplicated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every frontend developer knows this instinctively: it's the difference between &lt;code&gt;[{name, size, mtime}, ...]&lt;/code&gt; and one TypedArray per column. SoA beats AoS when you scan.&lt;/p&gt;

&lt;p&gt;The merciless details that stack up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Interned names.&lt;/strong&gt; "New folder", "package.json", "index.js" appear tens of thousands of times each. One copy in the blob; every row is just an 8-byte offset+length slice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;uint32&lt;/code&gt; Unix-seconds mtimes.&lt;/strong&gt; Halves the time column; 1970–2106 is plenty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowercase names that aren't there.&lt;/strong&gt; Case-insensitive search needs a lowercased name — but most filenames are &lt;em&gt;already&lt;/em&gt; lowercase. Each row carries a flag bit: if the folded name is byte-identical to the original, the lowercase view just reads the original blob. Saves nearly an entire second name pool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derived columns aren't persisted.&lt;/strong&gt; The parent FRN you need for path reconstruction is derivable from the parent-row column at query time, so the snapshot simply doesn't contain it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same data, different layout: hundreds of MB becomes a few MB. Structure, not optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Staying fresh: USN journal catch-up — and a subtle trap
&lt;/h2&gt;

&lt;p&gt;Full builds are one thing; filesystems keep changing. NTFS ships a change journal (USN records, monotonically increasing). The engine stores an anchor &lt;code&gt;(JournalId, NextUsn)&lt;/code&gt; and periodically replays new records.&lt;/p&gt;

&lt;p&gt;The trap I hit so you don't have to: &lt;strong&gt;the anchor must be taken &lt;em&gt;before&lt;/em&gt; enumeration starts, not after it finishes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first version grabbed the current USN after the full scan — logically airtight, right? Wrong. The scan takes seconds. Save a file &lt;em&gt;during&lt;/em&gt; the scan and it may not be on disk when your read passes its directory, yet its USN is already below your anchor. The journal replay skips it &lt;em&gt;forever&lt;/em&gt;. The index is silently missing a file, no error, no recovery.&lt;/p&gt;

&lt;p&gt;Store the pre-enumeration anchor. Replay may redundantly re-apply a change that the scan already caught — idempotent, harmless. Missing is not.&lt;/p&gt;

&lt;p&gt;Also: journals get trimmed and recreated. The anchor's JournalId detects that; on mismatch, abandon incremental catch-up and do a full rebuild. Correctness always outranks cleverness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistence: mmap snapshots
&lt;/h2&gt;

&lt;p&gt;Rebuilding on every boot is wasteful, so the index snapshots to disk per volume and loads via &lt;strong&gt;mmap&lt;/strong&gt; — O(1) open, column pointers point straight at mapped pages, zero copies. The query-accelerating structure (an ASCII bigram inverted index, VByte-delta compressed) is &lt;em&gt;materialized into the snapshot&lt;/em&gt;, so loading requires zero rebuilding.&lt;/p&gt;

&lt;p&gt;Two Windows-specific footnotes for anyone doing this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After mmap-loading, call &lt;code&gt;PrefetchVirtualMemory&lt;/code&gt;. Otherwise the first full scan pulls tens of MB in random 4 KB page faults (measured: ~6 s on my C: volume) while the OS pages things in lazily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A file you have mmap'd cannot be replaced.&lt;/strong&gt; Atomic snapshot replacement (&lt;code&gt;MoveFileEx&lt;/code&gt; over the old path) fails with &lt;code&gt;gle=5&lt;/code&gt; while your own process still holds a mapping of the old file — even though you're "just" swapping the path. Release your own mapping first, and guard the column-pointer swap with a reader-writer lock so in-flight queries don't race the swap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The scorecard
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Bundled Everything&lt;/th&gt;
&lt;th&gt;Homegrown (1.0.5)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Index memory&lt;/td&gt;
&lt;td&gt;Hundreds of MB (external service)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A few MB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External dependency&lt;/td&gt;
&lt;td&gt;Third-party install/bundle&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query syntax&lt;/td&gt;
&lt;td&gt;Everything's&lt;/td&gt;
&lt;td&gt;Compatible (&lt;code&gt;ext:pdf&lt;/code&gt;, &lt;code&gt;size:&amp;gt;100mb&lt;/code&gt;, &lt;code&gt;dm:today&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;Black box&lt;/td&gt;
&lt;td&gt;End-to-end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Was it worth it? For this product, yes — search is the palette's core experience, and resident memory is the metric that decides whether a tray app survives on someone's machine. The rule of thumb I'd offer from this round:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't build a wheel where the existing one is already optimized for you. Build it when your core requirement is exactly the dimension the dependency doesn't optimize for.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything is perfectly optimized for being Everything. It was never going to be optimized for being &lt;em&gt;inside&lt;/em&gt; something else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc3bp9zohozgd0dn0vrn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqc3bp9zohozgd0dn0vrn.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This shipped in yyzTools 1.0.5 — free, no accounts, no telemetry, Windows 10/11, at &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;. The same release also added a quick OCR-translate pipeline (screenshot → recognize → translate, all offline via the local RapidOCR engine). Happy to dig deeper into any part of this in the comments — the parallel-scan worker pool has a genuinely fun stack-lifetime crash story if anyone wants it.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>productivity</category>
      <category>programming</category>
      <category>search</category>
    </item>
    <item>
      <title>yyzTools: 40+ Windows Productivity Tools in One Install — Local-First, Free Forever</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:14:35 +0000</pubDate>
      <link>https://dev.to/jearry/yyztools-40-windows-productivity-tools-in-one-install-local-first-free-forever-43e0</link>
      <guid>https://dev.to/jearry/yyztools-40-windows-productivity-tools-in-one-install-local-first-free-forever-43e0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Tired of juggling a dozen separate utilities — a launcher, a clipboard manager, a translator, an OCR tool, a file previewer, a screenshot app, a batch renamer...? yyzTools fuses them all into a single, native Windows application with a shared data layer and unified keyboard shortcuts. No plugins. No accounts. No telemetry.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you're a Windows power user, your efficiency toolkit probably looks something like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Typical tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App launching&lt;/td&gt;
&lt;td&gt;PowerToys Run / Flow Launcher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File search&lt;/td&gt;
&lt;td&gt;Everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clipboard history&lt;/td&gt;
&lt;td&gt;Ditto / Windows built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Translation&lt;/td&gt;
&lt;td&gt;DeepL + browser extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR&lt;/td&gt;
&lt;td&gt;Online tool / Umi-OCR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File preview&lt;/td&gt;
&lt;td&gt;QuickLook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshots&lt;/td&gt;
&lt;td&gt;Snipaste&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch renaming&lt;/td&gt;
&lt;td&gt;Separate utility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON formatting&lt;/td&gt;
&lt;td&gt;Browser tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex testing&lt;/td&gt;
&lt;td&gt;regex101.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base64 encoding&lt;/td&gt;
&lt;td&gt;Browser tab&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each tool has its own shortcut, its own settings panel, its own update channel — and crucially, &lt;strong&gt;none of them talk to each other&lt;/strong&gt;. You can't pipe clipboard history into OCR, or send OCR output directly to translation, or push translation results back to the clipboard. Every cross-tool workflow requires manual copy-paste intermediation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt; (Yes Your Zen Tools) takes a different approach: &lt;strong&gt;fuse 40+ productivity tools into a single install&lt;/strong&gt;, sharing one data layer, one set of keyboard shortcuts, and one visual language.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Current version&lt;/strong&gt;: v1.0.3.1300&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform&lt;/strong&gt;: Windows 10 1809+ / 11, 64-bit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price&lt;/strong&gt;: Free forever, no account required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Languages&lt;/strong&gt;: 12 (English, Simplified Chinese, Traditional Chinese, Japanese, Korean, French, German, Spanish, Russian, Arabic, Portuguese, Italian)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture: C++ + WebView2
&lt;/h2&gt;

&lt;p&gt;yyzTools uses a hybrid architecture that's worth understanding as a developer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────┐
│     Frontend (Alpine.js + vanilla JS)   │
│   cmdplate / ocr / translate / ...      │
│   Unified via web/lib/zen_api.js        │
├─────────────────────────────────────────┤
│      WebView2 Bridge Layer              │
│   BindSync → synchronous return          │
│   BindAsync → async callback (OCR only)  │
├─────────────────────────────────────────┤
│         C++ NativeApi Layer              │
│  FileManager / ProcessManager /          │
│  ClipboardManager / OcrManager /        │
│  DownloadManager / CmdManager ...        │
├─────────────────────────────────────────┤
│            Win32 System API              │
└─────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key design decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebView2 over Electron&lt;/strong&gt;: Uses the Windows-built-in Edge engine, no bundled Chromium, lower memory footprint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpine.js (3KB) over React/Vue&lt;/strong&gt;: No virtual DOM overhead, no heavy build chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite multi-entry&lt;/strong&gt;: Each feature page is an independent bundle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend updates without recompiling C++&lt;/strong&gt;: UI changes ship as frontend dist, no .exe rebuild needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The JS ↔ C++ bridge is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Frontend calls go through ZenAPI wrapper, never raw window.Zen&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ZenAPI&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;isOk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Normalizes both number 0 and string "0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Zen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// C++ registration in NativeApi::SetupBindings()&lt;/span&gt;
&lt;span class="n"&gt;m_bindManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;BindSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"getConfig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="n"&gt;m_bindManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;BindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ocrRecognition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;NativeApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OcrRecognition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All methods return a JSON object &lt;code&gt;{ error, errorMsg, ... }&lt;/code&gt;. &lt;code&gt;error === 0&lt;/code&gt; means success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Command Palette — One Search Box for Everything
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Alt + Space&lt;/code&gt; brings up the unified entry point. Prefix-based domain switching:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Searches&lt;/th&gt;
&lt;th&gt;Replaces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;(none)&lt;/td&gt;
&lt;td&gt;Apps, system commands, settings, command modules&lt;/td&gt;
&lt;td&gt;PowerToys Run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All files (Everything SDK)&lt;/td&gt;
&lt;td&gt;Everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Running processes&lt;/td&gt;
&lt;td&gt;Task Manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browser bookmarks&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clipboard history&lt;/td&gt;
&lt;td&gt;Ditto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Command line&lt;/td&gt;
&lt;td&gt;Terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also includes &lt;strong&gt;instant math&lt;/strong&gt;: type &lt;code&gt;128*0.85&lt;/code&gt; or &lt;code&gt;sqrt(144)+3^2&lt;/code&gt; and the result appears in real-time, press Enter to copy.&lt;/p&gt;

&lt;p&gt;Supports &lt;strong&gt;pinyin search&lt;/strong&gt; for Chinese app names — typing &lt;code&gt;weixin&lt;/code&gt; or &lt;code&gt;wx&lt;/code&gt; finds WeChat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clipboard History + Cross-Feature Pipeline
&lt;/h3&gt;

&lt;p&gt;Text and image clipboard history with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pin images to desktop as floating windows (great for reference screenshots)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline&lt;/strong&gt;: select a text entry → send to translation; select an image → send to OCR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the "fusion" advantage — no manual copy-paste between tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local OCR (RapidOCR)
&lt;/h3&gt;

&lt;p&gt;Screenshot-to-text using the local RapidOCR engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully offline&lt;/strong&gt; — no cloud API, no upload, no latency&lt;/li&gt;
&lt;li&gt;Chinese-English mixed recognition&lt;/li&gt;
&lt;li&gt;Pipeline: Screenshot OCR → one-click translate → one-click copy to clipboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  File Preview
&lt;/h3&gt;

&lt;p&gt;Spacebar to preview without launching any app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Word / Excel / PowerPoint / PDF&lt;/li&gt;
&lt;li&gt;Images / Videos / Audio&lt;/li&gt;
&lt;li&gt;Code files (syntax highlighted)&lt;/li&gt;
&lt;li&gt;Archive files (browse directory tree)&lt;/li&gt;
&lt;li&gt;DXF drawings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer Tools
&lt;/h3&gt;

&lt;p&gt;Built-in utilities that replace browser tabs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Regex tester&lt;/td&gt;
&lt;td&gt;Real-time match highlighting, group viewer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encode/Decode&lt;/td&gt;
&lt;td&gt;Base64 / URL / HTML / Unicode / Hex / Morse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash calculator&lt;/td&gt;
&lt;td&gt;MD5 / SHA-1 / SHA-256 / SHA-512 for text and files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crypto&lt;/td&gt;
&lt;td&gt;OpenSSL-based symmetric/asymmetric encryption, key generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data conversion&lt;/td&gt;
&lt;td&gt;JSON ↔ YAML ↔ XML ↔ TOML ↔ CSV, hex/case/width conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data generation&lt;/td&gt;
&lt;td&gt;UUID v4, ULID, random strings, batch test data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Batch Processing — Three Heavy-Duty Engines
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Powers&lt;/th&gt;
&lt;th&gt;Capabilities&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ImageMagick&lt;/td&gt;
&lt;td&gt;Image batch&lt;/td&gt;
&lt;td&gt;Resize, crop, format convert, watermark, compress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FFmpeg&lt;/td&gt;
&lt;td&gt;Video batch&lt;/td&gt;
&lt;td&gt;Transcode, trim, extract frames, compress, extract audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pdfcpu + Ghostscript&lt;/td&gt;
&lt;td&gt;PDF batch&lt;/td&gt;
&lt;td&gt;Merge, split, watermark, compress, encrypt/decrypt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus a batch rename tool with preview: sequential numbering, find-replace, regex, case, EXIF date.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots + Recording
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Region screenshot&lt;/strong&gt;: Select area, copy or save&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scrolling screenshot&lt;/strong&gt;: Scroll to capture long pages/documents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin to screen&lt;/strong&gt;: Pin screenshots as desktop sticky notes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen recording&lt;/strong&gt;: Record screen + audio, optional webcam overlay, auto zoom-follow on mouse, keystroke display, one-click export&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Command Module System (.zenmod)
&lt;/h3&gt;

&lt;p&gt;379 built-in command modules across 5 categories:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System&lt;/td&gt;
&lt;td&gt;257&lt;/td&gt;
&lt;td&gt;All Windows Control Panel functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Tools&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;ChatGPT, DeepSeek, Gemini, Claude, Qwen, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apps&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;Volume control, shutdown, screenshot, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Translation, OCR, download, calculator, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OneNote, Microsoft To-Do&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Extending is trivial&lt;/strong&gt; — drop a JSON file into the &lt;code&gt;Modules/&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Tool"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"zh_cn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"我的工具"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"desc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Quick launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"zh_cn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"快速启动"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cmdPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yyzBrowser.exe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cmdArgs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"--url=https://example.com --size=800x600"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No compilation, no restart. The command appears in the palette immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Wallpaper + Desktop Effects
&lt;/h3&gt;

&lt;p&gt;Windows Composition API places web pages and videos &lt;strong&gt;below the desktop icon layer&lt;/strong&gt; — no icon obstruction, no click interference. Game mode pauses all effects to free resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Philosophy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local-First
&lt;/h3&gt;

&lt;p&gt;All data processed locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clipboard history → local storage&lt;/li&gt;
&lt;li&gt;OCR → local RapidOCR model, no cloud API&lt;/li&gt;
&lt;li&gt;File search → local Everything index&lt;/li&gt;
&lt;li&gt;App usage stats → local records&lt;/li&gt;
&lt;li&gt;File preview → local parsing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only translation (calls translation service API) and update checks require network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free Forever
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No paywalls, no premium tier, no trial countdown&lt;/li&gt;
&lt;li&gt;No account registration required&lt;/li&gt;
&lt;li&gt;No identity collection, no user profiling, no behavioral telemetry&lt;/li&gt;
&lt;li&gt;No ads&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Delta Updates
&lt;/h3&gt;

&lt;p&gt;Each component (main binary, frontend pages, command modules, FFmpeg, OCR models) is packaged independently. Updates download only the changed sub-packages — a frontend-only update might be a few hundred KB, not a full 1GB reinstall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;yyzTools&lt;/th&gt;
&lt;th&gt;PowerToys&lt;/th&gt;
&lt;th&gt;uTools&lt;/th&gt;
&lt;th&gt;Flow Launcher&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;C++ + WebView2&lt;/td&gt;
&lt;td&gt;C# + WPF&lt;/td&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;td&gt;C# + WPF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature source&lt;/td&gt;
&lt;td&gt;All built-in&lt;/td&gt;
&lt;td&gt;All built-in&lt;/td&gt;
&lt;td&gt;Plugin market&lt;/td&gt;
&lt;td&gt;Plugin market&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Translation&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR&lt;/td&gt;
&lt;td&gt;Built-in (offline)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch processing&lt;/td&gt;
&lt;td&gt;ImageMagick/FFmpeg/pdfcpu&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File preview&lt;/td&gt;
&lt;td&gt;Office/PDF/code/archive&lt;/td&gt;
&lt;td&gt;SVG/PDF/MD&lt;/td&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data pipeline&lt;/td&gt;
&lt;td&gt;Clipboard→OCR→Translate→Copy&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;No shared data layer&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extension method&lt;/td&gt;
&lt;td&gt;JSON file (zero compile)&lt;/td&gt;
&lt;td&gt;C# plugin&lt;/td&gt;
&lt;td&gt;Plugin market&lt;/td&gt;
&lt;td&gt;C# plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Paid tier&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key difference isn't feature count — it's &lt;strong&gt;integration&lt;/strong&gt;. yyzTools features share one data layer and can pipe into each other. The others are either loose collections of independent tools (PowerToys) or plugin platforms where plugins don't share state (uTools).&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Download&lt;/strong&gt;: &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requirements&lt;/strong&gt;: Windows 10 1809+ / 11, 64-bit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: Free, no registration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Languages&lt;/strong&gt;: 12 (English, Chinese Simplified/Traditional, Japanese, Korean, French, German, Spanish, Russian, Arabic, Portuguese, Italian)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quick start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download and install from &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Alt + Space&lt;/code&gt; to open the command palette&lt;/li&gt;
&lt;li&gt;Type a feature name or app name to search&lt;/li&gt;
&lt;li&gt;To add custom commands, drop a &lt;code&gt;.zenmod&lt;/code&gt; JSON file into the &lt;code&gt;Modules/&lt;/code&gt; directory&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;If you use 3+ of the tools yyzTools covers (clipboard manager, translator, OCR, file previewer, batch renamer, screenshot tool, regex tester, etc.), it's worth trying as a single replacement rather than maintaining a fragmented toolkit.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Website: &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt; · Free forever · Local-first · 12 languages&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Shipping Stock CLIs as Subprocess Instead of Static-Linking SDKs</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Sun, 23 Aug 2026 06:30:53 +0000</pubDate>
      <link>https://dev.to/jearry/shipping-stock-clis-as-subprocess-instead-of-static-linking-sdks-1jl8</link>
      <guid>https://dev.to/jearry/shipping-stock-clis-as-subprocess-instead-of-static-linking-sdks-1jl8</guid>
      <description>&lt;p&gt;I'm building yyzTools, which bundles 9 third-party engines (OpenSSL, FFmpeg, ImageMagick, pdfcpu, Aria2, 7-Zip, RapidOCR, Everything...). I chose to spawn them as subprocesses rather than static-link their SDKs. Here's why—and the cost.&lt;/p&gt;

&lt;p&gt;The conventional approach&lt;br&gt;
When your app needs OpenSSL crypto, FFmpeg video processing, ImageMagick image ops—you reach for the SDK. Link libssl, link libav*, link libMagick. One binary, no external deps, fast function calls. It's the textbook answer.&lt;/p&gt;

&lt;p&gt;I did the opposite. yyzTools ships the stock CLI binaries (openssl.exe, ffmpeg.exe, magick.exe, pdfcpu, aria2c, 7z) and spawns them as subprocesses. The C++ layer is a thin loop: build args → CreateProcess → read stdout → wrap as JSON → return. It doesn't know what -gravity southeast or sm4-cbc means. It just passes the algorithm name through.&lt;/p&gt;

&lt;p&gt;Why I went this way&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upgrades without recompiling
This is the big one for a desktop app. OpenSSL ships a CVE, or adds sm2/sm3/sm4 support in 3.x. If you've static-linked, you recompile the whole app, run full regression, re-release, and every user reinstalls.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the subprocess model, I drop in a new openssl.exe. Zero C++ changes. The update is a few-MB delta, not a full reinstall. For a product where users won't tolerate reinstalling for a library bump, this is the deciding factor.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;No symbol conflicts&lt;br&gt;
OpenSSL, zlib, libpng—multiple libraries want to own these symbols. Static linking them all into one binary is a recipe for "which inflate did I just call?" With subprocess CLIs, each tool brings its own dependencies in its own process. No conflict.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transparent supply chain&lt;br&gt;
openssl version, ffmpeg -version—auditing which version of each tool is live is trivial. It's an independent binary. Far easier than digging symbols out of a statically-linked blob.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Free crash isolation&lt;br&gt;
If ffmpeg.exe misbehaves, it exits non-zero and my host wraps that as an error. My main process keeps running. A static-linked bug can take down the whole app. The process boundary is a free fault domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Algorithm support is "front-end list + docs", not C++ rebuild&lt;br&gt;
Because the C++ side just passes algorithm names through, adding a new hash algorithm means adding a line to the front-end's ALGORITHMS list and updating docs. NativeApi doesn't change. I covered sm2/sm3/sm4 in a day this way.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The cost&lt;br&gt;
This isn't free. Let's be honest:&lt;/p&gt;

&lt;p&gt;Per-call fork overhead&lt;br&gt;
Spawning a process is ~tens of ms. For "compress this folder" or "hash this file," fine. For "verify 10,000 HMACs in a loop," brutal. You'd never build a high-throughput crypto service this way. I knew this going in—yyzTools hashes one file at a time, not a stream of requests. Low frequency is the prerequisite for this pattern.&lt;/p&gt;

&lt;p&gt;Large-file hashing is slower&lt;br&gt;
A static SHA256() call streams bytes in-process. The subprocess model means bytes cross the process boundary. For a 4GB ISO, you feel it. Acceptable for my use case; unacceptable for a backup service.&lt;/p&gt;

&lt;p&gt;The stdout contract is the weak point&lt;br&gt;
CLIs emit human-readable output, not machine-readable JSON. I hand-rolled a stable parser for each CLI's output. When a CLI upgrades and changes its output format, the parser can break. Easier to debug than a static ABI change (the CLI just fails loudly), but it's a maintenance surface.&lt;/p&gt;

&lt;p&gt;I wish there were a standard "CLI emits JSON" convention. Some tools have it (gh, kubectl), many don't (openssl enc's output is a mess to parse). The inconsistency is the real friction.&lt;/p&gt;

&lt;p&gt;Process lifecycle management&lt;br&gt;
You own timeouts, zombie handling, cancellation. A static function call doesn't hang. A subprocess can. I had to build a work-queue with concurrency limits (don't spawn 200 magick.exe for 200 images) and cancellation. That's extra code you don't write for a static link.&lt;/p&gt;

&lt;p&gt;Where I'd draw the line&lt;br&gt;
This pattern fits when:&lt;/p&gt;

&lt;p&gt;Low-frequency calls — desktop tools, not services&lt;br&gt;
Fast upstream upgrades matter — security CVEs, new algorithms&lt;br&gt;
You want transparent supply chain — auditable binaries&lt;br&gt;
The CLI is mature and stable — FFmpeg, OpenSSL have decades of CLI stability&lt;br&gt;
It fails when:&lt;/p&gt;

&lt;p&gt;High throughput — a TLS server, a per-request crypto service&lt;br&gt;
Large streaming I/O — backup, media transcoding at scale&lt;br&gt;
The CLI output is unstable — you'd spend your life chasing format changes&lt;br&gt;
I use the subprocess model for the 9 bundled CLIs in yyzTools. If I were building a media server or a crypto API gateway, I'd static-link (or use a proper service). The pattern is frequency-dependent.&lt;/p&gt;

&lt;p&gt;A pattern worth naming&lt;br&gt;
I haven't seen this called out as a named pattern, but it shows up: VS Code ships ripgrep and calls it. Many Electron apps bundle CLIs. The principle—"ship the stock CLI, spawn it, parse stdout"—is a middle ground between static linking and microservices.&lt;/p&gt;

&lt;p&gt;Position on the dependency spectrum:&lt;/p&gt;

&lt;p&gt;Static link Subprocess CLI  gRPC service&lt;br&gt;
Perf    fastest (function call) medium (fork + IPC) slowest (network)&lt;br&gt;
Upgrade cost    recompile + regression  drop a file redeploy service&lt;br&gt;
Crash isolation none    process boundary    network boundary&lt;br&gt;
Cross-language  compile-time binding    natural natural&lt;br&gt;
Best for    high-throughput core    low-freq trusted capability cross-machine&lt;br&gt;
The subprocess-CLI slot is underused. For a desktop app bundling many third-party capabilities, it's often the right slot.&lt;/p&gt;

&lt;p&gt;The honest takeaway&lt;br&gt;
If your tool calls a third-party engine a few times a session (not thousands of times a second), and you care about fast upgrades and transparent supply chain, shipping the stock CLI as a subprocess beats static-linking. You pay per-call latency and a parser to maintain; you gain zero-recompile upgrades and crash isolation.&lt;/p&gt;

&lt;p&gt;Don't use it for hot paths. Do use it for "I need OpenSSL/FFmpeg/ImageMagick occasionally, and I don't want to recompile when they patch a CVE."&lt;/p&gt;

&lt;p&gt;I'm building yyzTools — a free, local-first Windows productivity suite. Website: yyztools.com. The 9 bundled CLIs are OpenSSL, 7-Zip, FFmpeg, ImageMagick, pdfcpu, Aria2, RapidOCR, Everything, and Ghostscript.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>cli</category>
      <category>cpp</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Why I Built a Windows Desktop App with C++ + WebView2 Instead of Electron</title>
      <dc:creator>yyzTools</dc:creator>
      <pubDate>Fri, 21 Aug 2026 08:40:17 +0000</pubDate>
      <link>https://dev.to/jearry/why-i-built-a-windows-desktop-app-with-c-webview2-instead-of-electron-2o69</link>
      <guid>https://dev.to/jearry/why-i-built-a-windows-desktop-app-with-c-webview2-instead-of-electron-2o69</guid>
      <description>&lt;p&gt;&lt;em&gt;I'm building &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyzTools&lt;/a&gt;, a Windows productivity suite with 40+ tools fused into one install. This is the architecture decision I get asked about most.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The default path
&lt;/h2&gt;

&lt;p&gt;When a developer today wants to build a desktop app, the default reflex is Electron. It's rational: HTML/CSS/JS, cross-platform, mature ecosystem (VS Code, Slack, Discord proved it works). If your team is frontend-heavy, it's almost a non-decision.&lt;/p&gt;

&lt;p&gt;I didn't take that path. yyzTools is C++ (Win32) + WebView2 + Alpine.js + Vite. Windows-only. No bundled Chromium.&lt;/p&gt;

&lt;p&gt;This post isn't "Electron is bad." It's "for a resident, lightweight, multi-feature desktop tool, Electron's costs outweigh its benefits, and here's what I picked instead."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Electron didn't fit this project
&lt;/h2&gt;

&lt;p&gt;The key word is &lt;strong&gt;resident&lt;/strong&gt;. yyzTools is a tool you install once and it sits in your tray, running every time you hit &lt;code&gt;Alt+Space&lt;/code&gt; for the command palette, grab from clipboard history, fire OCR. It's not an app you open and close—it's always there.&lt;/p&gt;

&lt;p&gt;Electron's model is "bundle a full Chromium per app." For an app you open occasionally, the ~200MB memory and multi-second startup is tolerable. For something that's &lt;strong&gt;always resident&lt;/strong&gt;, it's a tax you pay every second the machine is on.&lt;/p&gt;

&lt;p&gt;Nobody wants a clipboard manager + launcher eating 400MB of RAM just by being installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternative: WebView2 + a C++ host
&lt;/h2&gt;

&lt;p&gt;Microsoft's WebView2 is a control that renders web content using the &lt;strong&gt;Edge engine already installed on Windows&lt;/strong&gt;. No bundled browser. The runtime ships with Windows 11; on Windows 10 the installer guides the user.&lt;/p&gt;

&lt;p&gt;So my stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;C++ / Win32 host&lt;/strong&gt; — owns the window, system calls, process management, all native work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebView2&lt;/strong&gt; — renders the frontend pages using the system Edge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpine.js (~3KB)&lt;/strong&gt; — frontend framework, no virtual DOM, no heavy build chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite multi-entry&lt;/strong&gt; — each feature page (command palette, OCR, translate, download...) is an independent bundle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is a four-layer sandwich:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend pages (Alpine.js + vanilla JS)
        ↓  via web/lib/zen_api.js (unified wrapper)
WebView2 bridge layer
   BindSync  → synchronous return (most methods)
   BindAsync → async callback (OCR, app-info)
        ↓
C++ NativeApi layer (FileManager / ProcessManager / ...)
        ↓
Win32 system API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend never calls &lt;code&gt;window.Zen&lt;/code&gt; directly. It goes through a &lt;code&gt;ZenAPI&lt;/code&gt; wrapper class that normalizes return values—the C++ side has two JSON paths (hand-rolled strings vs &lt;code&gt;boost::property_tree&lt;/code&gt;) where the same &lt;code&gt;error&lt;/code&gt; field can be number &lt;code&gt;0&lt;/code&gt; or string &lt;code&gt;"0"&lt;/code&gt;. The wrapper does &lt;code&gt;String(res.error) === '0'&lt;/code&gt; to paper over it. (That's documented debt, not a hidden clever trick.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What this buys
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lower memory.&lt;/strong&gt; No Chromium process resident. The main process is a thin C++ host plus the system Edge it borrows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster startup.&lt;/strong&gt; No spinning up a fresh Chromium instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller disk.&lt;/strong&gt; The web runtime is shared across the system, not bundled per-app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native when needed.&lt;/strong&gt; File ops, process management, OCR engine calls, screen recording—all C++, no IPC to a JS-land bridge for hot paths.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Let's be honest about the tradeoffs, because this isn't a free lunch:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows-only.&lt;/strong&gt; WebView2 is Microsoft's. If you want Mac/Linux, you need Electron or a full Qt port. yyzTools targets Windows, so this is fine—but it's the biggest constraint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hand-written bridge.&lt;/strong&gt; Electron gives you mature IPC for free. With WebView2 you hand-roll the JS↔C++ channel (a "method name → callable" registry, sync/async variants, thread marshaling for async callbacks back to the UI thread). It's not hard, but it's code you own and maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No type system on the boundary.&lt;/strong&gt; The &lt;code&gt;error&lt;/code&gt; field type drift I mentioned isn't caught by TypeScript—it's a runtime contract between C++ and JS. I normalize at the wrapper, but every new native method is a chance to introduce a new type mismatch. A typed IPC (like tRPC over a real protocol) would be safer; I chose simplicity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alpine.js means no React ecosystem.&lt;/strong&gt; Alpine is great for "many simple pages" (which a productivity suite is). But if I wanted a complex single-page app with a rich component library, I'd miss React's ecosystem. The pages here are intentionally simple—search box, settings panel, result grid—so Alpine fits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need C++ people.&lt;/strong&gt; A pure-frontend team can't extend the native side. This is a real org constraint. If your team can't staff Win32 expertise, Electron is the pragmatic call, period.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you'd make the same call
&lt;/h2&gt;

&lt;p&gt;I'd pick this again specifically when all four are true:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The app is resident&lt;/strong&gt; (tray, autostart, always-on) — the memory tax compounds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows is the target&lt;/strong&gt; — WebView2 is Windows-only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need native system access&lt;/strong&gt; — file/process/clipboard/OCR, where C++ beats a JS bridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You have C++ capacity&lt;/strong&gt; — someone can own the host and bridge&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of those is false, Electron (or Tauri) probably wins. yyzTools happened to clear all four, so the C++/WebView2 path was correct here. It would be wrong for a cross-platform app, or a resident app with no native API needs, or a team of pure-frontend devs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;"Electron vs native" isn't a moral question—it's a fit question. The reflexive "always Electron" is wrong for resident, native-heavy, Windows-targeted tools. The reflexive "never Electron" is wrong for cross-platform or frontend-heavy teams.&lt;/p&gt;

&lt;p&gt;The useful question to ask when you start a desktop project: &lt;strong&gt;is this app resident, and does it need native system access?&lt;/strong&gt; If yes to both and you're on Windows, the C++ + WebView2 path is worth the C++ staffing cost. If no, Electron's defaults will serve you faster.&lt;/p&gt;

&lt;p&gt;yyzTools is the former. Your project might be the latter. Pick on fit, not on what's trendy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building yyzTools — a free, local-first Windows productivity suite. Website: &lt;a href="https://yyztools.com" rel="noopener noreferrer"&gt;yyztools.com&lt;/a&gt;. I write about the architecture decisions behind it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>cpp</category>
      <category>productivity</category>
      <category>software</category>
    </item>
  </channel>
</rss>
