<?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: Niccolò Mascaro</title>
    <description>The latest articles on DEV Community by Niccolò Mascaro (@mascarock).</description>
    <link>https://dev.to/mascarock</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%2F4160118%2F91ccab6b-40c2-4523-a414-0aef3b8aa70a.png</url>
      <title>DEV Community: Niccolò Mascaro</title>
      <link>https://dev.to/mascarock</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mascarock"/>
    <language>en</language>
    <item>
      <title>I built Salt Wharf, a callboard where the later posted sheet wins</title>
      <dc:creator>Niccolò Mascaro</dc:creator>
      <pubDate>Sun, 04 Oct 2026 18:03:58 +0000</pubDate>
      <link>https://dev.to/mascarock/i-built-salt-wharf-a-callboard-where-the-later-posted-sheet-wins-4e5g</link>
      <guid>https://dev.to/mascarock/i-built-salt-wharf-a-callboard-where-the-later-posted-sheet-wins-4e5g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path Two: Vibe-Code Something Strange&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built Salt Wharf as a stage-door callboard for a fictional Valletta theatre company in tech week on &lt;em&gt;The Last Luzzu&lt;/em&gt;. Eight people, ten scenes. The public page is meant to feel like the sheet taped beside the stage door. It answers one question for tonight: who is actually called.&lt;/p&gt;

&lt;p&gt;The story is 4 October 2026. Two posted call sheets exist for that night. The earlier one still names Mara Camilleri as Rosa. The later one supersedes it and posts Lina Borg because Camilleri is off. The public door shows Borg and says why. The earlier sheet stays in the book, marked replaced.&lt;/p&gt;

&lt;p&gt;I did not want to overwrite history just to get the correct door. Both sheets stay as documents. The later posted sheet points at the earlier one with &lt;code&gt;supersedes&lt;/code&gt;. The door prints the posted sheet for that date that no other posted sheet supersedes.&lt;/p&gt;

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

&lt;p&gt;The public door is live, in fixture mode, with no login: &lt;a href="https://saltwharf.vibefy.net/" rel="noopener noreferrer"&gt;https://saltwharf.vibefy.net/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Walkthrough, with Lina Borg on the door and the earlier Mara Camilleri sheet marked replaced: &lt;a href="https://github.com/mascarock/salt-wharf/blob/main/docs/proof/salt-wharf-walkthrough.mp4" rel="noopener noreferrer"&gt;https://github.com/mascarock/salt-wharf/blob/main/docs/proof/salt-wharf-walkthrough.mp4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That page is not reading dataset &lt;code&gt;production&lt;/code&gt; on project &lt;code&gt;ebwymj6z&lt;/code&gt;. With no project id set, the build serves the committed seed. One visitor's backstage moves stay in that visitor's cookie.&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%2Fctnom25aw1oau6us3bgg.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%2Fctnom25aw1oau6us3bgg.png" alt="Public door: Lina Borg is Rosa" width="800" height="846"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How to judge it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://saltwharf.vibefy.net/" rel="noopener noreferrer"&gt;https://saltwharf.vibefy.net/&lt;/a&gt;. Lina Borg is Rosa. The slip says a later sheet replaced Mara Camilleri. Camilleri is not called. The book keeps the earlier sheet, marked replaced.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;/backstage&lt;/code&gt; and sign in as &lt;code&gt;elena&lt;/code&gt; / &lt;code&gt;callboard&lt;/code&gt;. This is a fake contest gate. It does not protect Sanity, GitHub, or any real system.&lt;/li&gt;
&lt;li&gt;Cover finder, role Rosa, night 4 October. Borg can cover. Vella fails range. Galea fails a concurrent scene. Camilleri fails the date. Micallef fails because they are already covering. Azzopardi fails a skill.&lt;/li&gt;
&lt;li&gt;Advance the 5 October draft. Each move writes a &lt;code&gt;workflowTransition&lt;/code&gt;. Posting it does not change 4 October, because supersedes is per date. The moves live in your browser only. Put the book back restores the committed story.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run verify&lt;/code&gt; asserts the door and the cover checks against the seed. The log is &lt;code&gt;docs/proof/verify.txt&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&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%2Fsrmt481aeiqle55bpb8v.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%2Fsrmt481aeiqle55bpb8v.png" alt="Backstage sheet stack" width="800" height="1316"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mascarock/salt-wharf
&lt;span class="nb"&gt;cd &lt;/span&gt;salt-wharf
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://127.0.0.1:43174" rel="noopener noreferrer"&gt;http://127.0.0.1:43174&lt;/a&gt;. No environment variables are required.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/mascarock/salt-wharf" rel="noopener noreferrer"&gt;https://github.com/mascarock/salt-wharf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Longer notes than this post: &lt;code&gt;BUILD.md&lt;/code&gt; in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Build Process
&lt;/h2&gt;

&lt;p&gt;I built this in Cursor as a Next.js app with Sanity behind it. I am not attaching a prompt transcript. The decisions that took the longest are the ones below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One role, one scene.&lt;/strong&gt; I kept a role as a character track with one scene, not "Rosa in every scene she plays." Splitting Rosa into Kitchen-Rosa, Storm-Rosa, and Finale-Rosa would make the call sheet honest about the book and ruin the cover story: Borg would already hold three tracks and fail "already covering two." &lt;code&gt;runsConcurrentWith&lt;/code&gt; is stored on the scene, in both directions, and the function also walks incoming edges, because a one-way link is easy to forget in Studio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coverage is computed.&lt;/strong&gt; There is no &lt;code&gt;cover&lt;/code&gt; document type. A later sheet may annotate an item with &lt;code&gt;note: "cover"&lt;/code&gt;. That is a house note after a decision, not the source of truth. The check is five predicates: range contains the role's required range, skills cover the role's skills, not cast or called in a concurrent scene, not unavailable that night, not already covering two other roles. Pawlu Galea fails Rosa only because he is cast as the Clerk in the harbour office, which runs with the kitchen. He has the range and the skills. A stored understudy list would have hidden that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow is documents, not the Workflows product.&lt;/strong&gt; A &lt;code&gt;workflowTransition&lt;/code&gt; records from, to, who, when, and why. Legal pairs are &lt;code&gt;draft → stageManagerReview → posted → struck&lt;/code&gt;, plus &lt;code&gt;posted → draft&lt;/code&gt; only with a note. Returning a posted sheet to draft takes it out of the posted set, so it stops hiding the sheet it superseded. I did not add a second mechanism for that. I also did not take a dependency on Sanity Workflows, the product. This is workflow-as-data next to the content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No App SDK app.&lt;/strong&gt; I read the current quickstart. A Dashboard app needs &lt;code&gt;sanity deploy&lt;/code&gt; and Dashboard auth. There is no App SDK deploy on this project, and I did not wrap unused SDK hooks to pretend there is one. What does run after an import is the TypeScript schema, a custom Studio structure (call sheets by status, the company graph, the transition log), and a Callboard tool that resolves the same standing sheet as the public door. In fixture mode, &lt;code&gt;/studio&lt;/code&gt; stays parked instead of booting Studio against a missing env.&lt;/p&gt;

&lt;p&gt;Skills stayed closed string ids, shared by people and roles, because a skill document would have made the seed harder to import. Vocal range is a pair of pitches, not the word "soprano." Containment is the math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project ID: &lt;code&gt;ebwymj6z&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Organization: &lt;code&gt;o1r4ucepz&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dataset: &lt;code&gt;production&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Seed the fixture and the import use: &lt;code&gt;sanity/seed.ndjson&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Schema: &lt;code&gt;sanity/schema&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The live door does not write to that dataset. Live writes need a local write token, and that token is not in the repo.&lt;/p&gt;

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

&lt;p&gt;I did not attach an agent transcript. There isn't one in the repo. The rerunnable proof is the live door, the five steps above, and &lt;code&gt;npm run verify&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>I built a harbour desk that refuses the old book</title>
      <dc:creator>Niccolò Mascaro</dc:creator>
      <pubDate>Sun, 04 Oct 2026 18:03:14 +0000</pubDate>
      <link>https://dev.to/mascarock/i-built-a-harbour-desk-that-refuses-the-old-book-396l</link>
      <guid>https://dev.to/mascarock/i-built-a-harbour-desk-that-refuses-the-old-book-396l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path One: Ship an Agent That Queries Real Content&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built Sounding, a tiny day-boat harbour desk, because I wanted the failure mode to feel concrete. Not "the chatbot hallucinated", but "a skipper searched the harbour name and got the old pocket note."&lt;/p&gt;

&lt;p&gt;The data is invented and is not for navigation. Eight central Mediterranean harbours, 140 seed documents. The desk walks relationships. A keyword hit on the harbour name is the wrong answer on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a keyword search returns
&lt;/h2&gt;

&lt;p&gt;Three cases, from the committed judge log (&lt;code&gt;docs/proof/judge.txt&lt;/code&gt;). All three verdicts are no-go. A harbour-name search opens the 2019 pocket note and stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marsamxett.&lt;/strong&gt; Friday after 18:00, 1.7 m draft, gregale expected. Keyword search for "Marsamxett" opens ALM-2019-MARSA-PONTOON and repeats the 2019 sheet: inner pontoons 2.1 m, visitors welcome. The walk does not stop there. HN-2024-17 supersedes that note and records 1.4 m on the inner fingers after winter silt. HN-2025-03 cites that notice and reserves Outer North and Outer South for the ferry on Fridays after 18:00. The gregale rule says those same two berths are the only sheltered visitor berths at 20 knots or more. The recommendation is no safe visitor berth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Syracuse.&lt;/strong&gt; Inner Basin East, 2.8 m draft. Keyword search opens ALM-2019-SYR-BASIN, the 2019 3.5 m note. The walk opens HN-2025-11, which cites silt survey SS-2025-02 (2.2 m) and supersedes the book. The wall is closed to drafts over 2.0 m. The survey matters only because the notice cites it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pozzallo.&lt;/strong&gt; Commercial quay, 2.9 m draft. Keyword search opens the 2019 commercial note. Standing depth is 2.8 m from HN-2024-44, which supersedes that note. HN-2025-08 is later and claims 3.2 m from an informal lead-line, and it does not supersede HN-2024-44, so 3.2 m is not standing. Latest document does not win.&lt;/p&gt;

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

&lt;p&gt;The desk is live, in fixture mode, with no login: &lt;a href="https://sounding.vibefy.net/" rel="noopener noreferrer"&gt;https://sounding.vibefy.net/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That page is the same fixture the judge runs locally. It does not call knowledge base &lt;code&gt;kbl6C1tJBXli&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Walkthrough, with the 2019 2.1 m note and HN-2024-17 at 1.4 m both on screen: &lt;a href="https://github.com/mascarock/sounding/blob/main/docs/proof/sounding-walkthrough.mp4" rel="noopener noreferrer"&gt;https://github.com/mascarock/sounding/blob/main/docs/proof/sounding-walkthrough.mp4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Judges can also run it locally with no token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mascarock/sounding
&lt;span class="nb"&gt;cd &lt;/span&gt;sounding
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://127.0.0.1:43173" rel="noopener noreferrer"&gt;http://127.0.0.1:43173&lt;/a&gt;. Leave the env file empty. Then &lt;code&gt;npm run judge&lt;/code&gt;. The log above is that command.&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%2Fwkf9qd4hoheokzioegda.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%2Fwkf9qd4hoheokzioegda.png" alt="Sounding desk: no safe visitor berth at Marsamxett" width="800" height="1444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk2bwm90els7x8zhw7sd7.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%2Fk2bwm90els7x8zhw7sd7.png" alt="Sounding desk on a phone-width screen" width="430" height="3600"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/mascarock/sounding" rel="noopener noreferrer"&gt;https://github.com/mascarock/sounding&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Sanity
&lt;/h2&gt;

&lt;p&gt;The Sanity project is &lt;code&gt;59vrectd&lt;/code&gt;, dataset &lt;code&gt;production&lt;/code&gt;, organization &lt;code&gt;o1r4ucepz&lt;/code&gt;, knowledge base &lt;code&gt;kbl6C1tJBXli&lt;/code&gt;. That knowledge base is the project's. The published proof does not call it.&lt;/p&gt;

&lt;p&gt;What a judge actually runs is fixture mode. It reads the committed corpus in &lt;code&gt;sanity/seed-data.ts&lt;/code&gt; through four tools: &lt;code&gt;initial_context&lt;/code&gt;, &lt;code&gt;schema_explorer&lt;/code&gt;, &lt;code&gt;groq_query&lt;/code&gt;, and &lt;code&gt;array_field_reader&lt;/code&gt;. No read token and no model key are in the repo. The screenshots and &lt;code&gt;npm run judge&lt;/code&gt; are that path. A local read token can point the same walker at project &lt;code&gt;59vrectd&lt;/code&gt;. I am not claiming the knowledge base resolved Marsamxett.&lt;/p&gt;

&lt;p&gt;The desk can also read a live wind from Open-Meteo and match it to a weather rule. The published judge states the wind in the question, so that log is not a weather-API transcript. The gregale rule fires from the wind in the question, not from "whichever document is newest."&lt;/p&gt;

&lt;p&gt;The deterministic walker is enough to judge. If a local model key is present, a model can call the same tools. The useful part is not the model. It is that the answer has to show the walk, and the old book loses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project ID: &lt;code&gt;59vrectd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Organization: &lt;code&gt;o1r4ucepz&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dataset: &lt;code&gt;production&lt;/code&gt; (public)&lt;/li&gt;
&lt;li&gt;Knowledge base: &lt;code&gt;kbl6C1tJBXli&lt;/code&gt; (named on the project; the published judge does not call it)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I did not attach an agent transcript. There isn't one in the repo. The proof a judge can rerun is &lt;code&gt;npm run judge&lt;/code&gt; and the two screenshots above.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>A private budget chat for Paola, checked by local Gemma</title>
      <dc:creator>Niccolò Mascaro</dc:creator>
      <pubDate>Sun, 04 Oct 2026 11:46:06 +0000</pubDate>
      <link>https://dev.to/mascarock/a-private-budget-chat-for-paola-checked-by-local-gemma-iec</link>
      <guid>https://dev.to/mascarock/a-private-budget-chat-for-paola-checked-by-local-gemma-iec</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;I built Local Computer for Paola, a marketer who wants to see spending against an approved budget without those numbers leaving her Mac.&lt;/p&gt;

&lt;p&gt;She types to one of five assistants. Ledger reads the CSV. Clerk logs one spend on a line that already exists. Watcher names a line only when the spend is over the approval, and that comparison is ordinary code, not a model guess. Remainder shows approved, spent, and what is left. Note asks a local copy of Gemma for one sentence, and the app drops that sentence if it names the wrong item or a number that is not in the tool result.&lt;/p&gt;

&lt;p&gt;The demo on GitHub is labeled sample data, not her real budget. In that sample the approved total stays 16,700. The only line over budget is the Q4 trade fair, by 200.&lt;/p&gt;

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

&lt;p&gt;The app is Mac-only. It binds to 127.0.0.1 and there is no hosted demo, on purpose.&lt;/p&gt;

&lt;p&gt;Color walkthrough, about 65 seconds: &lt;a href="https://github.com/mascarock/friend-practice/blob/main/docs/Local-Computer-color-walkthrough.mp4" rel="noopener noreferrer"&gt;Local-Computer-color-walkthrough.mp4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Direct file: &lt;a href="https://github.com/mascarock/friend-practice/raw/main/docs/Local-Computer-color-walkthrough.mp4" rel="noopener noreferrer"&gt;https://github.com/mascarock/friend-practice/raw/main/docs/Local-Computer-color-walkthrough.mp4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watcher, on the sample, naming only the line that is over:&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%2Fcm0ek90gbdy3opexjkt7.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%2Fcm0ek90gbdy3opexjkt7.png" alt="Watcher saying the Q4 trade fair is over by 200" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note, after the sentence was checked against the tool:&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%2F9l1dm0iadg8i146z50vc.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%2F9l1dm0iadg8i146z50vc.png" alt="Note with the checked sentence and the locked totals" width="800" height="575"&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/mascarock" rel="noopener noreferrer"&gt;
        mascarock
      &lt;/a&gt; / &lt;a href="https://github.com/mascarock/friend-practice" rel="noopener noreferrer"&gt;
        friend-practice
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Local Budget&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A local budget app for Paola in marketing. Import an approved CSV, record spending on this computer, and see the remaining balance immediately. Approved amounts are read-only.&lt;/p&gt;
&lt;p&gt;The interface uses a near-black background, white type, large totals, thin rules, and open space. All interface copy, validation messages, sample data, and Gemma instructions are in English.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Context&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Local Computer is a Mac-only budget companion for a marketer (Paola) who wants spend checked against an approved budget without the numbers leaving the computer. The demo uses labeled sample data, not her real budget. The approved total in the sample stays 16,700.&lt;/p&gt;
&lt;p&gt;Five assistants, each a chat:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ledger reads the CSV.&lt;/li&gt;
&lt;li&gt;Clerk logs one spend on an existing line.&lt;/li&gt;
&lt;li&gt;Watcher names a line only when spent is above approved, and that math is code.&lt;/li&gt;
&lt;li&gt;Remainder shows approved, spent, and remaining.&lt;/li&gt;
&lt;li&gt;Note asks local Gemma (Ollama on 127.0.0.1) for one sentence and…&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/mascarock/friend-practice" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/mascarock/friend-practice" rel="noopener noreferrer"&gt;https://github.com/mascarock/friend-practice&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The open model is Gemma 3 1B (&lt;code&gt;gemma3:1b&lt;/code&gt;), running through Ollama on 127.0.0.1:11434. Note computes the tool result first, then asks Gemma for one short English sentence. If the sentence names the wrong item, or a number that is not in that result, the app withholds it and shows the computed line instead. If Ollama is not running, budget tracking still works and the app says it cannot reach the model. It never invents a transcript.&lt;/p&gt;

&lt;p&gt;The other four assistants do not call a model. Watcher's over-budget check is code. That split is deliberate. A model is a bad place to decide whether a line is over.&lt;/p&gt;

&lt;p&gt;The interface is a local Electron app. Each assistant has its own accent. The screenshots in the repo are from that running app. The sample spend is saved in the browser on that machine. Nothing is sent to a cloud model, a database, or an analytics service.&lt;/p&gt;

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

&lt;p&gt;Paola's budget is not something I want to paste into a closed chat API. An open-weight model on the same computer means the CSV and the spend log never leave the machine. I can swap Gemma for another local model without rewriting the assistants. It costs nothing per question. A closed API would have been faster to wire up, and it would have put her numbers on someone else's server. That was the wrong trade for this.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;/ul&gt;

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