<?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: Mudit Palvadi</title>
    <description>The latest articles on DEV Community by Mudit Palvadi (@mudit-palvadi).</description>
    <link>https://dev.to/mudit-palvadi</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%2F4150203%2F5e530f0f-44dc-48ed-9583-76d2180b352d.jpg</url>
      <title>DEV Community: Mudit Palvadi</title>
      <link>https://dev.to/mudit-palvadi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mudit-palvadi"/>
    <language>en</language>
    <item>
      <title>LynkShare: a no-login, cross-platform, fast file transfer platform</title>
      <dc:creator>Mudit Palvadi</dc:creator>
      <pubDate>Sat, 24 Oct 2026 12:22:44 +0000</pubDate>
      <link>https://dev.to/mudit-palvadi/lynkshare-a-no-login-cross-platform-fast-file-transfer-platfom-41po</link>
      <guid>https://dev.to/mudit-palvadi/lynkshare-a-no-login-cross-platform-fast-file-transfer-platfom-41po</guid>
      <description>&lt;p&gt;I built LynkShare because I hate logging into my email on a device I don't own. The usual file-sharing sites aren't much better: they either want an account, or they get awkward the moment you're sending more than one file.&lt;/p&gt;

&lt;p&gt;LynkShare moves files between any two devices. No account, no email, and no one-file-at-a-time limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://lynkshare.app" rel="noopener noreferrer"&gt;lynkshare.app&lt;/a&gt; in a browser, or the Android app, and pick up to 10 files.&lt;/li&gt;
&lt;li&gt;It gives you a QR code and a six-digit code.&lt;/li&gt;
&lt;li&gt;On the other device, scan the QR, open the link, or type the six digits.&lt;/li&gt;
&lt;li&gt;The file goes directly between the two devices when the network allows it. When it doesn't, it goes through encrypted temporary storage instead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The encryption key is created on your device and never sent to the server. It travels in the part of a link that browsers don't transmit, so the server only ever holds ciphertext. The six-digit code is a shortcut for not typing a link on the other device - the key is handed to that device directly, not stored for later lookup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from either end
&lt;/h2&gt;

&lt;p&gt;Most sharing tools assume the sender goes first. LynkShare works in both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sender first.&lt;/strong&gt; Pick the files, then share a QR, a link, or the six-digit code. The receiver opens it whenever they're ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receiver first.&lt;/strong&gt; Open the receive screen on the device that wants the file and it shows a QR. Scan that from the sending device, and the files land on the device that asked for them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Receiver-first is the one I use most: open lynkshare.app on a computer that isn't mine, hit "Pair with Phone", scan the code with my phone, done. No typing a link on a keyboard I don't own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's different
&lt;/h2&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;Where it stops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AirDrop&lt;/td&gt;
&lt;td&gt;Apple devices only, and both have to be nearby.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick Share&lt;/td&gt;
&lt;td&gt;Android, Windows and ChromeOS; needs the app on the computer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WeTransfer&lt;/td&gt;
&lt;td&gt;Works everywhere, but uploads your file to their servers, and larger sends need an account.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapdrop / PairDrop&lt;/td&gt;
&lt;td&gt;Browser on both ends, but both devices must be open at the same time on the same network.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LocalSend&lt;/td&gt;
&lt;td&gt;Excellent, but needs the app on both devices and the same network.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LynkShare&lt;/td&gt;
&lt;td&gt;No account, no app on the receiving end, direct when possible, encrypted temporary storage when not.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The closest comparison is the browser-based P2P tools. The difference is the fallback: if the two devices can't reach each other, the transfer still completes instead of failing.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;It's for quick handoffs, not file hosting. Ten files at a time; 10 MB per transfer on the web, 15 MB over the cloud path and 20 MB direct in the app. A rewarded ad raises those caps in the app.&lt;/li&gt;
&lt;li&gt;Cloud copies are short-lived: 10 minutes by default, extendable in the app.&lt;/li&gt;
&lt;li&gt;The Android app is in closed testing. iPhone users can use the website (can't afford that app store fee gng 🥀).&lt;/li&gt;
&lt;li&gt;The interface and branding are temporary and I'll do a full revamp before the public release.&lt;/li&gt;
&lt;li&gt;Sorry for the ads but they keep the free tier running.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Offline transfer: both devices on the same Wi-Fi, no internet at all, nothing leaving the network. It's the next feature I want to build, and it will be ad-free when it ships.&lt;/p&gt;

&lt;h2&gt;
  
  
  I need Android testers
&lt;/h2&gt;

&lt;p&gt;I'm at the stage where Google Play wants 12 testers who stay opted in for 14 days before it will consider production access. So I'm asking for a small favour.&lt;/p&gt;

&lt;p&gt;Cover as many of these as you can - the point is the platform combinations, not any particular pair of devices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;app to app&lt;/strong&gt; with the six-digit code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;app to browser&lt;/strong&gt; and &lt;strong&gt;browser to app&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;browser to browser&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;at least one transfer in each direction&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;direct&lt;/strong&gt; transfer (both devices open) and a &lt;strong&gt;cloud&lt;/strong&gt; one, so both paths get exercised&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where the app asks where to save downloads, pick a folder. Receive a file, force-stop the app, then receive another - that proves the folder grant survived.&lt;/p&gt;

&lt;p&gt;Then tell me what you tested with, the OS version, and anything that broke. A screenshot of an error would be better than a paragraph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://groups.google.com/g/lynk-testers" rel="noopener noreferrer"&gt;Join the testing group&lt;/a&gt;&lt;/strong&gt; - &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=app.xynix.lynk&amp;amp;pli=1" rel="noopener noreferrer"&gt;Install from Google Play&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you'd rather not test, thoughts on the approach are welcome too, THANKS!! 🙏&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Website: &lt;a href="https://lynkshare.app" rel="noopener noreferrer"&gt;lynkshare.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/mudit-palvadi/lynk-web" rel="noopener noreferrer"&gt;Website source code&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>showdev</category>
      <category>flutter</category>
      <category>android</category>
      <category>privacy</category>
    </item>
    <item>
      <title>STILL: the first esport for lying down</title>
      <dc:creator>Mudit Palvadi</dc:creator>
      <pubDate>Sat, 24 Oct 2026 09:14:40 +0000</pubDate>
      <link>https://dev.to/mudit-palvadi/still-the-first-esport-for-lying-down-2adp</link>
      <guid>https://dev.to/mudit-palvadi/still-the-first-esport-for-lying-down-2adp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built the first esport for lying down.&lt;/p&gt;

&lt;p&gt;There is no controller. No keyboard. No skill tree. You give the browser permission to use your webcam, lie down, and try to stay completely still for 60 seconds while a referee powered by Gemma watches your skeleton and quietly judges you.&lt;/p&gt;

&lt;p&gt;That is the whole game. One rule: do not move.&lt;/p&gt;

&lt;p&gt;It sounds easy until the referee notices that your left wrist has been twitching and decides to do something about it. First it throws a dry little insult. Then it picks the joint you are least able to control and aims a temptation straight at it. A fly. An itch. A notification. If you move that exact joint, you lose double points. It is the most personal attack I have ever received from a language model.&lt;/p&gt;

&lt;p&gt;Your score comes from how still you were, how long your best freeze lasted, and how many times you betrayed yourself. The stillest human goes on the leaderboard. Everyone else gets a rank like Restless Meat Sack and a verdict that sounds like a disappointed butler.&lt;/p&gt;

&lt;p&gt;Who gets to be a little lazier? Everyone. This is the only ranked activity where the best possible strategy is to do nothing, and the only setup is a laptop and a floor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Play it here: &lt;a href="https://still-s88j.onrender.com" rel="noopener noreferrer"&gt;still-s88j.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open it on a laptop with a webcam.&lt;/li&gt;
&lt;li&gt;Allow the camera and lie down with your whole body in frame.&lt;/li&gt;
&lt;li&gt;Hold still for 60 seconds while Gemma tries to ruin your day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The free Render instance takes a short nap when nobody visits, so give it a minute to wake up. The video never leaves your browser. That is good for privacy and also good for everyone who does not need to see my ceiling.&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%2Fvgjkbh7lj2l2joxazg2t.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%2Fvgjkbh7lj2l2joxazg2t.png" alt="STILL landing page" width="800" height="1043"&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%2Fju9av3ve5jp9279lyv5l.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%2Fju9av3ve5jp9279lyv5l.png" alt="STILL result screen" width="800" height="805"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The code is here: &lt;a href="https://github.com/mudit-palvadi/still" rel="noopener noreferrer"&gt;github.com/mudit-palvadi/still&lt;/a&gt;. It is MIT licensed, because a game about doing nothing should be free to do nothing with.&lt;/p&gt;

&lt;p&gt;The stack is React, TypeScript, TensorFlow.js MoveNet, Express, and Gemma 4 through Google AI Studio. Render serves the whole thing. The leaderboard uses MongoDB when a database is available and memory when it is not, which is a polite way of saying it forgets you after a restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The camera part uses MoveNet, a pose model that runs in the browser and finds 17 points on your body. Every joint is measured against your torso length, so leaning toward the camera does not count as movement. Calibration watches you fidget for three seconds and learns your personal baseline, because some of us are born at 90 percent vibration.&lt;/p&gt;

&lt;p&gt;Gemma is the fun part. At a few cue points the browser sends a small packet of stats to the server: time, rating, betrayals, your weakest joints, and your last act of cowardice. Gemma has to reply with strict JSON, one line and sometimes one joint to target.&lt;/p&gt;

&lt;p&gt;The server checks the joint and cleans the line before it reaches you. If Gemma is slow or unavailable, a bank of offline lines keeps the round moving. The game never breaks. It just becomes less creative about your posture.&lt;/p&gt;

&lt;p&gt;My favorite bug of the week: the first live build used Gemma 3, which Google retired from the Gemini API. The referee did not crash. It quietly fell back to the offline lines, which is the AI equivalent of a waiter saying the kitchen is closed while still serving soup. I moved to Gemma 4, added a diagnostic to the health check, and confirmed the next live line actually came from the model. The first thing it did was threaten my wrist.&lt;/p&gt;

&lt;p&gt;I also ran a full round with a fake camera feed and found two scoring bugs. At low frame rates, a perfectly still player scored about 12 instead of 100. And a freeze of 60.3 seconds was rejected by the server because the limit was 60. Both are fixed. The lesson: always test your fake skeleton before you trust a real one.&lt;/p&gt;

&lt;p&gt;Two dev.to posts gave me the confidence that this was possible. Mathias Leonhardt built a &lt;a href="https://dev.to/ki-mathias/i-built-a-bird-flight-simulator-you-control-by-flapping-your-arms-threejs-mediapipe-ld6"&gt;bird flight simulator you control by flapping your arms&lt;/a&gt;. Muhammad Umer Farooq built an &lt;a href="https://dev.to/umersmx/how-i-built-an-in-browser-ai-workout-tracker-using-react-and-mediapipe-573b"&gt;AI workout tracker that runs entirely in the browser&lt;/a&gt;. Both proved that pose tracking in a web page is not a toy. I borrowed the confidence, not the code. The difference here is the loop: the model is not counting reps, it is actively trying to make you fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The pose model runs on your device. The video never leaves the laptop. That is a privacy win, but it is also a creative one: no upload, no queue, no bill that grows every time someone scratches their nose.&lt;/p&gt;

&lt;p&gt;Gemma has open weights. I tested the referee loop locally with Ollama before I trusted a hosted key. If the model changes tomorrow, I can point the server at another provider or run it myself. A closed API would make the referee a rental. Open weights make it a cast member I can recast.&lt;/p&gt;

&lt;p&gt;The whole project is small enough to read in an afternoon. Every interesting part is inspectable, from the scoring math to the prompt. That is the kind of open innovation I care about: not just free code, but a game you can take apart and make weirder.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;The pitch is a joke with a real edge. Most apps compete for your effort and attention. STILL asks for neither. It saves 100 percent of the effort by design and turns doing nothing into a ranked skill.&lt;/p&gt;

&lt;p&gt;The game will teach you that your nose moves more than you think. If you get CERTIFIED STATUE, screenshot it and tell everyone. Nobody will care, but you will know.&lt;/p&gt;

&lt;p&gt;If you try it, bring a webcam and the ability to ignore an itch. One of those is harder than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma 4 is the referee, the game director, and the reason a round has a personality. It chooses what to say, which joint to attack, and how to summarize your failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Render:&lt;/strong&gt; STILL runs on a Render web service created from a render.yaml blueprint, with a health check and a free instance that wakes up when someone wants to lie down.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>LeadRadar: Finding clients shouldn't cost you an afternoon of scrolling Google Maps</title>
      <dc:creator>Mudit Palvadi</dc:creator>
      <pubDate>Wed, 14 Oct 2026 18:02:17 +0000</pubDate>
      <link>https://dev.to/mudit-palvadi/leadradar-finding-clients-shouldnt-cost-you-an-afternoon-of-scrolling-google-maps-3070</link>
      <guid>https://dev.to/mudit-palvadi/leadradar-finding-clients-shouldnt-cost-you-an-afternoon-of-scrolling-google-maps-3070</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every freelancer I know has a secret ritual, and it is not making work.&lt;/strong&gt; You pick a city, open Google Maps, and start scrolling. Does this pizza place have a website? Does that salon answer its reviews? You open twenty tabs, you copy names into a spreadsheet, and eventually you write an email that begins "I came across your business" when the truth is you scrolled past four hundred businesses to find it.&lt;/p&gt;

&lt;p&gt;I have always hated that hour more than any actual client work. So for week 2 of Hacktoberfest I built the thing I wanted: &lt;strong&gt;LeadRadar&lt;/strong&gt;, an AI agent that reads Google Maps for me, tells me which local businesses genuinely need my help, and drafts the outreach email while the coffee is still hot.&lt;/p&gt;

&lt;p&gt;You give it your trade and your city, for example &lt;em&gt;graphic designer in Austin, TX&lt;/em&gt;. It searches, scores every result, and hands back a ranked list with an email ready for the top lead. Change the trade to &lt;em&gt;content writer&lt;/em&gt; or &lt;em&gt;web developer&lt;/em&gt; and the flow stays identical, because the trade only changes what the agent looks for, not how it thinks. That claim sounds simple. It took a friend telling me it was false to make it true, and that story is in the second half of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;LeadRadar has four moving parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A search layer&lt;/strong&gt; that builds one Google Maps query out of your trade and runs it through SerpApi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A scoring layer&lt;/strong&gt; that ranks every business out of 100. Most of each score comes from facts inside the search result itself: does this place have a website, is its rating quietly hurting it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An email layer&lt;/strong&gt; that drafts a short personal note for your top leads and signs it with your name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Temporal workflow&lt;/strong&gt; that runs search, scoring and drafting as separate steps with retries, so one rate limit does not kill a scan.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The stack is deliberately boring: Express for the API, MongoDB Atlas for storage, Groq's free tier for the model (allam-2-7b) with Ollama as the local fallback, and SerpApi for search. It deploys as a single Render service, is MIT licensed, and there are 68 tests keeping me honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://leadradar-7ukg.onrender.com" rel="noopener noreferrer"&gt;https://leadradar-7ukg.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs on Render's free plan, so the first visit after a quiet period takes about a minute while the instance wakes up.&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%2Ffgeamgu1nqlcim6x9dwq.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%2Ffgeamgu1nqlcim6x9dwq.png" alt="The LeadRadar landing page" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scan form asks four things: what you do, where you work, your name, and how many leads you want.&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%2Ftv9ym0vjw0dtbtjd1jw8.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%2Ftv9ym0vjw0dtbtjd1jw8.png" alt="The scan form" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the workflow runs, each step lights up as it finishes.&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%2Fe12k6lbj279mwwljbcqf.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%2Fe12k6lbj279mwwljbcqf.png" alt="A scan in progress" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the list comes back ranked, with the signals next to each score so you can see why a business placed where it did:&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%2Fn7a6jy7ophdvw5rlqwdj.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%2Fn7a6jy7ophdvw5rlqwdj.png" alt="Ranked leads with scores and signals" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the top lead already has an email written:&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%2F83jmeav36ck8u70qv3mr.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%2F83jmeav36ck8u70qv3mr.png" alt="A drafted outreach email" width="799" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works on a phone too, which matters if you are checking leads between calls.&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%2Fqm4t27fu26nngb4bwoyn.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%2Fqm4t27fu26nngb4bwoyn.png" alt="The landing page on a phone" width="390" height="844"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The whole thing is at &lt;strong&gt;&lt;a href="https://github.com/mudit-palvadi/lead-radar" rel="noopener noreferrer"&gt;github.com/mudit-palvadi/lead-radar&lt;/a&gt;&lt;/strong&gt; under the MIT license.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The query is written for your trade, not typed by you (SerpApi)
&lt;/h3&gt;

&lt;p&gt;You never compose a search yourself. You type "graphic designer", and the agent asks a model which local businesses would hire one, then joins the answer into a single Maps query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;coffee shop OR bakery OR restaurant OR bar OR food truck OR real estate agent OR gym OR salon ... in Austin, TX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;strong&gt;SerpApi&lt;/strong&gt; Google Maps call later, I have the leads: name, address, phone, website, rating, review count, category. Structured data I can reason about, instead of a page I have to scrape and hope the markup did not change.&lt;/p&gt;

&lt;p&gt;Two things here were learned the hard way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt only knew one trade.&lt;/strong&gt; When my graphic designer friend ran a scan, the agent proposed &lt;em&gt;toy store, pet store, car wash, bookstore&lt;/em&gt; (and &lt;em&gt;bookstore&lt;/em&gt; twice). Those are businesses, technically. None of them were sitting there waiting for a designer. The prompt had exactly one worked example in it, for web design, and the model never generalized past it. I ran the diagnostic script three times and watched it return the same wrong list, then rewrote the prompt to show examples across trades and to ask a sharper question: which businesses would an owner pay for out of their own pocket? I added deduplication, and taught the peer filter to catch near misses like &lt;em&gt;design studio&lt;/em&gt; when the trade is &lt;em&gt;graphic designer&lt;/em&gt;. The same script now returns coffee shops, bakeries, food trucks and an art gallery. I reran a real Austin search with it and got 15 businesses I could actually imagine emailing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SerpApi occasionally returns a row that is not a business.&lt;/strong&gt; Most often it is the city name handed back with no real title. A lead called "Austin" invites the model to invent details about it, so &lt;strong&gt;isUsableResult()&lt;/strong&gt; drops rows like that before they reach the list. My last scan logged "dropped 1 unusable result", which is exactly one fewer embarrassing email.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Facts come from the search, opinions come from the model
&lt;/h3&gt;

&lt;p&gt;The first version of the scorer trusted the model completely. It answered &lt;strong&gt;70 for every single lead&lt;/strong&gt;, whether the business had a website or not. A score that never changes is not a score, it is decoration.&lt;/p&gt;

&lt;p&gt;So the division of labor is now strict. &lt;strong&gt;detectSignals()&lt;/strong&gt; reads the raw SerpApi record and derives facts I can verify: no website is a strong buying signal for any trade, and a low rating with a pile of reviews is another. The model only returns a number and a sentence of reasoning, and the final score is a blend:&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="c1"&gt;// src/workflow/activities.ts&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&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="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no_website&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;55&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="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bad_reviews_no_response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;15&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;blended&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;75 percent of the score is arithmetic anyone can read. The model gets a quarter of the vote, enough to break ties, not enough to pretend it saw something it did not.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The emails survive contact with a 7B model
&lt;/h3&gt;

&lt;p&gt;The prompt asks the model to sign off and to stop commenting on things it never looked at. The model does it anyway, cheerfully. So the checks live in code: &lt;strong&gt;ensureSignOff()&lt;/strong&gt; looks at the tail of the draft and appends your name if the model forgets (it often does), &lt;strong&gt;cleanEmailBody()&lt;/strong&gt; strips sentences where it confesses it never checked the website and removes the "I hope this email finds you well" it was told not to write, and &lt;strong&gt;pitchGuidance()&lt;/strong&gt; keeps a graphic designer from offering to build a website, which is the kind of thing that makes an outreach email read like it was generated.&lt;/p&gt;

&lt;p&gt;The rule I settled on: never fight the model over instructions in the prompt when I can check the output in code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. One workflow, one free service
&lt;/h3&gt;

&lt;p&gt;The scan, classification and drafting steps run as Temporal activities. Batches of three classification calls keep me inside Groq's free tier token limit, and retries absorb the rate limits that come with it. Results land in MongoDB Atlas, so a completed scan is still there after a restart.&lt;/p&gt;

&lt;p&gt;Render's free plan does not allow background workers, so scripts/start-all.js boots the API and the worker together in one web service. The prompt fix from section 1 went live by itself when I pushed, because everything is defined in one render.yaml blueprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does not do yet
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Scores only matter next to each other inside one scan. An 81 in Austin is not a promise about your city.&lt;/li&gt;
&lt;li&gt;The emails are drafts. Read them before you send them. Mine survived that test with my friend's leads, but your name is the one on them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Because the tools that find clients are usually the ones sold back to the people who need them. For years my options for lead discovery were either a monthly database I could not afford or a scraper someone dressed up as a service. Meanwhile the libraries I actually build with are free and readable.&lt;/p&gt;

&lt;p&gt;I am not the first to treat Google Maps as a lead source: &lt;a href="https://dev.to/j3rry320/how-i-built-a-google-maps-scraper-to-generate-leads-for-my-new-agency-and-why-i-open-sourced-it-3lad"&gt;How I Built a Google Maps Scraper to Generate Leads for My New Agency (And Why I Open-Sourced It)&lt;/a&gt; by &lt;a href="https://dev.to/j3rry320"&gt;@j3rry320&lt;/a&gt; and &lt;a href="https://dev.to/wanjiang/build-a-local-lead-gen-machine-scraping-google-maps-with-n8n-reliably-2jhe"&gt;Build a Local Lead Gen Machine: Scraping Google Maps with n8n (Reliably)&lt;/a&gt; by &lt;a href="https://dev.to/wanjiang"&gt;@wanjiang&lt;/a&gt; go after the same data, and most advice for freelancers is still a list of things to do by hand, like &lt;a href="https://dev.to/ahmed_quadri_4dd8a159217f/how-to-get-freelance-design-clients-without-upwork-15-strategies-that-actually-make-sense-574n"&gt;How to Get Freelance Design Clients Without Upwork: 15 Strategies That Actually Make Sense&lt;/a&gt; by &lt;a href="https://dev.to/ahmed_quadri_4dd8a159217f"&gt;@ahmed_quadri_4dd8a159217f&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What open changes is the reasoning. Anyone can read why a bakery outranked a boutique, argue with the weights, and send a pull request. The black box lead list you can only buy and the readable one are not the same kind of product. I know which one I would rather hand a freelancer who is just starting out, because that was me not long ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;Before publishing this, I gave LeadRadar to two friends who freelance for real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The web developer&lt;/strong&gt; ran a scan for his city and was struck by how fast it was. He picked one of the leads, sent the outreach email the agent had drafted, and came back to tell me it had not converted. One email rarely does. What he actually said was that he liked the app and would surely use it again, and now he has a shortlist he did not have before, made in about a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The graphic designer&lt;/strong&gt; got the opposite result. The app could not find good leads for his trade, and he told me it looked like the app was more optimized for other skills. He was right, and he did not say it politely, which was the more useful part. That complaint is what sent me to the diagnostic script and the prompt rewrite in section 1. After the fix, his kind of scan returns coffee shops, bakeries and event venues instead of toy stores.&lt;/p&gt;

&lt;p&gt;Would they pay? Neither has paid me anything, and I did not ask. The developer promising to come back and use it again is the only vote I have so far, and it beats any demo I could record myself.&lt;/p&gt;

&lt;p&gt;Two lessons I am keeping:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Speed impressed the developer more than any feature did.&lt;/strong&gt; He was comparing LeadRadar against his own afternoon of scrolling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A "works for every freelance skill" claim is only honest once it works for every skill a real person has tested.&lt;/strong&gt; Two trades are tested now. The claim is still smaller than my ambition, and I would rather say that out loud than hide it in a footnote.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;One project, every category it genuinely uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of SerpApi&lt;/strong&gt;: the whole discovery layer, not a side feature. Every lead, signal and score starts from one Google Maps query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Render&lt;/strong&gt;: one free service runs both the API and the worker, defined in &lt;strong&gt;render.yaml&lt;/strong&gt;, rebuilt on every push.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Temporal&lt;/strong&gt;: scan, classify and draft run as retried activities, with their progress shown on screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of MongoDB Atlas&lt;/strong&gt;: scans, leads and drafted emails are stored in Atlas and survive restarts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;Try a scan for your own trade at &lt;a href="https://leadradar-7ukg.onrender.com" rel="noopener noreferrer"&gt;leadradar-7ukg.onrender.com&lt;/a&gt;. If the leads come back as wrong for your trade as my friend's did for his, open an issue and tell me. That is exactly how this gets better.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Touched Grass And All I Got Was This Absurd Receipt</title>
      <dc:creator>Mudit Palvadi</dc:creator>
      <pubDate>Sat, 10 Oct 2026 09:39:58 +0000</pubDate>
      <link>https://dev.to/mudit-palvadi/i-touched-grass-and-all-i-got-was-this-absurd-receipt-1i9g</link>
      <guid>https://dev.to/mudit-palvadi/i-touched-grass-and-all-i-got-was-this-absurd-receipt-1i9g</guid>
      <description>&lt;p&gt;I Touched Grass And All I Got Was This Absurd Receipt&lt;/p&gt;

&lt;p&gt;Built for the Hacktoberfest Open-Source AI Challenge, Week 1: &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Touch Grass&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One button. Tap it on the way out, tap it on the way back. While the phone is in your pocket it counts your steps and notices when you stop moving. When you come back, a language model running &lt;strong&gt;entirely on the device&lt;/strong&gt; prints a thermal receipt of your time outdoors.&lt;/p&gt;

&lt;p&gt;The screen is the shortest part of the experience — you look at the phone for ten seconds going out and ten seconds coming back.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is actually here
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                GRASS TOUCHED
              RECEIPT NO. 1
------------------------------------------------
WENT OUTSIDE                      12:32 AM
COME BACK                         12:33 AM
TIME BAND                      AFTER DARK
WALK TYPE                          STEP OUT
------------------------------------------------
0x   STEPS, ON YOUR GOOD LEG
1x   MINUTES AWAY FROM THE SCREEN
1x   THE FRONT DOOR
1x   COLD AIR, BRIEFLY
1x   A CHANGE OF MIND
------------------------------------------------
TOTAL                        $0.00

WHAT THE GRID MISSED
LESS THAN A MINUTE OFF THE GRID.

YOUR REVIEW
2 stars. Did not get far.

SIGNOFF
Come back tomorrow. Grass misses you.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That last receipt is from a real 20-second walk. It is not a screenshot from a design tool. It is what the app actually printed.&lt;/p&gt;


&lt;h2&gt;
  
  
  The most interesting thing that happened: Qwen3 would not stop thinking
&lt;/h2&gt;

&lt;p&gt;The first model I tried was &lt;a href="https://huggingface.co/litert-community/Qwen3-0.6B" rel="noopener noreferrer"&gt;Qwen3 0.6B&lt;/a&gt;. The first receipt on a real device came back as a template receipt — the app's honest fallback — and the log named the reason:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReceiptService: rejected (tooFewLines) on both attempts.
raw: &amp;lt;|channel&amp;gt;thought | Okay&amp;lt;channel|&amp;gt;&amp;lt;|channel&amp;gt;thought | ,&amp;lt;channel|&amp;gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every token of its 512-token output budget went into &lt;code&gt;thought&lt;/code&gt; blocks. The receipt itself never appeared.&lt;/p&gt;

&lt;p&gt;I tried the obvious fix first. &lt;code&gt;flutter_gemma&lt;/code&gt;'s &lt;code&gt;InferenceChat&lt;/code&gt; appends &lt;code&gt;/no_think&lt;/code&gt; to the prompt for Qwen3 when &lt;code&gt;isThinking: false&lt;/code&gt; — so I switched from the raw &lt;code&gt;createSession()&lt;/code&gt; to &lt;code&gt;createChat()&lt;/code&gt;, which is the only layer that knows about thinking models. That was necessary and &lt;strong&gt;not sufficient&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Grepping &lt;code&gt;flutter_gemma_litertlm&lt;/code&gt; for &lt;code&gt;enableThinking&lt;/code&gt;, &lt;code&gt;no_think&lt;/code&gt; or &lt;code&gt;extraContext&lt;/code&gt; returns &lt;strong&gt;nothing&lt;/strong&gt;. The &lt;code&gt;.litertlm&lt;/code&gt; engine never wires thinking off at all. On a real phone Qwen3 still thinks until it runs out of budget.&lt;/p&gt;

&lt;p&gt;So the default is now &lt;a href="https://huggingface.co/litert-community/SmolLM2-360M-Instruct" rel="noopener noreferrer"&gt;SmolLM2 360M&lt;/a&gt; — ungated, not a thinking model, and fast enough to answer between two taps. Qwen3 stays selectable with a note saying what it does, because a receipt that names its model should be honest about which ones do not work.&lt;/p&gt;

&lt;p&gt;This is also why the fallback exists and why the UI says &lt;em&gt;"Template receipt printed: … Your steps and minutes are still the real ones"&lt;/em&gt; instead of hiding it. A silent failure here would have looked like a working app with bad jokes.&lt;/p&gt;


&lt;h2&gt;
  
  
  What the AI model is actually for
&lt;/h2&gt;

&lt;p&gt;The honest answer, because it is the question a submission should be able to survive: &lt;strong&gt;the sensors measure, and the model judges.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The phone measures steps, minutes, time band and the longest standstill. It cannot decide what kind of walk those add up to, how many times something plausibly happened, or what the offline minutes meant for the natural world. Those are the model's job.&lt;/p&gt;

&lt;p&gt;Four things come from the model and not from the phone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A verdict on the walk&lt;/strong&gt; — &lt;code&gt;WALKTYPE&lt;/code&gt;, one of a closed set: &lt;code&gt;STEP OUT&lt;/code&gt;, &lt;code&gt;ERRAND&lt;/code&gt;, &lt;code&gt;A SIT&lt;/code&gt;, &lt;code&gt;LOOP&lt;/code&gt;, &lt;code&gt;A PROPER WALK&lt;/code&gt;, &lt;code&gt;WANDER&lt;/code&gt;, &lt;code&gt;ESCAPE&lt;/code&gt;, &lt;code&gt;COMMUTE&lt;/code&gt;. It is the line a reader can disagree with, which is the point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Item quantities.&lt;/strong&gt; The model writes &lt;code&gt;44x BIRD THAT SAW YOU&lt;/code&gt;, so a three-hour walk and a bathroom break do not produce receipts that look interchangeable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The nature line&lt;/strong&gt;, which must quote the measured minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The comedy&lt;/strong&gt; — item names, review, signoff.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Why the quantities are bounded, not trusted
&lt;/h3&gt;

&lt;p&gt;The model chooses them, so its output cannot be fully verified: nobody knows how many birds there were. What &lt;em&gt;can&lt;/em&gt; be checked is whether a number is &lt;strong&gt;possible&lt;/strong&gt;, and that is the one rule enforced in &lt;code&gt;parseOutput&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Nothing happened more times than there were minutes to happen in.&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;maxQty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A ten-minute walk cannot claim twenty birds. A claim of 999 steps on a 43-minute walk is thrown out entirely.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ReceiptLine.measured&lt;/code&gt; records which lines are the phone's. The measured step and duration lines are built in Dart and prepended, so a model that writes its own &lt;code&gt;12x STEPS&lt;/code&gt; line prints next to the pedometer's real count without ever replacing it.&lt;/p&gt;

&lt;p&gt;The nature line has its own check: it must contain the measured minutes as a whole number. A line that says "43 MINUTES" about a six-minute walk is decoration pretending to be fact. And a walk under a minute has no number to quote, so both the prompt and the check fall back to the phrase "LESS THAN A MINUTE" instead.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why open
&lt;/h2&gt;

&lt;p&gt;Every number above is true in the boring, checkable sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No server.&lt;/strong&gt; There is no backend, no database, no account, no analytics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No signal needed.&lt;/strong&gt; The receipt is written on the device. It works on a trail with no bars.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing leaves the phone.&lt;/strong&gt; Location is never requested. Steps and timestamps live in a value object and are discarded when the process ends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing to pay.&lt;/strong&gt; No API keys, no token metering, no inference bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable.&lt;/strong&gt; The prompt is model-agnostic and the model is a constant in &lt;code&gt;gemma_runtime.dart&lt;/code&gt;. Three are wired up and selectable in the app, so "open" is a switch you can flip rather than a claim in a README.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the point worth making in the write-up. A closed-API version of this app would be a &lt;code&gt;fetch&lt;/code&gt; and an API key. The open version is a 356 MB file, a prompt you own, and three models you can replace without asking anyone.&lt;/p&gt;


&lt;h2&gt;
  
  
  The printing
&lt;/h2&gt;

&lt;p&gt;The receipt does not appear already printed. It feeds out of the slot in &lt;strong&gt;14 discrete steps&lt;/strong&gt; over 1.7 seconds, with a dark band tracking its leading edge while it is still inside the machine.&lt;/p&gt;

&lt;p&gt;The steps are the part that matters. A single eased glide reads as a web page loading; the jumps read as a roller tick-tick-ticking. Each step reveals another ~1/14th of the paper, so the header prints first and the torn edge appears last.&lt;/p&gt;

&lt;p&gt;The torn edge itself is a &lt;code&gt;CustomClipper&lt;/code&gt; with 40 teeth at 4px depth, and the dashed rules are a &lt;code&gt;CustomPainter&lt;/code&gt; that clamps its last dash instead of letting it overshoot. Both are unit tested without rendering anything, because a clipper that closes at the wrong place still renders as a grey shape and looks like paper.&lt;/p&gt;


&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The walk ends if the process dies.&lt;/strong&gt; Sensor streams are held in the app; there is no background service. Closing the app mid-walk loses the walk. A &lt;code&gt;workmanager&lt;/code&gt; periodic sample would fix it and is the obvious next step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standing still is measured from gaps between step events&lt;/strong&gt;, not a motion classifier. Ten seconds without a step counts as a stop, so a red light does not register but a bench does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I would not have hardcoded an example number in the prompt.&lt;/strong&gt; A 20-second walk printed a template receipt because the model copied "43 MINUTES" out of the example and then failed the check it was meant to demonstrate. The example now carries the walk's own numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The receipt could be taller.&lt;/strong&gt; A six-line receipt with the machine above it fills a phone screen, so the paper is sized to its content rather than allowed to scroll.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mudit-palvadi" rel="noopener noreferrer"&gt;
        mudit-palvadi
      &lt;/a&gt; / &lt;a href="https://github.com/mudit-palvadi/grass-receipts" rel="noopener noreferrer"&gt;
        grass-receipts
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Grass Receipts&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;lines; the idea is older than both projects
Flutter, and the inventor is credited rather than ignored. The maths is three&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Grass Receipts&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;I touched grass and all I got was this absurd receipt.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One button. Tap it on the way out, tap it on the way back. While the phone is
in your pocket it counts your steps and notices when you stop moving. When you
come back, a language model running &lt;strong&gt;entirely on the device&lt;/strong&gt; prints a
thermal receipt of your time outdoors:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;                GRASS TOUCHED
              RECEIPT NO. 42
------------------------------------------------
WENT OUTSIDE                        4:04 PM
CAME BACK                           4:47 PM
TIME BAND                    LATE AFTERNOON
------------------------------------------------
1234x STEPS, ON YOUR GOOD LEG
43x   MINUTES AWAY FROM THE SCREEN
1x    BENCH OCCUPIED, BRIEFLY
3x    BIRD THAT SAW YOU
1x    BREEZE, MILD, SUSPICIOUS
------------------------------------------------
TOTAL                        $0.00

YOUR REVIEW
3 stars. Great breeze, no wifi.

SIGNOFF
Come back tomorrow. Grass misses you.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mudit-palvadi/grass-receipts" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub get
flutter run            &lt;span class="c"&gt;# downloads SmolLM2 on first launch, automatically&lt;/span&gt;
flutter &lt;span class="nb"&gt;test&lt;/span&gt;           &lt;span class="c"&gt;# 45 tests, no device or model needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First launch downloads 356 MB because the receipt writer has to live on the phone. Choosing Gemma adds a token field on the setup screen; it is held in memory for the length of one download and never written to disk.&lt;/p&gt;

&lt;p&gt;The repo is at &lt;a href="https://github.com/your-handle/grass-receipts" rel="noopener noreferrer"&gt;github.com/your-handle/grass-receipts&lt;/a&gt;. MIT licensed. The Gemma weights are downloaded at runtime under Google's Gemma Terms of Use.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge: Week 1&lt;/a&gt;. Tag: &lt;code&gt;#hf26challenge&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>devchallenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>I built an app that reads my dyslexic friend's email for him</title>
      <dc:creator>Mudit Palvadi</dc:creator>
      <pubDate>Mon, 05 Oct 2026 06:57:35 +0000</pubDate>
      <link>https://dev.to/mudit-palvadi/i-built-an-app-that-reads-my-dyslexic-friends-email-for-him-4cb</link>
      <guid>https://dev.to/mudit-palvadi/i-built-an-app-that-reads-my-dyslexic-friends-email-for-him-4cb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Heads Up — an Android home-screen widget that shows at most three emails that actually matter, as three short plain lines, each with a play button so it can be listened to instead of read.&lt;/p&gt;

&lt;p&gt;I built it for a friend of mine.&lt;/p&gt;

&lt;p&gt;He is dyslexic. He is smart and entirely capable of running his own inbox,  he just doesn't, because the inbox is a wall of text and the wall costs him more than the contents are worth. So important things sit there. A college form with a deadline. A question from a friend he wanted to answer. Things that would have taken four minutes, if he'd seen them on the day they arrived.&lt;/p&gt;

&lt;h2&gt;
  
  
  The insight
&lt;/h2&gt;

&lt;p&gt;The usual advice for "I can't keep up with email" is filters, rules, priority inboxes, muting senders. All of it still leaves him doing the reading.&lt;/p&gt;

&lt;p&gt;His problem isn't that the inbox is too noisy. It's that the inbox is somewhere he has to go. Nobody walks past their own front door. You have to make a decision, and deciding is the expensive part.&lt;/p&gt;

&lt;p&gt;So: don't make him check it. Bring it to him. Three things on the screen he already looks at forty times a day, written in the fewest words that still mean something, and a button instead of a paragraph.&lt;/p&gt;

&lt;p&gt;Three items maximum, ever. And when there's nothing, it says Nothing needs you today ✓ rather than showing an empty box — an empty widget gets deleted, and a deleted widget gets missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mudit-palvadi" rel="noopener noreferrer"&gt;
        mudit-palvadi
      &lt;/a&gt; / &lt;a href="https://github.com/mudit-palvadi/heads-up" rel="noopener noreferrer"&gt;
        heads-up
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Heads Up&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;MIT licensed — see &lt;a href="https://github.com/mudit-palvadi/heads-up/LICENSE" rel="noopener noreferrer"&gt;&lt;code&gt;LICENSE&lt;/code&gt;&lt;/a&gt;. That covers this source code only; the
Gemma weights are downloaded at runtime under Google's Gemma Terms of Use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The three things that need you today, on your home screen.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built for a friend who is dyslexic, rarely opens his email, and misses the
things that matter. Heads Up puts at most three flagged emails on his Android
home screen as short plain lines, each with a ▶ button so he can listen instead
of read.&lt;/p&gt;
&lt;p&gt;The widget is the product. The app is only setup and a control panel.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Status — what is actually verified&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This is the honest picture, because "it works" is not a useful claim.&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rules engine + cleaner&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Verified&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20 hand-written fixtures; every pass/fail row in &lt;code&gt;rules.md&lt;/code&gt; §8 asserted. Pure logic, no device needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-device inference&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Verified on hardware&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemma 3 1B-IT via LiteRT-LM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mudit-palvadi/heads-up" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. 32 commits, 277 tests, &lt;code&gt;flutter analyze&lt;/code&gt; clean under&lt;br&gt;
&lt;code&gt;strict-casts&lt;/code&gt; / &lt;code&gt;strict-inference&lt;/code&gt; / &lt;code&gt;strict-raw-types&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A["Gmail (IMAP)&amp;lt;br/&amp;gt;EXAMINE + BODY.PEEK"] --&amp;gt; B["Cleaner&amp;lt;br/&amp;gt;HTML to text, strip quotes and signatures"]
    B --&amp;gt; C["RulesEngine&amp;lt;br/&amp;gt;VIP, keywords, deadlines, bulk"]
    C --&amp;gt;|not important| X["dropped"]
    C --&amp;gt;|important| D["Gemma 3 1B-IT&amp;lt;br/&amp;gt;on-device via LiteRT-LM"]
    D --&amp;gt; E["Validator&amp;lt;br/&amp;gt;shape and deadline match"]
    E --&amp;gt;|rejected| F["Template fallback"]
    E --&amp;gt;|accepted| G["ElevenLabs&amp;lt;br/&amp;gt;summary to mp3"]
    F --&amp;gt; H["WidgetSync"]
    G --&amp;gt; H
    H --&amp;gt; I["Home screen widget&amp;lt;br/&amp;gt;max 3 rows"]&lt;/code&gt;&lt;/pre&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://huggingface.co/litert-community/Gemma3-1B-IT" rel="noopener noreferrer"&gt;Gemma 3 1B-IT&lt;/a&gt;&lt;/strong&gt; —
open-weight, running &lt;strong&gt;fully on-device&lt;/strong&gt; via
&lt;a href="https://pub.dev/packages/flutter_gemma" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_gemma&lt;/code&gt;&lt;/a&gt; / LiteRT-LM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt;&lt;/strong&gt; — the open-source agent harness I built
the whole thing with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://elevenlabs.io" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt;&lt;/strong&gt; — voice synthesis for the summaries.&lt;/li&gt;
&lt;li&gt;Flutter, &lt;a href="https://pub.dev/packages/enough_mail" rel="noopener noreferrer"&gt;&lt;code&gt;enough_mail&lt;/code&gt;&lt;/a&gt; for IMAP,
&lt;a href="https://pub.dev/packages/home_widget" rel="noopener noreferrer"&gt;&lt;code&gt;home_widget&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://pub.dev/packages/just_audio" rel="noopener noreferrer"&gt;&lt;code&gt;just_audio&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Two decisions that mattered more than the model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Importance is never inferred by the AI.&lt;/strong&gt; A 1B model asked "is this&lt;br&gt;
important?" will confidently declare a shopping newsletter urgent. So the rules&lt;br&gt;
engine decides — with auditable regex you can read and argue with — and the&lt;br&gt;
model's only job is rewriting text already judged worth showing. If the rules&lt;br&gt;
engine can explain why an email is on the widget, that explanation survives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model is never allowed to invent a date.&lt;/strong&gt; The rules engine extracts&lt;br&gt;
deadlines by regex and hands the value to the prompt. If the model's &lt;code&gt;BY:&lt;/code&gt;&lt;br&gt;
doesn't match, the entire output is &lt;strong&gt;rejected&lt;/strong&gt; and a template is used instead.&lt;br&gt;
For someone who might act on a wrong date, a dull correct line beats a fluent&lt;br&gt;
wrong one every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemma running on his phone isn't a nice-to-have. It's the whole&lt;br&gt;
architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;His inbox has personal things in it — medical, family, college. A cloud LLM&lt;br&gt;
would mean every one of those emails crosses a network to be read, and I'd have&lt;br&gt;
to tell him so. Running a 557 MB open-weight model locally means the most&lt;br&gt;
sensitive step never leaves the device. It also means &lt;strong&gt;$0 per email&lt;/strong&gt;, so&lt;br&gt;
there's no meter quietly encouraging him to read fewer of his own messages.&lt;/p&gt;

&lt;p&gt;The interesting part is what a &lt;em&gt;small&lt;/em&gt; model forces you to build. A frontier&lt;br&gt;
model could probably judge importance on its own. A 1B model can't — so the&lt;br&gt;
architecture gets organised around what the model &lt;strong&gt;can't&lt;/strong&gt; do: decide what&lt;br&gt;
matters, and invent a date. Those two jobs are pushed into a deterministic,&lt;br&gt;
readable, unit-tested layer, and the model is left doing the one thing it's&lt;br&gt;
good at, rewriting text into plainer text.&lt;/p&gt;

&lt;p&gt;That's a better shape regardless of model size: the parts that must be right are&lt;br&gt;
verifiable, and the part that's good at pattern-matching prose is allowed to be&lt;br&gt;
fuzzy because a validator stands behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I wasn't open, said plainly:&lt;/strong&gt; ElevenLabs is a proprietary cloud API.&lt;br&gt;
It's the voice — the part he actually experiences — and a synthetic voice that&lt;br&gt;
sounds like a robot would undercut the whole point. It's switchable off, and&lt;br&gt;
when it's off his phone's own TTS reads the line. The network call carries a&lt;br&gt;
~30-word summary, never an email, but it is still the one place content leaves&lt;br&gt;
the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 3 1B-IT runs fully on-device via LiteRT-LM and
rewrites every flagged email, with each output validated against a
rules-engine-extracted deadline before it's allowed to reach the screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — summaries are voiced with ElevenLabs and cached as
mp3s on the device so play is instant and works offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built with
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/litert-community/Gemma3-1B-IT" rel="noopener noreferrer"&gt;Gemma 3 1B-IT&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://elevenlabs.io" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt; · &lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; ·&lt;br&gt;
Flutter · &lt;a href="https://pub.dev/packages/enough_mail" rel="noopener noreferrer"&gt;enough_mail&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/home_widget" rel="noopener noreferrer"&gt;home_widget&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/just_audio" rel="noopener noreferrer"&gt;just_audio&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/flutter_tts" rel="noopener noreferrer"&gt;flutter_tts&lt;/a&gt; ·&lt;br&gt;
&lt;a href="https://pub.dev/packages/flutter_secure_storage" rel="noopener noreferrer"&gt;flutter_secure_storage&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
