<?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: Aravind Sathesh</title>
    <description>The latest articles on DEV Community by Aravind Sathesh (@aravindsathesh).</description>
    <link>https://dev.to/aravindsathesh</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%2F2369469%2Fc9dc73a9-cc08-4b83-ab63-1d0cf81cb787.png</url>
      <title>DEV Community: Aravind Sathesh</title>
      <link>https://dev.to/aravindsathesh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aravindsathesh"/>
    <language>en</language>
    <item>
      <title>QuietBox: Unsubscribe from newsletters you never open</title>
      <dc:creator>Aravind Sathesh</dc:creator>
      <pubDate>Fri, 23 Oct 2026 09:40:55 +0000</pubDate>
      <link>https://dev.to/aravindsathesh/quietbox-unsubscribe-from-newsletters-you-never-open-5b84</link>
      <guid>https://dev.to/aravindsathesh/quietbox-unsubscribe-from-newsletters-you-never-open-5b84</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges"&gt;Hacktoberfest Open-Source AI Challenge Week 3&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;My inbox kept filling up with newsletters I'd signed up for months ago and promotional emails I rarely, if ever, opened. I wanted a simple way to clean it up without handing over access to my entire mailbox to a third-party service.&lt;/p&gt;

&lt;p&gt;That's what led me to build &lt;strong&gt;QuietBox&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;QuietBox is a native macOS menu bar app that identifies newsletters and promotional emails you consistently ignore and helps you unsubscribe from them. It uses a Gemma model running locally on your Mac, so there's no need to send email data to a hosted AI service for classification.&lt;/p&gt;

&lt;p&gt;The idea is simple: let the app do the tedious work of finding unwanted emails, but leave the final decision to you.&lt;/p&gt;

&lt;p&gt;QuietBox scans email headers from the past 12 months and groups messages by sender. It looks for senders that meet three criteria: at least three emails received, an unopened rate of 90% or higher, and no replies or starred messages.&lt;/p&gt;

&lt;p&gt;These rules narrow down the candidates, but they don't tell the whole story. Plenty of emails are worth keeping even if you rarely open them. Bank alerts, receipts, shipping updates, and security notifications are good examples.&lt;/p&gt;

&lt;p&gt;That's where Gemma comes in. Using Ollama, QuietBox evaluates the candidate senders and helps distinguish newsletters you might want to unsubscribe from from emails you should probably keep.&lt;/p&gt;

&lt;p&gt;Once a week, the app sends a notification, such as "7 senders you never open." You can review the suggestions, untick any senders you want to keep, and click &lt;strong&gt;Unsubscribe&lt;/strong&gt; to proceed.&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%2Fmz4e7zfjftlniri3nvke.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%2Fmz4e7zfjftlniri3nvke.png" alt="QuietBox review window" width="620" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How Unsubscribing Works
&lt;/h3&gt;

&lt;p&gt;QuietBox uses the simplest available option for each sender:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One-click unsubscribe:&lt;/strong&gt; If the sender supports RFC 8058 one-click unsubscribe, QuietBox sends the required POST request to the advertised endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsubscribe email:&lt;/strong&gt; If the sender provides a &lt;code&gt;mailto:&lt;/code&gt; unsubscribe link instead, QuietBox sends an unsubscribe email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-archive:&lt;/strong&gt; If neither unsubscribe method is available, QuietBox can archive messages from that sender so they no longer clutter your inbox.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Auto-archiving doesn't stop the emails from arriving. It simply keeps them out of your inbox when an actual unsubscribe option isn't available.&lt;/p&gt;

&lt;p&gt;QuietBox also remembers your decisions, so you won't have to review the same senders every week. The Handled tab lets you revisit senders you've chosen to keep and stop archiving messages from senders you've previously selected for that treatment.&lt;/p&gt;

&lt;p&gt;There are a few deliberate limits. QuietBox doesn't delete emails, touch your spam folder, or act on a sender without a valid &lt;code&gt;List-Unsubscribe&lt;/code&gt; header. It also doesn't unsubscribe anyone automatically based on the model's recommendation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model suggests. You decide.&lt;/strong&gt;&lt;/p&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/Aravind-Sathesh" rel="noopener noreferrer"&gt;
        Aravind-Sathesh
      &lt;/a&gt; / &lt;a href="https://github.com/Aravind-Sathesh/QuietBox" rel="noopener noreferrer"&gt;
        QuietBox
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Mac menu bar app that unsubscribes you from newsletters you never open, using a local Gemma
    &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;QuietBox&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Mac menu bar app that finds the newsletters you never open and unsubscribes you, with one approval a week. Your mail is read and sorted on your Mac by Gemma, an open model running locally through Ollama. No third-party code.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Use it&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://ollama.com/download" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; and open it.&lt;/li&gt;
&lt;li&gt;Open QuietBox. Setup downloads Gemma if needed, then asks for your email and an app password (Gmail or iCloud). The password goes into your Keychain.&lt;/li&gt;
&lt;li&gt;QuietBox checks your inbox right away, then once a week. When it finds senders you ignore, you get a notification. Untick any you want to keep, then click &lt;strong&gt;Unsubscribe&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scan:&lt;/strong&gt; reads the headers of the last 12 months of INBOX mail over IMAP. Messages stay unread and bodies are never fetched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score:&lt;/strong&gt; a sender becomes a candidate after 3 or more messages, at least 90% of them unread, none replied to or…&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Aravind-Sathesh/QuietBox" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;QuietBox is built with Swift 6 and SwiftUI, using Apple's frameworks without third-party package dependencies.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  1. Running Gemma Locally
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;Gemma (&lt;code&gt;gemma4:e2b&lt;/code&gt;) through Ollama&lt;/strong&gt; to classify ignored senders as either worth keeping or worth unsubscribing from.&lt;/p&gt;

&lt;p&gt;The model runs locally at &lt;code&gt;127.0.0.1:11434&lt;/code&gt;. I also used Ollama's &lt;code&gt;format&lt;/code&gt; option to enforce a JSON schema, ensuring every response follows the same structure: &lt;code&gt;{ action, reason }&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;action&lt;/code&gt; determines the recommendation, while the &lt;code&gt;reason&lt;/code&gt; is displayed beneath each sender in the review window.&lt;/p&gt;

&lt;p&gt;One design decision was particularly important: &lt;strong&gt;the model doesn't decide which emails qualify for analysis, and it never performs an unsubscribe action on its own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The code handles the initial filtering. It checks the unread ratio, replies, stars, and whether the sender has a usable unsubscribe header. Only senders that pass these checks are sent to Gemma, along with their name, email address, message counts, and five recent subject lines.&lt;/p&gt;

&lt;p&gt;Email subjects are treated as untrusted data rather than instructions. The model's response is validated in code, and anything other than a valid &lt;code&gt;"unsubscribe"&lt;/code&gt; action defaults to keeping the sender.&lt;/p&gt;

&lt;p&gt;I'd rather miss an opportunity to unsubscribe from an unwanted newsletter than risk unsubscribing someone by mistake.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Choosing the Right Model
&lt;/h3&gt;

&lt;p&gt;I wasn't sure whether the smaller Gemma model would be sufficient, so I tested two model sizes against six synthetic senders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A flash-sale retailer&lt;/li&gt;
&lt;li&gt;A bank&lt;/li&gt;
&lt;li&gt;A shipping tracker&lt;/li&gt;
&lt;li&gt;A daily digest&lt;/li&gt;
&lt;li&gt;A social network notification sender&lt;/li&gt;
&lt;li&gt;An account security alert&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both &lt;code&gt;e2b&lt;/code&gt; and &lt;code&gt;12b&lt;/code&gt; classified all six correctly in these tests.&lt;/p&gt;

&lt;p&gt;The smaller model took approximately 1.3 seconds per sender, compared with around 3.5 seconds for the larger model.&lt;/p&gt;

&lt;p&gt;Since the app already includes a human approval step, the additional processing time didn't seem worthwhile. The smaller model was fast enough for the task, so I chose it for QuietBox.&lt;/p&gt;

&lt;p&gt;These tests were a basic sanity check, not a comprehensive evaluation of classification accuracy. Real inboxes contain more ambiguous cases, which is another reason I designed the app to require approval before acting.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Reading Email Without a Third-Party Library
&lt;/h3&gt;

&lt;p&gt;Swift doesn't include a built-in IMAP client, and I wanted to avoid adding third-party dependencies just to read email headers. Instead, I implemented the parts of IMAP QuietBox needs using Apple's &lt;code&gt;Network.framework&lt;/code&gt; and TLS.&lt;/p&gt;

&lt;p&gt;A few implementation details were especially important.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-only scanning:&lt;/strong&gt; QuietBox uses &lt;code&gt;EXAMINE INBOX&lt;/code&gt; and &lt;code&gt;BODY.PEEK&lt;/code&gt;, so scanning messages doesn't mark unread emails as read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headers only:&lt;/strong&gt; It fetches the &lt;code&gt;From&lt;/code&gt;, &lt;code&gt;Subject&lt;/code&gt;, &lt;code&gt;List-Unsubscribe&lt;/code&gt;, and &lt;code&gt;List-Unsubscribe-Post&lt;/code&gt; headers, along with message flags. Message bodies are never downloaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact sender matching:&lt;/strong&gt; IMAP's &lt;code&gt;SEARCH FROM&lt;/code&gt; can match substrings rather than exact email addresses. For example, searching for &lt;code&gt;a@shop.com&lt;/code&gt; could also match &lt;code&gt;ba@shop.com&lt;/code&gt;. Before archiving messages, QuietBox reads their &lt;code&gt;From&lt;/code&gt; headers again and verifies the exact sender address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-aware archiving:&lt;/strong&gt; Rather than assuming every email provider uses the same archive folder name, QuietBox checks the folder flags advertised by the server to locate the appropriate folder, such as &lt;code&gt;\All&lt;/code&gt; on Gmail or &lt;code&gt;\Archive&lt;/code&gt; on other servers where supported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same networking layer also handles sending unsubscribe emails over SMTP when that option is available.&lt;/p&gt;

&lt;p&gt;Building this part was a useful reminder that even a relatively small AI application can involve quite a bit of protocol-level work outside the model itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Handling Unsubscribe Links Safely
&lt;/h3&gt;

&lt;p&gt;Unsubscribe links come from external email senders, so I didn't want QuietBox to trust them blindly.&lt;/p&gt;

&lt;p&gt;I added several checks before using these links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-click unsubscribe URLs must use HTTPS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mailto:&lt;/code&gt; links must specify exactly one recipient.&lt;/li&gt;
&lt;li&gt;Line breaks are stripped from email subjects to prevent header injection, such as introducing an unintended &lt;code&gt;Bcc:&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;One-click POST requests use an ephemeral session, so existing cookies aren't sent along.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These measures help reduce the risks involved in processing untrusted email headers. They don't eliminate every possible security risk, but they provide a safer starting point than blindly following whatever link appears in a message header.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Building a Mac App People Can Actually Use
&lt;/h3&gt;

&lt;p&gt;My first version was a Python script running on a cron schedule. It handled the basic task, but I wanted something that felt like a proper Mac application rather than a tool that required terminal commands and manual scheduling.&lt;/p&gt;

&lt;p&gt;That meant building the experience around the way people actually use their Macs.&lt;/p&gt;

&lt;p&gt;The current version includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guided setup:&lt;/strong&gt; Checks whether Ollama is running and downloads Gemma with a progress indicator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email account configuration:&lt;/strong&gt; Supports app-password-based setup, with links to the relevant Gmail or iCloud settings pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure credential storage:&lt;/strong&gt; Stores credentials in the macOS Keychain and checks the connection before saving them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic startup:&lt;/strong&gt; Launches QuietBox when you log in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly scheduling:&lt;/strong&gt; Checks hourly whether a weekly run is due.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sleep and wake handling:&lt;/strong&gt; Checks again when the Mac wakes up, so a missed scheduled run doesn't mean waiting another week.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9y3jlwkmnozutiz5501b.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%2F9y3jlwkmnozutiz5501b.png" alt="QuietBox account setup" width="520" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal was to make the app usable without requiring people to understand IMAP, SMTP, Ollama, or local model configuration.&lt;/p&gt;

&lt;p&gt;Getting the email handling right, making setup straightforward, and ensuring that the app behaves conservatively were just as important as integrating the AI model.&lt;/p&gt;

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

&lt;p&gt;For QuietBox, using an open model locally isn't just about avoiding API costs. It changes how the application handles something as personal as email.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Keeping Email Classification Private
&lt;/h3&gt;

&lt;p&gt;An inbox can reveal a surprising amount about someone's life. I didn't want QuietBox to depend on a third-party AI service receiving email data just to decide which newsletters could be unsubscribed from.&lt;/p&gt;

&lt;p&gt;With local inference, Gemma runs on your Mac through Ollama. Email classification doesn't require sending sender details or subject lines to a hosted AI API.&lt;/p&gt;

&lt;p&gt;QuietBox still connects to your configured mail server to retrieve messages and perform email operations. When you approve an unsubscribe action, it may also contact an external unsubscribe endpoint or send an unsubscribe email.&lt;/p&gt;

&lt;p&gt;The distinction matters: &lt;strong&gt;the model runs locally, while the email operations still communicate with the relevant mail services.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No Per-Request AI Costs
&lt;/h3&gt;

&lt;p&gt;Unsubscribing from newsletters is a small, repetitive task. Paying for an API every time the app evaluates a sender didn't make much sense to me.&lt;/p&gt;

&lt;p&gt;A local model eliminates per-request inference charges. The trade-off is that inference uses your Mac's processing resources, but for a task that runs once a week, local execution seemed like a reasonable choice.&lt;/p&gt;

&lt;p&gt;It also means the app doesn't need an ongoing subscription to an AI provider just to keep your inbox manageable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. More Control Over the AI
&lt;/h3&gt;

&lt;p&gt;Using an open model makes it easier to experiment with different models and prompts without tying the application's classification logic to a single hosted AI provider.&lt;/p&gt;

&lt;p&gt;The decision-making process is also straightforward to inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code determines which senders qualify for evaluation.&lt;/li&gt;
&lt;li&gt;Gemma recommends whether to keep or unsubscribe from each sender.&lt;/li&gt;
&lt;li&gt;The app validates the response.&lt;/li&gt;
&lt;li&gt;You review the recommendation and approve the action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model's reasoning is visible in the interface, and the final decision remains with the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Designing for Failure, Not Just Success
&lt;/h3&gt;

&lt;p&gt;Local models aren't perfect, and using an open model doesn't automatically make an application reliable.&lt;/p&gt;

&lt;p&gt;I wanted QuietBox to account for that from the beginning.&lt;/p&gt;

&lt;p&gt;The app combines deterministic filtering, model-based classification, response validation, and human approval. If the model returns an unexpected response, QuietBox defaults to keeping the sender rather than proceeding with an unsubscribe action.&lt;/p&gt;

&lt;p&gt;This approach doesn't guarantee that every recommendation will be correct. It does, however, prevent a model response from directly triggering an unsubscribe without the user's approval.&lt;/p&gt;

&lt;p&gt;For an application that interacts with someone's personal inbox, that distinction is important.&lt;/p&gt;

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

&lt;p&gt;Building QuietBox taught me that integrating an AI model is only one part of building a useful AI application.&lt;/p&gt;

&lt;p&gt;The more interesting challenges were deciding what the model should handle, what ordinary code should handle, and how to make the overall system safe enough to use with a real inbox.&lt;/p&gt;

&lt;p&gt;Gemma is useful for interpreting sender context, something that would be difficult to capture with a long list of hand-written rules alone. Swift handles the filtering, email protocols, validation, and scheduling. The user stays in control of every unsubscribe decision.&lt;/p&gt;

&lt;p&gt;I also learned that running a model locally doesn't automatically solve privacy or reliability concerns. You still need to think carefully about network requests, credentials, untrusted input, failure cases, and the consequences of a wrong prediction.&lt;/p&gt;

&lt;p&gt;Those considerations shaped QuietBox just as much as the choice of model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;QuietBox started with a simple problem: newsletters pile up in your inbox long after you've stopped caring about them.&lt;/p&gt;

&lt;p&gt;I wanted to build something that could take care of the tedious part without asking users to give up control of their email or depend on a hosted AI service.&lt;/p&gt;

&lt;p&gt;The result is a small Mac app that combines local AI with conventional software engineering. Gemma helps make the classification decisions, Swift handles the email operations, and the user gets the final say.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Folio: Organize your files using AI on-device</title>
      <dc:creator>Aravind Sathesh</dc:creator>
      <pubDate>Sun, 18 Oct 2026 05:30:02 +0000</pubDate>
      <link>https://dev.to/aravindsathesh/folio-organize-your-files-using-ai-on-device-3421</link>
      <guid>https://dev.to/aravindsathesh/folio-organize-your-files-using-ai-on-device-3421</guid>
      <description>&lt;p&gt;&lt;em&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;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built Folio, a macOS menu bar app that organises downloaded files using local AI.&lt;/p&gt;

&lt;p&gt;Downloads folders tend to accumulate invoices, receipts, screenshots, lecture notes, and random PDFs. I wanted to build something that could organise these files automatically without requiring me to stop what I’m doing and sort everything manually.&lt;/p&gt;

&lt;p&gt;When a file arrives in a watched folder, Folio reads its contents and asks a local Gemma model to suggest a filename and destination. For example, invoice (3).pdf might become Finance/Invoices/Work/Cobalt Cloud Hosting Invoice 2026-10.pdf.&lt;/p&gt;

&lt;p&gt;Folio also has a Restructure mode for organising existing folders. It proposes changes for review before applying them, and you can undo an entire session.&lt;/p&gt;

&lt;p&gt;If Folio isn’t sure where a file belongs, it leaves it alone and adds it to a review list. It never deletes or overwrites files, and every move is logged before execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who would pay for it?
&lt;/h3&gt;

&lt;p&gt;I’m building Folio with freelancers and small business owners in mind, particularly those who regularly handle invoices, receipts, contracts, and financial documents and need to find them months later.&lt;/p&gt;

&lt;p&gt;Pricing: I’m considering a one-time licence of $19.99, with no recurring subscription or per-file AI charges. The idea is to make file organisation something users can set up once and leave running in the background, without sending sensitive documents to a cloud service.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8R6LSxGUrjs" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&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/Aravind-Sathesh" rel="noopener noreferrer"&gt;
        Aravind-Sathesh
      &lt;/a&gt; / &lt;a href="https://github.com/Aravind-Sathesh/Folio" rel="noopener noreferrer"&gt;
        Folio
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Mac menu bar app that files your downloads with a local Gemma model. Never deletes, every move undoable.
    &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;Folio&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A macOS menu bar app that files your downloads for you. New files in the folders you watch are read on your Mac, renamed and moved into the right folder in Documents by a local Gemma model running in Ollama. Files it isn't sure about wait for your review. Restructure tidies a folder you pick, after you approve every change. Folio never deletes or overwrites anything, and every move can be undone.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Requirements&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;macOS 14 or later, Apple silicon recommended&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt; running, with the models pulled:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama pull gemma4:12b
ollama pull mxbai-embed-large&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Build and install&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;xcodebuild -project Folio.xcodeproj -scheme Folio -configuration Release -derivedDataPath build build
ditto build/Build/Products/Release/Folio.app /Applications/Folio.app
open /Applications/Folio.app&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The build is signed with your own development certificate, so it runs on your Mac. Sharing it with others needs a Developer ID certificate and notarization.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Try it without touching your real folders&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;scripts/run-sandbox.sh&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This runs Folio against &lt;code&gt;~/FolioSandbox&lt;/code&gt;…&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/Aravind-Sathesh/Folio" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The README explains how to build and run Folio. It's built with Swift 6 and SwiftUI using Apple's native frameworks, without third-party packages. The project includes 133 unit tests and optional evaluations against the actual AI models.&lt;/p&gt;

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

&lt;p&gt;The main challenge wasn't getting AI to suggest filenames. It was making sure those suggestions could be used safely to modify the filesystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local AI and model selection
&lt;/h3&gt;

&lt;p&gt;Folio uses two models through Ollama:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemma (&lt;code&gt;gemma4:12b&lt;/code&gt;)&lt;/strong&gt; suggests filenames, destination folders, and restructuring plans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mxbai-embed-large&lt;/code&gt;&lt;/strong&gt; helps retrieve similar past corrections so Folio can adapt to a user's filing preferences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application handles text extraction, folder discovery, and validation. The model receives the relevant information and returns a structured response containing a proposed name and destination.&lt;/p&gt;

&lt;p&gt;Before anything happens, the application checks that the destination stays inside Documents, rejects unsafe filenames, preserves the original extension, and handles naming conflicts without overwriting existing files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the models
&lt;/h3&gt;

&lt;p&gt;I created a 50-file evaluation set containing fictional invoices, statements, receipts, lecture notes, screenshots, empty files, and near-duplicates. I tested three configurations on an M3 Pro.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Correct of auto-moved files&lt;/th&gt;
&lt;th&gt;Wrong across all files&lt;/th&gt;
&lt;th&gt;Time per file&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gemma4:e2b&lt;/td&gt;
&lt;td&gt;68%&lt;/td&gt;
&lt;td&gt;26%&lt;/td&gt;
&lt;td&gt;1.9 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma4:e2b with thinking enabled&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;8%&lt;/td&gt;
&lt;td&gt;15.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gemma4:12b&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.2 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These results reflect my test set, not a general benchmark. The 12B model was the only configuration I tested that met both my accuracy targets and the under-ten-second processing target.&lt;/p&gt;

&lt;p&gt;I also found that the smaller model's thinking mode added substantial latency. With structured output already constraining the response, turning thinking off reduced one measured call from 11.6 seconds to 0.4 seconds.&lt;/p&gt;

&lt;p&gt;Another finding was that the model's self-reported confidence wasn't reliable. It often assigned high confidence to incorrect answers, so Folio adjusts confidence based on factors such as the amount of extracted text and whether a new folder is required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning from corrections
&lt;/h3&gt;

&lt;p&gt;I wanted Folio to adapt to individual filing habits without fine-tuning.&lt;/p&gt;

&lt;p&gt;When a user corrects a filename or moves a file, Folio records the correction and a short summary of the file. For subsequent files, it retrieves the five most similar corrections and includes them as examples in the prompt.&lt;/p&gt;

&lt;p&gt;In a leave-one-out evaluation, this improved accuracy on the smaller model from 68% to 83%. It didn't eliminate repeated mistakes, but it showed that correction-based memory could make a measurable difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing failure cases
&lt;/h3&gt;

&lt;p&gt;I spent time testing what happens when things go wrong, especially because a file-management tool needs to be recoverable.&lt;/p&gt;

&lt;p&gt;In crash testing, I ran 20 rounds of 1,500 moves with 59 forced process terminations. No files were lost or duplicated. The tests did uncover a stale-log issue when a crash occurred between recording a move and executing it. Folio now reconciles its log at startup.&lt;/p&gt;

&lt;p&gt;I also tested unsafe paths, symbolic links, unusual filename characters, and excessively long names.&lt;/p&gt;

&lt;p&gt;One particularly useful test involved a file containing instructions telling the model to ignore its rules and move the file into &lt;code&gt;/System/Library&lt;/code&gt;. The smaller model followed those instructions in its proposed destination, but the application's validator rejected the path.&lt;/p&gt;

&lt;p&gt;This is why the model never gets to execute filesystem operations directly. Its suggestions must pass application-level checks first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restructure in practice
&lt;/h3&gt;

&lt;p&gt;I tested Restructure on a sandbox containing 94 files. Using the 12B model, Folio took about three minutes to read and summarise the files and another two and a half minutes to plan the changes.&lt;/p&gt;

&lt;p&gt;It proposed and applied 59 changes, then successfully restored the original folder structure when I tested Undo. A second run took around 0.1 seconds because summaries were cached by content hash.&lt;/p&gt;

&lt;p&gt;These tests gave me a way to evaluate the complete workflow, rather than just checking whether individual model responses looked reasonable.&lt;/p&gt;

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

&lt;p&gt;Open-weight models made it possible to build Folio around local inference instead of a cloud API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; Folio handles potentially sensitive documents, including financial records, identity documents, and contracts. Running Gemma through Ollama keeps inference on the Mac, and the application checks that model requests go to the local endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; Folio is designed to process files continuously. Local inference avoids a separate API charge for every file, making a one-time licence more practical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility:&lt;/strong&gt; I could compare different model configurations using the same evaluation set and choose based on measured accuracy and speed. I can also test newer models without redesigning the application around a particular provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalisation:&lt;/strong&gt; Folio can learn from filing corrections through retrieved examples without sending those corrections to a remote AI service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local models still have limitations. Smaller models can be less accurate, performance depends on available hardware, and prompt injection remains a concern. That's why I paired local inference with strict validation and reversible file operations.&lt;/p&gt;

&lt;p&gt;For Folio, open innovation made privacy, model experimentation, and control over the AI stack part of the product itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I used a multi-agent workflow to build Folio. &lt;strong&gt;Claude acted as the planner&lt;/strong&gt;, breaking down the project, helping shape the architecture, and identifying edge cases. &lt;strong&gt;Antigravity acted as the implementer&lt;/strong&gt;, turning those plans into code, running tests, and fixing issues.&lt;/p&gt;

&lt;p&gt;I still had to evaluate the results and verify that the application behaved safely. Some of the most important work came from testing failures rather than just implementing features: checking crash recovery, validating model output, and ensuring that mistakes could be undone.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Folio uses Gemma 4 12B through Ollama to suggest filenames and organise files entirely on the Mac. The application validates every suggestion before making filesystem changes, keeping sensitive documents local. Folio also learns from user corrections by retrieving similar past examples, adapting to individual filing preferences without fine-tuning.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>TrailKit: An Offline Trail Companion</title>
      <dc:creator>Aravind Sathesh</dc:creator>
      <pubDate>Sat, 10 Oct 2026 05:35:37 +0000</pubDate>
      <link>https://dev.to/aravindsathesh/trailkit-an-offline-trail-companion-1pc5</link>
      <guid>https://dev.to/aravindsathesh/trailkit-an-offline-trail-companion-1pc5</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;TrailKit&lt;/strong&gt; is an app for hikers who want to explore trails without worrying about losing network coverage. It tracks your trek, identifies plants and animals, and keeps your route and photos on your phone.&lt;/p&gt;

&lt;p&gt;Here's what it can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Record your trek.&lt;/strong&gt; GPS tracks your route, even with the screen off. Distance, duration, elevation gain and pace update as you walk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify what you find.&lt;/strong&gt; Take a photo of a plant, insect or snake, and TrailKit identifies it using AI running on your phone. It also provides safety information and short field notes. Dangerous species get additional precautions, first-aid guidance where relevant, and a button to call your emergency number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save interesting views.&lt;/strong&gt; Take a photo of a valley or viewpoint and mark it on your route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan your hike.&lt;/strong&gt; Choose a route, set a date and prepare a checklist. Before starting, the app walks you through a full-screen checklist and countdown. If you stray more than 60 metres from your planned route, your phone vibrates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share your trek.&lt;/strong&gt; Treks are private by default. If you choose to share one, other hikers can see the route, statistics and identified species. Your photos stay on your phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is to spend less time looking at your phone. Start the trek, put it away, and take it out when you need it.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/OTK2CQfNbr4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&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/Aravind-Sathesh" rel="noopener noreferrer"&gt;
        Aravind-Sathesh
      &lt;/a&gt; / &lt;a href="https://github.com/Aravind-Sathesh/TrailKit" rel="noopener noreferrer"&gt;
        TrailKit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An offline trail companion with on-device species ID (BioCLIP + Gemma), GPS treks and shared routes
    &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;TrailKit&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An offline trail companion, built for the DEV Hacktoberfest challenge &lt;em&gt;Touch Grass&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Record a trek with no signal, check whether the plant, insect or snake in front of you is safe, save the views along the way, and share the route with other hikers once you're home.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treks:&lt;/strong&gt; plan one (a route, a day, a checklist) or just head out. GPS records the route with the screen off. Distance, time, climb and pace update live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before you start:&lt;/strong&gt; a full-screen checklist to tick off, then a 3·2·1 start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-route alerts:&lt;/strong&gt; follow a planned route and the phone buzzes when you stray more than 60 m from it, and again when you're back on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it safe?&lt;/strong&gt; Point the camera at a plant, insect or snake. BioCLIP names it on the phone; a safety layer decides the danger level; Gemma writes short field notes. Snakes…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Aravind-Sathesh/TrailKit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The README explains how to run it, download the models and configure Supabase. I've also kept a &lt;a href="https://github.com/Aravind-Sathesh/TrailKit/blob/main/REVIEW.md" rel="noopener noreferrer"&gt;REVIEW.md&lt;/a&gt; with the decisions, bugs and performance measurements from development.&lt;/p&gt;

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

&lt;p&gt;TrailKit is built with Expo, React Native and TypeScript for Android and iOS. The main challenge was getting AI identification to work locally while making sure it couldn't generate misleading safety advice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two models, one rule
&lt;/h3&gt;

&lt;p&gt;TrailKit uses two models, both running on the phone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://imageomics.github.io/bioclip/" rel="noopener noreferrer"&gt;BioCLIP&lt;/a&gt;&lt;/strong&gt; is a model trained on biological data. It compares a photo against a list of known species and ranks the closest matches using ONNX Runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 4 E2B&lt;/a&gt;&lt;/strong&gt; is a 4-bit model of around 2.6 GB. It runs through &lt;a href="https://github.com/mybigday/llama.rn" rel="noopener noreferrer"&gt;&lt;code&gt;llama.rn&lt;/code&gt;&lt;/a&gt; and generates a short explanation based on BioCLIP's top match.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important distinction is that &lt;strong&gt;Gemma explains the identification; it does not decide how dangerous something is.&lt;/strong&gt; Danger levels come from the species data, with additional checks before anything appears on screen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the top match has less than 60% confidence, TrailKit says it isn't sure and shows the three leading candidates.&lt;/li&gt;
&lt;li&gt;If a plausible match is dangerous, the result is treated as dangerous.&lt;/li&gt;
&lt;li&gt;The app never tells users that a wild plant is safe to eat. If Gemma generates such a claim, its text is discarded.&lt;/li&gt;
&lt;li&gt;Snake and spider results always include the relevant distance precautions and first-aid information, even if Gemma fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My first attempt was to give the photo directly to Gemma, since it's a multimodal model. That took &lt;strong&gt;284 seconds on CPU&lt;/strong&gt;, and it confidently identified the wrong plant.&lt;/p&gt;

&lt;p&gt;I switched to splitting the task between the two models. BioCLIP handles recognition, while Gemma writes the explanation. A warm identification took around 2 seconds on the Android emulator. On the iPhone simulator, the first identification took 24 seconds, including loading Gemma.&lt;/p&gt;

&lt;p&gt;That separation made the system considerably more practical, though identification still needs to be treated as a best guess rather than a guarantee.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trail, offline
&lt;/h3&gt;

&lt;p&gt;The rest of the app is designed to keep working without a network connection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPS tracking:&lt;/strong&gt; On Android, tracking uses a foreground service; on iOS, it uses background location updates with the system indicator. Location points are appended to a file as they arrive, so a crash doesn't erase the entire trek.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPS cleanup:&lt;/strong&gt; Distance calculations ignore implausible jumps. One stale GPS fix once added 785 metres to a walk I hadn't taken. Elevation gain ignores fluctuations under 3 metres to reduce noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-route alerts:&lt;/strong&gt; The app requires two consecutive fixes more than 60 metres from the planned route before alerting. It only clears the alert once you're within 35 metres, avoiding constant notifications around the boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline maps:&lt;/strong&gt; TrailKit uses &lt;a href="https://maplibre.org" rel="noopener noreferrer"&gt;MapLibre&lt;/a&gt; with free tiles from &lt;a href="https://openfreemap.org" rel="noopener noreferrer"&gt;OpenFreeMap&lt;/a&gt;, based on &lt;a href="https://www.openstreetmap.org/copyright" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt; data. You can download the map around a planned route before leaving.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to prepare everything beforehand so losing reception doesn't interrupt the hike.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community, without giving up privacy
&lt;/h3&gt;

&lt;p&gt;The optional community features use &lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; for authentication, Postgres storage and profile photos. There's no custom backend server.&lt;/p&gt;

&lt;p&gt;A shared trek contains the route, statistics and identified species, but not the photos taken along the way. Row-level security policies restrict accounts to their own data, and a test script checks that unauthenticated writes are rejected and other users cannot delete your posts.&lt;/p&gt;

&lt;p&gt;Deleting an account also removes its local data and takes down its posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Credits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://imageomics.github.io/bioclip/" rel="noopener noreferrer"&gt;BioCLIP&lt;/a&gt;&lt;/strong&gt; by the Imageomics Institute: species recognition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma 4&lt;/a&gt;&lt;/strong&gt; by Google, with QAT GGUF builds from &lt;a href="https://huggingface.co/unsloth" rel="noopener noreferrer"&gt;Unsloth&lt;/a&gt;: field notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/mybigday/llama.rn" rel="noopener noreferrer"&gt;llama.rn&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://onnxruntime.ai" rel="noopener noreferrer"&gt;ONNX Runtime&lt;/a&gt;&lt;/strong&gt;: on-device inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://maplibre.org" rel="noopener noreferrer"&gt;MapLibre&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://openfreemap.org" rel="noopener noreferrer"&gt;OpenFreeMap&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.openstreetmap.org/copyright" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;&lt;/strong&gt; contributors: maps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;&lt;/strong&gt;: authentication and community features. &lt;strong&gt;&lt;a href="https://expo.dev" rel="noopener noreferrer"&gt;Expo&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;React Native&lt;/strong&gt;: app development.&lt;/li&gt;
&lt;li&gt;First-aid guidance follows the R.I.G.H.T. snakebite protocol. Icons are from &lt;a href="https://feathericons.com" rel="noopener noreferrer"&gt;Feather&lt;/a&gt;, and the app icon is the "route" icon from &lt;a href="https://www.svgrepo.com" rel="noopener noreferrer"&gt;SVG Repo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Test photo: &lt;em&gt;Calotropis gigantea&lt;/em&gt; by Peterwchen, &lt;a href="https://commons.wikimedia.org/wiki/File:Calotropis_gigantea-flower.jpg" rel="noopener noreferrer"&gt;CC BY-SA 4.0&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;For this project, open models made it possible to run identification and text generation directly on a phone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It works without a signal.&lt;/strong&gt; Trails often have poor coverage. Since both models run locally, identification doesn't depend on reaching a cloud API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photos and location history stay private.&lt;/strong&gt; The app processes photos on the device and stores GPS tracks locally. Sharing a trek doesn't upload its photos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I could choose the right model for each task.&lt;/strong&gt; BioCLIP is designed for biological identification, while Gemma handles explanations. Having access to both models let me run them on a phone and give each a specific role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There are no per-identification costs.&lt;/strong&gt; Once the models are downloaded, users can identify as many things as they want without paying for API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The trade-offs are different.&lt;/strong&gt; A cloud vision model might recognise more species accurately, but it needs connectivity, incurs usage costs and sends photos to an external service. TrailKit prioritises offline availability and privacy, even if that means accepting the limitations of smaller on-device models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open models weren't just a cheaper alternative here. They let me build something that can still be useful when the network disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I used Claude Code to plan TrailKit and break the work into tasks. Antigravity handled most of the implementation using those task files, while Claude Code reviewed the changes against the actual code, emulator behaviour and logs.&lt;/p&gt;

&lt;p&gt;That review caught several issues, including incorrect benchmark numbers, a timing budget that was off by a factor of seven, and unsafe generated outputs.&lt;/p&gt;

&lt;p&gt;I recorded the development decisions, bugs and performance measurements in &lt;a href="https://github.com/Aravind-Sathesh/Hacktoberfest-2026/blob/main/WEEK%201/REVIEW.md" rel="noopener noreferrer"&gt;REVIEW.md&lt;/a&gt;.&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 4 E2B runs entirely on the device through &lt;code&gt;llama.rn&lt;/code&gt;, generating field notes from BioCLIP's identification. Its role is deliberately limited: it explains the result but never determines danger, and its output passes through safety checks before being displayed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TrailKit is intended to help hikers learn about their surroundings, not replace professional medical advice or reliable species identification. Never eat a wild plant based on an app's identification. In an emergency, contact your local emergency services.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Codeforces Buddy: A Focus Companion</title>
      <dc:creator>Aravind Sathesh</dc:creator>
      <pubDate>Sat, 03 Oct 2026 16:04:26 +0000</pubDate>
      <link>https://dev.to/aravindsathesh/codeforces-buddy-a-focus-companion-204i</link>
      <guid>https://dev.to/aravindsathesh/codeforces-buddy-a-focus-companion-204i</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;My friend Rajeev (&lt;a href="https://codeforces.com/profile/ksrkasyap" rel="noopener noreferrer"&gt;ksrkasyap&lt;/a&gt; on Codeforces) wants to reach &lt;strong&gt;1800 by December 31&lt;/strong&gt;. He's currently at 1097, so that's a pretty big jump in about 90 days.&lt;/p&gt;

&lt;p&gt;I asked him what usually gets in the way when he tries to practise. It wasn't really that he didn't know what problems to solve. It was his phone.&lt;/p&gt;

&lt;p&gt;He'd sit down to solve something, pick up his phone for "a second", and suddenly a lot of time was gone.&lt;/p&gt;

&lt;p&gt;So I built him &lt;strong&gt;codeforces buddy&lt;/strong&gt;: an Android app that uses the phone as part of the focus system instead of letting it be the distraction.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A focus tree, inspired by Forest.&lt;/strong&gt; He starts a session, puts his phone face-up beside his laptop, and a tree grows for 80 minutes through eight stages. If he leaves the app for more than 10 seconds, the tree dies. When Codeforces reports an accepted submission, the tree is saved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Today's problems, from the sheet he already uses.&lt;/strong&gt; Rajeev practises from TLE Eliminators' CP-31 sheet. The app reads his Codeforces history and figures out where he is on the sheet. Right now, he's on 1300, problem #19, so the next cards are &lt;strong&gt;#20, #21, #25, #29&lt;/strong&gt;, followed by the problems he skipped and then 1400 #1. He can swipe through them, start a focus session on one, or enter any other problem number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hints that try not to spoil the problem.&lt;/strong&gt; When a focus session starts, Gemma runs on his phone and turns the problem's editorial into four hints. They go from a vague direction to the important observation. The hints are face-down, and he has to hold a card to reveal the next one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A streak and daily progress.&lt;/strong&gt; There's one circle for each problem in the day's target. Solved problems are marked, and solving beyond the target gives a different marker. There's also a home screen widget showing the day's progress and streak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule I used throughout the app is simple: &lt;strong&gt;a problem is solved only when Codeforces says &lt;code&gt;OK&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There isn't a "mark as done" button.&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/9xSK1_QFeCY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Download the APK from the &lt;a href="https://github.com/Aravind-Sathesh/codeforces-buddy/releases/latest" rel="noopener noreferrer"&gt;latest release&lt;/a&gt; (Android, arm64). GitHub Actions builds it on every tag.&lt;/p&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/Aravind-Sathesh" rel="noopener noreferrer"&gt;
        Aravind-Sathesh
      &lt;/a&gt; / &lt;a href="https://github.com/Aravind-Sathesh/Codeforces-Buddy" rel="noopener noreferrer"&gt;
        Codeforces-Buddy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A focus buddy for a friend's road to Codeforces 1800, with on-device Gemma hints
    &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;codeforces buddy&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A focus buddy for one friend's road to Codeforces 1800. Built for &lt;a href="https://codeforces.com/profile/ksrkasyap" rel="nofollow noopener noreferrer"&gt;Rajeev&lt;/a&gt; for the DEV Hacktoberfest Weekend Challenge: &lt;em&gt;Build for a Friend&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;He solves on his laptop. The phone sits beside it with a tree growing for 80 minutes; leave the app and the tree dies.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Aravind-Sathesh/Codeforces-Buddy/demo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FAravind-Sathesh%2FCodeforces-Buddy%2FHEAD%2Fdemo.png" alt="today, history and focus"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Today:&lt;/strong&gt; the next problems from TLE Eliminators' CP-31 sheet, picked up from his latest new sheet solve (or any unsolved problem 100–300 above his rating). Fixed for the day, as a swipeable card stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; a tree that grows through eight stages over 80 minutes and dies if he leaves the app for more than 10 seconds. An accepted Codeforces submission saves it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hints:&lt;/strong&gt; on-device Gemma reads the problem's editorial (code stripped) and writes four graded hints. They sit face-down; hold a card to reveal the next one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progress:&lt;/strong&gt; daily tree row, streaks, a 16-week heatmap, solve…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Aravind-Sathesh/Codeforces-Buddy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The app uses Expo SDK 57 / React Native 0.86 and TypeScript strict. It's Android-only.&lt;/p&gt;

&lt;p&gt;There are about 2,900 lines of app code and 35 unit tests covering the parts that actually make decisions: target calculation, CP-31 ordering, hint filtering, rate limiting and widget drawing.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Gemma 4 E2B, running on the phone
&lt;/h3&gt;

&lt;p&gt;The interesting part of the app is that the AI doesn't run on a server.&lt;/p&gt;

&lt;p&gt;It uses &lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; (&lt;a href="https://huggingface.co/unsloth/gemma-4-E2B-it-qat-GGUF" rel="noopener noreferrer"&gt;model&lt;/a&gt;), an instruction-tuned, quantization-aware trained 4-bit GGUF model that is about 2.6 GB, through &lt;a href="https://github.com/mybigday/llama.rn" rel="noopener noreferrer"&gt;&lt;code&gt;llama.rn&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The model is downloaded once over Wi-Fi. Before offering the download, the app checks whether the phone has enough memory to run it.&lt;/p&gt;

&lt;p&gt;I first tested the prompts on my Mac using Ollama, then tried the same prompts on the smaller model.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Gemma actually does
&lt;/h3&gt;

&lt;p&gt;The first version used Gemma for a lot more.&lt;/p&gt;

&lt;p&gt;It picked problems, chatted with Rajeev when he was stuck, and even had some ideas about what to say when his tree died.&lt;/p&gt;

&lt;p&gt;Rajeev didn't like it.&lt;/p&gt;

&lt;p&gt;His main points were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2.6 GB is already a big download.&lt;/li&gt;
&lt;li&gt;If he wants to talk to a chatbot about a problem, he already has a much smarter one open on his laptop.&lt;/li&gt;
&lt;li&gt;He didn't see a reason for the app to duplicate that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I agreed, so most of that was removed.&lt;/p&gt;

&lt;p&gt;Gemma now has one job: &lt;strong&gt;generate hints from an editorial without giving away the solution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pipeline is roughly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Codeforces blocks normal fetches, so a hidden WebView follows the contest page to the tutorial/editorial. Before passing anything to Gemma, the app removes every &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;When a focus session starts, Gemma generates exactly four hints using a JSON schema. The first two are supposed to point in the right direction, while the later ones get closer to the main observation.&lt;/li&gt;
&lt;li&gt;Plain code checks the generated hints. The first two can't use the problem's tags or copy six or more consecutive words from the editorial. Hints 3 and 4 are allowed to overlap more because they're supposed to contain the important observation.&lt;/li&gt;
&lt;li&gt;Generation happens in the background while Rajeev is reading the problem, so the hints are ready by the time he actually needs them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The point isn't to make a worse chatbot. It's to make a chatbot that &lt;strong&gt;doesn't immediately give him the answer&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why E2B?
&lt;/h3&gt;

&lt;p&gt;Rajeev's other complaint was the model size, so I tested smaller models before settling on E2B.&lt;/p&gt;

&lt;p&gt;I ran the same hint prompt on four editorials in his rating range:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemma3:1b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.8 GB&lt;/td&gt;
&lt;td&gt;Often vague or wrong, and relied heavily on terminology from the editorial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gemma4:e2b&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.6 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Produced useful hint ladders on 3 of 4 problems, around 3 seconds each on my Mac&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemma4:12b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;Better overall, but not practical for this use case on his phone&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1B model wasn't reliable enough for what I wanted. E2B was the smallest model that I was reasonably happy with, so that's what stayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything else is regular code
&lt;/h3&gt;

&lt;p&gt;One decision made the app much easier to reason about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code computes numbers. The model writes text.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model doesn't decide how many problems Rajeev should solve, which problems come next, or how busy his day is.&lt;/p&gt;

&lt;p&gt;Those are all deterministic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily target:&lt;/strong&gt; 4 problems on weekdays and 6 on weekends, reduced based on his busy time from the calendar. On contest days, the target becomes 2 warm-ups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CP-31 queue:&lt;/strong&gt; TLE Eliminators' sheet has 372 problems across ratings from 800 to 1900. It ships with the app. The queue starts from Rajeev's most recent solved problem rather than his current Codeforces rating. There's also a practice-level setting if he wants to jump ahead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codeforces API:&lt;/strong&gt; Requests are serialized and limited to one every 2 seconds. The 2.3 MB problem set is cached for a day, and the day's suggestions don't change every time the app refreshes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I initially had Gemma involved in choosing problems too, but there wasn't much benefit to that. A fixed, explainable order works better for something Rajeev does every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Feedback Loop
&lt;/h2&gt;

&lt;p&gt;I gave Rajeev a build on Friday night and let him use it.&lt;/p&gt;

&lt;p&gt;He found a lot of things wrong with it.&lt;/p&gt;

&lt;p&gt;I fixed them and gave him another build on Saturday. Most of the app ended up being a response to something he actually complained about.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;He said&lt;/th&gt;
&lt;th&gt;What changed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Too much refreshing."&lt;/td&gt;
&lt;td&gt;Load once when the app opens, then use pull-to-refresh. Switching tabs doesn't reload everything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"3 GB is too much. I'd just ask the chatbot on my laptop."&lt;/td&gt;
&lt;td&gt;Removed the chatbot and narrowed Gemma down to spoiler-free hints.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"When I keep pressing next, it still shows problem 1."&lt;/td&gt;
&lt;td&gt;Added a proper swipeable card stack with the sheet position shown as &lt;code&gt;#20 · CP-31 · 1300&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Refreshing changes the suggested problems."&lt;/td&gt;
&lt;td&gt;Suggestions are selected once per day and stored.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"I solve from TLE CP-31."&lt;/td&gt;
&lt;td&gt;CP-31 became the default source, continuing from his last solved problem.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"This doesn't really make sense as a mobile app. I barely solve on mobile."&lt;/td&gt;
&lt;td&gt;That's actually why the app is on mobile.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last point was something I had to think about.&lt;/p&gt;

&lt;p&gt;He's right. He shouldn't be solving problems on his phone.&lt;/p&gt;

&lt;p&gt;The problem statement, editor and Codeforces judge are all on his laptop. The phone is just sitting beside it, face-up and awake using &lt;code&gt;expo-keep-awake&lt;/code&gt;, with the tree growing.&lt;/p&gt;

&lt;p&gt;If he picks it up and starts scrolling, the tree starts dying.&lt;/p&gt;

&lt;p&gt;So the app isn't trying to move competitive programming onto a phone. It's trying to make the phone less useful as a distraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built With Agents
&lt;/h2&gt;

&lt;p&gt;I split the development work between two agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code was the planner.&lt;/strong&gt; It wrote the initial project spec and broke the work into tasks in &lt;code&gt;PLAN.md&lt;/code&gt;. I also gave it the project rules in &lt;code&gt;AGENTS.md&lt;/code&gt;, including things like conventional commits, no notifications because Rajeev didn't want them, and the privacy requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot was the implementer.&lt;/strong&gt; It took those tasks and actually built the app: writing the React Native code, adding the features, and writing the tests. I reviewed what it produced, fixed issues when needed, and fed the resulting changes back into the next tasks.&lt;/p&gt;

&lt;p&gt;The split worked pretty well for this project because Claude was focused on deciding &lt;strong&gt;what needed to be built and in what order&lt;/strong&gt;, while Copilot was focused on &lt;strong&gt;actually building it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;GitHub Actions handles the last part: whenever I push a &lt;code&gt;v*&lt;/code&gt; tag, it builds the Android APK and attaches it to a GitHub Release.&lt;/p&gt;

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

&lt;p&gt;The open model ended up mattering less because it was "AI" and more because of what the app is doing with personal data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;His calendar.&lt;/strong&gt; The app uses his calendar only to estimate how busy he is that day. Events are immediately reduced to &lt;code&gt;{ start, end }&lt;/code&gt;; titles, locations and attendees are discarded and never stored, logged or sent to the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;His practice history.&lt;/strong&gt; Focus sessions, dead trees and skipped sessions are all stored locally on his phone. There is no backend, analytics system or account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost.&lt;/strong&gt; There is no API key and no per-request bill. Once the model is downloaded, generating hints doesn't cost anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline use.&lt;/strong&gt; Once the model and the day's data are on the phone, the hint system doesn't need an internet connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being able to change it.&lt;/strong&gt; Since the model is open, I could actually compare the 1B, E2B and 12B models and see which one made sense for this particular task. If I want to fine-tune a model around the kinds of mistakes Rajeev makes, I can experiment with that too.&lt;/p&gt;

&lt;p&gt;For this app, running the model locally wasn't just a technical experiment. It let me build something around one person's actual habits without sending those habits to a server.&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 4 E2B runs on-device through llama.cpp (&lt;code&gt;llama.rn&lt;/code&gt;) and turns editorials into graded hints, with filtering to reduce spoilers. I also compared it against Gemma 3 1B and Gemma 4 12B.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of GitHub Copilot:&lt;/strong&gt; Copilot implemented part of the app from the written project plan, and GitHub Actions builds and publishes the APK on every tag.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Thanks to Rajeev for being a brutally honest first user.&lt;/em&gt;&lt;/p&gt;

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