<?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: Adarsh Singh</title>
    <description>The latest articles on DEV Community by Adarsh Singh (@adarsh-singh106).</description>
    <link>https://dev.to/adarsh-singh106</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%2F4159726%2F4f06aa3c-221f-48ea-94fc-72acc438530f.jpeg</url>
      <title>DEV Community: Adarsh Singh</title>
      <link>https://dev.to/adarsh-singh106</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adarsh-singh106"/>
    <language>en</language>
    <item>
      <title>SnapSort Lite: My Screenshots Had Timestamps, Not Names</title>
      <dc:creator>Adarsh Singh</dc:creator>
      <pubDate>Mon, 26 Oct 2026 03:11:57 +0000</pubDate>
      <link>https://dev.to/adarsh-singh106/snapsort-lite-my-screenshots-had-timestamps-not-names-1f91</link>
      <guid>https://dev.to/adarsh-singh106/snapsort-lite-my-screenshots-had-timestamps-not-names-1f91</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 screenshots folder was becoming a timeline instead of a library.&lt;/p&gt;

&lt;p&gt;A filename like &lt;code&gt;Screenshot 2026-10-25 184500.png&lt;/code&gt; tells me when I captured something, but not why I saved it. Finding a useful code snippet, document, or webpage meant opening screenshots one by one.&lt;/p&gt;

&lt;p&gt;I wanted to automate that repetitive first pass without uploading my screenshots to a cloud service.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;SnapSort Lite&lt;/strong&gt;, a local-first Python CLI that uses open-source Tesseract OCR to extract visible text from screenshots, suggest meaningful filenames, and organize copies into five categories: &lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Documents&lt;/code&gt;, &lt;code&gt;Conversations&lt;/code&gt;, &lt;code&gt;Web&lt;/code&gt;, and &lt;code&gt;Other&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The workflow is intentionally simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select a folder containing screenshots.&lt;/li&gt;
&lt;li&gt;Let Tesseract OCR read the visible text.&lt;/li&gt;
&lt;li&gt;Generate descriptive filenames and classify the images using keyword rules.&lt;/li&gt;
&lt;li&gt;Review the proposed operations.&lt;/li&gt;
&lt;li&gt;Apply the changes when satisfied.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The important part: nothing is copied by default.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SnapSort Lite starts in dry-run mode. Only &lt;code&gt;--apply&lt;/code&gt; creates organized copies. Original screenshots remain untouched, filename collisions receive numeric suffixes, and a JSON manifest records the operations.&lt;/p&gt;

&lt;p&gt;This isn't a magical screenshot-understanding AI. It's a small tool designed to eliminate a boring task while keeping the user in control.&lt;/p&gt;

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

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

&lt;p&gt;The video is an edited walkthrough assembled from genuine application outputs, command executions, and filesystem results. It uses synthetic screenshots rather than private user data.&lt;/p&gt;

&lt;p&gt;For the demonstration, I generated three screenshots using Pillow: a fictional invoice, a Python code snippet, and a fictional webpage.&lt;/p&gt;

&lt;p&gt;Here are the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fictional-invoice.png
  → Documents/fictional-data-for-demo.png

fictional-web.png
  → Web/welcome-to-the-fictional-demo-site.png

python-code.png
  → Code/return-f-hello-name.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Flgfz5mh5bah0xu3zeu5o.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%2Flgfz5mh5bah0xu3zeu5o.png" alt="Before and after screenshot organization" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The suggested filenames are more informative than timestamps, although they're not always perfect. A filename extracted from a code line, for example, might not be the title a developer would choose manually.&lt;/p&gt;

&lt;p&gt;That's one reason I kept the preview step.&lt;/p&gt;

&lt;p&gt;Run a dry-run preview:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python snapsort.py ./screenshots &lt;span class="nt"&gt;--output&lt;/span&gt; ./organized
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then apply the plan:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python snapsort.py ./screenshots &lt;span class="nt"&gt;--output&lt;/span&gt; ./organized &lt;span class="nt"&gt;--apply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first command previews the operations without copying files. The second creates organized copies and a manifest while preserving the originals.&lt;/p&gt;

&lt;p&gt;The local verification run processed all three demonstration screenshots successfully.&lt;/p&gt;

&lt;p&gt;You can also explore the &lt;a href="https://github.com/adarsh-singh106/snapsort-lite/blob/main/demo/DEMO.md" rel="noopener noreferrer"&gt;demo walkthrough&lt;/a&gt; and &lt;a href="https://github.com/adarsh-singh106/snapsort-lite/blob/main/submission/VERIFICATION.md" rel="noopener noreferrer"&gt;verification notes&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/adarsh-singh106" rel="noopener noreferrer"&gt;
        adarsh-singh106
      &lt;/a&gt; / &lt;a href="https://github.com/adarsh-singh106/snapsort-lite" rel="noopener noreferrer"&gt;
        snapsort-lite
      &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;SnapSort Lite&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;SnapSort Lite is a privacy-first Python CLI for making small, safe improvements to a screenshot collection. It reads visible text with the local Tesseract OCR engine, proposes a short filename, and copies each image into a transparent category.&lt;/p&gt;

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

&lt;p&gt;Windows screenshots often accumulate under timestamp-based names. SnapSort Lite turns that timeline into a reviewable first pass without deleting or uploading originals.&lt;/p&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;PNG, JPG, JPEG, and WebP support&lt;/li&gt;
&lt;li&gt;Local OCR with Pillow and pytesseract&lt;/li&gt;
&lt;li&gt;Deterministic, filesystem-safe filenames&lt;/li&gt;
&lt;li&gt;Rule-based &lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Documents&lt;/code&gt;, &lt;code&gt;Conversations&lt;/code&gt;, &lt;code&gt;Web&lt;/code&gt;, and &lt;code&gt;Other&lt;/code&gt; categories&lt;/li&gt;
&lt;li&gt;Dry-run preview by default; &lt;code&gt;--apply&lt;/code&gt; is required to copy&lt;/li&gt;
&lt;li&gt;Never overwrites an existing destination; uses &lt;code&gt;-2&lt;/code&gt;, &lt;code&gt;-3&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;SHA-256 and OCR details in a JSON manifest&lt;/li&gt;
&lt;li&gt;Originals are always retained&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation on Windows&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Install Python 3.10 or newer, then from this folder:&lt;/p&gt;

&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;python &lt;span class="pl-k"&gt;-&lt;/span&gt;m pip install &lt;span class="pl-k"&gt;-&lt;/span&gt;r requirements.txt&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Install Tesseract OCR separately, for example from…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/adarsh-singh106/snapsort-lite" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/adarsh-singh106/snapsort-lite" rel="noopener noreferrer"&gt;https://github.com/adarsh-singh106/snapsort-lite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is open-source under the MIT license.&lt;/p&gt;

&lt;p&gt;The repository includes the Python CLI, automated tests, synthetic demonstration inputs, documentation, and video production materials.&lt;/p&gt;

&lt;p&gt;To get started, clone the repository, install the Python dependencies, and install Tesseract OCR separately.&lt;/p&gt;

&lt;p&gt;No API key or hosted inference service is required for the OCR workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&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%2Fopozqj51lw97dll9fxrl.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%2Fopozqj51lw97dll9fxrl.png" alt="SnapSort Lite architecture" width="800" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The architecture is deliberately straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screenshot → Pillow → Tesseract OCR → Filename Generation → Keyword Classification → Dry Run / Apply → Organized Copies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what happens internally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Image discovery:&lt;/strong&gt; The CLI identifies supported PNG, JPG, JPEG, and WebP images in the selected directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OCR:&lt;/strong&gt; Pillow loads each image, and &lt;code&gt;pytesseract&lt;/code&gt; passes it to the locally installed Tesseract engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filename generation:&lt;/strong&gt; A promising recognized text line is converted into a short, filesystem-safe filename.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classification:&lt;/strong&gt; Deterministic keyword rules select one of five destination categories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe execution:&lt;/strong&gt; The application prepares a collision-safe copy plan and displays it before writing anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit trail:&lt;/strong&gt; When applied, it creates copies using &lt;code&gt;shutil.copy2&lt;/code&gt; and records operations in a JSON manifest with file hashes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Where the open-source AI fits
&lt;/h3&gt;

&lt;p&gt;The core recognition capability comes from &lt;strong&gt;Tesseract OCR&lt;/strong&gt;, an open-source optical character recognition engine.&lt;/p&gt;

&lt;p&gt;Tesseract recognizes text inside images. It isn't a large language model or a vision-language model, and SnapSort Lite's category selection is ordinary Python logic.&lt;/p&gt;

&lt;p&gt;I wanted to make that distinction clear rather than market simple keyword matching as sophisticated AI reasoning.&lt;/p&gt;

&lt;p&gt;The current version works best when screenshots contain readable text. It cannot reliably understand text-free diagrams, recognize application interfaces visually, or infer the full meaning of an image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing and safety
&lt;/h3&gt;

&lt;p&gt;I separated planning from applying so the tool could show what it intends to do before touching the destination folder.&lt;/p&gt;

&lt;p&gt;I also designed the OCR dependency to be replaceable during testing, added collision handling, and made missing Tesseract installations produce explicit errors.&lt;/p&gt;

&lt;p&gt;In the latest local verification reported during development, &lt;strong&gt;12 automated tests passed&lt;/strong&gt;, and the three synthetic demonstration screenshots were processed successfully.&lt;/p&gt;

&lt;p&gt;There are still improvements worth exploring: content-based deduplication, better filename selection, and an optional local open-weight vision-language model for screenshots without useful text.&lt;/p&gt;

&lt;p&gt;Those are future ideas, not features I'm claiming to have implemented.&lt;/p&gt;

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

&lt;p&gt;Screenshots can contain private conversations, source code, invoices, or account information.&lt;/p&gt;

&lt;p&gt;For a tool that processes those images, local execution matters.&lt;/p&gt;

&lt;p&gt;Using Tesseract OCR means the recognition step can run on the user's machine without sending screenshots to a third-party inference API.&lt;/p&gt;

&lt;p&gt;Open-source components also make the pipeline inspectable and adaptable. Developers can change the naming heuristics, improve the classifier, add OCR language packs, or replace the recognition engine entirely.&lt;/p&gt;

&lt;p&gt;The tool doesn't depend on a proprietary API key or a remote model whose behavior might change without notice.&lt;/p&gt;

&lt;p&gt;That doesn't mean local processing automatically guarantees security. Users still need to protect their files, review proposed operations, and account for OCR errors.&lt;/p&gt;

&lt;p&gt;But it provides a useful foundation: the images and the processing workflow remain under the user's control.&lt;/p&gt;

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

&lt;p&gt;I used an AI coding assistant to help develop the implementation, automated tests, documentation, and demonstration materials.&lt;/p&gt;

&lt;p&gt;The demo video was assembled from actual application outputs and synthetic screenshots, rather than a simulated interactive recording.&lt;/p&gt;

&lt;p&gt;I haven't published a DevRelay session, so I'm not including an invented session link.&lt;/p&gt;

&lt;p&gt;One thing this project reinforced for me: the most important part of automation isn't always the cleverest algorithm. Sometimes it's making sure the automation behaves predictably and gives users a chance to review its decisions.&lt;/p&gt;

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

&lt;p&gt;Before SnapSort Lite, organizing screenshots meant opening each image, deciding what it contained, renaming it, and moving it into the right folder.&lt;/p&gt;

&lt;p&gt;Now I can generate a first-pass organization proposal with one command, review it, and apply it with another.&lt;/p&gt;

&lt;p&gt;In the three-image synthetic demonstration, the application produced categorized copies while keeping the original screenshots intact.&lt;/p&gt;

&lt;p&gt;I haven't measured time savings across a large personal screenshot collection, so I won't invent a percentage.&lt;/p&gt;

&lt;p&gt;For me, the payoff is simple: fewer repetitive decisions, more useful filenames, and a folder that's easier to navigate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slow and steady means giving automation a small, reviewable job before trusting it with something bigger.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>The Deployment Was Approved. The Dangerous Change Wasn't.</title>
      <dc:creator>Adarsh Singh</dc:creator>
      <pubDate>Mon, 19 Oct 2026 05:17:29 +0000</pubDate>
      <link>https://dev.to/adarsh-singh106/the-deployment-was-approved-the-dangerous-change-wasnt-4370</link>
      <guid>https://dev.to/adarsh-singh106/the-deployment-was-approved-the-dangerous-change-wasnt-4370</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

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

&lt;p&gt;Imagine reviewing a deployment script line by line. Everything looks fine, so you approve it. The script doesn't change afterward. But just before the deployment, someone changes the configuration file it reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- "require_human_review": true
&lt;/span&gt;&lt;span class="gi"&gt;+ "require_human_review": false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the approval check watches only the script, it can miss an edit that changes what the script &lt;em&gt;does&lt;/em&gt;. &lt;strong&gt;The approved file hasn't changed, but the behavior you approved might have.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the problem I explored with &lt;strong&gt;ShadowInputs&lt;/strong&gt;, an open-source Python CLI that records SHA-256 hashes for a &lt;em&gt;reviewed set of repository-local inputs&lt;/em&gt;, then checks whether those exact files have changed before a simulated deployment. It is a focused proof of concept, not a general-purpose CI security product.&lt;/p&gt;

&lt;p&gt;I started with Md Imran Hossen's October 2026 research paper, &lt;a href="https://arxiv.org/abs/2610.15137" rel="noopener noreferrer"&gt;&lt;em&gt;When the Approved File Is Unchanged: Authority-Set Integrity for Deployment and Coding-Agent Approvals&lt;/em&gt;&lt;/a&gt;. The paper examines approvals that bind one script or workflow even though execution depends on additional files. ShadowInputs is my small, reproducible experiment with that idea, not an implementation of the paper's full authority-set model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who might pay for this?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Business hypothesis, not proven demand:&lt;/strong&gt; Small DevOps consultancies and teams maintaining sensitive release pipelines might pay for dependency-mapping audits, custom adapters, or integration of reviewed-input checks into their existing CI workflows. The CLI remains open source under MIT; a genuine commercial offering would require customer interviews, broader dependency discovery, and stronger approval and artifact integrity controls. I haven't acquired a paying user.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://youtu.be/3HlSjik4lzk" rel="noopener noreferrer"&gt;Watch the ShadowInputs video walkthrough on YouTube&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The demonstration uses a synthetic GitHub Actions workflow, a Python &lt;code&gt;deploy.py&lt;/code&gt; script, and a local &lt;code&gt;policy.json&lt;/code&gt;. The workflow and entrypoint are supplied explicitly to the CLI; the prototype does &lt;strong&gt;not&lt;/strong&gt; automatically understand every GitHub Actions step or runtime dependency.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Observed CLI result&lt;/th&gt;
&lt;th&gt;Exit code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approved inputs unchanged&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PASS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unrelated documentation edit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PASS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approved &lt;code&gt;policy.json&lt;/code&gt; changed while &lt;code&gt;deploy.py&lt;/code&gt; stayed unchanged&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLOCK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approved &lt;code&gt;policy.json&lt;/code&gt; deleted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;BLOCK&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The local Windows 11 / Python 3.14 verification run reported &lt;strong&gt;20 tests passed, 1 skipped&lt;/strong&gt;. The skipped symlink test required a Windows privilege unavailable in that environment. These tests validate the prototype's documented behaviors, not production security.&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%2Fhaq3s9wrmcitlfldxxqt.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%2Fhaq3s9wrmcitlfldxxqt.png" alt="Test output rendered from the local run" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What happened when I tried real Gemma inference?
&lt;/h3&gt;

&lt;p&gt;I used the open-weight &lt;strong&gt;Gemma 3 1B&lt;/strong&gt; model through a local &lt;strong&gt;Ollama&lt;/strong&gt; server to suggest file dependencies that deterministic analysis might overlook. This was a real model call, not a mocked response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;shadowinputs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\examples\deployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;github&lt;/span&gt;&lt;span class="nx"&gt;/workflows/deploy.yml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy.py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gemma3:1b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ai-review.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The static scanner identified the workflow entry, &lt;code&gt;deploy.py&lt;/code&gt;, and &lt;code&gt;policy.json&lt;/code&gt;. &lt;strong&gt;Gemma contributed zero accepted additional file candidates.&lt;/strong&gt; Its response included a placeholder path (&lt;code&gt;relative/path&lt;/code&gt;), which the path validator rejected.&lt;/p&gt;

&lt;p&gt;That's not the result I hoped for. But it matters: &lt;strong&gt;a successful model call is not proof of successful dependency discovery&lt;/strong&gt;. In an integrity-sensitive workflow, model suggestions must remain untrusted until they have been checked and reviewed. The deterministic scanner and hash verifier, not Gemma, produced the successful PASS/BLOCK behavior shown above.&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%2Fs4fhcec5o3kuhf8y7rol.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%2Fs4fhcec5o3kuhf8y7rol.png" alt="Gemma inspection result, presented as a rendered terminal card" width="800" height="450"&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%2F74h79l4w9jw9015akjfu.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%2F74h79l4w9jw9015akjfu.png" alt="PASS and BLOCK output, presented as a rendered terminal card" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Evidence note: These images are rendered cards based on local terminal results, not unedited desktop screenshots. The video is linked separately for the walkthrough.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/adarsh-singh106/shadowinputs" rel="noopener noreferrer"&gt;GitHub: adarsh-singh106/shadowinputs&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The implementation is deliberately small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;shadowinputs/core.py&lt;/code&gt;: limited Python AST inspection for literal file reads, safe path handling, SHA-256 manifests, and comparison.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shadowinputs/ai.py&lt;/code&gt;: local Ollama integration, treating model-suggested paths as untrusted hints.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shadowinputs/cli.py&lt;/code&gt;: &lt;code&gt;inspect&lt;/code&gt;, &lt;code&gt;approve&lt;/code&gt;, and &lt;code&gt;verify&lt;/code&gt; subcommands with meaningful exit codes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;examples/deployment/&lt;/code&gt;: synthetic workflow, script, and policy file for reproduction.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/&lt;/code&gt;: checks for drift, missing files, unsafe paths, and other defined boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create and verify a manifest from the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;shadowinputs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;approve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\examples\deployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;github&lt;/span&gt;&lt;span class="nx"&gt;/workflows/deploy.yml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy.py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;TEMP&lt;/span&gt;&lt;span class="s2"&gt;\shadowinputs-approved.json"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;shadowinputs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\examples\deployment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;TEMP&lt;/span&gt;&lt;span class="s2"&gt;\shadowinputs-approved.json"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;approve&lt;/code&gt; command makes a manifest &lt;em&gt;after the operator has reviewed the intended scope&lt;/em&gt;. It does not authenticate that operator or cryptographically sign an approval. Any real deployment integration would need to protect the manifest and bind approval to an authorized reviewer.&lt;/p&gt;

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

&lt;p&gt;The design separates &lt;strong&gt;discovery, review, and verification&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Explicit scope and static analysis.&lt;/strong&gt; The operator supplies entry files. A Python AST scanner finds certain literal file reads, such as &lt;code&gt;Path('policy.json').read_text()&lt;/code&gt;. Some unresolved dynamic paths cause the tool to stop rather than claim completeness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local open-weight model.&lt;/strong&gt; Gemma runs via Ollama on loopback, proposing additional &lt;em&gt;candidate&lt;/em&gt; paths. The prototype validates those suggestions instead of automatically trusting them. In the reported run it provided no accepted additions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operator-reviewed manifest.&lt;/strong&gt; For the chosen files, the CLI calculates SHA-256 digests and saves a snapshot outside the example repository. That is not a signed approval mechanism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic verification.&lt;/strong&gt; The CLI compares listed paths and their current contents against the manifest. An altered or missing listed file causes the check to fail. It does not itself launch or secure a real deployment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the prototype cannot guarantee
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It does &lt;strong&gt;not&lt;/strong&gt; identify every input consumed by arbitrary Python, Bash, GitHub Actions, imports, environment variables, or external services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files omitted from the manifest are not protected.&lt;/strong&gt; &lt;code&gt;PASS&lt;/code&gt; means only that the listed inputs still match.&lt;/li&gt;
&lt;li&gt;An external manifest path alone is &lt;strong&gt;not tamper-proof&lt;/strong&gt;. A real system needs protected storage, access controls, and reviewer authentication.&lt;/li&gt;
&lt;li&gt;Inputs may change &lt;strong&gt;after&lt;/strong&gt; verification. This time-of-check/time-of-use gap requires immutable artifacts or controlled execution.&lt;/li&gt;
&lt;li&gt;The local Gemma run did &lt;strong&gt;not&lt;/strong&gt; improve dependency coverage in the experiment shown.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These boundaries are part of the project rather than footnotes: someone should be able to understand exactly what a green check does and does not mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Innovation Matters
&lt;/h2&gt;

&lt;p&gt;Deployment source code can be sensitive, and security-related tooling benefits from being inspectable. Using Python, an open-weight Gemma model, and local Ollama inference lets developers experiment without shipping repository snippets to a hosted model API. The static checks and manifests can also be reproduced without depending on a model's answer.&lt;/p&gt;

&lt;p&gt;The more lasting open-source value is the &lt;strong&gt;reproducible failure case and extensible discovery tooling&lt;/strong&gt;. Another developer can inspect the hash calculation, contribute a parser for a different deployment language, test a tricky input pattern, or challenge our assumptions. That kind of scrutiny is especially useful when the tool is about trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Test Next
&lt;/h2&gt;

&lt;p&gt;Before claiming that AI helps, I'd assemble real deployment patterns in which the static scanner misses an input, then compare &lt;strong&gt;static analysis alone versus static analysis plus Gemma suggestions&lt;/strong&gt;. I'd measure accepted-candidate precision, dependency recall, and runtime. Until then, the only demonstrated security behavior is the deterministic, manifest-based drift check.&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; ShadowInputs integrates an open-weight Gemma 3 model, served locally through Ollama, as an experimental dependency-suggestion component. I have disclosed its observed result: zero accepted additional candidates in the demonstrated run. This establishes a real integration, not an accuracy improvement or a production-grade AI security system.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>ClaimTrail: a 15-minute walk has to include coming back</title>
      <dc:creator>Adarsh Singh</dc:creator>
      <pubDate>Mon, 12 Oct 2026 04:53:43 +0000</pubDate>
      <link>https://dev.to/adarsh-singh106/claimtrail-a-15-minute-walk-has-to-include-coming-back-35l7</link>
      <guid>https://dev.to/adarsh-singh106/claimtrail-a-15-minute-walk-has-to-include-coming-back-35l7</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;Would anyone open an application just to go outside?&lt;/p&gt;

&lt;p&gt;That is the question behind ClaimTrail. I wanted to explore whether AI could give a short walk a purpose without making the walk another screen-based activity. Finding a park is already easy. Having one small thing to notice there is a different starting point.&lt;/p&gt;

&lt;p&gt;ClaimTrail turns an available place description or review excerpt into an observation question. You enter your actual starting landmark or address, choose parks, gardens or public squares, and select a complete-trip budget. After choosing a feasible candidate, you read the source and a question, put the phone away, and return to record what you noticed.&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%2F8srcrl51jp5riekklrr8.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%2F8srcrl51jp5riekklrr8.png" alt="ClaimTrail search form showing the starting landmark and 15-minute trip budget" width="799" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MOCKED/SYNTHETIC UI DEMONSTRATION: current application interface; invented starting landmark and mocked service status. No location request was sent.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The intended user is someone with a little spare time: a student between tasks, or anyone curious about familiar surroundings. There is no account, route tracking, photo requirement or leaderboard. The three outcomes are “Saw it,” “Didn't see it” and “Couldn't tell.” A personal observation stays separate from what an online source says.&lt;/p&gt;

&lt;p&gt;I have not established that this makes people go outside more often. It is a product hypothesis, not a measured result. The experiment is to make the screen useful briefly, then get it out of the way.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://youtu.be/jYzxKEjvtxY" rel="noopener noreferrer"&gt;Watch the captioned synthetic software demonstration on YouTube&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Evidence status:&lt;/strong&gt; the captioned software video and interface captures use mocked provider and model responses. They demonstrate current UI behavior, not a successful current SerpApi-to-Gemma run. An earlier live demonstration exists locally, but it predates the round-trip changes. A fresh real-provider rehearsal remains unverified. No physical outdoor visit is claimed.&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%2Fuqb5d2waczzlcewixatz.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%2Fuqb5d2waczzlcewixatz.png" alt="Synthetic park recommendation with estimated round-trip walking time" width="800" height="871"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MOCKED/SYNTHETIC UI DEMONSTRATION: the returned candidate is fictional. The recommendation shows travel and buffer; the mission view below also exposes the five-minute activity allocation.&lt;/em&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%2Fny3f22igv0k3dtf22zkh.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%2Fny3f22igv0k3dtf22zkh.png" alt="ClaimTrail displaying a sourced excerpt and observation question" width="800" height="708"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MOCKED/SYNTHETIC UI DEMONSTRATION: neither the excerpt nor the selected question came from a live provider/model call during this capture. The question uses the application's actual approved wording.&lt;/em&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%2Fihyxzuew736id9fajt8s.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%2Fihyxzuew736id9fajt8s.png" alt="Synthetic reflection receipt saved in browser storage" width="800" height="827"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MOCKED/SYNTHETIC UI DEMONSTRATION: the reflection is simulated, not field evidence. Saving, refreshing and deleting use the real frontend's local-storage behavior.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A video is preferable to exposing the backend publicly. This version is a local, loopback-only application, not a hardened internet-facing API service.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/adarsh-singh106/claimtrail" rel="noopener noreferrer"&gt;ClaimTrail source and setup instructions on GitHub&lt;/a&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/adarsh-singh106" rel="noopener noreferrer"&gt;
        adarsh-singh106
      &lt;/a&gt; / &lt;a href="https://github.com/adarsh-singh106/claimtrail" rel="noopener noreferrer"&gt;
        claimtrail
      &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;ClaimTrail&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Less scrolling. More discovering.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A real listing detail becomes a small outdoor observation question, followed by a
private reflection. The world is the experience; the app is preparation and reflection.&lt;/p&gt;
&lt;p&gt;New project: October 11, 2026. Hacktoberfest Week 1 deadline: &lt;strong&gt;October 12, 12:29 PM IST&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Implemented&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Real SerpApi Maps search/detail and walking-directions filtering, bounded review
fallback, local Gemma structured output, deterministic grounding/card validation
React search-to-reflection flow, browser-local persistence, copy and deletion. A
candidate must fit an estimated round-trip budget from the entered starting landmark
or address: outbound walk + on-site task + return walk + safety buffer. The provider's
resolved origin is not independently verified; do not enter only a city. Walking pace
and conditions can differ. Reminders run while the app is active, not as background
alarms. See &lt;a href="https://github.com/adarsh-singh106/claimtrail/docs/BUILD-LOG.md" rel="noopener noreferrer"&gt;BUILD-LOG&lt;/a&gt; for actual
verification. &lt;strong&gt;No outdoor visit, public deployment or submission is claimed.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Run locally — Windows PowerShell&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Prerequisites: Python…&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/adarsh-singh106/claimtrail" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The application code is MIT licensed. Gemma and upstream place data have their own terms; the repository's license does not relicense either. The README explains the local services, credential setup and limitations.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Keep the model's job small
&lt;/h3&gt;

&lt;p&gt;React and TypeScript provide the field-journal interface. FastAPI handles search, evidence retrieval and mission preparation. SerpApi supplies Google Maps place results, walking estimates and available source text. Ollama runs &lt;code&gt;gemma3:1b&lt;/code&gt; locally.&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%2Fdyyy7nzatz6zd9g42lqu.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%2Fdyyy7nzatz6zd9g42lqu.png" alt="ClaimTrail architecture connecting React, FastAPI, SerpApi and local Gemma" width="800" height="1333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Implementation diagram, not a deployment or live-test result. Location queries leave the machine through SerpApi; reflection notes do not enter the backend pipeline.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The backend filters place categories and travel feasibility before offering candidates. When a user selects one, it checks the selection's origin and budget, retrieves matching place details, and looks for an eligible description. If necessary, one reviews request examines a small bounded set of excerpts. Missing usable evidence means no mission, not an invented fallback.&lt;/p&gt;

&lt;p&gt;Gemma selects a supported feature and source quote, then returns structured JSON with an approved observation card. The current choices are deliberately narrow: paths, seating and signs. This is constrained selection, not an open-ended adventure generator.&lt;/p&gt;

&lt;p&gt;JSON matching a schema is not enough. The backend also checks provenance and meaning. This excerpt from &lt;code&gt;backend/app/mission.py&lt;/code&gt;, lines 93–104, runs after schema parsing:&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;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;evidence&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;normalize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exact_quote&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;source&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;MissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The model quote could not be traced to this place. No mission was created.&lt;/span&gt;&lt;span class="sh"&gt;'&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;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;eligible_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;eligible_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;MissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The source does not support an allowed observation. Try another place.&lt;/span&gt;&lt;span class="sh"&gt;'&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;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;estimated_on_site_minutes&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;MissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The model changed the requested activity time. No mission was created.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CARDS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;MissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The model mixed unsupported observation details. No mission was created.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quote must occur in the selected evidence after whitespace normalization. The feature must be supported, the activity time must match, and the card fields must agree. These checks establish a traceable question, not proof that a listing is true today.&lt;/p&gt;

&lt;p&gt;Earlier build logs record both an abstention and a mixed-card response being rejected before an accepted local-model result. Those are historical development results, not proof that the revised routing flow has passed a fresh live run.&lt;/p&gt;

&lt;h3&gt;
  
  
  A fifteen-minute mission has to include coming back
&lt;/h3&gt;

&lt;p&gt;The most important correction was not in the prompt. A destination ten minutes away is not suitable for a fifteen-minute break if the user must also explore and return.&lt;/p&gt;

&lt;p&gt;The initial experience treated activity time separately from travel. The current implementation requests outbound and return walking directions independently. It does not simply double the outward estimate or turn straight-line distance into a confident walking time.&lt;/p&gt;

&lt;p&gt;It rounds the two estimates upward, reserves activity time, and adds a buffer of at least three minutes. For a fifteen-minute budget, the activity allowance is five minutes. These lines from &lt;code&gt;backend/app/places.py&lt;/code&gt;, 97–107, make feasibility a deterministic decision:&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;activity_minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ACTIVITY_MINUTES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;total_minutes&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;buffer_minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SAFETY_BUFFER_MINUTES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outbound_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;estimated_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outbound_minutes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;activity_minutes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;return_minutes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;buffer_minutes&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;budget_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;outbound_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;outbound_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;return_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;return_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;activity_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;activity_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;buffer_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buffer_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;estimated_total_minutes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;estimated_total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fits_budget&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;estimated_total&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;total_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Excerpt ends within the returned dictionary; additional metadata and the closing brace are omitted.&lt;/em&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%2Ftll6eqj7fa8duop89kit.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%2Ftll6eqj7fa8duop89kit.png" alt="Time-budget comparison showing accepted and rejected round-trip missions" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Illustrative synthetic calculations, not measured routes: 3 + 5 + 3 + 3 = 14 fits; 10 + 5 + 10 + 3 = 28 does not.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An empty result is better than quietly spending time the user does not have. Even an accepted estimate depends on the entered origin, provider routing, walking pace and conditions. The app cannot certify entrances, access or safety.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tests needed to follow the browser
&lt;/h3&gt;

&lt;p&gt;The release audit found an embarrassing boundary mismatch: normal searches returned HTTP 422 while 50 backend tests passed. The browser sent &lt;code&gt;total_minutes=15&lt;/code&gt; as query text, but the endpoint used numeric literals. Tests calling functions or omitting the parameter missed that path.&lt;/p&gt;

&lt;p&gt;The correction accepts the four textual query values and converts them after validation. HTTP-level regressions now exercise the actual values the browser sends.&lt;/p&gt;

&lt;p&gt;Desktop controls also overlapped. Changing an origin or budget could leave old recommendations visible, and late responses could restore stale state. Separate grid rows and revision-bound requests corrected those cases. The return reminder now reserves the return walk plus its buffer; it updates while the app is active, not as a background alarm.&lt;/p&gt;

&lt;p&gt;The latest release record reports &lt;strong&gt;72 passing backend tests, 14 passing mocked Chromium tests and a passing production build&lt;/strong&gt;. Browser coverage includes three widths, stale responses, reminders and reflection persistence/deletion. These are useful checks, not live-provider or outdoor validation.&lt;/p&gt;

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

&lt;p&gt;Local open-weight inference makes the model boundary inspectable. I can change the schema, examine rejected outputs and experiment with the configured model without depending on a proprietary hosted language-model endpoint. Alternative models would still need their own evaluation; changing a name is not evidence of compatibility.&lt;/p&gt;

&lt;p&gt;The model is central to mission preparation: an unavailable or rejected response does not silently become a template-only success. But with only three approved cards, a simpler non-AI selector is a fair baseline to compare later. I have not measured whether Gemma improves on it.&lt;/p&gt;

&lt;p&gt;Local inference does not make the whole application offline, completely private or free. Starting-point queries reach SerpApi. Discovery needs internet and provider credits; inference needs a running local model. Personal reflections remain in browser storage and can be deleted, but anyone using that browser profile may read them.&lt;/p&gt;

&lt;p&gt;There are further limits: provider latency, sparse place coverage, repetitive questions and no verified user adoption. Even request cost needs care: five displayed results can require examining more than five candidates.&lt;/p&gt;

&lt;p&gt;My main lesson is that the model should not own promises the application can check. Let it select a question. Make ordinary code enforce the source, time and state boundaries. Whether that question gives someone a worthwhile reason to step outside is the next thing to test.&lt;/p&gt;

&lt;p&gt;This is a solo project. AI coding assistance contributed to implementation and tests, and an AI assistant drafted this article. The technical account is based on source inspection and recorded verification; it does not represent independent manual authorship of every line.&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; local &lt;code&gt;gemma3:1b&lt;/code&gt; selects the source-linked observation card; deterministic backend validation controls acceptance. Historical inference evidence exists, with the current complete live flow still pending verification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of SerpApi:&lt;/strong&gt; place discovery, independently requested walking legs and retrieved evidence determine which destinations and questions are possible. It is a functional data dependency, not a decorative search call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to the maintainers of Gemma, Ollama, SerpApi, React and FastAPI. The distinction I want ClaimTrail to preserve is simple: a source claim, an AI-selected question and a person's observation are three different things.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Explain Aloud: If the Screen Disappears, Does the Answer Still Make Sense?</title>
      <dc:creator>Adarsh Singh</dc:creator>
      <pubDate>Mon, 05 Oct 2026 05:41:46 +0000</pubDate>
      <link>https://dev.to/adarsh-singh106/explain-aloud-if-the-screen-disappears-does-the-answer-still-make-sense-5ghd</link>
      <guid>https://dev.to/adarsh-singh106/explain-aloud-if-the-screen-disappears-does-the-answer-still-make-sense-5ghd</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;p&gt;My girlfriend already has a listening workflow. She uses Read Aloud in both ChatGPT and Claude while doing other things — cooking, commuting, winding down after a long day. It works fine for ordinary prose. Then ChatGPT answers a question with a table, or a code block, or a bulleted list with sub-items, and the whole thing falls apart. A three-column table about people, ages, and roles starts sounding like a leaderboard. A Python snippet becomes a stream of colons and underscores. A heading that says "Next Steps" arrives as "Next, small List."&lt;/p&gt;

&lt;p&gt;She would skip those answers, or glance at her phone to reconstruct the layout, which defeats the point entirely.&lt;/p&gt;

&lt;p&gt;That problem is the reason for Explain Aloud. Not the voice output — she already had that. The missing piece was the structure-aware step that happens before speech.&lt;/p&gt;

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

&lt;p&gt;Explain Aloud is a Chrome MV3 extension that attaches to completed ChatGPT responses, extracts their structure into typed semantic blocks, builds a narration plan segment by segment, and sends each segment to Kokoro for in-browser speech synthesis.&lt;/p&gt;

&lt;p&gt;It handles five block types: paragraphs, headings, lists, code blocks, and tables. Two modes are available: Literal Mode takes the deterministic path for everything; Natural Mode can route code and table blocks through a local Gemma model running in Ollama for listening-oriented phrasing. The side panel shows playback controls, the narration segments in sequence, and each segment's provenance — whether it came from a rule, from the model, or from a fallback.&lt;/p&gt;

&lt;p&gt;The core question guiding every design decision was: if the screen disappears, does the answer still make sense?&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://youtu.be/sPxWW-xJ0Eg" rel="noopener noreferrer"&gt;Demo-Video&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Demo voiceover generated with ElevenLabs. The Explain Aloud narration heard inside the product is Kokoro.&lt;/p&gt;
&lt;/blockquote&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%2Fbrhba8y28dvhwh5sdca2.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%2Fbrhba8y28dvhwh5sdca2.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The extension attaches to a completed response. These model figures are example response content, not measurements of Explain Aloud.&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%2Fhxrccetkz4supwttiaay.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%2Fhxrccetkz4supwttiaay.png" alt=" " width="432" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Panel captured in stopped state with a rule-generated segment selected. The "Verified" label is the application's own validation result, not an independent semantic proof. The response identifier has been redacted.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/adarsh-singh106/explain_aloud" rel="noopener noreferrer"&gt;adarsh-singh106/explain_aloud&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  From DOM to typed blocks
&lt;/h3&gt;

&lt;p&gt;The first step is extraction. When the extension detects a completed ChatGPT response, the content script runs the semantic DOM extractor over it and builds a &lt;code&gt;ResponseIR&lt;/code&gt; — a structured representation of the response as a typed block sequence. Paragraphs, headings, list items, code blocks, and tables each carry their own fields. A code block knows its language. A table block knows its headers and cell values. A list item knows its nesting depth.&lt;/p&gt;

&lt;p&gt;The extraction is deterministic. No model is involved yet, and no interpretation is made. A Python code block that ChatGPT explicitly labels as Python is extracted with &lt;code&gt;language: "python"&lt;/code&gt;. An unmarked &lt;code&gt;print(...)&lt;/code&gt; is not guessed — it arrives as generic code. Seven regression fixture variants cover this explicitly, including the case where a Python header appears alongside a generic plaintext class. That conservatism matters when the language label will eventually reach a prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Gemma's job actually is
&lt;/h3&gt;

&lt;p&gt;In Natural Mode, code and table blocks are handed to a local Gemma call through Ollama. The boundary I wanted was specific: Gemma receives the pre-extracted facts, not the raw HTML. For a table, the deterministic parser has already produced the headers, row entities, numeric values, and unit-normalized metrics before the model sees anything. Gemma's task is to choose phrasing, not to read the table.&lt;/p&gt;

&lt;p&gt;The model's output is a candidate narration. Candidate narrations pass through conservative block-specific validation before entering the narration plan. Where the validator cannot establish enough support, the system falls back to source-derived deterministic narration. The fallback is always a rule-based transformation of what was already extracted — never an invented summary.&lt;/p&gt;

&lt;p&gt;The configured default is &lt;code&gt;gemma4:e4b&lt;/code&gt;, with &lt;code&gt;gemma4:e2b&lt;/code&gt; as the model-not-found fallback. Which model actually ran in a given browser session was not separately recorded.&lt;/p&gt;

&lt;h3&gt;
  
  
  The test suite was green. The contract wasn't.
&lt;/h3&gt;

&lt;p&gt;This is where the real engineering tension lives.&lt;/p&gt;

&lt;p&gt;The test suite grew substantially during a source-fidelity hardening pass. Parsers were correctly extracting table data. The UI was rendering the right buttons. The pipeline was moving blocks from extraction through to the plan. Green across the board. But none of that guaranteed the listening contract.&lt;/p&gt;

&lt;p&gt;The contract is more specific: a table with three people and their ages and roles should arrive as three separate row descriptions, not as a comparative ranking of who is oldest. A code block explained by Gemma should not reverse a comparison or invent behavior. A heading like "Next Steps" should smooth into speech without the word "Steps" collapsing into a list signal.&lt;/p&gt;

&lt;p&gt;Passing the fixtures did not prove those things automatically. It took deliberate adversarial review — asking what happens when Gemma returns something plausible-but-wrong — to see where the boundary was missing. That review produced the segment validator and its strict fallback contract:&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="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;NarrationSegment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&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="nl"&gt;sourceBlockIds&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="nl"&gt;factIds&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="nl"&gt;provenance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Provenance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text&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="nl"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fallbackReason&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="nl"&gt;pauseAfterMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Provenance&lt;/code&gt; is &lt;code&gt;rule&lt;/code&gt;, &lt;code&gt;llm&lt;/code&gt;, or &lt;code&gt;literal&lt;/code&gt;. Every segment declares where it came from. The inspector in the side panel shows this per segment. If you want to know whether the model touched a narration or a rule produced it, you can see that without digging into logs.&lt;/p&gt;

&lt;p&gt;Conservative validation and fallback paths reduce unsupported narration. They do not eliminate it entirely — the current validator is a heuristic safety net, not a formal semantic proof — but they establish a checked path from extraction to voice.&lt;/p&gt;

&lt;h3&gt;
  
  
  A small table should not become a leaderboard
&lt;/h3&gt;

&lt;p&gt;One concrete example: a table with three rows — Name, Age, Role — for Alex, Sam, and John. The earlier deterministic summary path would lead with comparison framing, emphasizing who was oldest. That preserved the numbers correctly, but it invented a ranking the original response did not contain.&lt;/p&gt;

&lt;p&gt;The final patch adds a row-wise path for complete rectangular tables with two to four rows and two to four columns. It ignores relationships between rows and speaks each one directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The table has three entries. Name: Alex, Age: 20, and Role: Student. Name: Sam, Age: 21, and Role: Developer. Name: John, Age: 22, and Role: Engineer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That exact output is what the regression test asserts, character for character. Gemma is not called on this path. Validation does not fall back. The facts are preserved in the order they appear.&lt;/p&gt;

&lt;p&gt;Larger or irregular tables stay on the existing path. Merged-cell tables stay on the existing path. The row-wise route applies only where the structure is simple enough to be safe.&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%2Fy6wb3ca0kx5c134xw466.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%2Fy6wb3ca0kx5c134xw466.png" alt=" " width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a test-evidence graphic, not a browser screenshot. The narration is copied directly from the passing regression assertion.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The production WASM failure
&lt;/h3&gt;

&lt;p&gt;Before those listening improvements, there was a more basic failure that showed up only in the production Chrome extension — not in tests, not in the TypeScript check, not in the build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;no available backend found
[wasm] TypeError: Failed to fetch dynamically imported module
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kokoro's underlying ONNX Runtime was trying to import its JavaScript module from a CDN at runtime. The extension build had succeeded. The bundle was present. But Chrome's extension sandbox blocked the external module fetch, and the speech backend could not initialize.&lt;/p&gt;

&lt;p&gt;The fix was to bundle the matching ONNX Runtime &lt;code&gt;.mjs&lt;/code&gt; and &lt;code&gt;.wasm&lt;/code&gt; files as local extension assets, then configure their URLs explicitly before the model loads:&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasmPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;mjs&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="nx"&gt;ortModuleUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;wasm&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="nx"&gt;ortWasmUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The packaged files were verified against the installed runtime files by SHA-256. After reloading the extension, actual Kokoro speech was confirmed in Chrome. That confirmation was a browser result, separate from the green build that had preceded it.&lt;/p&gt;

&lt;p&gt;This is the distinction the project kept surfacing: a passing test or a successful build is evidence that a specific thing worked correctly, not that the user's experience works. The Kokoro WASM failure would not have appeared in any automated check.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI, testing, and what the evidence covers
&lt;/h3&gt;

&lt;p&gt;The CI pipeline runs on push to &lt;code&gt;main&lt;/code&gt; and on pull requests. It installs dependencies, runs tests, typechecks, and builds the extension — in that order, under &lt;code&gt;permissions: contents: read&lt;/code&gt; after a final hardening step.&lt;/p&gt;

&lt;p&gt;The verified counts: 191 tests passed across 14 test files. TypeScript compilation passed. The Chrome MV3 production build passed at 24.11 MB. A chunk-size warning remains but is non-blocking.&lt;/p&gt;

&lt;p&gt;What those numbers establish and do not establish is worth being direct about:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Recorded result&lt;/th&gt;
&lt;th&gt;Boundary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test suite&lt;/td&gt;
&lt;td&gt;191/191, 14/14 files&lt;/td&gt;
&lt;td&gt;Fixture and regression coverage. Not a universal faithfulness guarantee.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tsc --noEmit&lt;/code&gt; passed&lt;/td&gt;
&lt;td&gt;Static type checking.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production build&lt;/td&gt;
&lt;td&gt;Chrome MV3, 24.11 MB&lt;/td&gt;
&lt;td&gt;Packaging. A large-chunk warning remains.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real Chrome&lt;/td&gt;
&lt;td&gt;Production extension manually tested; Kokoro speech confirmed&lt;/td&gt;
&lt;td&gt;Developer-reported browser result.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The narration pipeline uses local Ollama/Gemma and in-browser Kokoro WASM. Complete offline operation was not separately verified. The original ChatGPT conversation also remains a ChatGPT conversation — this extension does not change that service's privacy boundary.&lt;/p&gt;

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

&lt;p&gt;Explain Aloud exists because local models and open browser APIs make the stack possible without a backend. Explain Aloud does not intentionally send extracted response content to an additional cloud backend for narration. Its narration path uses local Ollama/Gemma and in-browser Kokoro. Complete offline operation was not separately verified.&lt;/p&gt;

&lt;p&gt;That matters for a use case like this, where the content being narrated is personal conversations. The extension attaches to ChatGPT, which already has its own privacy boundary. Inside the extension's own narration pipeline, the path stays local. Open models and open runtimes make that design available without infrastructure.&lt;/p&gt;

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

&lt;p&gt;I enabled Entire late in the finalization phase. The permanent checkpoint below captures a Codex documentation session that recorded the final CI review evidence. It does not represent the entire development history of Explain Aloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checkpoint:&lt;/strong&gt; &lt;code&gt;2d9f0c278df4&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Linked commit:&lt;/strong&gt; &lt;code&gt;3aa189b&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The Codex review found no shipping blockers. Its only recommendation was adding &lt;code&gt;permissions: contents: read&lt;/code&gt; to the CI workflow for least-privilege hardening. That was applied before submission.&lt;/p&gt;

&lt;p&gt;Earlier implementation work — including the narration hardening and WASM debugging — happened before this permanent Entire checkpoint was created.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Best Use of Gemma
&lt;/h3&gt;

&lt;p&gt;Gemma's role in Explain Aloud is bounded by design. It does not read raw HTML or receive an open-ended instruction to "explain this." The deterministic pipeline extracts facts first — table headers and cell values, code structure and language — and then provides those pre-extracted facts to the model with a specific wording task.&lt;/p&gt;

&lt;p&gt;The candidate Gemma produces must pass the segment validator before it reaches the narration plan. If it fails, the system falls back to a source-derived deterministic summary and records the fallback reason in the segment's &lt;code&gt;fallbackReason&lt;/code&gt; field. The model's output is a phrasing candidate, not the source of truth.&lt;/p&gt;

&lt;p&gt;This bounded role — where open local inference contributes without being trusted unconditionally — is the core design argument for using Gemma rather than a simpler rule expansion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of Entire
&lt;/h3&gt;

&lt;p&gt;I enabled Entire late in the finalization phase to preserve agent-session provenance against Git history.&lt;/p&gt;

&lt;p&gt;The permanent checkpoint 2d9f0c278df4, linked to commit 3aa189b, captures a Codex documentation session that recorded the final CI review evidence. That evidence included the earlier Codex review result of BLOCKERS: None and SAFE TO SHIP: YES, plus the least-privilege permissions: contents: read hardening that followed.&lt;/p&gt;

&lt;p&gt;The checkpoint does not represent the broader implementation history. Earlier work — narration hardening, the WASM fix, table narration changes, and the original CI review — happened before this permanent checkpoint was created.&lt;/p&gt;

&lt;p&gt;What Entire gives me here is a traceable record connecting the final evidence, agent context, and Git commit at submission time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of GitHub Copilot
&lt;/h3&gt;

&lt;p&gt;I used GitHub Actions to automate test, typecheck, and production-build verification on pushes to main and pull requests targeting main, with least-privilege contents: read permissions.&lt;/p&gt;

&lt;p&gt;The workflow runs npm test, npm run compile, and npm run build in sequence, giving relevant pushes and pull requests the same automated verification path used during finalization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Use of ElevenLabs
&lt;/h3&gt;

&lt;p&gt;ElevenLabs generated the voiceover for the demo video. It was not used in any part of the running extension. The Explain Aloud application's speech synthesis is Kokoro.js running as WASM inside the Chrome extension, with locally bundled ONNX Runtime assets. ElevenLabs touches only the recorded demonstration.&lt;/p&gt;




&lt;p&gt;The problem that started this was small and specific. My girlfriend wanted to listen to ChatGPT answers while doing something else. The built-in Read Aloud features mostly worked — until the answer had structure. Then it stopped making sense.&lt;/p&gt;

&lt;p&gt;Explain Aloud's answer to that is to treat structure seriously before it reaches the voice. A table is not a paragraph read aloud. A code block is not a prose sentence with odd punctuation. A heading is a transition, not an item.&lt;/p&gt;

&lt;p&gt;The test suite reaching 191 passing tests was a milestone. Hearing actual Kokoro speech in the running Chrome extension — after fixing the WASM module failure that a green build had concealed — was a different kind of milestone. And deciding to speak a three-person table one row at a time, rather than as a ranking exercise, was the kind of decision that only matters if you are actually trying to answer the question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If the screen disappears, does the answer still make sense?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That question does not have a final answer yet. But it has a more honest one than before.&lt;/p&gt;

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