<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dev on Ricardo Dantas</title><link>https://ricardodantas.me/categories/dev/</link><description>Recent content in Dev on Ricardo Dantas</description><generator>Hugo -- 0.157.0</generator><language>en-us</language><lastBuildDate>Thu, 26 Mar 2026 06:00:00 +0100</lastBuildDate><atom:link href="https://ricardodantas.me/categories/dev/feed.xml" rel="self" type="application/rss+xml"/><item><title>Building LowfiTimer: A Focus App That Runs Everywhere</title><link>https://ricardodantas.me/posts/building-lowfitimer/</link><pubDate>Thu, 26 Mar 2026 06:00:00 +0100</pubDate><guid>https://ricardodantas.me/posts/building-lowfitimer/</guid><description>&lt;p&gt;I&amp;rsquo;ve tried every focus timer out there. And I mean every one. They&amp;rsquo;re either too simple (just a countdown), too bloated (social features, gamification, AI coaching), or too expensive ($40/year for a timer?). The ones that actually worked well always came with a subscription attached.&lt;/p&gt;
&lt;p&gt;That bothered me.&lt;/p&gt;
&lt;p&gt;A timer doesn&amp;rsquo;t need a server. Sounds don&amp;rsquo;t expire. There&amp;rsquo;s no reason a focus app should cost more per year than some people pay for music streaming. So I decided to build my own.&lt;/p&gt;
&lt;h2 id="the-idea"&gt;The Idea&lt;/h2&gt;
&lt;p&gt;The core concept was simple: combine the Pomodoro technique with ambient sound mixing. I&amp;rsquo;ve always used background sounds to focus. Rain, coffee shop noise, brown noise. But I&amp;rsquo;d have one app for the timer and another for the sounds, and they never worked together.&lt;/p&gt;
&lt;p&gt;I wanted one app that handled both. Set your timer, pick your sounds, and go.&lt;/p&gt;
&lt;p&gt;What I didn&amp;rsquo;t anticipate was how far I&amp;rsquo;d take it.&lt;/p&gt;
&lt;h2 id="going-universal"&gt;Going Universal&lt;/h2&gt;
&lt;p&gt;I started with iOS. That&amp;rsquo;s the natural starting point for most people. But once the architecture was in place, I kept thinking: why not the Mac? I already sit in front of one all day. A menu bar timer with ambient sounds would be perfect for work sessions.&lt;/p&gt;
&lt;p&gt;Then watchOS. Being able to glance at your wrist and see how much focus time is left, without reaching for your phone, felt like a genuine quality-of-life improvement.&lt;/p&gt;
&lt;p&gt;And then, honestly just because I could, tvOS. The idea of turning your living room into an ambient focus environment was too fun to pass up.&lt;/p&gt;
&lt;p&gt;The result is a single codebase (with platform-specific adaptations) running on iPhone, iPad, Mac, Apple Watch, and Apple TV. One purchase covers everything.&lt;/p&gt;
&lt;h2 id="the-sound-engine"&gt;The Sound Engine&lt;/h2&gt;
&lt;p&gt;This was the part I enjoyed building the most. LowfiTimer ships with 25 ambient sounds, all CC0 field recordings. Rain on a window, a crackling campfire, forest birds, coffee shop chatter, ocean waves. Plus generated noise types: white, brown, pink, gray, and blue.&lt;/p&gt;
&lt;p&gt;You can layer up to 5 sounds simultaneously on iOS/macOS/tvOS (3 on watchOS), each with independent volume control. The mixing happens in real-time with AVFoundation.&lt;/p&gt;
&lt;p&gt;Finding the right sounds took longer than writing the audio code. I spent days listening to rain recordings, trying to find ones that looped naturally without obvious seams. The campfire sound went through four iterations before it felt right. Subtle things matter when you&amp;rsquo;re going to hear them for hours.&lt;/p&gt;
&lt;p&gt;I also added distinct transition sounds: a singing bowl ding when focus resumes, and a deep breath (inhale/exhale) when a break starts. Small touches, but they make the transitions feel intentional rather than jarring.&lt;/p&gt;
&lt;h2 id="watchos-was-the-hardest-part"&gt;watchOS Was the Hardest Part&lt;/h2&gt;
&lt;p&gt;Building for the watch taught me things I didn&amp;rsquo;t expect. The biggest constraint? No audio files. Bundling 25 sound files on watchOS would bloat the app beyond what&amp;rsquo;s reasonable for a watch.&lt;/p&gt;
&lt;p&gt;So I went a different route: procedural synthesis. The watchOS sound engine generates all 16 available sounds mathematically in real-time. Rain is filtered noise with random amplitude modulation. A campfire is layered crackling patterns. It&amp;rsquo;s not identical to the recorded versions on iPhone, but it&amp;rsquo;s surprisingly close, and it uses almost no storage.&lt;/p&gt;
&lt;p&gt;The other challenge was background execution. watchOS is aggressive about suspending apps. To keep the timer running while your wrist is down, I used &lt;code&gt;WKExtendedRuntimeSession&lt;/code&gt;, which gives you up to 30 minutes of background time. For longer sessions, the app re-requests the session before it expires.&lt;/p&gt;
&lt;p&gt;Digital Crown integration for volume control was a nice win. Twist the crown to adjust your mix volume without looking at the screen. It&amp;rsquo;s one of those details that makes the watch version feel native rather than a shrunken phone app.&lt;/p&gt;
&lt;h2 id="13-presets-and-unlimited-custom-ones"&gt;13 Presets (and Unlimited Custom Ones)&lt;/h2&gt;
&lt;p&gt;I knew not everyone wants to build their own sound mix from scratch. So I created 13 built-in presets: Deep Focus, Cafe Writer, Library Study, Rainy Cabin, Forest Morning, and more. Each one is a curated combination that I actually use.&lt;/p&gt;
&lt;p&gt;But the real feature is custom presets. Build your perfect mix, save it with a name and emoji, and it syncs across all your devices via CloudKit. Your &amp;ldquo;Late Night Coding&amp;rdquo; preset on your iPhone is already there on your Mac.&lt;/p&gt;
&lt;h2 id="the-stats-i-actually-wanted"&gt;The Stats I Actually Wanted&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve always found that tracking focus time motivates me to do more of it. But most apps either show you nothing or drown you in charts you&amp;rsquo;ll never look at.&lt;/p&gt;
&lt;p&gt;I kept it focused (pun intended): a daily progress ring, a weekly bar chart, streak tracking with a flame that grows the longer you maintain it, and a GitHub-style heatmap. Enough to see patterns and feel progress, not so much that the stats screen becomes its own distraction.&lt;/p&gt;
&lt;p&gt;Sessions also log as mindful minutes in Apple Health if you opt in, so your focus time counts toward your overall wellness tracking.&lt;/p&gt;
&lt;h2 id="siri-widgets-and-the-little-things"&gt;Siri, Widgets, and the Little Things&lt;/h2&gt;
&lt;p&gt;Seven App Intents power the Siri integration. &amp;ldquo;Hey Siri, start a focus session.&amp;rdquo; &amp;ldquo;Load my Rainy Cabin preset.&amp;rdquo; &amp;ldquo;How much have I focused today?&amp;rdquo; No setup required, they just work once you install the app.&lt;/p&gt;
&lt;p&gt;Widgets show your daily progress and streak on the home screen and lock screen. On watchOS, Smart Stack widgets put your focus stats right in the watch face complications.&lt;/p&gt;
&lt;p&gt;Focus Filters let LowfiTimer respond to iOS Focus modes. When you switch to your &amp;ldquo;Work&amp;rdquo; focus, the app can automatically adjust. These are the kinds of platform integrations that make an app feel like it belongs on Apple devices rather than just running on them.&lt;/p&gt;
&lt;h2 id="no-server-no-tracking-no-excuses"&gt;No Server, No Tracking, No Excuses&lt;/h2&gt;
&lt;p&gt;This is the part I care about most. LowfiTimer has no backend. Zero. There&amp;rsquo;s no analytics SDK, no crash reporter phoning home, no &amp;ldquo;anonymous&amp;rdquo; usage data. The app literally doesn&amp;rsquo;t have networking code beyond iCloud sync.&lt;/p&gt;
&lt;p&gt;Your focus sessions, presets, and settings live on your device and in your private iCloud account. I never see them. I can&amp;rsquo;t see them. That&amp;rsquo;s by design.&lt;/p&gt;
&lt;p&gt;The business model is aligned with this: a one-time purchase, and it&amp;rsquo;s yours forever. All platforms included. I don&amp;rsquo;t need to track you because I&amp;rsquo;m not selling your attention to advertisers. I don&amp;rsquo;t need a subscription because there&amp;rsquo;s no server to pay for.&lt;/p&gt;
&lt;h2 id="what-id-do-differently"&gt;What I&amp;rsquo;d Do Differently&lt;/h2&gt;
&lt;p&gt;If I started over, I&amp;rsquo;d extract the shared models into a Swift package earlier. I did it eventually (SharedModels target), but the initial approach of duplicating model code across platforms created merge headaches.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d also prototype the watchOS audio engine first instead of last. The constraints of that platform forced architectural decisions that would have been easier to make at the beginning.&lt;/p&gt;
&lt;p&gt;And I&amp;rsquo;d spend less time on the mesh background animation. It looks beautiful, and it shifts colors based on your timer state, but the amount of time I spent tweaking gradient control points was&amp;hellip; excessive. Sometimes you have to ship.&lt;/p&gt;
&lt;h2 id="try-it"&gt;Try It&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re someone who uses background sounds to focus, or if you&amp;rsquo;ve been looking for a Pomodoro timer that doesn&amp;rsquo;t try to be a social network, give LowfiTimer a look.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://apps.apple.com/app/lowfitimer/id6759457336"&gt;Download on the App Store&lt;/a&gt; | &lt;a href="https://lowfitimer.app"&gt;Website&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Building BragLog: Turning Daily Wins Into Brag Documents</title><link>https://ricardodantas.me/posts/building-braglog/</link><pubDate>Mon, 16 Mar 2026 06:00:00 +0100</pubDate><guid>https://ricardodantas.me/posts/building-braglog/</guid><description>&lt;p&gt;Performance review season is universally dreaded. Not because people don&amp;rsquo;t do great work, but because nobody remembers what they did six months ago. You sit down with a blank document, try to recall that critical bug you fixed in July, that feature you shipped under pressure in September, and end up writing something generic that undersells everything.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been there too many times.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The advice is always the same: &amp;ldquo;keep a brag document.&amp;rdquo; Write down your wins as they happen. Julia Evans wrote a great post about it years ago, and the concept stuck with me. But every time I tried, I&amp;rsquo;d keep it up for a week, forget about it, and find myself back at square one come review time.&lt;/p&gt;
&lt;p&gt;The friction was the problem. Opening a text file, context-switching from whatever I was working on, writing something coherent. It&amp;rsquo;s just enough resistance that you skip it on busy days, which are exactly the days worth documenting.&lt;/p&gt;
&lt;h2 id="what-i-built"&gt;What I Built&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://braglog.app"&gt;BragLog&lt;/a&gt; is a native app for iPhone, iPad, and Mac. The core loop is simple: log a quick win (takes seconds), and when review time comes, tap a button and get an AI-generated brag document ready to paste into whatever form your company uses.&lt;/p&gt;
&lt;p&gt;Six categories keep things organized: Shipped, Led, Fixed, Learned, Mentored, Improved. They map roughly to the things managers care about in reviews. The app suggests a category as you type using on-device AI, so you don&amp;rsquo;t even have to think about it.&lt;/p&gt;
&lt;h2 id="on-device-ai-with-tool-calling"&gt;On-Device AI With Tool Calling&lt;/h2&gt;
&lt;p&gt;The brag document generation is the feature I&amp;rsquo;m most proud of, and also the one that caused the most headaches.&lt;/p&gt;
&lt;p&gt;I used Apple&amp;rsquo;s FoundationModels framework, which runs entirely on-device. No API keys, no cloud calls, no data leaving the phone. The model generates structured brag documents using Tool Calling: it can fetch entries for a specific period, get category breakdowns, and pull streak information, then weave it all into a coherent narrative.&lt;/p&gt;
&lt;p&gt;You can choose a tone (professional, confident, or modest) and the AI adapts accordingly. The output comes as structured sections that you can export as Markdown, plain text, or HTML compatible with Word, Pages, and Google Docs.&lt;/p&gt;
&lt;p&gt;The catch? FoundationModels requires Apple Silicon. On older devices, the AI features gracefully degrade. You can still log, organize, and export everything manually. The AI is a convenience layer, not a hard dependency.&lt;/p&gt;
&lt;p&gt;One lesson I learned the hard way: the &lt;code&gt;@Generable&lt;/code&gt; macro and Tool Calling have quirks. You can&amp;rsquo;t capture SwiftData &lt;code&gt;@Model&lt;/code&gt; objects in tool closures because they&amp;rsquo;re not &lt;code&gt;Sendable&lt;/code&gt;. I had to create a &lt;code&gt;SendableEntry&lt;/code&gt; pattern where I extract plain data from model objects before passing them to the AI pipeline. Not complicated once you know, but it cost me a few hours of confusing compiler errors.&lt;/p&gt;
&lt;h2 id="intelligent-search"&gt;Intelligent Search&lt;/h2&gt;
&lt;p&gt;Standard text search feels limiting once you have hundreds of entries. &amp;ldquo;I know I fixed something related to database performance, but I didn&amp;rsquo;t use the word &amp;lsquo;database&amp;rsquo; in the entry.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;BragLog&amp;rsquo;s search uses FoundationModels for semantic matching. Search &amp;ldquo;database&amp;rdquo; and it&amp;rsquo;ll surface entries about SQL migrations, query optimization, and PostgreSQL tuning even if those exact words aren&amp;rsquo;t in the query. There&amp;rsquo;s a debounce (0.8 seconds) and a cap at 200 entries to keep it responsive.&lt;/p&gt;
&lt;h2 id="the-streak-game"&gt;The Streak Game&lt;/h2&gt;
&lt;p&gt;I added streak tracking because I know myself. A GitHub-style contribution heatmap, daily progress, and milestone celebrations at 7, 30, and 100 days. It&amp;rsquo;s a small psychological trick, but it works. There&amp;rsquo;s a grace period built into the streak calculator so missing a weekend doesn&amp;rsquo;t break a work-week streak.&lt;/p&gt;
&lt;p&gt;The streak also shows as a Live Activity on the Lock Screen and Dynamic Island on iOS. It&amp;rsquo;s a subtle reminder throughout the day. &amp;ldquo;You&amp;rsquo;ve logged for 23 days straight. Don&amp;rsquo;t break the chain.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="17-tips-and-an-animated-onboarding"&gt;17 Tips and an Animated Onboarding&lt;/h2&gt;
&lt;p&gt;I went deep on TipKit. Seventeen contextual tips that appear progressively as you use the app. First-time composing? You get a tip about AI category suggestions. Hit 10 entries? A tip about generating your first brag doc. Reach the insights screen? Tips about the heatmap and streak tracking.&lt;/p&gt;
&lt;p&gt;The onboarding is a 4-page animated flow with MeshGradient backgrounds, floating particles, and spring animations. I probably spent more time on this than I should have, but first impressions matter, and I wanted the app to feel polished from the moment you open it.&lt;/p&gt;
&lt;h2 id="making-it-feel-native-everywhere"&gt;Making It Feel Native Everywhere&lt;/h2&gt;
&lt;p&gt;The iOS version uses an adaptive tab bar with Liquid Glass styling on iOS 26. On iPad, it switches to a sidebar. On Mac, it&amp;rsquo;s a full NavigationSplitView with a menu bar extra for quick capture. You can log a win from the menu bar without switching away from your code editor.&lt;/p&gt;
&lt;p&gt;Widgets cover Home Screen, Lock Screen, StandBy, and Control Center. Seven Siri Shortcuts let you log wins, check your streak, or generate summaries by voice. Handoff lets you start composing on your iPhone and continue on your Mac. Spotlight indexes all entries so you can find them from system search.&lt;/p&gt;
&lt;p&gt;None of these integrations are individually complex, but together they make the app feel like it belongs on the platform rather than just running on it. That&amp;rsquo;s the goal.&lt;/p&gt;
&lt;h2 id="zero-dependencies"&gt;Zero Dependencies&lt;/h2&gt;
&lt;p&gt;BragLog is built entirely with Apple frameworks. No SPM packages, no CocoaPods, nothing. Swift 6 with strict concurrency, SwiftUI, SwiftData, CloudKit, FoundationModels, TipKit, WidgetKit, AppIntents, CoreSpotlight, ActivityKit. About 7,700 lines across 48 Swift files.&lt;/p&gt;
&lt;p&gt;I chose this constraint deliberately. Every third-party dependency is a maintenance burden and a potential privacy concern. For an app that handles people&amp;rsquo;s work accomplishments, I wanted to be able to say with certainty: nothing in this app talks to anyone except Apple&amp;rsquo;s iCloud, and only if you have it enabled.&lt;/p&gt;
&lt;h2 id="what-id-do-differently"&gt;What I&amp;rsquo;d Do Differently&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;@Generable&lt;/code&gt; struct wrappers for FoundationModels output were clunky at first. I&amp;rsquo;d design the model layer around the AI output format from the start instead of retrofitting it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d also invest in screenshot automation earlier. I built a &lt;code&gt;DebugDataSeeder&lt;/code&gt; that generates realistic sample data for App Store screenshots, but I added it late in the process. Having it from day one would have made design iteration faster.&lt;/p&gt;
&lt;p&gt;And the 17 TipKit tips? I&amp;rsquo;d still do all of them, but I&amp;rsquo;d define the milestone triggers in a configuration file rather than scattering them across views. They work great, but they&amp;rsquo;re harder to maintain than they should be.&lt;/p&gt;
&lt;h2 id="try-it"&gt;Try It&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;ve ever stared at a blank self-review form trying to remember what you did last quarter, BragLog might help. Log your wins as they happen, let AI do the heavy lifting at review time.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://apps.apple.com/app/braglog/id6759666989"&gt;Download on the App Store&lt;/a&gt; | &lt;a href="https://braglog.app"&gt;Website&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Launching Calometric: A Nutrition Tracker That Respects Your Privacy</title><link>https://ricardodantas.me/posts/launching-calometric/</link><pubDate>Thu, 05 Mar 2026 10:00:00 +0100</pubDate><guid>https://ricardodantas.me/posts/launching-calometric/</guid><description>&lt;p&gt;I&amp;rsquo;ve never been great at tracking what I eat. I&amp;rsquo;d download a calorie tracker, spend five minutes manually searching for &amp;ldquo;grilled chicken breast 150g,&amp;rdquo; give up after three days, and delete the app. The friction was always too high.&lt;/p&gt;
&lt;p&gt;Most nutrition apps want you to weigh everything, type everything, and subscribe to everything. And in return, they collect your data and sell it to advertisers. That never sat well with me.&lt;/p&gt;
&lt;p&gt;So I built something different.&lt;/p&gt;
&lt;h2 id="introducing-calometric"&gt;Introducing Calometric&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://calometric.app"&gt;Calometric&lt;/a&gt; is a nutrition tracker for iPhone and Apple Watch. Take a photo of your meal, and it tells you the calories and macros. That&amp;rsquo;s the core idea.&lt;/p&gt;
&lt;p&gt;What makes it different is where the AI runs: entirely on your device. The food recognition uses Core ML, and the portion estimation uses Apple&amp;rsquo;s Foundation Models. Nothing gets sent to a server. There&amp;rsquo;s no account to create. No data to leak.&lt;/p&gt;
&lt;h2 id="five-ways-to-log"&gt;Five Ways to Log&lt;/h2&gt;
&lt;p&gt;Not every meal is a photo opportunity. Sometimes you&amp;rsquo;re eating a protein bar with a barcode. Sometimes you&amp;rsquo;re driving and just want to say &amp;ldquo;two eggs and toast.&amp;rdquo; So I built five input methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Photo scan&lt;/strong&gt; for when you can snap a picture&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Barcode scanner&lt;/strong&gt; for packaged foods (via Open Food Facts)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Voice logging&lt;/strong&gt; for hands-free moments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Food search&lt;/strong&gt; against a local USDA database with 177+ foods&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nutrition label OCR&lt;/strong&gt; for when you want exact numbers from the package&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each one feeds into the same pipeline and saves consistently to SwiftData, HealthKit, Spotlight, and widgets.&lt;/p&gt;
&lt;h2 id="why-on-device-ai"&gt;Why On-Device AI?&lt;/h2&gt;
&lt;p&gt;Privacy was a hard requirement from day one. I didn&amp;rsquo;t want to build something that phones home every time you eat lunch. But beyond privacy, there&amp;rsquo;s a practical benefit: it works offline. On a plane, in the mountains, in your kitchen with spotty Wi-Fi. It just works.&lt;/p&gt;
&lt;p&gt;The trade-off is that on-device models aren&amp;rsquo;t as powerful as cloud models. The AI portion estimation requires an iPhone 15 Pro or newer with Apple Intelligence enabled. On older devices, everything still works, you just get default portion sizes instead of smart estimates.&lt;/p&gt;
&lt;h2 id="apple-watch-too"&gt;Apple Watch Too&lt;/h2&gt;
&lt;p&gt;I wanted to log meals from my wrist. The watchOS app is fully independent, with voice logging, a quick-add menu of common foods, and complication widgets showing your daily progress. It syncs with the iPhone via WatchConnectivity.&lt;/p&gt;
&lt;h2 id="the-business-model"&gt;The Business Model&lt;/h2&gt;
&lt;p&gt;One-time purchase. That&amp;rsquo;s it. No subscriptions, no ads, no in-app purchases. One price covers iPhone and Apple Watch.&lt;/p&gt;
&lt;p&gt;I keep coming back to this model because I believe in it. If you&amp;rsquo;re building a tool that respects people&amp;rsquo;s privacy, charging them a recurring fee to keep accessing their own data feels contradictory.&lt;/p&gt;
&lt;h2 id="try-it"&gt;Try It&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re looking for a nutrition tracker that stays out of your way and keeps your data on your device, give Calometric a look.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://apps.apple.com/app/id6759844251"&gt;Download on the App Store&lt;/a&gt; | &lt;a href="https://calometric.app"&gt;Website&lt;/a&gt;&lt;/p&gt;</description></item><item><title>ratatui-themes: Building a Theme Library and Finding a Community</title><link>https://ricardodantas.me/posts/ratatui-themes-building-a-theme-library/</link><pubDate>Mon, 02 Mar 2026 10:00:00 +0100</pubDate><guid>https://ricardodantas.me/posts/ratatui-themes-building-a-theme-library/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every side project where you push your code to the world and think, &amp;ldquo;Well, that&amp;rsquo;s done.&amp;rdquo; You close the laptop, maybe feel a brief spark of accomplishment, and move on. Nobody&amp;rsquo;s going to notice. It&amp;rsquo;s just another crate in a sea of crates.&lt;/p&gt;
&lt;p&gt;And then someone opens an issue.&lt;/p&gt;
&lt;h2 id="the-backstory"&gt;The Backstory&lt;/h2&gt;
&lt;p&gt;While building &lt;a href="https://perch.ricardodantas.me"&gt;Perch&lt;/a&gt;, &lt;a href="https://feedo.ricardodantas.me"&gt;Feedo&lt;/a&gt;, and &lt;a href="https://hazelnut.ricardodantas.me"&gt;Hazelnut&lt;/a&gt;, I kept running into the same problem: theming. Every ratatui application handles colors differently. Some hardcode hex values. Others define their own theme structs. There&amp;rsquo;s no shared vocabulary for &amp;ldquo;give me a Dracula theme&amp;rdquo; or &amp;ldquo;switch to Catppuccin.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I wanted a simple, reusable library. Define a theme by name, get consistent semantic colors (foreground, background, accent, error, warning, success) that any ratatui app could use. No runtime overhead. No complex configuration. Just &lt;code&gt;Theme::new(ThemeName::Dracula)&lt;/code&gt; and you&amp;rsquo;re done.&lt;/p&gt;
&lt;p&gt;So I built &lt;a href="https://github.com/ricardodantas/ratatui-themes"&gt;ratatui-themes&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="what-it-does"&gt;What It Does&lt;/h2&gt;
&lt;p&gt;The crate is deliberately minimal. It gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;15+ popular themes&lt;/strong&gt; - Dracula, Nord, Catppuccin (all four flavors), Gruvbox, Tokyo Night, Solarized, One Dark, Kanagawa, Everforest, Rosé Pine, and a few originals&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic colors&lt;/strong&gt; - Every theme maps to the same set of named colors, so your app looks consistent regardless of which theme the user picks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theme cycling&lt;/strong&gt; - Built-in &lt;code&gt;next()&lt;/code&gt; and &lt;code&gt;prev()&lt;/code&gt; methods, because every TUI app needs a &amp;ldquo;press T to switch theme&amp;rdquo; feature&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Light/dark detection&lt;/strong&gt; - Programmatically check if a theme is light or dark, useful for adapting UI elements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional serde support&lt;/strong&gt; - For persisting the user&amp;rsquo;s theme choice in a config file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Zero dependencies beyond ratatui itself. The whole thing compiles in seconds.&lt;/p&gt;
&lt;h2 id="publishing-to-cratesio"&gt;Publishing to crates.io&lt;/h2&gt;
&lt;p&gt;Publishing a crate is oddly nerve-wracking every time. You run &lt;code&gt;cargo publish&lt;/code&gt;, watch the progress bar, and suddenly your code is on &lt;a href="https://crates.io/crates/ratatui-themes"&gt;crates.io&lt;/a&gt; for anyone to install. There&amp;rsquo;s no review process, no gatekeeper. Just you and the Rust ecosystem&amp;rsquo;s trust model.&lt;/p&gt;
&lt;p&gt;I wrote thorough documentation: rustdoc with examples for every public function, a detailed README, a changelog. Partly because good docs matter. Partly because if someone was actually going to use this, I didn&amp;rsquo;t want them to have to read my source code to figure out how.&lt;/p&gt;
&lt;p&gt;Then I waited. Not expectantly. I genuinely assumed the crate would sit there with maybe a handful of downloads from bots and search indexers.&lt;/p&gt;
&lt;h2 id="the-first-issue"&gt;The First Issue&lt;/h2&gt;
&lt;p&gt;A few days later, I got a GitHub notification. Someone had opened an issue. My first reaction was a small adrenaline spike, the kind you get when your phone rings unexpectedly. Is it a bug report? Did I break something?&lt;/p&gt;
&lt;p&gt;It wasn&amp;rsquo;t a bug. It was a feature request. And not just any feature request, but a thoughtful, well-structured proposal for &amp;ldquo;associated colors.&amp;rdquo; The idea was to add extended color palettes (reds, greens, blues, etc.) that go beyond the core semantic colors, useful for things like charts, graphs, and data visualization.&lt;/p&gt;
&lt;p&gt;The message opened with: &lt;em&gt;&amp;ldquo;I was just looking for something like this three days ago when you published it. Amazing timing!&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I read that line three times. Someone had been looking for exactly what I built. The timing was coincidental, but the validation was real. This wasn&amp;rsquo;t a theoretical exercise anymore. Someone had a concrete use case, and my crate was close enough to what they needed that they wanted to help shape it.&lt;/p&gt;
&lt;h2 id="the-first-pull-request"&gt;The First Pull Request&lt;/h2&gt;
&lt;p&gt;Then came the pull request. They&amp;rsquo;d built a &lt;strong&gt;theme gallery example&lt;/strong&gt;, a visual picker that renders all available themes side by side so you can see what they look like before choosing one. 192 lines of additions, cleanly written, with a new &lt;code&gt;ThemePicker&lt;/code&gt; widget.&lt;/p&gt;
&lt;p&gt;I reviewed the code, left some comments, we discussed the approach, and I merged it. The whole interaction was respectful, constructive, and genuinely fun. This is open source at its best: a stranger on the internet saw something they liked, made it better, and shared the improvement with everyone.&lt;/p&gt;
&lt;h2 id="why-this-matters"&gt;Why This Matters&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve contributed to open source projects before. I&amp;rsquo;ve opened PRs, filed issues, and reviewed code on other people&amp;rsquo;s repos. But being on the receiving end is a completely different experience.&lt;/p&gt;
&lt;p&gt;When someone uses your library, they&amp;rsquo;re trusting your decisions. Your API design, your color choices, your documentation. When they open an issue, they&amp;rsquo;re investing their time to make your project better. When they submit a PR, they&amp;rsquo;re saying &amp;ldquo;I believe in this enough to write code for it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a powerful thing. And it&amp;rsquo;s easy to take for granted if you&amp;rsquo;ve been in the open source world for a while. But with ratatui-themes, it hit differently.&lt;/p&gt;
&lt;h2 id="what-i-learned"&gt;What I Learned&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re sitting on a library or tool that you built for yourself, consider publishing it. Here&amp;rsquo;s what I learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Someone needs what you built.&lt;/strong&gt; You wrote it to solve your problem. Chances are, someone else has the same problem. The timing might just line up.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Documentation is your first impression.&lt;/strong&gt; Good docs with real examples lower the barrier to adoption. People will judge your crate by its README before they ever read a line of source code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Start small.&lt;/strong&gt; ratatui-themes does one thing. It doesn&amp;rsquo;t try to be a full UI framework or a design system. Focused libraries are easier to maintain and easier for others to adopt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Be responsive.&lt;/strong&gt; When that first issue comes in, engage with it. The early contributors are the ones who&amp;rsquo;ll shape your project&amp;rsquo;s direction. Treat their input with respect.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It doesn&amp;rsquo;t have to be perfect.&lt;/strong&gt; Version 0.1.0 had rough edges. That&amp;rsquo;s fine. Ship it, iterate, improve. The crate is on 0.2.0 now, and each version is better because of real-world feedback.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;The &amp;ldquo;associated colors&amp;rdquo; proposal from that first issue is still being discussed. We&amp;rsquo;re figuring out the right API. I suggested a &lt;code&gt;HashMap&amp;lt;String, Vec&amp;lt;Color&amp;gt;&amp;gt;&lt;/code&gt; approach instead of fixed fields, which would be more flexible and future-proof.&lt;/p&gt;
&lt;p&gt;The crate is integrated into &lt;a href="https://feedo.ricardodantas.me"&gt;Feedo&lt;/a&gt;, &lt;a href="https://perch.ricardodantas.me"&gt;Perch&lt;/a&gt;, and my other TUI apps. It&amp;rsquo;s doing exactly what I built it to do: making theming consistent and easy across projects. But now it&amp;rsquo;s also doing something I didn&amp;rsquo;t plan for: connecting me with other developers who care about making terminal apps look beautiful.&lt;/p&gt;
&lt;p&gt;And honestly? That&amp;rsquo;s better than any download count.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check it out:&lt;/strong&gt; &lt;a href="https://crates.io/crates/ratatui-themes"&gt;crates.io/crates/ratatui-themes&lt;/a&gt; | &lt;a href="https://github.com/ricardodantas/ratatui-themes"&gt;GitHub&lt;/a&gt; | &lt;a href="https://docs.rs/ratatui-themes"&gt;Docs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PRs and new theme suggestions are always welcome. 🎨&lt;/p&gt;</description></item><item><title>Perch: Social Media from the Terminal</title><link>https://ricardodantas.me/posts/perch-a-terminal-social-client/</link><pubDate>Fri, 27 Feb 2026 10:00:00 +0100</pubDate><guid>https://ricardodantas.me/posts/perch-a-terminal-social-client/</guid><description>&lt;p&gt;I&amp;rsquo;ve been on a bit of a streak with Rust and terminal apps. After &lt;a href="https://ricardodantas.me/posts/building-tui-apps-with-rust/"&gt;Feedo and Hazelnut&lt;/a&gt;, I kept looking for things I do daily that could live in the terminal. Social media was an obvious one, and an interesting challenge.&lt;/p&gt;
&lt;p&gt;The thing about social media clients is that they&amp;rsquo;re either overwhelming web apps with infinite scroll and algorithmic noise, or they&amp;rsquo;re bare-bones API wrappers that feel like work. I wanted something in between: a beautiful, keyboard-driven interface that makes social media feel intentional instead of addictive.&lt;/p&gt;
&lt;h2 id="why-a-terminal-client"&gt;Why a Terminal Client?&lt;/h2&gt;
&lt;p&gt;I spend most of my day in the terminal. Between coding, managing files with Hazelnut, reading feeds with Feedo, and everything else, the terminal is home. Opening a browser tab for Mastodon or Bluesky always felt like a context switch. And context switches are expensive.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also something philosophical about using social media in a terminal. It strips away the visual manipulation: the infinite scroll, the notifications badge, the algorithmic feed. You see the content, you engage with it, and you move on. It&amp;rsquo;s social media on your terms.&lt;/p&gt;
&lt;h2 id="what-perch-does"&gt;What Perch Does&lt;/h2&gt;
&lt;p&gt;Perch supports both Mastodon and Bluesky, which covers the decentralized social networks I actually use. You authenticate once, and then everything works through either the TUI (text user interface) or the CLI.&lt;/p&gt;
&lt;p&gt;The TUI is a three-panel layout: accounts on the left, timeline in the center, and post detail on the right. It&amp;rsquo;s navigated entirely with vim keybindings, which at this point feels as natural as breathing. You can browse your home timeline, like and boost posts, reply, compose new posts, everything you&amp;rsquo;d do in a web client.&lt;/p&gt;
&lt;p&gt;But the part I&amp;rsquo;m most proud of is &lt;strong&gt;cross-posting&lt;/strong&gt;. Write a post once, and Perch sends it to both Mastodon and Bluesky simultaneously. I&amp;rsquo;ve been maintaining a presence on both networks, and doing it manually was getting old. Now it&amp;rsquo;s one command or one keystroke.&lt;/p&gt;
&lt;p&gt;The CLI side is equally useful. You can script your social media: schedule posts, fetch timelines, manage accounts, all from shell scripts or cron jobs. It composes nicely with other tools.&lt;/p&gt;
&lt;h2 id="the-technical-bits"&gt;The Technical Bits&lt;/h2&gt;
&lt;p&gt;Building a multi-network social client in Rust was more complex than my previous projects. Mastodon uses OAuth and a REST API, while Bluesky uses the AT Protocol with app passwords. Abstracting these behind a unified &lt;code&gt;SocialApi&lt;/code&gt; trait took some careful design, but the result is clean. Adding a new network in the future would be straightforward.&lt;/p&gt;
&lt;p&gt;Credentials are stored in the system keyring (via the &lt;code&gt;keyring&lt;/code&gt; crate), so no plaintext tokens lying around in config files. There&amp;rsquo;s also a SQLite-backed cache for offline reading, which means Perch works even when your connection doesn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;For theming, I integrated &lt;a href="https://github.com/ricardodantas/ratatui-themes"&gt;ratatui-themes&lt;/a&gt;, a theme library I built separately. Perch ships with 15 themes out of the box, from Dracula to Catppuccin to a neon Cyberpunk theme that&amp;rsquo;s entirely too fun.&lt;/p&gt;
&lt;h2 id="what-i-learned-this-time"&gt;What I Learned This Time&lt;/h2&gt;
&lt;p&gt;Each Rust project teaches me something different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trait-based abstraction pays off.&lt;/strong&gt; The &lt;code&gt;SocialApi&lt;/code&gt; trait let me treat Mastodon and Bluesky uniformly throughout the app. When I added cross-posting, it was trivial because the abstraction was already there.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OAuth in Rust is painful.&lt;/strong&gt; Not because of Rust itself, but because OAuth flows involve HTTP redirects, local servers, token exchange&amp;hellip; it&amp;rsquo;s inherently messy. Getting it right took more time than I&amp;rsquo;d like to admit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQLite is the right default.&lt;/strong&gt; Every time I reach for SQLite in a desktop/CLI app, I&amp;rsquo;m reminded how good it is. Zero configuration, embedded, fast. The &lt;code&gt;rusqlite&lt;/code&gt; crate makes it feel native to Rust.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;People care about themes.&lt;/strong&gt; I added theming almost as an afterthought in Feedo, and it was the most commented-on feature. For Perch, I made it a first-class concern from day one.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="try-it"&gt;Try It&lt;/h2&gt;
&lt;p&gt;Perch is open source and available now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://perch.ricardodantas.me"&gt;perch.ricardodantas.me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/ricardodantas/perch"&gt;github.com/ricardodantas/perch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;brew install ricardodantas/tap/perch&lt;/code&gt; or &lt;code&gt;cargo install perch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you use Mastodon, Bluesky, or both, give it a try. It might change how you think about social media. Or at least save you from opening another browser tab. 🐦&lt;/p&gt;</description></item><item><title>Launching Pikr: A Color Picker for Accessibility-Minded Developers</title><link>https://ricardodantas.me/posts/launching-pikr/</link><pubDate>Tue, 17 Feb 2026 16:00:00 +0100</pubDate><guid>https://ricardodantas.me/posts/launching-pikr/</guid><description>&lt;p&gt;If you&amp;rsquo;ve ever worked on a UI, whether as a developer or designer, you know the dance. Pick a color. Check if it&amp;rsquo;s accessible. Open another tool. Paste the hex code. Check the contrast ratio. Realize it fails AA. Go back. Adjust. Repeat.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s tedious. And it&amp;rsquo;s a workflow I&amp;rsquo;ve done hundreds of times.&lt;/p&gt;
&lt;p&gt;So I built something to fix it.&lt;/p&gt;
&lt;h2 id="introducing-pikr"&gt;Introducing Pikr&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://getpikr.app"&gt;Pikr&lt;/a&gt; is a color picker with built-in WCAG accessibility contrast checking. Pick any color from your screen, and instantly see if it meets AA or AAA compliance standards. No switching between apps. No copy-pasting hex codes into web tools. Just pick and know.&lt;/p&gt;
&lt;p&gt;It sounds simple because it should be simple. That&amp;rsquo;s the whole point.&lt;/p&gt;
&lt;h2 id="the-problem-it-solves"&gt;The Problem It Solves&lt;/h2&gt;
&lt;p&gt;Accessibility isn&amp;rsquo;t optional anymore, and it shouldn&amp;rsquo;t be. But the tooling around it has always felt fragmented. You have your design tool, your color picker, and then some separate contrast checker (usually a website). Every time you want to verify a color choice, you&amp;rsquo;re context-switching.&lt;/p&gt;
&lt;p&gt;I wanted something integrated. Pick a color, see the contrast ratio against your background, know immediately if it passes. All in one place.&lt;/p&gt;
&lt;h2 id="what-it-does"&gt;What It Does&lt;/h2&gt;
&lt;p&gt;Pikr keeps things focused:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pick colors from anywhere on screen&lt;/strong&gt; - Works system-wide, not locked to any specific app&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instant WCAG contrast checking&lt;/strong&gt; - See AA and AAA compliance as you pick&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple color formats&lt;/strong&gt; - Hex, RGB, HSB, HSL, LAB, and OpenGL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean, minimal interface&lt;/strong&gt; - It stays out of your way until you need it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing more than necessary. I&amp;rsquo;ve grown to appreciate tools that do one thing well.&lt;/p&gt;
&lt;h2 id="why-tauri"&gt;Why Tauri?&lt;/h2&gt;
&lt;p&gt;After spending time with Rust building &lt;a href="https://ricardodantas.me/posts/building-tui-apps-with-rust/"&gt;Feedo and Hazelnut&lt;/a&gt;, I wanted to apply what I learned to a desktop app with an actual GUI. &lt;a href="https://tauri.app/"&gt;Tauri&lt;/a&gt; was the natural choice. It combines a Rust backend with a web-based frontend, resulting in small, fast, native applications.&lt;/p&gt;
&lt;p&gt;The development experience was smooth. Tauri handles the cross-platform complexity (system tray, global shortcuts, native dialogs) while letting me use familiar web technologies for the interface. The final binary is tiny compared to Electron-based alternatives, and it feels snappy.&lt;/p&gt;
&lt;p&gt;Pikr currently runs on macOS and Linux, with Windows support coming soon.&lt;/p&gt;
&lt;h2 id="shipping-something"&gt;Shipping Something&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s something satisfying about taking a personal frustration and turning it into a tool others can use. Pikr started as a solution to my own workflow problem, but accessibility affects everyone building interfaces. If it saves someone else from the tab-switching dance, that&amp;rsquo;s a win.&lt;/p&gt;
&lt;p&gt;The app is available at &lt;a href="https://getpikr.app"&gt;getpikr.app&lt;/a&gt; for $5, or $3 right now with a 40% launch discount. It&amp;rsquo;s not free, but it&amp;rsquo;s also not trying to be a subscription or harvest your data. Just a small tool that does its job.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s Next?&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ll keep refining Pikr based on feedback and my own usage. Windows support is the immediate priority. Beyond that, I have some ideas for features, but I want to be careful not to bloat it. The simplicity is intentional.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a developer or designer who cares about accessibility (and you should), give it a look. And if you have feedback, I&amp;rsquo;d love to hear it.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see where this one goes. 🎨&lt;/p&gt;</description></item><item><title>Building TUI Apps with Rust: Feedo and Hazelnut</title><link>https://ricardodantas.me/posts/building-tui-apps-with-rust/</link><pubDate>Wed, 04 Feb 2026 17:00:00 +0100</pubDate><guid>https://ricardodantas.me/posts/building-tui-apps-with-rust/</guid><description>&lt;p&gt;My family traveled abroad for a month, and suddenly I had something rare: free time. Instead of binge-watching series or scrolling endlessly, I decided to channel that energy into something I&amp;rsquo;ve been curious about for a while: Rust development.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been watching Rust from the sidelines for years. The language always intrigued me: its focus on memory safety, the passionate community, the promise of &amp;ldquo;fearless concurrency.&amp;rdquo; But between work and family life, I never found the right moment to dive in properly.&lt;/p&gt;
&lt;p&gt;Well, the moment arrived.&lt;/p&gt;
&lt;h2 id="why-rust"&gt;Why Rust?&lt;/h2&gt;
&lt;p&gt;As a software developer, I&amp;rsquo;ve worked with many languages over the years. JavaScript and TypeScript dominate my day job, and I&amp;rsquo;ve dabbled in Python, Go, and others. But Rust felt different. The learning curve is famously steep, but the payoff (fast, safe, reliable code) seemed worth it.&lt;/p&gt;
&lt;p&gt;What really drew me in was the terminal ecosystem. Rust has incredible libraries for building command-line and TUI (Text User Interface) applications. Tools like &lt;a href="https://ratatui.rs/"&gt;ratatui&lt;/a&gt; make it surprisingly pleasant to create rich terminal interfaces.&lt;/p&gt;
&lt;h2 id="feedo-a-tui-rss-reader"&gt;Feedo: A TUI RSS Reader&lt;/h2&gt;
&lt;p&gt;The first project I tackled was &lt;strong&gt;Feedo&lt;/strong&gt;, a terminal-based RSS feed reader.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve always liked RSS. It&amp;rsquo;s the antidote to algorithm-driven feeds: you choose what you want to read, and that&amp;rsquo;s what you get. But most RSS readers are either web-based or clunky desktop apps. I wanted something fast, keyboard-driven, and integrated into my terminal workflow.&lt;/p&gt;
&lt;p&gt;Feedo lets you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add and organize RSS/Atom feeds&lt;/li&gt;
&lt;li&gt;Browse articles with vim-like keybindings&lt;/li&gt;
&lt;li&gt;Open articles in your browser&lt;/li&gt;
&lt;li&gt;Search through your feeds&lt;/li&gt;
&lt;li&gt;Choose from multiple themes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building it taught me a lot about async Rust, HTTP clients, and XML parsing. The ratatui library made the UI work enjoyable, and I&amp;rsquo;m genuinely happy with how it turned out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check it out:&lt;/strong&gt; &lt;a href="https://feedo.ricardodantas.me"&gt;feedo.ricardodantas.me&lt;/a&gt; | &lt;a href="https://github.com/ricardodantas/feedo"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="hazelnut-automated-file-organization"&gt;Hazelnut: Automated File Organization&lt;/h2&gt;
&lt;p&gt;The second project, &lt;strong&gt;Hazelnut&lt;/strong&gt;, scratches a different itch. It&amp;rsquo;s inspired by &lt;a href="https://www.noodlesoft.com/"&gt;Hazel&lt;/a&gt;, the popular macOS file automation tool.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve always wanted something like Hazel, but cross-platform and terminal-based. Hazelnut watches folders and automatically organizes files based on rules you define. Downloaded a PDF? Move it to Documents. Old screenshots? Archive them. Temporary files older than 30 days? Delete them.&lt;/p&gt;
&lt;p&gt;The architecture is split into two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;hazelnut&lt;/strong&gt; - The TUI for managing rules and watches&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hazelnutd&lt;/strong&gt; - The background daemon that does the actual work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building the daemon was particularly interesting. It uses the &lt;code&gt;notify&lt;/code&gt; crate for cross-platform file watching (inotify on Linux, kqueue on macOS) and supports hot-reloading configuration via Unix signals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check it out:&lt;/strong&gt; &lt;a href="https://hazelnut.ricardodantas.me"&gt;hazelnut.ricardodantas.me&lt;/a&gt; | &lt;a href="https://github.com/ricardodantas/hazelnut"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="what-i-learned"&gt;What I Learned&lt;/h2&gt;
&lt;p&gt;This month of intense Rust development taught me more than I expected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The compiler is your friend.&lt;/strong&gt; Yes, it&amp;rsquo;s strict. Yes, it will reject your code often. But when it compiles, it usually works. The confidence that brings is remarkable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TUI development is fun.&lt;/strong&gt; There&amp;rsquo;s something deeply satisfying about building interfaces that run in the terminal. It feels efficient and focused.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Side projects need deadlines.&lt;/strong&gt; Having a month with clear free time created natural pressure to ship something. Without that, these projects might have lingered in &amp;ldquo;someday&amp;rdquo; territory forever.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rust is worth the investment.&lt;/strong&gt; The learning curve is real, but the language&amp;rsquo;s design makes you think more carefully about your code. That translates to better habits in other languages too.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s Next?&lt;/h2&gt;
&lt;p&gt;Both projects are open source and available on GitHub. I&amp;rsquo;ve already published them on &lt;a href="https://crates.io/"&gt;crates.io&lt;/a&gt; and set up Homebrew formulas, so you can install them with a simple &lt;code&gt;brew install ricardodantas/tap/feedo&lt;/code&gt; or &lt;code&gt;brew install ricardodantas/tap/hazelnut&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s still plenty to improve: better documentation, more features, and refinements based on real-world usage. But for now, I&amp;rsquo;m happy to have shipped two actual tools that I use daily.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re curious about Rust or TUI development, I encourage you to try building something small. The ecosystem is welcoming, the tooling is excellent, and there&amp;rsquo;s something special about seeing your creation run in the terminal.&lt;/p&gt;
&lt;p&gt;Now I just need to figure out what to build next. 🦀&lt;/p&gt;</description></item><item><title>Leveling up my dotfiles</title><link>https://ricardodantas.me/posts/leveling-up-my-dotfiles/</link><pubDate>Fri, 23 Jan 2026 23:54:21 +0100</pubDate><guid>https://ricardodantas.me/posts/leveling-up-my-dotfiles/</guid><description>&lt;p&gt;For months, I have sought better ways to automate reinstalling my work tools when switching to a new laptop. Years ago, I began using &lt;a href="https://www.freecodecamp.org/news/dotfiles-what-is-a-dot-file-and-how-to-create-it-in-mac-and-linux/"&gt;dotfiles&lt;/a&gt;. Initially, I used some tooling I wasn&amp;rsquo;t entirely happy with, but given the options at the time, it was what I had. After a few months, I simplified my approach, adopting only &lt;a href="https://www.gnu.org/software/stow/"&gt;Stow&lt;/a&gt; and a Bash Script for managing my dotfiles. This made the setup process much faster and easier, significantly reducing overhead. However, I was concerned about the security of my secrets, since they were stored in plain text in the config files. Although my repository was private and I used a password manager via a Bash Script, I didn’t feel comfortable with unencrypted passwords in the configs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://mise.jdx.dev/"&gt;Mise (or “mise-en-place”)&lt;/a&gt; is an open-source development environment setup tool designed to make local dev environments easy, consistent, and reproducible across projects and languages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://fnox.jdx.dev/"&gt;Fnox&lt;/a&gt; is a developer-focused secret management CLI tool that helps you securely manage and use sensitive configuration values (like API keys, database passwords, tokens) across development, CI/CD, and production workflows. It combines encrypted storage in version control with support for remote secret providers into a single workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&amp;rsquo;t like giving other apps permission to integrate with my password manager, and that is non-negotiable. This was one of the main problems in integrating my secrets with my old workflow. With Fnox and Mise, I can pull the secrets from my password manager during the setup script, encrypt them with a disposable key, and that is it. No secrets exposed in the config files.&lt;/p&gt;
&lt;p&gt;Maintaining my Bash scripts became much easier thanks to &lt;a href="https://mise.jdx.dev/tasks/"&gt;Mise Tasks&lt;/a&gt;. It reminded me of &lt;a href="https://www.gnu.org/software/make/manual/make.html"&gt;Makefiles&lt;/a&gt;, but easier to use. Additionally, with support for managing versions of Node.js, Rust, Ruby, and many other packages (since it also functions as a package manager), along with integration for &lt;a href="https://mise.jdx.dev/configuration.html#idiomatic-version-files"&gt;idiomatic version files&lt;/a&gt; like .ruby-version, Gemfile, .nvmrc, environment variable management, and CI/CD integration, it fulfilled all my needs.&lt;/p&gt;
&lt;p&gt;As a result, my dotfiles are now easier to maintain, more secure, and quicker to install. I might consider making my dotfiles public at some point. Yeah&amp;hellip; maybe. 🤔&lt;/p&gt;
&lt;p&gt;And, Yes! I rotated all my secrets while refactoring my dotfiles and recreated my repo from scratch. 😎&lt;/p&gt;</description></item><item><title>My first Micro.blog contribution: Instagram plugin</title><link>https://ricardodantas.me/posts/my-first-microblog-contribution-instagram/</link><pubDate>Mon, 14 Jul 2025 21:51:49 +0100</pubDate><guid>https://ricardodantas.me/posts/my-first-microblog-contribution-instagram/</guid><description>&lt;p&gt;This is my first contribution to the Micro.blog community. I’ve created a simple plugin that lets you embed Instagram posts and reels right into your Micro.blog posts.&lt;/p&gt;
&lt;p&gt;To install it, just search for Instagram in the plugins directory.&lt;/p&gt;
&lt;p&gt;Code available on &lt;a href="https://github.com/ricardodantas/mbplugin-instagram-embedded-posts"&gt;Github&lt;/a&gt;.&lt;/p&gt;</description></item></channel></rss>