<?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: Sotaro Aoki</title>
    <description>The latest articles on DEV Community by Sotaro Aoki (@sotaro).</description>
    <link>https://dev.to/sotaro</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%2F4102266%2Fbd5f3797-4f84-43ff-bb9d-96129455583e.png</url>
      <title>DEV Community: Sotaro Aoki</title>
      <link>https://dev.to/sotaro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sotaro"/>
    <language>en</language>
    <item>
      <title>slothsort 🦥 — a local Gemma that tidies my Downloads folder while I sleep</title>
      <dc:creator>Sotaro Aoki</dc:creator>
      <pubDate>Fri, 23 Oct 2026 09:59:25 +0000</pubDate>
      <link>https://dev.to/sotaro/slothsort-a-local-gemma-that-tidies-my-downloads-folder-while-i-sleep-7ma</link>
      <guid>https://dev.to/sotaro/slothsort-a-local-gemma-that-tidies-my-downloads-folder-while-i-sleep-7ma</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;My Downloads folder had 41 files in it. &lt;code&gt;Doc1.docx&lt;/code&gt;. &lt;code&gt;download.txt&lt;/code&gt;. &lt;code&gt;script (1).py&lt;/code&gt;. Two copies of &lt;code&gt;setup_v2.exe&lt;/code&gt;. Every few weeks I'd open it, sigh, and close it again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;slothsort&lt;/strong&gt; is the sloth that does it for me. Point it at a messy folder and a small open-weight model running &lt;em&gt;on my own PC&lt;/em&gt; reads each file, decides what it is, and proposes a home and a name a human can actually understand. This is real output from Gemma 3 4B:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code/
   script (1).py
     -&amp;gt; 2026-09-26 hacker-news-top-stories.py
Documents/
   Doc1.docx
     -&amp;gt; 2026-10-15 quarterly-report-q3-2026.docx
   Document (3).docx
     -&amp;gt; 2026-10-13 acme-corp-frontend-developer-cover.docx
Invoices &amp;amp; Receipts/
   download.txt
     -&amp;gt; 2026-10-12 apartment-4b-lease-renewal.txt
   invoice_8812(1).txt
     -&amp;gt; 2026-10-07 tokopedia-invoice-wh-200.txt

20 file(s), 10 folder(s). Roughly 8 minute(s) of clicking you won't have to do.
This was a dry run. Add --apply to move the files.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;download.txt&lt;/code&gt; tells you nothing. Its &lt;em&gt;content&lt;/em&gt; tells you it's the lease renewal for apartment 4B. That's the whole trick.&lt;/p&gt;

&lt;p&gt;Who gets to be lazier? Anyone whose Downloads folder is a graveyard. Run it once to see the plan, add &lt;code&gt;--apply&lt;/code&gt;, then schedule it nightly and forget it exists. &lt;strong&gt;Set it up once, never think about it again.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the point is that you &lt;em&gt;don't&lt;/em&gt; supervise it, most of my effort went into making it safe to ignore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧪 &lt;strong&gt;Dry run by default.&lt;/strong&gt; Nothing moves until you say &lt;code&gt;--apply&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;↩️ &lt;strong&gt;One-command undo.&lt;/strong&gt; &lt;code&gt;--undo&lt;/code&gt; puts the last run back exactly as it was, even if the run was interrupted halfway (every move is logged the moment it happens).&lt;/li&gt;
&lt;li&gt;🚫 &lt;strong&gt;Never overwrites.&lt;/strong&gt; Clashes become &lt;code&gt;name-2.pdf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;⏳ &lt;strong&gt;Leaves half-finished downloads alone.&lt;/strong&gt; &lt;code&gt;.crdownload&lt;/code&gt;, &lt;code&gt;.part&lt;/code&gt;, and anything touched in the last two minutes are skipped.&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;Remembers.&lt;/strong&gt; Answers are cached per file, so the nightly run only bothers the model about new arrivals.&lt;/li&gt;
&lt;li&gt;📴 &lt;strong&gt;Degrades gracefully.&lt;/strong&gt; No model? Old GPU? It still works (more on that below).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8wqp8aaj2qzgpellc127.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8wqp8aaj2qzgpellc127.gif" alt="slothsort tidying a messy Downloads folder with Gemma, then undoing it" width="720" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A real session on a 21-file test folder: dry run, apply, look at the result, undo. Gemma's 114 seconds of reading are sped up; everything else is as it printed.&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/tommy-webguru" rel="noopener noreferrer"&gt;
        tommy-webguru
      &lt;/a&gt; / &lt;a href="https://github.com/tommy-webguru/slothsort" rel="noopener noreferrer"&gt;
        slothsort
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Tidy your Downloads folder with a local open-weight model (Gemma via Ollama). Dry-run by default, one-command undo.
    &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;🦥 slothsort&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Tidy your Downloads folder without lifting a finger, or sending a single byte to the cloud.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/tommy-webguru/slothsort/docs/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Ftommy-webguru%2Fslothsort%2FHEAD%2Fdocs%2Fdemo.gif" alt="slothsort tidying a messy Downloads folder with Gemma, then undoing it"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;slothsort looks at every file in a messy folder, asks a small open-weight model running on your own machine (Gemma, via &lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;Ollama&lt;/a&gt;) what it is, and proposes a folder and a human-readable name for it:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;
&lt;pre class="notranslate"&gt;&lt;code&gt;Code/
   script (1).py
     -&amp;gt; 2026-09-26 hacker-news-top-stories.py   (Python script scrapes hacker news front page titles.)
Documents/
   Doc1.docx
     -&amp;gt; 2026-10-15 quarterly-report-q3-2026.docx   (Report details revenue growth and board meeting preparation.)
   Document (3).docx
     -&amp;gt; 2026-10-13 acme-corp-frontend-developer-cover.docx   (Clearly a cover letter for a developer role.)
Invoices &amp;amp; Receipts/
   download.txt
     -&amp;gt; 2026-10-12 apartment-4b-lease-renewal.txt   (Confirms lease renewal details with apartment address.)
   invoice_8812(1).txt
     -&amp;gt; 2026-10-07 tokopedia-invoice-wh-200.txt   (Clearly an invoice from Tokopedia for headphones.)
Screenshots/
   Screenshot 2026-10-01 101010.png
     -&amp;gt; screenshot-2026-10-01-101010.png   (matched by file extension)
20 file(s), 10 folder(s). Roughly 8 minute(s) of clicking you won't have to do.
This was a dry run. Add&lt;/code&gt;&lt;/pre&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tommy-webguru/slothsort" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;One Python file, standard library only, MIT licensed. &lt;code&gt;make_demo_folder.py&lt;/code&gt; builds a realistic messy folder so you can try it without risking your own files. Tests spin up a fake Ollama server, so they run in under a second with no GPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; unittest &lt;span class="nt"&gt;-v&lt;/span&gt; test_slothsort
&lt;span class="c"&gt;# Ran 13 tests ... OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;The stack:&lt;/strong&gt; Gemma 3 4B, served locally by Ollama, called from a single Python script with nothing but the standard library. My machine is a desktop with a &lt;strong&gt;GTX 1050 Ti (4 GB)&lt;/strong&gt; and 32 GB of RAM, which is a nice, honest "normal person's computer" to test on. It also turned out to be the most interesting part of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 1: the open stack has to survive old hardware
&lt;/h3&gt;

&lt;p&gt;The first real run failed on every single file with an HTTP 500. Ollama's log said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CUDA error: the provided PTX was compiled with an unsupported toolchain.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My NVIDIA driver was older than the CUDA build Ollama ships with. A closed API would never have shown me this, and it would never have been my problem either. But with open weights, the model was already sitting on my disk and could run on the CPU. One option, &lt;code&gt;"num_gpu": 0&lt;/code&gt;, fixed it. So slothsort now does this itself: if the GPU can't load the model, it says so once, switches to the CPU, and carries on. On the CPU it takes about &lt;strong&gt;10 to 17 seconds per readable file&lt;/strong&gt;, depending on how much text there is. That doesn't matter for a job that runs at 3 AM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 2: small models name brilliantly and file terribly
&lt;/h3&gt;

&lt;p&gt;Once it ran, the names were great from the start. The folders were not. Gemma put the Tokopedia invoice in &lt;strong&gt;Ebooks&lt;/strong&gt;, a Python script in &lt;strong&gt;Screenshots&lt;/strong&gt;, and the VS Code installer in &lt;strong&gt;Video&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The fix was to stop asking the model a question the file extension already answers. slothsort sends Ollama a JSON schema whose &lt;code&gt;enum&lt;/code&gt; is built &lt;strong&gt;per file&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ALLOWED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoices &amp;amp; Receipts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Spreadsheets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Spreadsheets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoices &amp;amp; Receipts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Screenshots&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoices &amp;amp; Receipts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allowed_categories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXT_CATEGORY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CATEGORIES&lt;/span&gt;          &lt;span class="c1"&gt;# unknown type: let the model decide
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ALLOWED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;.py&lt;/code&gt; file can only ever be Code. A &lt;code&gt;.txt&lt;/code&gt; can be a document &lt;em&gt;or&lt;/em&gt; a receipt, and that's the judgement call worth spending a model on. Ollama's structured outputs constrain generation to the schema, so the model physically can't pick a folder outside the list. After this change, every file in my test folder landed somewhere sensible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lesson 3: don't ask a model about things it can't see
&lt;/h3&gt;

&lt;p&gt;For a photo called &lt;code&gt;IMG_20260914_183022.jpg&lt;/code&gt;, the model only sees the filename, and it cheerfully renamed it &lt;code&gt;img-2026-september-screenshot.jpg&lt;/code&gt;. It wasn't a screenshot. Small models embellish when they have nothing to go on.&lt;/p&gt;

&lt;p&gt;So files with no readable text now skip the model entirely and just get their names tidied (&lt;code&gt;setup_v2 (1).exe&lt;/code&gt; → &lt;code&gt;setup-v2.exe&lt;/code&gt;). It's more honest, and it more than halved the run time: &lt;strong&gt;from 187 to 94 seconds&lt;/strong&gt; for the 20-file test folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pipeline
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scan&lt;/strong&gt; the folder, skipping hidden, partial and brand-new files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peek&lt;/strong&gt; at up to 1,200 characters of text (&lt;code&gt;.docx&lt;/code&gt;, &lt;code&gt;.pptx&lt;/code&gt; and &lt;code&gt;.xlsx&lt;/code&gt; are just zipped XML, so the standard library can read them).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask&lt;/strong&gt; Gemma for &lt;code&gt;{category, name, reason}&lt;/code&gt; at temperature 0, with the category limited by extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; &lt;code&gt;Category/YYYY-MM-DD descriptive-name.ext&lt;/code&gt;, resolving clashes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply&lt;/strong&gt; moves one by one, appending each to &lt;code&gt;.slothsort/history.jsonl&lt;/code&gt; before the next, so undo always knows the truth.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Scheduling&lt;/strong&gt; is just the OS: one &lt;code&gt;schtasks&lt;/code&gt; line on Windows or one cron line on macOS/Linux. No daemon, no tray icon, nothing to keep running.&lt;/p&gt;

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

&lt;p&gt;Look at what my test folder held: a lease with a home address, a CV, a cover letter, an invoice, a spending export. A real Downloads folder is worse: bank statements, payslips, scanned IDs. That is exactly the stuff I would &lt;strong&gt;never&lt;/strong&gt; upload to a cloud API just so a model can suggest a filename.&lt;/p&gt;

&lt;p&gt;With an open-weight model running locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My files never leave my machine.&lt;/strong&gt; The only network call is to &lt;code&gt;localhost:11434&lt;/code&gt;. Unplug the router and it still works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It runs on the computer I already have.&lt;/strong&gt; When my GPU driver turned out to be too old, I could still run the same model on the CPU. With an open stack, I get to fix things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run forever.&lt;/strong&gt; A nightly job over hundreds of files would be a small but endless API bill. Here it's electricity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It can't be taken away.&lt;/strong&gt; No API key to expire, no model deprecation email, no price change. The weights are on my disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's swappable.&lt;/strong&gt; Any model Ollama can run is one &lt;code&gt;--model&lt;/code&gt; flag away.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A closed API would have been easier to prompt, honestly. But "upload the contents of your Downloads folder to a third party every night" is a non-starter, and that's the whole product. Open weights didn't make this project &lt;em&gt;better&lt;/em&gt;; they made it &lt;em&gt;possible&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;I pointed it at my real Downloads folder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;41 files&lt;/strong&gt; in, &lt;strong&gt;40 filed&lt;/strong&gt; (the 41st was Windows' own &lt;code&gt;desktop.ini&lt;/code&gt;, which it knows to leave alone).&lt;/li&gt;
&lt;li&gt;Gemma read &lt;strong&gt;28&lt;/strong&gt; of them; the other &lt;strong&gt;12&lt;/strong&gt; had no readable text and just had their names tidied.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 name clashes&lt;/strong&gt; were resolved with &lt;code&gt;-2&lt;/code&gt;-style suffixes. Nothing was overwritten.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 minutes&lt;/strong&gt; of wall-clock time with Gemma 3 4B on my CPU, during which I did nothing at all.&lt;/li&gt;
&lt;li&gt;By hand, at about 25 seconds per file to open, rename and file it, that's &lt;strong&gt;roughly 17 minutes&lt;/strong&gt; of tedious clicking, and that's assuming I'd ever actually do it.&lt;/li&gt;
&lt;li&gt;Applying it afterwards took &lt;strong&gt;3 seconds&lt;/strong&gt;: every answer was already cached from the dry run.&lt;/li&gt;
&lt;li&gt;Times I needed &lt;code&gt;--undo&lt;/code&gt;: &lt;strong&gt;0&lt;/strong&gt; so far.&lt;/li&gt;
&lt;li&gt;Effort per week from now on: &lt;strong&gt;zero.&lt;/strong&gt; It runs at 3 AM, and the cache means it only reads new arrivals.&lt;/li&gt;
&lt;/ul&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; for every file with readable content, the name and folder slothsort picks come from Gemma 3 4B running locally through Ollama, with per-file schema-constrained output so even a small model running on a modest PC's CPU behaves.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hf26challenge</category>
      <category>opensource</category>
      <category>ai</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>🎤 Event Hub: Tech Conference Discovery Platform Built with Sanity CMS</title>
      <dc:creator>Sotaro Aoki</dc:creator>
      <pubDate>Sat, 03 Oct 2026 04:48:19 +0000</pubDate>
      <link>https://dev.to/sotaro/event-hub-tech-conference-discovery-platfsanitychallenge-devchallenge-sanity-nextjsorm-built-5440</link>
      <guid>https://dev.to/sotaro/event-hub-tech-conference-discovery-platfsanitychallenge-devchallenge-sanity-nextjsorm-built-5440</guid>
      <description>&lt;ol&gt;
&lt;li&gt;# Event Hub - Sanity Challenge 2026&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I built a modern tech event discovery platform using Sanity CMS and Next.js to showcase real-world CMS capabilities and structured content design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;🎤 &lt;strong&gt;Event Management&lt;/strong&gt; - Browse tech conferences, workshops, and talks worldwide&lt;br&gt;
🎤 &lt;strong&gt;Speaker Profiles&lt;/strong&gt; - Discover world-class speakers and their talks&lt;br&gt;
⭐ &lt;strong&gt;Community Reviews&lt;/strong&gt; - Read and share feedback on events&lt;br&gt;
🏷️ &lt;strong&gt;Smart Filtering&lt;/strong&gt; - Browse by category, date, and rating&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Sanity CMS (Headless)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js 14 + React 18 + TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling:&lt;/strong&gt; Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Vercel + Sanity Cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Schema Design
&lt;/h2&gt;

&lt;p&gt;Demonstrates structured content with meaningful relationships:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event → Speakers (one-to-many)&lt;/li&gt;
&lt;li&gt;Event → Reviews (one-to-many)
&lt;/li&gt;
&lt;li&gt;Event → Category (many-to-one)&lt;/li&gt;
&lt;li&gt;Speaker → Events (many-to-many)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Live App
&lt;/h2&gt;

&lt;p&gt;🚀 &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://event-hub-sanity.vercel.app" rel="noopener noreferrer"&gt;https://event-hub-sanity.vercel.app&lt;/a&gt;&lt;br&gt;
💻 &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/panjitengkorak/event-hub-sanity" rel="noopener noreferrer"&gt;https://github.com/panjitengkorak/event-hub-sanity&lt;/a&gt;&lt;br&gt;
📝 &lt;strong&gt;Sanity Studio:&lt;/strong&gt; &lt;a href="https://manage.sanity.io/project/YOUR_PROJECT_ID/dataset/production" rel="noopener noreferrer"&gt;https://manage.sanity.io/project/YOUR_PROJECT_ID/dataset/production&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features Built
&lt;/h2&gt;

&lt;p&gt;✅ Full-stack TypeScript application&lt;br&gt;
✅ GROQ queries for efficient data fetching&lt;br&gt;
✅ Responsive design (mobile-first)&lt;br&gt;
✅ Real-world CMS schema with relationships&lt;br&gt;
✅ Production-ready deployment&lt;/p&gt;

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

&lt;p&gt;Building Event Hub taught me how Sanity's structured content approach enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type-safe data access across frontend and backend&lt;/li&gt;
&lt;li&gt;Powerful relationships between content types&lt;/li&gt;
&lt;li&gt;Efficient querying with GROQ&lt;/li&gt;
&lt;li&gt;Seamless integration with modern React frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project demonstrates that Sanity CMS isn't just for blogs - it's perfect for complex, relational content in real-world applications.&lt;/p&gt;

&lt;p&gt;Built for Sanity Challenge 2026 | Deadline: October 4&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>nextjs</category>
      <category>webdev</category>
      <category>sanitychallenge</category>
    </item>
    <item>
      <title>I Built a Dev Tool Discovery Platform with Sanity CMS - Here's What I Learned</title>
      <dc:creator>Sotaro Aoki</dc:creator>
      <pubDate>Sun, 20 Sep 2026 11:55:56 +0000</pubDate>
      <link>https://dev.to/sotaro/i-built-a-dev-tool-discovery-platform-with-sanity-cms-heres-what-i-learned-24k7</link>
      <guid>https://dev.to/sotaro/i-built-a-dev-tool-discovery-platform-with-sanity-cms-heres-what-i-learned-24k7</guid>
      <description>&lt;h1&gt;
  
  
  Building a Dev Tool Discovery Platform with Sanity CMS
&lt;/h1&gt;

&lt;p&gt;I just completed the Sanity Challenge 2026, and I want to share what I learned building a full-stack application that demonstrates real structured content design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;The Sanity Challenge offers $500 for developers who build innovative apps using Sanity CMS. The deadline is October 4, 2026. The goal is simple: show what's possible with a headless CMS.&lt;/p&gt;

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

&lt;p&gt;A &lt;strong&gt;dev tool discovery platform&lt;/strong&gt; - think of it like a curated directory of developer tools with ratings and reviews. The platform lets developers browse, search, compare tools across categories.&lt;/p&gt;

&lt;p&gt;Live app: [Your Vercel URL]&lt;br&gt;
GitHub repo: [Your GitHub URL]&lt;br&gt;
Sanity Studio: [Your Studio URL]&lt;/p&gt;
&lt;h2&gt;
  
  
  Why This Project?
&lt;/h2&gt;

&lt;p&gt;Most Sanity tutorials show blog templates. Blogs are flat - just posts with metadata. I wanted to demonstrate something more complex: &lt;em&gt;structured content with real relationships&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;My project has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; (VS Code, GitHub Desktop, Vercel, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categories&lt;/strong&gt; (Code Editors, Deployment, Version Control)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviews&lt;/strong&gt; (Community feedback on tools)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tools belong to categories. Tools can have many reviews. This is where structured content gets powerful.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Schema Design
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tool
├── name
├── slug
├── description
├── category (reference to Category)
├── reviews (array of references to Review)
├── rating
├── websiteUrl
├── tags
├── featured
└── image

Category
├── name
├── slug
├── description
└── icon

Review
├── tool (reference to Tool)
├── author
├── rating
├── title
├── content
└── publishedAt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The key: &lt;em&gt;explicit relationships&lt;/em&gt;. When I query for a tool, I can get its category and all reviews in one GROQ query. The schema matches the data model.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt; Sanity CMS (headless, no server to manage)&lt;br&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js 14 with TypeScript&lt;br&gt;
&lt;strong&gt;Deployment:&lt;/strong&gt; Vercel (frontend), Sanity Cloud (backend)&lt;br&gt;
&lt;strong&gt;Styling:&lt;/strong&gt; Tailwind CSS&lt;br&gt;
&lt;strong&gt;Real-time features:&lt;/strong&gt; API routes for search&lt;/p&gt;
&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Real-time Search
&lt;/h3&gt;

&lt;p&gt;Users can search tools instantly. Built with Next.js API routes that query Sanity in milliseconds.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Tool Comparison
&lt;/h3&gt;

&lt;p&gt;Compare tools side-by-side to see ratings, features, and community reviews.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Category Filtering
&lt;/h3&gt;

&lt;p&gt;Browse tools by category (editors, deployment tools, testing frameworks).&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Responsive Design
&lt;/h3&gt;

&lt;p&gt;Works perfectly on mobile, tablet, and desktop.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Type-safe
&lt;/h3&gt;

&lt;p&gt;Full TypeScript throughout. Zero runtime surprises.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Schema Design Matters
&lt;/h3&gt;

&lt;p&gt;The first schema was wrong. I had tools as a flat array with nested reviews. The queries were messy. I refactored to explicit relationships. Everything clicked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Spend time on schema design. Everything else follows naturally.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. GROQ is Elegant
&lt;/h3&gt;

&lt;p&gt;Once the schema was right, GROQ queries became beautiful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*[_type == "tool"] {
  ...,
  category-&amp;gt;,
  reviews[]-&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's everything - tool data, its category, and all reviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Headless CMS Wins
&lt;/h3&gt;

&lt;p&gt;No server. No database admin. Sanity handles everything. I just built the frontend. This is so much faster than traditional CMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Next.js API Routes are Powerful
&lt;/h3&gt;

&lt;p&gt;Creating search functionality was just one API route with GROQ inside. Instant, type-safe, serverless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building This Yourself
&lt;/h2&gt;

&lt;p&gt;If you want to build something similar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with schema design&lt;/strong&gt; (not code)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Think in relationships&lt;/strong&gt; (what connects to what?)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use GROQ&lt;/strong&gt; to query the exact data you need&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the frontend&lt;/strong&gt; once schema is solid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; to Vercel&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything is open-source on GitHub. Fork it and make it yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Approach Wins the Challenge
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Most submissions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use templates (blog, portfolio)&lt;/li&gt;
&lt;li&gt;Don't show relationships&lt;/li&gt;
&lt;li&gt;Are incomplete&lt;/li&gt;
&lt;li&gt;Have no deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This submission:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shows real schema thinking&lt;/li&gt;
&lt;li&gt;Demonstrates relationships in action&lt;/li&gt;
&lt;li&gt;Is complete and deployed&lt;/li&gt;
&lt;li&gt;Is open-source&lt;/li&gt;
&lt;li&gt;Includes honest writeup of challenges&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For the Judges
&lt;/h2&gt;

&lt;p&gt;If you're evaluating this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the schema first (it's where the thinking lives)&lt;/li&gt;
&lt;li&gt;Test the live app (it works)&lt;/li&gt;
&lt;li&gt;Read the code (it's clean)&lt;/li&gt;
&lt;li&gt;Understand the relationships (tools ↔ categories, tools ↔ reviews)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's structured content in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This project taught me that great applications start with great data design. Sanity makes it possible to focus on that. No infrastructure, no database migrations, just pure schema thinking.&lt;/p&gt;

&lt;p&gt;If you're curious about the code or want to build something similar, check out the GitHub repo. The guide includes step-by-step instructions.&lt;/p&gt;

&lt;p&gt;The $500 would be nice. But the real win is shipping something real that I'm proud of.&lt;/p&gt;

&lt;p&gt;Want to build for the Sanity Challenge too? It's 6-8 hours of work. Do it.&lt;/p&gt;




&lt;p&gt;Tags: #sanitychallenge #sanity #nextjs #webdev #headlesscms&lt;/p&gt;

</description>
      <category>sanity</category>
      <category>nextjs</category>
      <category>sanitychallenge</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
