<?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: Łukasz Oleniuk</title>
    <description>The latest articles on DEV Community by Łukasz Oleniuk (@ukash).</description>
    <link>https://dev.to/ukash</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3817053%2F52d92b6b-37fa-4f5b-81b0-20897b3d83e4.jpg</url>
      <title>DEV Community: Łukasz Oleniuk</title>
      <link>https://dev.to/ukash</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ukash"/>
    <language>en</language>
    <item>
      <title>I built a Markdown editor with Tauri — here's what I learned</title>
      <dc:creator>Łukasz Oleniuk</dc:creator>
      <pubDate>Tue, 10 Mar 2026 14:13:00 +0000</pubDate>
      <link>https://dev.to/ukash/i-built-a-markdown-editor-with-tauri-heres-what-i-learned-4f5l</link>
      <guid>https://dev.to/ukash/i-built-a-markdown-editor-with-tauri-heres-what-i-learned-4f5l</guid>
      <description>&lt;p&gt;I've been writing in Markdown for years — documentation, blog posts, notes, project specs. I tried dozens of editors. Some were fast but ugly. Some were beautiful but Electron-heavy. Some locked my files in proprietary formats or demanded a cloud subscription for basic features.&lt;/p&gt;

&lt;p&gt;So I built my own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CrabPad&lt;/strong&gt; is a desktop Markdown editor built with Tauri (Rust backend + React/TypeScript frontend). It's local-first, keyboard-driven, free, and runs on macOS, Windows, and Linux.&lt;/p&gt;

&lt;p&gt;In this post I want to share the technical decisions, trade-offs, and lessons learned from building it.&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.amazonaws.com%2Fuploads%2Farticles%2Fxhf1p51poovnypz18drk.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.amazonaws.com%2Fuploads%2Farticles%2Fxhf1p51poovnypz18drk.png" alt="CrabPad main interface" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Tauri over Electron?
&lt;/h2&gt;

&lt;p&gt;The decision was easy. Tauri gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust backend&lt;/strong&gt; — memory-safe, fast, tiny binaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System webview&lt;/strong&gt; — no bundled Chromium, so the app weighs ~15MB instead of 150MB+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native OS integration&lt;/strong&gt; — file dialogs, menus, auto-update, CLI args — all built in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universal macOS binary&lt;/strong&gt; — ARM64 + x86_64 in one package, out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off? You're at the mercy of each platform's webview quirks. Safari-based WebKit on macOS behaves differently than WebView2 on Windows. CSS rendering, font smoothing, keyboard events — all slightly different. But for a text editor, the savings in memory and startup time are absolutely worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────┐
│        React + TypeScript    │  ← UI, editor, preview, tabs
│        TailwindCSS           │
├──────────────────────────────┤
│        Tauri IPC (invoke)    │  ← commands bridge
├──────────────────────────────┤
│        Rust backend          │  ← Markdown parsing, file I/O,
│        pulldown-cmark        │     search, settings persistence
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend handles all UI state — open files, tabs, preview, keybindings. The backend handles everything that touches the filesystem or needs performance: Markdown parsing, file read/write, workspace search, and user settings persistence.&lt;/p&gt;

&lt;p&gt;Communication happens through Tauri's &lt;code&gt;invoke()&lt;/code&gt; — essentially async IPC calls that feel like calling a local function.&lt;/p&gt;




&lt;h2&gt;
  
  
  Markdown rendering: more than you'd think
&lt;/h2&gt;

&lt;p&gt;I started with &lt;code&gt;pulldown-cmark&lt;/code&gt; on the Rust side for the core GFM parsing (tables, footnotes, strikethrough, task lists). But modern Markdown needs way more than that.&lt;/p&gt;

&lt;p&gt;I ended up building a preprocessing pipeline in Rust that runs &lt;em&gt;before&lt;/em&gt; pulldown-cmark:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Emoticon → Emoji&lt;/strong&gt; shortcode replacement (&lt;code&gt;:-)&lt;/code&gt; → 😊)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emoji shortcodes&lt;/strong&gt; via &lt;code&gt;gh-emoji&lt;/code&gt; crate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Superscript/subscript&lt;/strong&gt; (&lt;code&gt;^sup^&lt;/code&gt;, &lt;code&gt;~sub~&lt;/code&gt;) via regex&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insert/Mark&lt;/strong&gt; (&lt;code&gt;++inserted++&lt;/code&gt;, &lt;code&gt;==highlighted==&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abbreviation definitions&lt;/strong&gt; (&lt;code&gt;*[HTML]: HyperText Markup Language&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inline footnotes&lt;/strong&gt; (&lt;code&gt;^[This is an inline footnote]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Definition lists&lt;/strong&gt; (term + &lt;code&gt;: definition&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub-style admonitions&lt;/strong&gt; (&lt;code&gt;&amp;gt; [!NOTE]&lt;/code&gt;, &lt;code&gt;&amp;gt; [!WARNING]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom containers&lt;/strong&gt; (&lt;code&gt;::: warning ... :::&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then pulldown-cmark parses the result. On the frontend, KaTeX handles math and Mermaid renders diagrams from fenced code blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned:&lt;/strong&gt; Don't try to extend a Markdown parser at the AST level if all you need is text preprocessing. Regex-based passes before parsing are pragmatic and fast enough for real-time preview.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keyboard-first design
&lt;/h2&gt;

&lt;p&gt;Every feature in CrabPad is accessible without a mouse. The core shortcuts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Shortcut&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Command Palette&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMD+Shift+P&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle Preview&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMD+P&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle Outline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMD+Shift+E&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zen Mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMD+Alt+Z&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global Search&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMD+Shift+F&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMD+,&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Building the Command Palette was straightforward — a fuzzy-search input over a list of registered commands. The hard part was &lt;strong&gt;keyboard event handling on macOS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;e.key&lt;/code&gt; is unreliable with modifier keys. Press &lt;code&gt;CMD+Alt+Z&lt;/code&gt; on macOS and &lt;code&gt;e.key&lt;/code&gt; becomes &lt;code&gt;Ω&lt;/code&gt;, not &lt;code&gt;Z&lt;/code&gt;. The fix: fall back to &lt;code&gt;e.code&lt;/code&gt; (physical key position) when &lt;code&gt;e.key&lt;/code&gt; doesn't match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkShortcut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KeyboardEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shortcut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shortcut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; 
    &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;`key&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;keyMatch&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cmd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metaKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctrlKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shift&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;altKey&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;Another gotcha: if you have a &lt;strong&gt;shortcut recorder&lt;/strong&gt; (for custom keybindings) and a &lt;strong&gt;global keyboard handler&lt;/strong&gt;, they fight each other. My solution was a global flag &lt;code&gt;window.__crabpadRecordingShortcut&lt;/code&gt; — the recorder sets it to &lt;code&gt;true&lt;/code&gt;, the global handler checks it and bails out. Not elegant, but battle-tested.&lt;/p&gt;




&lt;h2&gt;
  
  
  Zen Mode
&lt;/h2&gt;

&lt;p&gt;Zen Mode hides everything — tabs, sidebar, status bar — leaving only the editor centered on screen. It sounds simple, but the devil is in the details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State must persist across sessions (user closes app in Zen Mode, reopens → still in Zen Mode)&lt;/li&gt;
&lt;li&gt;Escape should exit Zen Mode, but also close modals — so you need a priority chain&lt;/li&gt;
&lt;li&gt;The editor width needs to be constrained for readability (&lt;code&gt;max-width&lt;/code&gt; + &lt;code&gt;justify-center&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fcbzr5187pspj528u5wmf.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.amazonaws.com%2Fuploads%2Farticles%2Fcbzr5187pspj528u5wmf.png" alt="CrabPad Zen Mode" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The state persistence is handled by saving all UI preferences to a &lt;code&gt;settings.json&lt;/code&gt; in Tauri's app data directory. This file survives app updates — unlike &lt;code&gt;localStorage&lt;/code&gt; which lives inside the WebView storage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-update with progress
&lt;/h2&gt;

&lt;p&gt;Tauri has a built-in updater plugin. You publish a signed JSON manifest (&lt;code&gt;update.json&lt;/code&gt;) at a public URL, and the app checks it on startup.&lt;/p&gt;

&lt;p&gt;My first implementation used native OS dialogs for the update flow. It worked, but the UX was terrible — click "Download", see a system alert saying "please wait", then... nothing. The download happened in the background with progress only visible in &lt;code&gt;console.log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I replaced it with a custom React modal that shows every stage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Checking&lt;/strong&gt; → spinner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Available&lt;/strong&gt; → version, release notes, "Download &amp;amp; Install" button&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downloading&lt;/strong&gt; → progress bar with MB counter (&lt;code&gt;3.2 MB of 12.8 MB&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready&lt;/strong&gt; → "Restart Now" button (uses &lt;code&gt;@tauri-apps/plugin-process&lt;/code&gt; for relaunch)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error&lt;/strong&gt; → message + link to contact form&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CI/CD pipeline (GitHub Actions) builds for all 3 platforms, signs the bundles with minisign, generates &lt;code&gt;update.json&lt;/code&gt;, deploys binaries to a VPS, and updates the Homebrew Cask — all automatically on tag push.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local-first: a conscious choice
&lt;/h2&gt;

&lt;p&gt;CrabPad has no backend, no accounts, no sync. Files are plain &lt;code&gt;.md&lt;/code&gt; on disk.&lt;/p&gt;

&lt;p&gt;This isn't just a privacy feature — it's an architectural simplification. No auth flows, no conflict resolution, no server costs, no GDPR headaches. The app works offline, files load instantly, and if CrabPad disappears tomorrow, your documents are still standard Markdown.&lt;/p&gt;

&lt;p&gt;The only network request the app makes is the optional update check to &lt;code&gt;crabpad.app/update.json&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Distribution
&lt;/h2&gt;

&lt;p&gt;Getting a desktop app to users in 2026 is harder than it should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS:&lt;/strong&gt; Unsigned apps trigger Gatekeeper warnings. Apple Developer Program costs $99/year. My workaround: distribute via &lt;strong&gt;Homebrew&lt;/strong&gt; (&lt;code&gt;brew install --cask LukaszOleniuk/tap/crabpad&lt;/code&gt;), which strips the quarantine attribute automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows:&lt;/strong&gt; NSIS installer works well, but SmartScreen may flag unknown publishers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux:&lt;/strong&gt; AppImage for universal compatibility, &lt;code&gt;.deb&lt;/code&gt; for Debian/Ubuntu. No Flatpak or Snap yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned:&lt;/strong&gt; Homebrew Cask is the single best distribution channel for indie macOS apps without Apple notarization. Zero friction for the user.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Windows/Linux testing and polish&lt;/li&gt;
&lt;li&gt;Plugin system for custom renderers&lt;/li&gt;
&lt;li&gt;Export to PDF/HTML&lt;/li&gt;
&lt;li&gt;Vim keybinding mode&lt;/li&gt;
&lt;li&gt;Exploring code signing / notarization&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://crabpad.app" rel="noopener noreferrer"&gt;crabpad.app&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; LukaszOleniuk/tap/crabpad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Changelog:&lt;/strong&gt; &lt;a href="https://crabpad.app/changelog" rel="noopener noreferrer"&gt;crabpad.app/changelog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd genuinely love feedback — what works, what doesn't, what's missing. You can reach me via the &lt;a href="https://crabpad.app/contact/" rel="noopener noreferrer"&gt;contact form&lt;/a&gt; or find me on &lt;a href="https://www.linkedin.com/in/lukasz-oleniuk" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>react</category>
      <category>rust</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
