<?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: Gustavo Sanchez</title>
    <description>The latest articles on DEV Community by Gustavo Sanchez (@gusanchedev).</description>
    <link>https://dev.to/gusanchedev</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%2F456876%2F03c3972a-7109-4b9d-a22b-c3deab1ec7fa.png</url>
      <title>DEV Community: Gustavo Sanchez</title>
      <link>https://dev.to/gusanchedev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gusanchedev"/>
    <language>en</language>
    <item>
      <title>I Built My Wife a Patient English Idiom Coach That Runs on Our Laptop</title>
      <dc:creator>Gustavo Sanchez</dc:creator>
      <pubDate>Sun, 04 Oct 2026 00:15:40 +0000</pubDate>
      <link>https://dev.to/gusanchedev/i-built-my-wife-a-patient-english-idiom-coach-that-runs-on-our-laptop-2m0h</link>
      <guid>https://dev.to/gusanchedev/i-built-my-wife-a-patient-english-idiom-coach-that-runs-on-our-laptop-2m0h</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 wife Eugenia moved to San Francisco and is learning English. She's at an &lt;strong&gt;A2 (elementary) level&lt;/strong&gt;, and the thing that trips her up most isn't grammar, it's &lt;strong&gt;idioms&lt;/strong&gt;. People here say "it's a piece of cake", "let's call it a day" and "I'm under the weather" all the time, and a textbook doesn't teach you how to &lt;em&gt;say&lt;/em&gt; them.&lt;/p&gt;

&lt;p&gt;So I built her &lt;strong&gt;IdiomsMasterGirl&lt;/strong&gt;: a patient practice partner that teaches &lt;strong&gt;one common idiom a day&lt;/strong&gt; and then listens to her use it.&lt;/p&gt;

&lt;p&gt;A daily session takes under 20 minutes (her first full session did) and has six steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Learn&lt;/strong&gt; – the idiom, its meaning in simple English, a "when to use it" note, a literal-vs-figurative note and 3 example sentences. Every line has a ▶ button with a natural American voice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speak (try 1)&lt;/strong&gt; – she records the idiom and each example. She gets &lt;strong&gt;accuracy&lt;/strong&gt;, &lt;strong&gt;clarity&lt;/strong&gt; and a &lt;strong&gt;combined score&lt;/strong&gt;, plus 1–3 warm tips that name the exact word that went wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speak (try 2)&lt;/strong&gt; – she records everything again and sees &lt;strong&gt;+N points&lt;/strong&gt; next to each line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write&lt;/strong&gt; – she writes 3 sentences of her own. Each one gets an idiom-use grade, a grammar grade, a corrected version with the change highlighted, and a short &lt;em&gt;why&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read aloud&lt;/strong&gt; – she listens to her corrected sentences, then reads them and gets scored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score&lt;/strong&gt; – a daily score, a 30-day chart and a 🔥 streak.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The part I care most about is the feedback. It's never "wrong". It's &lt;em&gt;"I heard “banana” for “school”. Say 'school' slowly. Make sure you say the 'oo' sound clearly."&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;The app runs on our laptop on purpose: the speech recognition, the tutor model and her recordings all stay there. So there's no hosted link. The screenshots below are the real app on a demo account. To keep anyone's real voice out of the post, the recordings were made with the Mac's built-in voice, with one word swapped on purpose so you can see a tip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn&lt;/strong&gt; — today's idiom, with a play button on every line:&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%2Fskdtkh3imnj9jpd7oyhd.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%2Fskdtkh3imnj9jpd7oyhd.png" alt="Learn step: the idiom " width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speak&lt;/strong&gt; — every recording is scored, and the tips name the actual words:&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%2Flqzfgej2sqvr8xveo03v.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%2Flqzfgej2sqvr8xveo03v.png" alt="Speak step: a score of 92 with accuracy and clarity, a " width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write&lt;/strong&gt; — her own sentences, corrected with the smallest change and a reason:&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%2F2nl1t86ipvdih0p4e17i.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%2F2nl1t86ipvdih0p4e17i.png" alt="Write step: " width="800" height="592"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progress&lt;/strong&gt; — a streak and a 30-day chart, to keep her coming back:&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%2Fdk90xvhk19ji2x53fo81.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%2Fdk90xvhk19ji2x53fo81.png" alt="Progress page: a 5-day streak, 14 days practiced, an average of 79, and a line chart of daily scores" width="800" height="342"&gt;&lt;/a&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/gusanchefullstack" rel="noopener noreferrer"&gt;
        gusanchefullstack
      &lt;/a&gt; / &lt;a href="https://github.com/gusanchefullstack/idioms-master-girl" rel="noopener noreferrer"&gt;
        idioms-master-girl
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      One English idiom a day – learn it, say it, write it. A local-first practice partner built for my wife (Gemma via Ollama + faster-whisper).
    &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;IdiomsMasterGirl 💬&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;One English idiom a day: learn it, hear it, say it, write it, and read it aloud.&lt;/strong&gt; A
local-first practice partner for an A2 English learner. It listens to her speak, tells her
kindly which exact words to fix, and corrects her own sentences. The AI runs on the laptop
so her voice and mistakes stay private.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/b8cadaa967891081f8f165695470689986c028821dd8a040132f6e661795dc0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c7565"&gt;&lt;img src="https://camo.githubusercontent.com/b8cadaa967891081f8f165695470689986c028821dd8a040132f6e661795dc0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c7565" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3e592dc9fab467e9454644992f27f0167f4a06b2d6a93de584c64dcff938ed81/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e6f64652d25334525334432362d3333393933333f6c6f676f3d6e6f6465646f746a73266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/3e592dc9fab467e9454644992f27f0167f4a06b2d6a93de584c64dcff938ed81/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e6f64652d25334525334432362d3333393933333f6c6f676f3d6e6f6465646f746a73266c6f676f436f6c6f723d7768697465" alt="Node.js"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/acb4bcc287a08738500b5c70aba1a5ae3eb70cf2c06a914cecdf5f8038d5af5b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31322d3337373641423f6c6f676f3d707974686f6e266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/acb4bcc287a08738500b5c70aba1a5ae3eb70cf2c06a914cecdf5f8038d5af5b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31322d3337373641423f6c6f676f3d707974686f6e266c6f676f436f6c6f723d7768697465" alt="Python"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/8c3ae444958c6a370b6960696d0ec2242e61aad90d2137ab4b793153ee0892ee/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6578742e6a732d31362d3030303030303f6c6f676f3d6e657874646f746a73266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/8c3ae444958c6a370b6960696d0ec2242e61aad90d2137ab4b793153ee0892ee/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6578742e6a732d31362d3030303030303f6c6f676f3d6e657874646f746a73266c6f676f436f6c6f723d7768697465" alt="Next.js"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/a404566f2cdad6daeddac889c0185b98fab7b669388f6c01f238c04f9261e595/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f6c6c616d612d47656d6d61253230342532304532422d3030303030303f6c6f676f3d6f6c6c616d61266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/a404566f2cdad6daeddac889c0185b98fab7b669388f6c01f238c04f9261e595/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f6c6c616d612d47656d6d61253230342532304532422d3030303030303f6c6f676f3d6f6c6c616d61266c6f676f436f6c6f723d7768697465" alt="Ollama"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/a94d5573ebd0e2b7219fff405dca6c320d6c17111d132c379f58399e626015d1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3535253230756e697425323025324225323034253230736d6f6b652d627269676874677265656e"&gt;&lt;img src="https://camo.githubusercontent.com/a94d5573ebd0e2b7219fff405dca6c320d6c17111d132c379f58399e626015d1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3535253230756e697425323025324225323034253230736d6f6b652d627269676874677265656e" alt="Tests"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ccb60519979fec500ff4014c14e40fcd15700cc48f15f04299f53347a76a06c1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275696c74253230776974682d537065632532304b69742d366634326331"&gt;&lt;img src="https://camo.githubusercontent.com/ccb60519979fec500ff4014c14e40fcd15700cc48f15f04299f53347a76a06c1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6275696c74253230776974682d537065632532304b69742d366634326331" alt="Spec Kit"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/06584450b37ef9f4988fdd0b12c3c949db2668886f72551239191d3b6a916751/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6173742d636f6d6d69742f677573616e63686566756c6c737461636b2f6964696f6d732d6d61737465722d6769726c"&gt;&lt;img src="https://camo.githubusercontent.com/06584450b37ef9f4988fdd0b12c3c949db2668886f72551239191d3b6a916751/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6173742d636f6d6d69742f677573616e63686566756c6c737461636b2f6964696f6d732d6d61737465722d6769726c" alt="Last commit"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/gusanchefullstack/idioms-master-girl/assets/screenshots/02-speak.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%2Fgusanchefullstack%2Fidioms-master-girl%2FHEAD%2Fassets%2Fscreenshots%2F02-speak.png" alt="The Speak step: each recording gets accuracy and clarity scores, the words that were missed or unclear, and a warm tip"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#why-this-exists" rel="noopener noreferrer"&gt;Why this exists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#how-it-works" rel="noopener noreferrer"&gt;How it works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#quick-start" rel="noopener noreferrer"&gt;Quick start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#configuration" rel="noopener noreferrer"&gt;Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#api-reference" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#project-structure" rel="noopener noreferrer"&gt;Project structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#tests" rel="noopener noreferrer"&gt;Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#roadmap" rel="noopener noreferrer"&gt;Roadmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#what-i-learned" rel="noopener noreferrer"&gt;What I learned&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#open-models-privacy-and-why-open-matters" rel="noopener noreferrer"&gt;Open models, privacy and why open matters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#known-limitations" rel="noopener noreferrer"&gt;Known limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#credits" rel="noopener noreferrer"&gt;Credits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#screenshots" rel="noopener noreferrer"&gt;Screenshots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gusanchefullstack/idioms-master-girl#author" rel="noopener noreferrer"&gt;Author&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this exists&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;I built this for &lt;strong&gt;Eugenia&lt;/strong&gt;, my wife. She is learning English in San Francisco at an A2
(elementary) level. Idioms are everywhere in American life ("it's a piece of cake", "let's call
it a day"), and they are hard to learn from a textbook.&lt;/p&gt;
&lt;p&gt;General apps don't fit her well. Flashcard…&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/gusanchefullstack/idioms-master-girl" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It's MIT-licensed. Setup is four commands (&lt;code&gt;nvm use&lt;/code&gt;, &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;npm run setup&lt;/code&gt;, &lt;code&gt;npm run dev&lt;/code&gt;). The README covers configuration, the API and what I learned along the way.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome (localhost)
  │  records webm/opus · falls back to the Mac's on-device voice
  ▼
Next.js 16 API routes
  ├── scoring ───► pure TypeScript: word alignment (WER), clarity, problem words
  ├── tutor ─────► Ollama + Gemma 4 E2B (4.6B, Apache 2.0), JSON-schema output
  ├── listening ─► Python sidecar: FastAPI + faster-whisper small.en
  ├── voice ─────► ElevenLabs (opt-in, text only, cached on disk)
  └── storage ───► SQLite via Node 26's built-in node:sqlite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Gemma is the tutor.&lt;/strong&gt; It writes the 3 example sentences for each day's idiom, the coaching hints, and the grades and corrections for her sentences. Every call uses Ollama's JSON-schema &lt;code&gt;format&lt;/code&gt;, and the code then &lt;strong&gt;validates&lt;/strong&gt; the answer: an example must contain the idiom and have at most 15 words, a correction must keep the idiom, and an explanation must be 40 words or fewer. A failed answer is retried once, then falls back honestly. The app never shows an invented grade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whisper is the ear.&lt;/strong&gt; &lt;a href="https://github.com/SYSTRAN/faster-whisper" rel="noopener noreferrer"&gt;faster-whisper&lt;/a&gt; gives a &lt;strong&gt;confidence for every word&lt;/strong&gt;, which is the whole scoring model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy&lt;/strong&gt; = &lt;code&gt;100 × (1 − word error rate)&lt;/code&gt; against the target sentence (case, punctuation and contractions ignored).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarity&lt;/strong&gt; = the average word confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combined&lt;/strong&gt; = 70% accuracy + 30% clarity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Problem words&lt;/strong&gt; = words that were missed, replaced, or heard with less than 0.6 confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;code&gt;temperature=0.0&lt;/code&gt; and &lt;code&gt;condition_on_previous_text=False&lt;/code&gt;, the same recording gets the same score every time. In my tests it was identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lesson that changed the design
&lt;/h3&gt;

&lt;p&gt;My first version asked Gemma to write the tips. The tips were warm but vague, full of other idioms ("don't let tricky words get under your skin!"). In my first test, &lt;strong&gt;0 of 4&lt;/strong&gt; named the word that was actually missed.&lt;/p&gt;

&lt;p&gt;The fix was to &lt;strong&gt;split the job&lt;/strong&gt;. The code writes the facts it already knows, and Gemma writes only the coaching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** The factual part of a tip is written in code, so every tip names a real word. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;leadFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProblemWord&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;substituted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`I heard “&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heard&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;” for “&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;”.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;missing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`I didn't hear “&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;”.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unclear&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`“&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;word&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;” was a little unclear.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// final tip = leadFor(word) + " " + gemmaHint(word)   // e.g. "Make the 'oo' sound long."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that change, in a check of &lt;strong&gt;20 recordings, 20 of 20 tips named the right word&lt;/strong&gt;, and 18 of 20 hints were fully written by Gemma. When a recording is clean, there's no model call at all, just praise, so it never invents a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measured on a MacBook (M4):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Score and tips after a recording:&lt;/strong&gt; 1.1–2.9 s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Today's examples:&lt;/strong&gt; about 2.6 s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saved audio replay:&lt;/strong&gt; 17 ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grammar corrections:&lt;/strong&gt; 13 of 15 known errors fixed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I planned and built it spec-first with &lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;GitHub Spec Kit&lt;/a&gt;. The spec, plan, research, contracts and tasks are all in the repo under &lt;code&gt;specs/&lt;/code&gt;, with Claude Code as my pair programmer.&lt;/p&gt;

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

&lt;p&gt;For this project, open isn't a nice extra. It's the reason it works for her.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Her mistakes stay hers.&lt;/strong&gt; A learner's recordings are personal: the hesitations, the wrong words, the accent. With an open-weight model and open speech recognition running locally, &lt;strong&gt;her voice never leaves our laptop&lt;/strong&gt;. Recordings go to a helper on &lt;code&gt;127.0.0.1&lt;/code&gt;, are scored in memory and are never saved. There's no telemetry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's built to work offline.&lt;/strong&gt; The tutor, the speech recognition and the database all run locally, and the speech helper is started in offline mode. Only new natural-voice audio needs the internet, and saved clips plus the Mac's own voice cover that. (A full run with Wi-Fi switched off is next on my list.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's tuned to one person.&lt;/strong&gt; The prompts are Markdown files, the idioms are a curated JSON list I control, and the model is one line in &lt;code&gt;config/settings.json&lt;/code&gt;. Changing the tutor's personality ("warm, patient, playful big-sister tutor") is editing a text file. Grounding it in a &lt;strong&gt;curated list of 100 real A2–B1 idioms&lt;/strong&gt; means it can't invent fake idioms, which is a real risk with a 4.6B model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; There's no per-session API bill for something meant to be used every day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where open beat closed:&lt;/strong&gt; the scoring needs &lt;strong&gt;per-word confidence&lt;/strong&gt; and &lt;strong&gt;repeatable&lt;/strong&gt; results. Open Whisper gives both directly. A closed speech API would hide the confidences, or change them under me without notice. Being able to read and change every prompt also let me fix the vague-tips problem in an evening.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one non-open piece is the optional ElevenLabs voice, and I kept it honest. It's &lt;strong&gt;off by default&lt;/strong&gt;, it only ever receives the sentence to speak (never her audio or answers), and each sentence is generated &lt;strong&gt;once&lt;/strong&gt; and cached.&lt;/p&gt;

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

&lt;p&gt;I built this with Claude Code, following the Spec Kit flow from constitution to spec, plan, tasks and implementation.&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 (via Ollama) is the tutor. It writes the daily examples, the pronunciation coaching and the writing grades and corrections, all with schema-constrained output that the code validates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; – the natural American voice she imitates. It's used in a privacy-respecting way: opt-in, text only, cached per sentence, with an on-device fallback when it's off or offline.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This post was written with AI assistance (Claude Code) and checked by me before publishing.&lt;/em&gt;&lt;/p&gt;

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