<?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: Gaurav Sethi</title>
    <description>The latest articles on DEV Community by Gaurav Sethi (@gaurav0s).</description>
    <link>https://dev.to/gaurav0s</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%2F4092856%2Fb449ad75-2409-490f-8edb-4a7ab7f700f6.png</url>
      <title>DEV Community: Gaurav Sethi</title>
      <link>https://dev.to/gaurav0s</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gaurav0s"/>
    <language>en</language>
    <item>
      <title>Touch Grass — A Bird ID App That Works When There's No Signal</title>
      <dc:creator>Gaurav Sethi</dc:creator>
      <pubDate>Wed, 07 Oct 2026 06:48:02 +0000</pubDate>
      <link>https://dev.to/gaurav0s/touch-grass-a-bird-id-app-that-works-when-theres-no-signal-dkl</link>
      <guid>https://dev.to/gaurav0s/touch-grass-a-bird-id-app-that-works-when-theres-no-signal-dkl</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;I built &lt;strong&gt;Touch Grass&lt;/strong&gt; because identifying a bird call shouldn't mean uploading your life to someone else's server. Most nature apps work like this: you record audio, it ships to a cloud API, you wait, you get a result, and somewhere a company logs that you were near a lake at 7am. I didn't want any of that.&lt;/p&gt;

&lt;p&gt;Touch Grass is a Progressive Web App that runs open-weight AI models entirely inside the browser. You record a 3-second bird clip, the app converts it to a mel spectrogram locally, and an ONNX model predicts the species — all without a network call after the initial download. The same approach works for plants and insects from photos. Once the model is cached in IndexedDB, the app works completely offline. No accounts, no tracking, no "please sign in."&lt;/p&gt;

&lt;p&gt;It's for people who actually want to spend time outside. The whole philosophy is: identify quickly, then put the phone back in your pocket and enjoy the trees.&lt;/p&gt;

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

&lt;p&gt;Live site: &lt;a href="https://touch-grass-143.pages.dev" rel="noopener noreferrer"&gt;https://touch-grass-143.pages.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install it like a normal app — the service worker caches the shell, the manifest is set up, and offline identification works after the first model download. I tested the PWA install on Android Chrome: add to home screen, open it, allow the mic, record 3 seconds, and you'll see results come back from the local BirdNET model with no signal required. I haven't done a long birding trip yet, but the whole point is that it works exactly where the map stops — on the trail with no bars.&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/Gaurav5189" rel="noopener noreferrer"&gt;
        Gaurav5189
      &lt;/a&gt; / &lt;a href="https://github.com/Gaurav5189/touch-grass" rel="noopener noreferrer"&gt;
        touch-grass
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Progressive Web App that helps people identify birds, plants, and insects using open-weight AI models running entirely on-device. Works offline, keeps all data local, and minimizes screen time so you can get back to nature.
    &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;Touch Grass — Local-First Nature Identification PWA&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A Progressive Web App that helps people identify birds, plants, and insects using open-weight AI models running entirely on-device. Works offline, keeps all data local, and minimizes screen time so you can get back to nature.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Philosophy:&lt;/strong&gt; Identify quickly, then put the phone away and enjoy the outdoors.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bird Call ID&lt;/strong&gt; — Record 3-second audio clips, identify species via BirdNET-ONNX (~20MB, quantized, 3000+ species), all on-device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plant &amp;amp; Insect Photo ID&lt;/strong&gt; — Capture or import a photo, get top-5 matches from a MobileNetV3 model (~5MB), with descriptions and similar species.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully Offline&lt;/strong&gt; — After initial model downloads, no internet required. Service worker caches the app shell; IndexedDB stores observations and model cache.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private by Default&lt;/strong&gt; — No accounts, no tracking, no server requests for inference. Optional GPS tagging is opt-in only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observation History&lt;/strong&gt; — Auto-save after every identification. Filter, search, and export…&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/Gaurav5189/touch-grass" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
Repo: &lt;a href="https://github.com/Gaurav5189/touch-grass" rel="noopener noreferrer"&gt;Gaurav5189/touch-grass&lt;/a&gt;

&lt;p&gt;Key parts worth looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src/features/bird-id/&lt;/code&gt; — AudioRecorder hook, BirdResults UI, RegionFilter&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/features/photo-id/&lt;/code&gt; — CameraCapture, GalleryImport, MobileNet inference stub&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/features/history/&lt;/code&gt; — Observation list, filters, CSV/JSON/iNaturalist export&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src/workers/inference.worker.ts&lt;/code&gt; — ONNX Runtime Web with WebGPU preferred, WASM fallback&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;public/manifest.json&lt;/code&gt; + &lt;code&gt;vite-plugin-pwa&lt;/code&gt; — full PWA setup&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Stack is straightforward: React 18, TypeScript 5.3, Vite 5, Tailwind 3.4. Nothing exotic. The interesting part is what runs inside the browser.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bird identification&lt;/strong&gt;: BirdNET-ONNX (quantized INT8, ~20MB, Apache-2.0, 3000+ species). It runs in a Web Worker via &lt;code&gt;onnxruntime-web&lt;/code&gt;, with WebGPU first and WASM fallback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plant / insect identification&lt;/strong&gt;: MobileNetV3 + custom iNaturalist head (~5MB, Apache-2.0 / CC-BY data).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline storage&lt;/strong&gt;: IndexedDB (&lt;code&gt;idb&lt;/code&gt;) for observations and model cache; Cache API for the app shell.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;: Zero server requests for inference. Optional GPS is opt-in only. No analytics, no telemetry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used OpenCode for the main structure — scaffolding the PWA, setting up the folder layout, and getting the build pipeline clean (CI with typecheck, lint, build). Then Antigravity helped with refinements and bug fixes — fixing TypeScript casting issues with &lt;code&gt;navigator.gpu&lt;/code&gt;, cleaning up import paths for &lt;code&gt;onnxruntime-web.d.ts&lt;/code&gt;, resolving build errors in &lt;code&gt;useModelDownload&lt;/code&gt;, and fixing the Playwright / Vitest conflict so E2E tests could run separately.&lt;/p&gt;

&lt;p&gt;The model strategy is open-weight all the way: BirdNET from the BirdNET-Analyzer repo, MobileNet weights fine-tuned on public iNaturalist data. All licenses documented in &lt;code&gt;DOCS/RULES.md&lt;/code&gt;. No proprietary or GPL dependencies.&lt;/p&gt;

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

&lt;p&gt;If this was a closed API, you couldn't swap the bird model for a regional 8MB pack. You couldn't upload your own ONNX file for a custom species set. You couldn't verify that your audio isn't being stored somewhere else. And you definitely couldn't run it in a cabin with no signal.&lt;/p&gt;

&lt;p&gt;Open-weight models make this possible because the weights are files you download, not services you call. The BirdNET team released their quantized model under Apache-2.0. The iNaturalist dataset is CC-BY. That means I can bundle them into a browser app without licensing anxiety or vendor lock-in. Open innovation here isn't a marketing line — it's the reason the app can work offline, stay private, and let users control which model they run.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/li&gt;
&lt;li&gt;Open Source / Open Weight Models&lt;/li&gt;
&lt;li&gt;Open Source AI Challenge&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>privacy</category>
    </item>
    <item>
      <title>LocalCommit: Offline Conventional Git Commits with Local LLMs</title>
      <dc:creator>Gaurav Sethi</dc:creator>
      <pubDate>Sun, 04 Oct 2026 21:20:48 +0000</pubDate>
      <link>https://dev.to/gaurav0s/localcommit-offline-conventional-git-commits-with-local-llms-opk</link>
      <guid>https://dev.to/gaurav0s/localcommit-offline-conventional-git-commits-with-local-llms-opk</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend (&lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;https://dev.to/challenges/hacktoberfest-weekend-2026-10-01&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built LocalCommit, a lightweight CLI tool that inspects your staged git diffs and drafts concise Conventional Commit messages (feat(auth): add refresh token rotation) running entirely on your local machine using Ollama.&lt;br&gt;
I built this for my friend Manish, a software engineer working under strict client non-disclosure agreements. Manish's team requires strict Conventional Commits for automated changelogs and semantic releases, but company policy strictly forbids piping source code or git diffs to proprietary cloud LLM APIs (like OpenAI or Anthropic). Whenever Manish makes multi-file changes across large codebases, writing detailed conventional commit messages by hand slows down their workflow.&lt;br&gt;
LocalCommit solves this by reading git diff --staged and running inference on an open-weights model locally. Zero tokens leave the machine, no API keys are required, and the tool works even when working offline on a train or during an internet outage.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here is how running LocalCommit looks in terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git add src/auth/token.ts src/middleware/jwt.ts
&lt;span class="nv"&gt;$ &lt;/span&gt;npx localcommit
&lt;span class="o"&gt;[&lt;/span&gt;localcommit] Checking staged changes...
&lt;span class="o"&gt;[&lt;/span&gt;localcommit] Sending diff to qwen2.5-coder:3b...

Proposed commit message:
  feat&lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;: implement refresh token rotation and validation

Commit with this message? &lt;span class="o"&gt;[&lt;/span&gt;y/N/e &lt;span class="o"&gt;(&lt;/span&gt;edit&lt;span class="o"&gt;)&lt;/span&gt;/r &lt;span class="o"&gt;(&lt;/span&gt;regenerate&lt;span class="o"&gt;)]&lt;/span&gt;: y
&lt;span class="o"&gt;[&lt;/span&gt;main 7f2a1b9] feat&lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;: implement refresh token rotation and validation
 2 files changed, 48 insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;, 6 deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
You can confirm with &lt;span class="o"&gt;[&lt;/span&gt;y] to commit immediately, hit &lt;span class="o"&gt;[&lt;/span&gt;e] to edit &lt;span class="k"&gt;in &lt;/span&gt;place, &lt;span class="o"&gt;[&lt;/span&gt;r] to regenerate with a slightly different phrasing, or &lt;span class="o"&gt;[&lt;/span&gt;n] to cancel without touching git state.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;The source code and setup instructions are open source on GitHub:&lt;br&gt;
&lt;/p&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/Gaurav5189" rel="noopener noreferrer"&gt;
        Gaurav5189
      &lt;/a&gt; / &lt;a href="https://github.com/Gaurav5189/LocalCommit" rel="noopener noreferrer"&gt;
        LocalCommit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Local-first CLI for generating conventional commit messages via Ollama.
    &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;LocalCommit&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Local-first CLI for generating conventional commit messages with Ollama.&lt;/p&gt;

&lt;p&gt;LocalCommit reads your staged Git diff, generates a Conventional Commit message
with the configured local model, and asks for confirmation before committing.&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;Node.js 18+&lt;/li&gt;
&lt;li&gt;Git CLI&lt;/li&gt;
&lt;li&gt;Ollama&lt;/li&gt;
&lt;li&gt;A model configured in the root &lt;code&gt;model&lt;/code&gt; file (default: &lt;code&gt;qwen2.5-coder:3b&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Ollama is not installed, follow the platform-specific instructions in
&lt;a href="https://github.com/Gaurav5189/LocalCommit/INSTRUCTION.md" rel="noopener noreferrer"&gt;INSTRUCTION.md&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&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;npm run setup&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The setup command verifies Node.js and Git, checks that Ollama is running,
downloads &lt;code&gt;qwen2.5-coder:3b&lt;/code&gt; if it is missing, installs dependencies, and
builds the project.&lt;/p&gt;
&lt;p&gt;If setup reports that Ollama is unavailable, install it and start the Ollama
service by following &lt;a href="https://github.com/Gaurav5189/LocalCommit/INSTRUCTION.md" rel="noopener noreferrer"&gt;INSTRUCTION.md&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Start Ollama in a separate terminal if it is not already running:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;ollama serve&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Stage your changes in a Git repository, then run:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npx localcommit&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Inside a checkout of this project, &lt;code&gt;npx localcommit&lt;/code&gt; resolves the local package
binary. To use it from…&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/Gaurav5189/LocalCommit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
Repository link: &lt;a href="https://github.com/Gaurav5189/LocalCommit" rel="noopener noreferrer"&gt;Gaurav5189/LocalCommit&lt;/a&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;LocalCommit is written in TypeScript and connects to Ollama (&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;https://ollama.com/&lt;/a&gt;) running locally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Default Model: By default, it targets qwen2.5-coder:3b—a fast, open-weights code model capable of running with low latency on modest CPU or laptop GPU hardware without draining battery.&lt;/li&gt;
&lt;li&gt;Context Auto-Detection: Instead of assuming a static token limit, LocalCommit queries Ollama's /api/ps and &lt;code&gt;/api/show&lt;/code&gt; endpoints at runtime to discover the exact context window allocated by your environment.&lt;/li&gt;
&lt;li&gt;Two-Stage Summarization for Large Diffs:&lt;/li&gt;
&lt;li&gt;Small diffs fit in a single prompt and produce an immediate message.&lt;/li&gt;
&lt;li&gt;For larger, multi-file changes exceeding the context window, it switches to a hierarchical strategy: it prioritizes source code files (src/, tests/), generates concise per-file summaries bounded by a strict 60-second budget, and feeds that synthesized context into a final commit prompt.&lt;/li&gt;
&lt;li&gt;Deterministic Post-Processing: Small local models can occasionally output markdown fences or descriptive preambles. LocalCommit passes the raw output through a deterministic formatting engine that enforces standard type(scope): subject syntax, cleans generic scopes, and converts verbs into imperative mood (add, fix, refactor).&lt;/li&gt;
&lt;li&gt;Cross-Platform Host Detection: Handles network boundary quirks across Linux, macOS, Windows, and containerized setups (like Docker) by probing OLLAMA_HOST, 127.0.0.1:11434, and localhost:11434.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Open-weights models and local runtimes like Ollama make use cases possible that cloud AI endpoints fundamentally cannot support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete Data Sovereignty &amp;amp; Privacy: Codebases under NDAs, proprietary IP, or internal air-gapped networks cannot send raw git diffs over external APIs. Open models let developers use AI assistance without ever leaking code.&lt;/li&gt;
&lt;li&gt;Offline Reliability: You don't need active Wi-Fi, metered bandwidth, or cloud service uptime to commit your work.&lt;/li&gt;
&lt;li&gt;Zero Ongoing Costs &amp;amp; No API Keys: Developers don't have to manage credit cards, rate limits, or monthly token bills.&lt;/li&gt;
&lt;li&gt;Flexibility to Swap Models: You can drop in any local model (e.g. llama3.2, codellama, deepseek-coder) simply by editing the model file or setting the LOCALCOMMIT_MODEL environment variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;The development session where I debugged the container connectivity issues and fixed the Ollama host resolution:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="OpenCode"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;LocalCommit: Fixing cross-platform Ollama connectivity for containers&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The setup script and CLI are failing to connect to Ollama in GitHub Codespaces because localhost resolves to ::1 but Ollama binds to &lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;. Need multi-host resolution and proper URL construction.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;I'll create a shared Ollama client utility for host resolution and fix both the setup script and CLI.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-write"&gt;write&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;src/ollama-client.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;ollama&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Created&lt;/span&gt; &lt;span class="nx"&gt;shared&lt;/span&gt; &lt;span class="nx"&gt;utility&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;getOllamaHosts&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;ollamaFetch&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;checkOllamaReachable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;src/llm.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Updated&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="nx"&gt;resolution&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/api/&lt;/span&gt;&lt;span class="nx"&gt;ps&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;src/env-check.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Updated&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;shared&lt;/span&gt; &lt;span class="nf"&gt;checkOllamaReachable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scripts/setup.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Fixed&lt;/span&gt; &lt;span class="nx"&gt;double&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;slash&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt; &lt;span class="nf"&gt;bug &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/tags&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="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;reordered&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="nx"&gt;priority&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Build succeeded but setup still fails - need to debug the http.get call in setup.js&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Found the issue - urlObj.toString() + '/api/tags' creates double slash. Fixed with new URL(path, base).&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scripts/setup.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Fixed&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt; &lt;span class="nx"&gt;construction&lt;/span&gt; &lt;span class="nx"&gt;using&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/tags&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="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Setup now works. But when model in 'model' file doesn't match installed Ollama model, error message is unclear. Add guidance to run 'npm run setup'.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Added special case handling for 404 'model not found' error with actionable message pointing to 'npm run setup'.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-edit"&gt;edit&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;src/llm.ts&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Added&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="nx"&gt;handling&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt; &lt;span class="nx"&gt;guidance&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Now help me write a DEV post for Hacktoberfest Weekend Challenge submission.&lt;/p&gt;

                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        7 of 6 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;ul&gt;
&lt;li&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/li&gt;
&lt;li&gt;Open-Source AI / Open Weights Integration (Llama 3.3, Qwen 2.5, gemma 4)&lt;/li&gt;
&lt;li&gt;Local Inference &amp;amp; Edge AI (Ollama 100% offline laptop execution)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  LICENSE
&lt;/h2&gt;

&lt;p&gt;This project is open source and available under the MIT License.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Building LinkCut: A Sub-20ms URL Shortener with Next.js, FastAPI, and Redis</title>
      <dc:creator>Gaurav Sethi</dc:creator>
      <pubDate>Mon, 24 Aug 2026 19:29:42 +0000</pubDate>
      <link>https://dev.to/gaurav0s/building-linkcut-a-sub-20ms-url-shortener-with-nextjs-fastapi-and-redis-52a1</link>
      <guid>https://dev.to/gaurav0s/building-linkcut-a-sub-20ms-url-shortener-with-nextjs-fastapi-and-redis-52a1</guid>
      <description>&lt;p&gt;Hi, i'm Gaurav. This is my first post to dev.to&lt;/p&gt;

&lt;h2&gt;
  
  
  What is LinkCut?
&lt;/h2&gt;

&lt;p&gt;LinkCut is a free, open-source URL shortener with no signup required — paste a link, cut it, share it. No accounts, no tracking cookies, no dashboard bloat.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 Live app: &lt;a href="https://linkcut-nine.vercel.app/" rel="noopener noreferrer"&gt;https://linkcut-nine.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 Repo: &lt;a href="https://github.com/Gaurav5189/url-shortener" rel="noopener noreferrer"&gt;https://github.com/Gaurav5189/url-shortener&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I wanted to design a system where redirect latency actually mattered, not just a CRUD app with a UI wrapper. That meant thinking about caching, write-amplification, and decoupling reads from writes — not just "shorten a string and save it to a DB."&lt;/p&gt;

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

&lt;p&gt;The core idea: &lt;strong&gt;separate the fast path from the slow path.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast path (Edge Middleware):&lt;/strong&gt; checks Redis first, returns a 302 redirect directly from the edge, and fires a non-blocking &lt;code&gt;INCR&lt;/code&gt; for click tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow path (Backend fallback):&lt;/strong&gt; on a cache miss, FastAPI decodes the Base62 short code to an integer ID for an O(1) indexed lookup in SQLite, then repopulates Redis with a 12-hour TTL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background path (Cron):&lt;/strong&gt; flushes Redis click counters into SQLite periodically using an atomic Lua script — read-and-delete in a single Redis transaction, so counts never get double-counted or lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps redirects fast (sub-20ms) while analytics writes stay off the hot path entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend/Edge:&lt;/strong&gt; Next.js (App Router) on Vercel Edge Middleware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache/Counter:&lt;/strong&gt; Redis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; FastAPI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; SQLite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding:&lt;/strong&gt; Base62, 5-character codes from auto-incrementing IDs (~916M capacity)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I underestimated this going in. The URL shortening part is trivial — the interesting engineering is in the caching strategy, avoiding database write-amplification on every click, and making sure the analytics counter flush is atomic so nothing gets lost or double-counted under concurrent traffic.&lt;/p&gt;

&lt;p&gt;Feedback on the caching strategy or architecture decisions welcome — always looking to improve it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>fastapi</category>
      <category>nextjs</category>
      <category>redis</category>
    </item>
  </channel>
</rss>
