<?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: Yash Kumar Saini</title>
    <description>The latest articles on DEV Community by Yash Kumar Saini (@yashksaini).</description>
    <link>https://dev.to/yashksaini</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1242333%2F27bdd497-8c93-4290-a901-94eec3759056.jpeg</url>
      <title>DEV Community: Yash Kumar Saini</title>
      <link>https://dev.to/yashksaini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yashksaini"/>
    <language>en</language>
    <item>
      <title>I Revived DevNotion (multi agent blog workflow) — And Finally Perfected it</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 06 Jun 2026 13:58:54 +0000</pubDate>
      <link>https://dev.to/yashksaini/i-revived-devnotion-multi-agent-blog-workflow-and-finally-perfected-it-411p</link>
      <guid>https://dev.to/yashksaini/i-revived-devnotion-multi-agent-blog-workflow-and-finally-perfected-it-411p</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every Monday standup, someone asks: &lt;em&gt;"What'd you ship last week?"&lt;/em&gt; And every Monday I freeze. Did I merge that PR Tuesday or Wednesday? Was the gnarly part in the harvest code or the publisher? I'd sit there scrolling my own commit history like it belonged to a stranger.&lt;/p&gt;

&lt;p&gt;So back in March I built something to answer the question for me. &lt;strong&gt;&lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/strong&gt; — a &lt;a href="https://mastra.ai" rel="noopener noreferrer"&gt;Mastra&lt;/a&gt; pipeline that reads a week of my GitHub activity, writes it up as a first-person blog post, and ships it to Notion and DEV.to. It won &lt;strong&gt;$500&lt;/strong&gt; in the Notion MCP Challenge. Then I did what everyone does with a hackathon project: nothing, for two months.&lt;/p&gt;

&lt;p&gt;Here's the thing, though. It worked, but I never fully trusted it. One LLM, no say in it. It published the instant it finished writing — no preview, no edit, no "hang on, let me read that first." And when I opened the repo back up for the Finish-Up-A-Thon, the very first run failed in the most embarrassing way I can think of. Honestly? Best thing that could've happened. It showed me exactly what "finish this" was supposed to mean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DevNotion v2&lt;/strong&gt; is the version I should've shipped the first time. It writes a draft, shows it to me, lets me edit it, and &lt;em&gt;only&lt;/em&gt; publishes when I say go. Multiple LLM providers. Three publishing targets. A deterministic cover image, much better data, a real test suite. Seven phases, 51 tests, a one-command setup wizard — and, most of all, a failure mode that protects my name instead of betting it.&lt;/p&gt;

&lt;p&gt;One idea ran under the whole rebuild: a tool that writes in your voice and posts under your name has to be trustworthy before it gets to be convenient.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;github.com/yashksaini-coder/DevNotion&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/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;
        DevNotion
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DevNotion is an agentic server built using 3 mastra agents to work as a pipeline to publish weekly blogs on Notion Pages &amp;amp; DEV.to 
    &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;DevNotion&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Turn your weekly GitHub activity into a polished, illustrated blog post — narrated by an LLM, reviewed by you, and published to Notion, DEV.to, and Hashnode.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;DevNotion is a &lt;a href="https://mastra.ai" rel="nofollow noopener noreferrer"&gt;Mastra&lt;/a&gt; pipeline of specialist agents: it harvests a week of your GitHub work, narrates it into a first-person post, generates images, and lets you preview/edit before it publishes anywhere. Originally built for the DEV.to × Notion MCP Challenge (where it won $500); &lt;strong&gt;v2&lt;/strong&gt; was rebuilt for the &lt;a href="https://dev.to/challenges/github-2026-05-21" rel="nofollow"&gt;GitHub Finish-Up-A-Thon&lt;/a&gt; with multi-LLM support, a review dashboard, image generation, richer harvesting, and a fail-safe publishing flow.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What's new in v2&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;v1&lt;/th&gt;
&lt;th&gt;v2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemini only&lt;/td&gt;
&lt;td&gt;Gemini · OpenAI · Anthropic (provider abstraction; default &lt;code&gt;gemini-3-flash-preview&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Publish targets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Notion + DEV.to&lt;/td&gt;
&lt;td&gt;Notion + DEV.to + &lt;strong&gt;Hashnode&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;generate → publish immediately&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;generate → preview → edit → approve → publish&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;none (CLI/cron)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;web dashboard&lt;/strong&gt; (trigger, preview, edit, history)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Images&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;deterministic stats&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Ff2zds547d4aqf68cgzx6.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.amazonaws.com%2Fuploads%2Farticles%2Ff2zds547d4aqf68cgzx6.png" alt="Dashboard run history — dark UI, status badges (Preview Ready / Published), Review &amp;amp; Publish actions" width="800" height="377"&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.amazonaws.com%2Fuploads%2Farticles%2Fblikw1h9n5nivwqqpz7n.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.amazonaws.com%2Fuploads%2Farticles%2Fblikw1h9n5nivwqqpz7n.png" alt="Preview &amp;amp; edit screen — the generated post in an editable markdown box, an Approve &amp;amp; Publish button, and a stats grid" width="799" height="398"&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.amazonaws.com%2Fuploads%2Farticles%2Fguc8z5bi9duc5g3u6su0.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.amazonaws.com%2Fuploads%2Farticles%2Fguc8z5bi9duc5g3u6su0.png" alt="Weekly stats card used as the cover — " width="800" height="420"&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.amazonaws.com%2Fuploads%2Farticles%2F9vs4ctqrse1cbs8dkuc9.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.amazonaws.com%2Fuploads%2Farticles%2F9vs4ctqrse1cbs8dkuc9.png" alt="Public landing page" width="799" height="391"&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.amazonaws.com%2Fuploads%2Farticles%2F2p5edyiq3kkch9awd7rp.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.amazonaws.com%2Fuploads%2Farticles%2F2p5edyiq3kkch9awd7rp.png" alt="Architecture — three agents, generation split from publishing by a human approval gate" width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three specialist agents, with generation and publishing deliberately split by a human approval gate:&lt;/p&gt;

&lt;p&gt;Each agent does one thing and only one thing. &lt;strong&gt;Harvest&lt;/strong&gt; is pure data — two quick GraphQL/REST passes over the week, no LLM anywhere near it. &lt;strong&gt;Narrate&lt;/strong&gt; is the single model call: raw data goes in, a first-person post comes out, in whatever tone you picked. &lt;strong&gt;Publish&lt;/strong&gt; is plumbing — a Notion page, a DEV.to draft, a Hashnode draft if you've set it up, plus the cover and the footer. And right in the middle, between writing and shipping, there's a human. That gate is the whole reason v2 exists.&lt;/p&gt;

&lt;p&gt;The rule I kept from v1: an LLM only shows up where it earns its spot. Harvesting the data, drawing the stats card, pushing to each platform — that's all plain deterministic code. No tokens, no rate limits, no chance of the thing inventing a number. The model writes the prose. Everything around it is code I can unit-test. That's the only reason I'm comfortable letting it post under my name without re-reading every figure first.&lt;/p&gt;

&lt;p&gt;In practice it's quick. I kick off a run (scheduled, or by hand from the dashboard) and a few seconds later there's a draft sitting there marked &lt;strong&gt;Preview Ready&lt;/strong&gt;. Not published. I read it, fix a sentence or two right in the browser, hit &lt;strong&gt;Approve &amp;amp; Publish&lt;/strong&gt;. &lt;em&gt;Then&lt;/em&gt; it goes out — and even then it goes out as a draft on each platform, not a live post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where v1 actually was
&lt;/h3&gt;

&lt;p&gt;I genuinely thought I'd left v1 in decent shape. The first run set me straight in about four seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Narrate step: LLM call failed: You exceeded your current quota …
  limit: 0, model: gemini-2.0-flash
Publish: Created Notion page: https://app.notion.com/…
Publish: Published DEV.to article: https://dev.to/…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Read it twice. The narration &lt;strong&gt;failed&lt;/strong&gt; — and the thing &lt;strong&gt;published anyway&lt;/strong&gt;. v1 had this well-meaning "always produce a blog" fallback, so a quota error quietly shipped a bare stats stub to my real DEV.to account. For a tool whose entire job is to represent my work? That's about the worst thing it could possibly do.&lt;/p&gt;

&lt;p&gt;That one log was hiding three separate bugs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrong model, wired to nothing.&lt;/strong&gt; The live workflow read a different config key than the one I'd been carefully setting. So it quietly fell back to a &lt;em&gt;retired&lt;/em&gt; &lt;code&gt;gemini-2.0-flash&lt;/code&gt;, whose free quota is now &lt;code&gt;0&lt;/code&gt;. I'd been turning a knob connected to thin air.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The failure was silent.&lt;/strong&gt; A &lt;code&gt;try/catch&lt;/code&gt; swapped in a deterministic stub and marched straight on to publishing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing ever let me look.&lt;/strong&gt; No preview, no gate, no pause before it went live.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And a sneakier one underneath all that: a week with 13 direct commits reported &lt;strong&gt;&lt;code&gt;+0/-0&lt;/code&gt; lines changed&lt;/strong&gt;, because line stats were summed from pull requests only. The posts came out technically accurate and completely dead.&lt;/p&gt;

&lt;p&gt;What actually bothered me wasn't any single bug. It was that the &lt;em&gt;worst&lt;/em&gt; one was the helpful one. That &lt;code&gt;try/catch&lt;/code&gt;-into-a-stub got added in v1 on purpose, to guarantee a post always shipped. And that good intention is exactly what turned a recoverable quota error into a published embarrassment. Turns out "always succeed" is the wrong goal for something that talks for you. "Never lie" is the right one.&lt;/p&gt;

&lt;p&gt;So I rebuilt it in seven phases. Each one got a quick spec, a plan, the actual work, a review, and a green test run before I let myself start the next. Not ceremony — it's the fail-loud rule pointed at my own process. Every phase had to prove itself (types clean, tests green) before anything got stacked on top, so nothing could quietly rot three phases deep. Here's how it went.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 1 — Make narration trustworthy
&lt;/h3&gt;

&lt;p&gt;First, one place to choose the model, defaulting to &lt;code&gt;gemini-3-flash-preview&lt;/code&gt; (which, unlike the thing it replaced, actually has a free tier). Then the part that matters most — narration that &lt;strong&gt;fails loud&lt;/strong&gt;:&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="c1"&gt;// src/llm/narrate.ts&lt;/span&gt;
&lt;span class="c1"&gt;// FAIL-LOUD: throws on provider error or unparseable output. NEVER returns a&lt;/span&gt;
&lt;span class="c1"&gt;// deterministic fallback — the caller decides what to do with a failure.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;narrateBlog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildNarratorSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tone&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;casual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focusAreas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Generate a blog post from this GitHub contribution data:\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Gemini 3 is a thinking model — small budgets starve the answer&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Narration failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blog&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;If narration throws, the step throws, the run is marked &lt;strong&gt;failed&lt;/strong&gt;, and the publish step never runs. A quota error can't reach a single reader anymore. (The old deterministic builder is still in there — but now it's a manual escape hatch you reach for on purpose, never something that fires on its own.)&lt;/p&gt;

&lt;p&gt;Trustworthy also meant not living and dying by one vendor's quota. v1 was Gemini or nothing. v2 runs every call through a tiny provider interface, so an outage or a quota wall is a config change instead of a rewrite:&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="c1"&gt;// src/llm/provider.ts — one interface, three interchangeable backends&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;LLMProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LLM_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createOpenAIProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anthropic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createAnthropicProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createGeminiProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Gemini side also rotates through a comma-separated list of keys (&lt;code&gt;keys[keyIndex++ % keys.length]&lt;/code&gt;). Three free keys gets you somewhere around 1,500 requests a day between them, which is plenty for a weekly run plus a day of messing around. Swapping the whole brain of the pipeline is one env var now.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 2 — Split generate from publish
&lt;/h3&gt;

&lt;p&gt;This was the keystone, and it's the reason v1 never had a preview to begin with. The old workflow welded harvest → narrate → publish into one chain, and that chain's output schema &lt;strong&gt;dropped the blog body on the floor&lt;/strong&gt;. There was nothing to preview because the content never survived the handoff between steps. The smoking gun was the workflow's own output type: it carried URLs and counts and no &lt;code&gt;content&lt;/code&gt; field at all. You can't bolt a preview screen onto something that throws the draft away. So splitting it wasn't a nice-to-have stacked on v1. It was the thing that had to happen before anything else could.&lt;/p&gt;

&lt;p&gt;v2 cuts the pipeline in half. A &lt;code&gt;generate&lt;/code&gt; phase makes a full, previewable draft and &lt;strong&gt;stops&lt;/strong&gt;. A separate &lt;code&gt;publish&lt;/code&gt; step runs only when you approve, edits and all:&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="c1"&gt;// src/server/routes/run.ts&lt;/span&gt;
&lt;span class="c1"&gt;// POST /run  → generates a draft, stores it, sets status "preview". Publishes NOTHING.&lt;/span&gt;
&lt;span class="c1"&gt;// POST /publish/:jobId → applies your edited markdown, then calls publishBlog().&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That one cut bought three things at once: a safe failure mode, an editable preview in the browser, and run history that finally shows what actually got written. And the publisher turned into a single function that both the dashboard and the cron job call:&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="c1"&gt;// src/publish/publish-content.ts — one publisher, used by cron AND the dashboard&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;publishBlog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;weeklyData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;publishMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;images&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;statsCardPath&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="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PublishResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* notion → devto → hashnode → write planner + footer */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Tying it together is a little JSON-backed &lt;strong&gt;run store&lt;/strong&gt; that's basically the pipeline's state machine. Every run walks a fixed path — &lt;code&gt;running → preview → publishing → published&lt;/code&gt;, or &lt;code&gt;failed&lt;/code&gt; — and that one record is what the dashboard lists, what the preview renders from, and what publish reads back when you hit approve. It's also where the &lt;code&gt;Dev log #n&lt;/code&gt; counter lives, so the numbers stay in order whether a post came from the dashboard or the cron job. One ledger, two doors in.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 3 — Harvest the real diff
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;+0/-0&lt;/code&gt; bug is dead. A second, bounded GraphQL pass walks each active repo's commits — real additions and deletions, changed-file counts, and the &lt;strong&gt;directories you actually touched&lt;/strong&gt; — so the narrator can talk about specifics instead of waving its hands. That same week that used to report nothing now says:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;week 2026-05-27 → totals +2,252/-293
- DevNotion:        +2,220/-282, files=25, areas=[src/server, src/tools, bin]
- yashksaini-coder: +24/-3,      files=5,  areas=[assets, .github/workflows]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It's quota-safe on purpose: hard caps on how many repos and commits it'll look at, and every commit wrapped in its own &lt;code&gt;try/catch&lt;/code&gt; so one bad fetch can't sink the whole week:&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="c1"&gt;// src/tools/github-commits.ts — bounded, best-effort changed-file harvest&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sha&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;shas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.github.com/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/commits/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="k"&gt;of &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// skip this commit on any error — never sink the whole week&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;Two passes, on purpose. GraphQL is great for breadth — a whole week across a pile of repos in one query. The per-commit file lists come from a few targeted REST calls instead, each one cheap and each one isolated. This was the single thing v1's readers kept telling me: commit titles are cryptic, the diff is where the story actually is. Now it's in there.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 4 — Sharper writing + an author footer
&lt;/h3&gt;

&lt;p&gt;I tightened the narrator's instructions. Lead with a character or a moment, not a stat line. A hard &lt;em&gt;no stat-dumping&lt;/em&gt; rule — the numbers live on the card, the prose doesn't need to recite them. And lean on the new touched-areas data, so a post can say "a week deep in &lt;code&gt;src/server&lt;/code&gt;" instead of "made several changes." You still get to pick the tone — &lt;code&gt;casual&lt;/code&gt;, &lt;code&gt;professional&lt;/code&gt;, &lt;code&gt;technical&lt;/code&gt;, &lt;code&gt;storytelling&lt;/code&gt; — but those rules hold no matter which one you choose. Then I gave every post, on every platform, the same author footer, built from one config file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
**Yash K Saini** — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.

[GitHub] · [X] · [LinkedIn] · [Portfolio]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It gets stamped on at publish time, not written by the narrator. So you can't accidentally delete it in the preview, and Hashnode gets it for free.&lt;/p&gt;

&lt;p&gt;You can see the difference in the titles. One real run opened with &lt;em&gt;"Bridging the Gap: TLS Interop, p2p Hardening, and Neovim Refinement"&lt;/em&gt; — pulled straight out of the directories I'd been living in that week. Not "This week I made 35 commits." That's what the touched-areas data buys you.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 5 — The cover image
&lt;/h3&gt;

&lt;p&gt;My first instinct was the obvious one: an AI cover. Nano Banana (&lt;code&gt;gemini-2.5-flash-image&lt;/code&gt;), prompted off the week's headline. I wired the whole thing up… and found out the free tier gives image generation a quota of &lt;code&gt;limit: 0&lt;/code&gt;. It never produced a single cover. So I was carrying a flaky, rate-limited dependency that did, in practice, nothing.&lt;/p&gt;

&lt;p&gt;So I ripped it out and used something I already had. The &lt;strong&gt;stats card&lt;/strong&gt; — a hand-built SVG rendered to PNG with &lt;code&gt;@resvg/resvg-js&lt;/code&gt;, showing the week's &lt;em&gt;exact&lt;/em&gt; numbers — happens to already be 1200×630, which is cover/OG size. So I just made it the cover:&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="c1"&gt;// src/images/stats-card.ts — the numbers come from code, never from a model&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalCommits&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;commits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`+&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalAdditions&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;added&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalDeletions&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;removed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It leads with the post title and the headline numbers (commits, PRs, reviews, lines, repos) and becomes the cover everywhere: DEV.to's &lt;code&gt;main_image&lt;/code&gt;, the Hashnode cover, the Notion page cover. The &lt;code&gt;Dev log #n&lt;/code&gt; bit stays on the article title, not the card, so the banner is all title and stats, and a long title wraps instead of running off the edge. Because the card is built from code, no model can ever round &lt;code&gt;2,252&lt;/code&gt; down to "about 2,000." And here's the bit I liked: ripping out the AI cover made the feature &lt;em&gt;more&lt;/em&gt; reliable, not less. It went from never working to always working in the same commit that deleted code. No API, no quota, no fallback path. It just renders.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 6 — A dashboard worth screenshotting
&lt;/h3&gt;

&lt;p&gt;My three dashboard routes had quietly drifted into three slightly-different stylesheets. I pulled them back into one design system — shared tokens, one shell, shared components — and gave it a deliberate &lt;strong&gt;Swiss / International Typographic&lt;/strong&gt; feel: tight type scale, one orange accent, lots of breathing room. It's all server-rendered HTML, no build step, straight through &lt;code&gt;tsx&lt;/code&gt;, which keeps the footprint tiny and the dependency list short. Most of the effort went into the preview/edit screen, because honestly that screen &lt;em&gt;is&lt;/em&gt; the product. It's where you read the draft, tweak a line, and decide to ship.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 7 — Tests
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;51 tests across 18 suites&lt;/strong&gt; now cover the frontmatter parser, the fail-loud path, the deterministic fallback, tag normalization, provider and model selection, publish-target selection, the diff aggregation, and the stats-card builder (cover title-strip and wrap included). They run in under two seconds and they're deliberately boring. The point isn't cleverness — it's a regression net under the deterministic spine, so something like swapping out the cover implementation can't quietly break publishing.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setup in one command
&lt;/h3&gt;

&lt;p&gt;v1's setup was a &lt;code&gt;.env&lt;/code&gt; scavenger hunt: copy the example, guess which keys you actually need, find out you missed one when a run blows up halfway through. &lt;code&gt;npx devnotion init&lt;/code&gt; is a wizard instead. It asks for each credential, &lt;strong&gt;checks it with a live API call&lt;/strong&gt; (green check or red error, right there in the terminal), and writes a clean &lt;code&gt;.env.local&lt;/code&gt;. Small thing. But something that calls itself &lt;em&gt;finished&lt;/em&gt; shouldn't make the first five minutes the worst five.&lt;/p&gt;
&lt;h3&gt;
  
  
  Before vs after
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before (v1)&lt;/th&gt;
&lt;th&gt;After (v2)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode&lt;/td&gt;
&lt;td&gt;silently published a stub&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;fail-loud&lt;/strong&gt; — publishes nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review&lt;/td&gt;
&lt;td&gt;none — instant publish&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;preview, edit, approve&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLMs&lt;/td&gt;
&lt;td&gt;Gemini only&lt;/td&gt;
&lt;td&gt;Gemini / OpenAI / Anthropic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publish targets&lt;/td&gt;
&lt;td&gt;Notion + DEV.to&lt;/td&gt;
&lt;td&gt;+ Hashnode (Subscription)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Line stats&lt;/td&gt;
&lt;td&gt;PR-only (&lt;code&gt;+0/-0&lt;/code&gt; on commit weeks)&lt;/td&gt;
&lt;td&gt;real per-commit deltas + touched areas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cover image&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;Deterministic stats card, used as the cover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;hand-edit &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npx devnotion init&lt;/code&gt; wizard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;a handful&lt;/td&gt;
&lt;td&gt;51 across 18 suites&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; "It works, but you have to trust it blindly."&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; "It works, and it gets out of your way — safely."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Publishing responsibly (the research that turned into a feature)
&lt;/h2&gt;

&lt;p&gt;Around phase five I almost bolted on auto-publishing to a couple more platforms. Then I did the boring thing first and actually read the rules. The question I typed into the search bar was blunt: &lt;em&gt;"is automated/AI publishing even allowed here, and can it get my account restricted?"&lt;/em&gt; The answers ended up reshaping the design.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Medium&lt;/strong&gt; is a dead end for automation, on two counts. Its &lt;a href="https://github.com/Medium/medium-api-docs" rel="noopener noreferrer"&gt;publishing API was archived in 2023&lt;/a&gt; (no new integration tokens), and its &lt;a href="https://help.medium.com/hc/en-us/articles/22576852947223-Artificial-Intelligence-AI-content-policy" rel="noopener noreferrer"&gt;AI content policy&lt;/a&gt; gives undisclosed AI writing "Network Only" distribution and can pull it outright. An auto-posted AI dev-log is exactly what that policy is aimed at — so Medium's off the roadmap by design, not by accident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hashnode&lt;/strong&gt; has a real &lt;a href="https://apidocs.hashnode.com/" rel="noopener noreferrer"&gt;publishing API&lt;/a&gt;, but its &lt;a href="https://hashnode.com/code-of-conduct" rel="noopener noreferrer"&gt;Code of Conduct&lt;/a&gt; bans "automated or bulk posting" and self-promotion without contributing, and its &lt;a href="https://hashnode.com/terms" rel="noopener noreferrer"&gt;terms&lt;/a&gt; let them suspend accounts at their discretion. A genuine weekly post is fine. Blind, scheduled, multi-account auto-posting is the pattern they're watching for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEV.to&lt;/strong&gt; is the friendliest to drafts and review, which is exactly the workflow I landed on anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix wasn't to publish &lt;em&gt;less&lt;/em&gt;. It was to publish honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Draft by default, human approval required.&lt;/strong&gt; Every platform gets a &lt;em&gt;draft&lt;/em&gt;; I review and edit it in the dashboard, then publish on-platform myself. Nothing ships unattended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A disclosure footer&lt;/strong&gt; on every post — small, bold, &lt;strong&gt;Generated by DevNotion&lt;/strong&gt;. No hiding the tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;Dev log #n&lt;/code&gt; title prefix&lt;/strong&gt;, so the series is honest about what it is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The thread through all of it is disclosure over cleverness. A reader — and a platform's moderation team — can tell exactly what this is and whose name is on it. That's cheaper than building some workaround, and a lot cheaper than getting an account suspended.&lt;/p&gt;

&lt;p&gt;That one search turned a feature I'd have rushed into a constraint that made the whole thing more defensible. "Finished" doesn't just mean it runs. It means it won't get your accounts pulled.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Reopening a codebase you haven't touched in months is mostly just remembering. You spend the first few hours working out why past-you did what they did before you can change any of it. That's where pairing with AI actually paid off, and it was almost never "write this function." It was "help me understand this again." A few moments stuck with me more than any autocomplete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diagnosing the silent bug.&lt;/strong&gt; The most useful assist wasn't generated code — it was tracing &lt;em&gt;why&lt;/em&gt; setting the model config changed nothing. Walking the two divergent code paths with an AI partner surfaced the split-brain config in minutes, instead of an afternoon of &lt;code&gt;console.log&lt;/code&gt; archaeology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The provider abstraction.&lt;/strong&gt; Describing "one interface, swap Gemini / OpenAI / Anthropic" in plain English got me a clean factory I refined, instead of writing it from a blank file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relearning an unfamiliar API.&lt;/strong&gt; Mastra's workflow primitives had shifted since I first used them. Rather than spelunking changelogs, I described the harvest → narrate → publish shape I wanted, let the assistant scaffold the &lt;code&gt;createStep&lt;/code&gt;/&lt;code&gt;createWorkflow&lt;/code&gt; calls, then fixed the schema wiring against the real types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowing when to delete.&lt;/strong&gt; I started wiring an AI cover via the Nano Banana API and paired to figure out its quirks — Gemini image models return bytes via &lt;code&gt;result.files&lt;/code&gt;, and you call &lt;code&gt;generateText&lt;/code&gt;, not some dedicated image function. The genuinely useful assist came right after: confirming the free tier's image quota was &lt;code&gt;limit: 0&lt;/code&gt;, which made dropping the whole thing the obvious call instead of a stubborn one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests.&lt;/strong&gt; Generating the first pass of each unit test from the module's signature, then tightening the assertions by hand, is what made a 51-test suite cheap enough to actually write.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Frq3urpk266z9yw0acckw.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.amazonaws.com%2Fuploads%2Farticles%2Frq3urpk266z9yw0acckw.png" alt="GitHub Copilot in the workflow — e.g. Copilot Chat generating the provider interface, or inline completion filling a provider class" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The thing that changed most wasn't speed. It was confidence coming back into a cold codebase. It didn't just finish my lines — it helped me remember what the code did and figure out where to take it next. Nobody warns you that's the hard part of "finishing" something. That's the part pairing helped with most.&lt;/p&gt;
&lt;h2&gt;
  
  
  Results &amp;amp; Validation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7 phases, shipped incrementally&lt;/strong&gt; — each verified against &lt;code&gt;tsc&lt;/code&gt; and the test suite before the next.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;51 tests / 18 suites, green.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The narration path was confirmed live on the free tier (&lt;code&gt;gemini-3-flash-preview&lt;/code&gt;), the harvest fix verified on a real week (&lt;code&gt;+0/-0&lt;/code&gt; → &lt;code&gt;+2,252/-293&lt;/code&gt;), and the stats card rendered to a real 1200×630 PNG.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proven end-to-end on real weeks.&lt;/strong&gt; I ran v2 against three separate January 2026 weeks; each produced a Notion page and a DEV.to draft with the deterministic stats card attached as the cover — verified straight from the DEV.to API, where &lt;code&gt;cover_image&lt;/code&gt; resolved to the generated card for every post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is theoretical. The rebuild got verified the same way it got built — incrementally. Every phase ended with a clean &lt;code&gt;tsc&lt;/code&gt; and a green suite before the next one started. The failing first run became a passing one I watched run end to end. And the final proof was deliberately mundane: three real weeks, published as reviewable drafts, covers and all, with nothing shipped that I hadn't read first.&lt;/p&gt;
&lt;h2&gt;
  
  
  What "finishing" actually meant
&lt;/h2&gt;

&lt;p&gt;I came into this assuming "finishing" meant features. More platforms, nicer output, an AI cover. Almost none of it was that. The phases that actually mattered were the ones that made the tool &lt;strong&gt;honest&lt;/strong&gt; — failing loud instead of shipping junk, showing me the draft before it went live, putting real diffs where guesses used to be, reading each platform's rules before automating against them.&lt;/p&gt;

&lt;p&gt;And the clearest tell? How often "finishing" turned out to mean &lt;em&gt;deleting&lt;/em&gt;. The AI cover, gone. The silent fallback, out of the hot path. A whole config key wired to nothing, gone. The version that finally felt done wasn't the one with the most code. It was the one I'd trust to post under my name while I wasn't looking.&lt;/p&gt;



&lt;p&gt;The repo is &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;yashksaini-coder/DevNotion&lt;/a&gt;: &lt;code&gt;npx devnotion init&lt;/code&gt;, point it at a week, and watch the draft land in the dashboard &lt;em&gt;before&lt;/em&gt; anything ships. If you try it, open an issue — I read them now, and "I read them now" is its own small proof the thing is finished. ⭐ it if the rebuild resonates.&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/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;
        DevNotion
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DevNotion is an agentic server built using 3 mastra agents to work as a pipeline to publish weekly blogs on Notion Pages &amp;amp; DEV.to 
    &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;DevNotion&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Turn your weekly GitHub activity into a polished, illustrated blog post — narrated by an LLM, reviewed by you, and published to Notion, DEV.to, and Hashnode.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;DevNotion is a &lt;a href="https://mastra.ai" rel="nofollow noopener noreferrer"&gt;Mastra&lt;/a&gt; pipeline of specialist agents: it harvests a week of your GitHub work, narrates it into a first-person post, generates images, and lets you preview/edit before it publishes anywhere. Originally built for the DEV.to × Notion MCP Challenge (where it won $500); &lt;strong&gt;v2&lt;/strong&gt; was rebuilt for the &lt;a href="https://dev.to/challenges/github-2026-05-21" rel="nofollow"&gt;GitHub Finish-Up-A-Thon&lt;/a&gt; with multi-LLM support, a review dashboard, image generation, richer harvesting, and a fail-safe publishing flow.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What's new in v2&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;v1&lt;/th&gt;

&lt;th&gt;v2&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;LLM&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Gemini only&lt;/td&gt;

&lt;td&gt;Gemini · OpenAI · Anthropic (provider abstraction; default &lt;code&gt;gemini-3-flash-preview&lt;/code&gt;)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Publish targets&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Notion + DEV.to&lt;/td&gt;

&lt;td&gt;Notion + DEV.to + &lt;strong&gt;Hashnode&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Flow&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;generate → publish immediately&lt;/td&gt;

&lt;td&gt;&lt;strong&gt;generate → preview → edit → approve → publish&lt;/strong&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;UI&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;none (CLI/cron)&lt;/td&gt;

&lt;td&gt;
&lt;br&gt;
&lt;strong&gt;web dashboard&lt;/strong&gt; (trigger, preview, edit, history)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Images&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;none&lt;/td&gt;

&lt;td&gt;&lt;strong&gt;deterministic stats&lt;/strong&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yashksaini-coder/DevNotion" 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;Connect:&lt;/strong&gt; &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with &lt;a href="https://mastra.ai" rel="noopener noreferrer"&gt;Mastra&lt;/a&gt;, the &lt;a href="https://ai-sdk.dev" rel="noopener noreferrer"&gt;Vercel AI SDK&lt;/a&gt;, Gemini 3 Flash for narration, &lt;code&gt;@resvg/resvg-js&lt;/code&gt; for the stats-card cover, and the &lt;a href="https://developers.notion.com" rel="noopener noreferrer"&gt;Notion&lt;/a&gt; / &lt;a href="https://developers.forem.com/api" rel="noopener noreferrer"&gt;DEV.to&lt;/a&gt; / &lt;a href="https://apidocs.hashnode.com/" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt; APIs. By &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Yash K Saini&lt;/a&gt; — &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. Star it if it made you smile.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
    </item>
    <item>
      <title>From Rust CLI bug-hunting to a massive Portfolio overhaul</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 06 Jun 2026 08:34:48 +0000</pubDate>
      <link>https://dev.to/yashksaini/from-rust-cli-bug-hunting-to-a-massive-portfolio-overhaul-7nn</link>
      <guid>https://dev.to/yashksaini/from-rust-cli-bug-hunting-to-a-massive-portfolio-overhaul-7nn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;23 commits, 3 PRs, and a 7-day streak. This week was a balance of deep-diving into Rust CLI logic, refining my Neovim setup, and a massive +4,500 line overhaul of my portfolio.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;It’s been a while since I’ve had a "perfect week," but I finally hit that 7-day commit streak. 23 commits, 3 PRs, and 1 closed issue across 4 different repos—this week was the definition of a polyglot grind. I spent my time bouncing between surgical Rust fixes in a CLI tool, deep-diving into Python backend logic, and completely ripping apart my portfolio for a modern redesign.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I BUILT
&lt;/h2&gt;

&lt;p&gt;The week was split between high-level visual work and low-level logic. I managed to touch four different repositories, each serving a very different purpose in my workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  DeepRead
&lt;/h3&gt;

&lt;p&gt;I spent a good chunk of the week in &lt;a href="https://github.com/yashksaini-coder/DeepRead" rel="noopener noreferrer"&gt;DeepRead&lt;/a&gt;. With 8 commits, this was my most active repo in terms of frequency. It’s a Python-heavy project, and I was focused on refining the core processing logic. Python is usually my go-to for anything involving heavy data or AI-adjacent backend work, and this week was no exception. I didn't open a PR here yet, but the 8 commits represent a lot of iterative testing and refining of the internal modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  nvim
&lt;/h3&gt;

&lt;p&gt;You know it’s a productive week when you’re also tweaking your tools. I pushed 7 commits to my &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; config. Most of this was Lua-based (obviously), and I was mainly focused on tightening up my LSP settings and UI. There’s something incredibly satisfying about spending 20 minutes fixing a minor annoyance in your editor and then seeing it pay off for the rest of the week. If my environment isn't perfect, I can't get into that deep flow state.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitBanner
&lt;/h3&gt;

&lt;p&gt;I’ve been working on &lt;a href="https://github.com/yashksaini-coder/GitBanner" rel="noopener noreferrer"&gt;GitBanner&lt;/a&gt;, a TypeScript tool for generating GitHub profile banners. I pushed 6 commits and opened a new feature PR: &lt;a href="https://github.com/yashksaini-coder/GitBanner/pull/2" rel="noopener noreferrer"&gt;feat: add ignore-languages input to filter generated/vendored files&lt;/a&gt;. The goal here was to give users more control over what shows up in their stats. Nobody wants their "top languages" to be skewed by a massive vendored library or a bunch of generated CSS files. It’s a small DX improvement, but those are the ones that make a tool actually usable.&lt;/p&gt;

&lt;h3&gt;
  
  
  trx
&lt;/h3&gt;

&lt;p&gt;Finally, I did some surgical work on &lt;a href="https://github.com/pie-314/trx" rel="noopener noreferrer"&gt;trx&lt;/a&gt;, a Rust-based CLI tool. This was a classic "scratch your own itch" situation. I noticed a bug where the tool would repeatedly prompt for an update even after I’d already upgraded. I opened an issue for it—&lt;a href="https://github.com/pie-314/trx/issues/24" rel="noopener noreferrer"&gt;bug: TRX repeatedly prompts for update even after successfully updating&lt;/a&gt;—and then immediately went in to fix it. Rust is great for this kind of thing because once you get the logic right and the compiler is happy, you know it’s solid.&lt;/p&gt;

&lt;h2&gt;
  
  
  PULL REQUESTS
&lt;/h2&gt;

&lt;p&gt;I opened three PRs this week, and they tell the story of where my head was at.&lt;/p&gt;

&lt;p&gt;The most significant one in terms of sheer volume was for my &lt;a href="https://github.com/yashksaini-coder/portfolio/pull/13" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;. The PR, &lt;a href="https://github.com/yashksaini-coder/portfolio/pull/13" rel="noopener noreferrer"&gt;feat: portfolio modern redesign + scrolling GitHub PR sidebar&lt;/a&gt;, clocked in at a massive +4,509 lines. I decided it was time to move away from my old layout and go for something much more modern. The highlight of this redesign is a new scrolling sidebar that pulls in my latest GitHub PRs. It’s a bit meta—building a tool to show the tools I’m building—but I love how it looks. It’s still open as I’m fine-tuning some of the CSS transitions, but the bulk of the work is done.&lt;/p&gt;

&lt;p&gt;On the Rust side, I got &lt;a href="https://github.com/pie-314/trx/pull/25" rel="noopener noreferrer"&gt;fix: stop re-prompting update after user skips or upgrades&lt;/a&gt; merged into &lt;code&gt;trx&lt;/code&gt;. This was the fix for the bug I mentioned earlier. It involved tightening up the version check logic to ensure the CLI respects the user's choice to skip an update or correctly identifies when the local version matches the remote.&lt;/p&gt;

&lt;p&gt;I also have the &lt;a href="https://github.com/yashksaini-coder/GitBanner/pull/2" rel="noopener noreferrer"&gt;GitBanner&lt;/a&gt; PR open, which adds the &lt;code&gt;ignore-languages&lt;/code&gt; input. This was a fun bit of TypeScript logic, essentially filtering the language data before it hits the rendering engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  ISSUES &amp;amp; DISCUSSIONS
&lt;/h2&gt;

&lt;p&gt;I only opened one issue this week, but it was a productive one. I reported the update prompt bug in &lt;a href="https://github.com/pie-314/trx/issues/24" rel="noopener noreferrer"&gt;trx&lt;/a&gt; and then closed it myself with the PR I mentioned above. I’m a big believer in documenting bugs even if you’re the one fixing them five minutes later. It creates a paper trail for anyone else who might run into the same weird behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  TECH STACK
&lt;/h2&gt;

&lt;p&gt;This was a true polyglot week. My language breakdown is all over the place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; for the heavy lifting in DeepRead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; for the GitBanner logic and the massive Portfolio redesign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; for the CLI stability in trx.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lua&lt;/strong&gt; for the Neovim configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The numbers tell an interesting story: +4,614 additions and -1,081 deletions. That’s a huge net positive, mostly driven by the portfolio redesign. Usually, I like to see more deletions (refactoring is a gift), but when you’re shipping a whole new UI, the additions are going to win every time.&lt;/p&gt;

&lt;p&gt;And of course, the 7-day streak. It wasn't a forced grind; I just happened to be in a really good rhythm where I had something I wanted to ship every single day.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;Next week is all about landing those open PRs. I want to get the portfolio redesign merged and live—there are still a few responsive design bugs I need to squash in that new scrolling sidebar. I also want to see the GitBanner feature through to completion. Beyond that, I’m planning to dive back into the Python side of things with DeepRead. There’s some performance optimization I’ve been putting off, and now that my environment is dialed in and my portfolio is looking fresh, I’ve got no excuses left.&lt;/p&gt;

&lt;p&gt;Catch you all next week!&lt;/p&gt;




&lt;h1&gt;
  
  
  python #typescript #rust #frontend #dx #consistency · 4 min read · Generated 2026-05-24 by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;
&lt;/h1&gt;

</description>
      <category>python</category>
      <category>typescript</category>
      <category>rust</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Polishing the Rust CLI and Planning the Long Game</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 06 Jun 2026 08:34:30 +0000</pubDate>
      <link>https://dev.to/yashksaini/polishing-the-rust-cli-and-planning-the-long-game-14mh</link>
      <guid>https://dev.to/yashksaini/polishing-the-rust-cli-and-planning-the-long-game-14mh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;39 commits, 11 PRs, and 50 issues across 5 repos. This week was all about making trx feel like a first-class tool and dumping my entire brain into the issue tracker to map out the future. Hit a perfect 7-day streak, too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;It’s been one of those weeks where the code just flows. I managed to hit a perfect 7-day streak, pushing 39 commits and merging 9 out of 11 PRs. Most of my energy went into &lt;code&gt;trx&lt;/code&gt;, my Rust-based package manager CLI, where I focused on hardening the Homebrew backend and refining the UI. I also went on a bit of a documentation and planning spree, opening 50 issues to define the roadmap for the next few months. &lt;/p&gt;

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

&lt;p&gt;The star of the show this week was definitely &lt;a href="https://github.com/pie-314/trx" rel="noopener noreferrer"&gt;trx&lt;/a&gt;. If you’ve been following along, you know I’m trying to build a unified interface for package management that doesn't feel like a chore to use. &lt;/p&gt;

&lt;p&gt;I spent a significant chunk of time on the Homebrew backend. In &lt;a href="https://github.com/pie-314/trx/pull/30" rel="noopener noreferrer"&gt;PR #30&lt;/a&gt;, I completely overhauled how we handle search results. Instead of just dumping raw text, I’m now pulling in rich JSON details. This means better metadata, more accurate versioning, and a much cleaner internal data structure. I also had to fix a gnarly little edge case in &lt;a href="https://github.com/pie-314/trx/pull/26" rel="noopener noreferrer"&gt;PR #26&lt;/a&gt; where the tool would try to clean up &lt;code&gt;/usr/local/bin&lt;/code&gt; on macOS even if the directory didn't exist. (Pro tip: always guard your filesystem operations, even if you think the path is 'standard'.)&lt;/p&gt;

&lt;p&gt;On the UI side, I’m obsessed with making the terminal feel responsive. I added tiered match priorities to the fuzzy search scoring in &lt;a href="https://github.com/pie-314/trx/pull/28" rel="noopener noreferrer"&gt;PR #28&lt;/a&gt;. Now, if you type a direct match, it actually floats to the top instead of getting buried by some weird partial match logic. I also added distinct empty states in &lt;a href="https://github.com/pie-314/trx/pull/32" rel="noopener noreferrer"&gt;PR #32&lt;/a&gt;—so instead of a blank screen, you get "Start typing", "Searching…", or "No results found". It sounds minor, but it makes the tool feel alive.&lt;/p&gt;

&lt;p&gt;Outside of the Rust world, I did some maintenance on my &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; config (7 commits of Lua tweaking) and touched my &lt;a href="https://github.com/yashksaini-coder/DeepRead" rel="noopener noreferrer"&gt;DeepRead&lt;/a&gt; Python project. I also pushed a small update to &lt;a href="https://github.com/yashksaini-coder/claude-skills" rel="noopener noreferrer"&gt;claude-skills&lt;/a&gt;, which is a little Shell-based helper I've been tinkering with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;I opened 11 PRs this week, and seeing 9 of them get merged feels like a solid win. &lt;/p&gt;

&lt;p&gt;One of the most functional updates was adding new keybindings. In &lt;a href="https://github.com/pie-314/trx/pull/34" rel="noopener noreferrer"&gt;PR #34&lt;/a&gt;, I added a per-package update command bound to the &lt;code&gt;u&lt;/code&gt; key. I followed that up in &lt;a href="https://github.com/pie-314/trx/pull/36" rel="noopener noreferrer"&gt;PR #36&lt;/a&gt; with a manual update-check bound to &lt;code&gt;C&lt;/code&gt;. I’m trying to make the entire experience navigable without ever touching the mouse, and these shortcuts are a big part of that.&lt;/p&gt;

&lt;p&gt;I also spent some time on the "boring" but necessary stuff. &lt;a href="https://github.com/pie-314/trx/pull/31" rel="noopener noreferrer"&gt;PR #31&lt;/a&gt; was a massive README overhaul. I added a full keybinding reference, a configuration guide, and an updated roadmap. If I want people to actually use this thing, the docs need to be as sharp as the code.&lt;/p&gt;

&lt;p&gt;I did have two PRs that I ended up closing without merging (&lt;a href="https://github.com/pie-314/trx/pull/29" rel="noopener noreferrer"&gt;PR #29&lt;/a&gt; and &lt;a href="https://github.com/pie-314/trx/pull/27" rel="noopener noreferrer"&gt;PR #27&lt;/a&gt;). Usually, this happens when I realize I’m trying to do too much in one go. I ended up breaking those features down into smaller, more manageable PRs like #34 and #35 to keep the git history clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;If you look at my activity graph, you’ll see a massive spike in issues. I opened 50 issues this week, almost all of them in &lt;a href="https://github.com/pie-314/trx" rel="noopener noreferrer"&gt;trx&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I realized that I had a dozen different ideas floating around in my head and nowhere to put them. So, I sat down and did a massive brain dump. I categorized them into [UI], [Backend], [Feature], and [Performance]. &lt;/p&gt;

&lt;p&gt;Some of the ones I’m most excited about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[Backend] Flatpak and DNF support&lt;/strong&gt;: This is the next big hurdle for making &lt;code&gt;trx&lt;/code&gt; truly cross-distro.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[UI] Real-time Installation Progress&lt;/strong&gt;: Right now, the installation happens, but I want to pipe that stdout directly into a pretty UI component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[Performance] Adaptive Search Debounce&lt;/strong&gt;: To make the fuzzy search even snappier on older hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also closed out a &lt;a href="https://github.com/pie-314/trx/issues/41" rel="noopener noreferrer"&gt;Comprehensive Backend Guide&lt;/a&gt; issue, which was a big milestone for the project's internal documentation.&lt;/p&gt;

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

&lt;p&gt;This was a very polyglot week, though Rust was the clear winner for the heavy lifting. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt;: 27 commits and nearly 1,000 lines of code. I’m really leaning into the type system to handle the different package manager backends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lua&lt;/strong&gt;: Used for my Neovim setup. I’m constantly tweaking my dev environment to shave off those extra seconds of friction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python &amp;amp; Shell&lt;/strong&gt;: Mostly for utility scripts and my AI-related side projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stats show 970 additions and 279 deletions. That’s a healthy ratio for a feature-heavy week. I’m building out new functionality, but I’m also not afraid to rip out old code when I find a better way to do things (like the Homebrew backend refactor).&lt;/p&gt;

&lt;p&gt;And yeah, that 7-day streak? It wasn't a grind. When you're working on tools that you actually use every day, the motivation just takes care of itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week is going to be about tackling that mountain of issues I just created. I want to start with the [UI] improvements—specifically the scrollable help overlay and the keyboard hints in the status bar. I also want to start prototyping the Flatpak backend. &lt;/p&gt;

&lt;p&gt;If I can get the real-time installation progress working, that’ll be a huge UX win. There’s something incredibly satisfying about watching a progress bar move in the terminal. &lt;/p&gt;

&lt;p&gt;Catch you in the next update!&lt;/p&gt;




&lt;h1&gt;
  
  
  rust #cli #devtools #consistency #backend · 4 min read · Generated 2026-05-31 by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;
&lt;/h1&gt;

</description>
      <category>rust</category>
      <category>cli</category>
      <category>devtools</category>
      <category>consistency</category>
    </item>
    <item>
      <title>I built a local document Q&amp;A tool around Gemma 4 E4B's 128K context — five days, no RAG, no cloud</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 24 May 2026 07:16:52 +0000</pubDate>
      <link>https://dev.to/yashksaini/i-built-a-local-document-qa-tool-around-gemma-4-e4bs-128k-context-five-days-no-rag-no-cloud-2e1k</link>
      <guid>https://dev.to/yashksaini/i-built-a-local-document-qa-tool-around-gemma-4-e4bs-128k-context-five-days-no-rag-no-cloud-2e1k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Five days, an 8 GB laptop GPU, and a stubborn belief that for the kind of documents I actually read — research papers, internal memos, the API docs of one project — RAG is over-engineering. &lt;strong&gt;DeepRead&lt;/strong&gt; loads PDFs into Gemma 4 E4B's 128K context as page images and answers questions with footnote citations pointing to the exact page. No vector DB. No chunker. No retriever. ~500 lines of Python.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is my submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Build with Gemma 4&lt;/a&gt; prompt. The full repo is at &lt;code&gt;github.com/yashksaini-coder/DeepRead&lt;/code&gt;. The model is &lt;code&gt;gemma4:e4b&lt;/code&gt; (the 4-billion-parameter Gemma 4) served by Ollama. Everything runs offline.&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.amazonaws.com%2Fuploads%2Farticles%2Fa1l0pwmehq0wewkjme4u.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.amazonaws.com%2Fuploads%2Farticles%2Fa1l0pwmehq0wewkjme4u.png" alt="Main Chainlit Interface" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You start with an empty chat. The right sidebar has a &lt;em&gt;Papers&lt;/em&gt; picker — five classic CS papers ship bundled (Attention, GFS, MapReduce, Raft, Bitcoin), or you upload your own PDF. Click one and it ingests in about half a second; the sidebar's Plotly bar fills green to show how much of the 128K window is now in your prompt.&lt;/p&gt;

&lt;p&gt;Then you ask whatever you want. The answer streams back with footnote markers &lt;code&gt;[^1]&lt;/code&gt;, &lt;code&gt;[^2]&lt;/code&gt; that resolve to specific pages of specific papers at the bottom of the message. The model is constrained at prompt-construction time to use &lt;em&gt;only&lt;/em&gt; page IDs from a known list — so it physically can't hallucinate a page number.&lt;/p&gt;

&lt;p&gt;Diagnostics live in the same chat as slash commands. &lt;code&gt;/bench show&lt;/code&gt; renders the latest needle-in-a-haystack sweep as three Plotly charts (pass rate, tok/s, time-to-first-token). &lt;code&gt;/bench run --ctx 5000 20000 60000 --needles 5&lt;/code&gt; kicks off a fresh sweep. No tab switching, no separate session, no chat clearing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why E4B specifically
&lt;/h2&gt;

&lt;p&gt;I'm going to quote the model-selection paragraph straight from the README, because the rubric explicitly weights this and I don't want to bury it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;E4B is the only model in the Gemma 4 family — and arguably the only open model at this size today — that combines four properties at once:&lt;/strong&gt; a 128K context window wide enough to hold a complete research paper plus supplementary material in a single call; native vision that handles PDF pages rendered at 150 DPI without an OCR pipeline; native audio input (held in reserve for the next iteration); and a ~9.6 GB on-disk footprint that runs on an 8 GB laptop GPU. The 26B and 31B variants would push reasoning quality up, but they would kill the laptop story — and the whole point of DeepRead is that nothing leaves the machine. E2B was tempting for portability but loses fidelity on multi-step reasoning across long context. E4B is the precise sweet spot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three sentences, one decision, the rest of the build defends it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision I'm most proud of: no RAG
&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.amazonaws.com%2Fuploads%2Farticles%2Fahuvb5i5fano6v38iotp.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.amazonaws.com%2Fuploads%2Farticles%2Fahuvb5i5fano6v38iotp.png" alt="RAG VS DeepRead" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every "AI document assistant" I've used in the past two years has the same shape — chunk, embed into a vector DB, retrieve top-k, prompt a hosted LLM with the retrieved chunks. RAG. It works. It's also a small mountain of moving parts that all need to stay aligned: chunk size, overlap, embedding model version, top-k value, rerank threshold. And at the end of all that, your documents have been shipped to someone else's machine.&lt;/p&gt;

&lt;p&gt;DeepRead is a bet that for a non-trivial class of documents — research papers, internal memos, a few weeks of meeting notes — none of that is necessary. Drop the PDFs into the prompt as rendered page images, ask the question, get an answer with page citations. The whole tool is &lt;code&gt;gemma4:e4b&lt;/code&gt; running locally via Ollama, plus about 500 lines of Python.&lt;/p&gt;

&lt;p&gt;I did a full math comparison which I will post in a later article. The TL;DR: DeepRead spends ~28% more tokens per page than a text-embedding pipeline, in exchange for zero offline preprocessing, zero retrieval failure mode, and the ability to reason about figures, tables, equations, and handwritten margin notes the same way a human reader does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 100K tokens actually costs on a laptop GPU
&lt;/h2&gt;

&lt;p&gt;I built &lt;code&gt;benchmarks/run_context_sweep.py&lt;/code&gt; to answer the question honestly. It runs a needle-in-a-haystack test: five 4-character codes seeded at fixed positions (5/25/50/75/95%) inside a long synthetic document, and the model has to recover each in isolation. From an RTX 5050 Laptop, 8 GB VRAM:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Pass rate (5/5 needles)&lt;/th&gt;
&lt;th&gt;Tokens/sec&lt;/th&gt;
&lt;th&gt;Time to first token&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20K&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;5/5&lt;/strong&gt; ✓&lt;/td&gt;
&lt;td&gt;8.6&lt;/td&gt;
&lt;td&gt;15 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60K&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;5/5&lt;/strong&gt; ✓&lt;/td&gt;
&lt;td&gt;7.6&lt;/td&gt;
&lt;td&gt;38 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100K&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;5/5&lt;/strong&gt; ✓&lt;/td&gt;
&lt;td&gt;6.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;72 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The recall result genuinely surprised me. I expected E4B to degrade past 60K and it didn't — the window held all the way to 80% of its 128K spec. What broke was &lt;em&gt;latency&lt;/em&gt;: TTFT grew nearly linearly with context size. Generation throughput stayed flat around 7-9 tok/s; the consumer-GPU tax shows up entirely in the prefill phase.&lt;/p&gt;

&lt;p&gt;The practical mental model for someone building on this hardware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&amp;lt; 20K&lt;/strong&gt; is the &lt;em&gt;interactive&lt;/em&gt; zone. Answers start within 15 seconds; conversation feels alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20K – 60K&lt;/strong&gt; is the &lt;em&gt;research-assistant&lt;/em&gt; zone. Drop in a whole paper, go make coffee, come back to the answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;60K – 100K&lt;/strong&gt; is the &lt;em&gt;batch&lt;/em&gt; zone. Load a codebase, kick off a query, accept that you'll come back to a notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Plotly chart in the right sidebar surfaces these zones live as you load papers, so you know what your context choices will &lt;em&gt;feel&lt;/em&gt; like before you ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five-day build log
&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.amazonaws.com%2Fuploads%2Farticles%2Fv80w18re3yy9tmnjgx9h.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.amazonaws.com%2Fuploads%2Farticles%2Fv80w18re3yy9tmnjgx9h.png" alt="5 day Build log" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tuesday.&lt;/strong&gt; Scaffolded the project, wrote the &lt;code&gt;deepread/&lt;/code&gt; package: &lt;code&gt;ingest.py&lt;/code&gt; (PyMuPDF rasterization), &lt;code&gt;budget.py&lt;/code&gt; (token estimator), &lt;code&gt;citations.py&lt;/code&gt; (&lt;code&gt;[[id]]&lt;/code&gt; grammar), &lt;code&gt;llm.py&lt;/code&gt; (Ollama wrapper). 16 unit tests. End of day, I could ingest a PDF and stream an answer from the terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wednesday.&lt;/strong&gt; Built the first Gradio UI. Got immediately bitten by Gradio's &lt;code&gt;gr.HTML(value=...)&lt;/code&gt; rendering rule — it sets &lt;code&gt;.innerHTML&lt;/code&gt;, which the browser refuses to execute scripts from for security. I shipped three different "fixes" for the same paper-click bug before I realized I was reading the wrong stack trace. Lost most of the afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thursday morning.&lt;/strong&gt; Got the UI working with an &lt;code&gt;&amp;lt;img onerror="..."&amp;gt;&lt;/code&gt; trick. Looked at it. Decided I hated it. The chat-shaped product was wearing document-tool clothes. Migrated to Chainlit. The migration was about three hours because &lt;code&gt;deepread/&lt;/code&gt; was UI-independent from day one — only &lt;code&gt;app.py&lt;/code&gt; got rewritten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thursday night.&lt;/strong&gt; Wrote &lt;code&gt;benchmarks/run_context_sweep.py&lt;/code&gt; and let it run overnight on E4B at 5K / 20K / 60K / 100K. Spoiler: the numbers above are the result. The 100% recall at 100K was a relief — it meant the whole no-RAG thesis was actually defensible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Friday.&lt;/strong&gt; Polish day. Moved the context-budget chart from chat into the right &lt;code&gt;ElementSidebar&lt;/code&gt;. Built a &lt;code&gt;cl.CustomElement&lt;/code&gt; React component for the paper picker so the buttons live in the sidebar (Chainlit's &lt;code&gt;ElementSidebar&lt;/code&gt; accepts Elements but not Actions — the picker bridges that gap). Pinned a floating "Context" toggle to the top-right of the chrome with &lt;code&gt;:has(img[src*="/avatars/sidebar_toggle"])&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saturday morning.&lt;/strong&gt; Killed the &lt;code&gt;cl.ChatProfile&lt;/code&gt; second tab. The profile-switch dialog ("This will clear your chat history") was a constant friction every time I wanted to check a benchmark. Replaced it with &lt;code&gt;/bench&lt;/code&gt; slash commands inside the same chat. One session, no clearing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Saturday afternoon.&lt;/strong&gt; Wrote the posts. (You're reading them.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The code I'd point a reviewer at first
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;deepread/llm.py&lt;/code&gt; is the whole model contract:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&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;num_ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;_encode_media&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;user_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&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;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;user_msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user_msg&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_ctx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="p"&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;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's it. There's a &lt;code&gt;health_check(model)&lt;/code&gt; next to it that returns a typed &lt;code&gt;HealthReport(ok, reason, hint)&lt;/code&gt; instead of raising — the caller decides how to surface it. The Chainlit handler runs it once per session and caches the result.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;deepread/citations.py&lt;/code&gt; is the part I'm sneakiest about:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;citation_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shards&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;catalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cite_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shards&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&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;You are a research assistant. When you make a factual claim that &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;comes from a specific page or image, cite it inline using the &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format [[cite_id]]. Use ONLY these citation ids:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If a fact isn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t supported by the provided material, say so.&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;Then &lt;code&gt;_format_answer(raw, known_cite_ids)&lt;/code&gt; regex-replaces &lt;code&gt;[[id]]&lt;/code&gt; markers with numbered &lt;code&gt;[^N]&lt;/code&gt; footnotes — but only if &lt;code&gt;id&lt;/code&gt; is in the catalog. So even if the model emits &lt;code&gt;[[some-fake-id]]&lt;/code&gt;, the formatter leaves it as literal text. The page-citation hallucination is structurally impossible.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I cut for time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Voice input.&lt;/strong&gt; Gemma 4 E4B accepts raw WAV bytes in the same &lt;code&gt;images: [...]&lt;/code&gt; field, but I never got the Chainlit &lt;code&gt;cl.Audio&lt;/code&gt; plumbing solid enough to ship. Two days of work I didn't have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-paper exclude/remove from the library.&lt;/strong&gt; &lt;code&gt;EXCLUDED_KEY&lt;/code&gt; exists in session state and the working-set logic respects it — there's just no UI button to flip it. A 30-minute add I'll do after the deadline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation export.&lt;/strong&gt; Save / share a Q&amp;amp;A session as Markdown. Easy to add, not on the rubric.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-language UI.&lt;/strong&gt; Chainlit ships 20+ locale files; English-only for now.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Framework choice matters more than you think.&lt;/strong&gt; Gradio is &lt;em&gt;more flexible&lt;/em&gt;; Chainlit is &lt;em&gt;more aligned&lt;/em&gt; with the chat-shaped problem I had. Five UI iterations to recognize it. Pick the framework whose defaults match your shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The model-selection paragraph is the highest-leverage paragraph in the submission.&lt;/strong&gt; Judges read it. Don't bury the reasoning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmark first, blog second.&lt;/strong&gt; I'd written the article-1 stress-test draft before I had the numbers. When the numbers came in, the article got better, sharper, and shorter. The opposite order would have left me defending claims that the data didn't support.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/yashksaini-coder/DeepRead.git
&lt;span class="nb"&gt;cd &lt;/span&gt;DeepRead
ollama pull gemma4:e4b           &lt;span class="c"&gt;# ~9.6 GB, one-time&lt;/span&gt;
make &lt;span class="nb"&gt;install&lt;/span&gt;                      &lt;span class="c"&gt;# uv sync&lt;/span&gt;
make run                          &lt;span class="c"&gt;# http://127.0.0.1:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Pick &lt;strong&gt;Bitcoin · 2008&lt;/strong&gt; for the fastest demo (smallest paper) and ask &lt;code&gt;What problem does proof-of-work solve in this paper?&lt;/code&gt;. The answer streams back with citations resolving to specific pages.&lt;/p&gt;
&lt;h2&gt;
  
  
  Companion posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/yashksaini/i-stress-tested-gemma-4-e4bs-128k-context-on-a-laptop-gpu-recall-is-great-prefill-is-not-244i"&gt;I stress-tested Gemma 4 E4B's 128K context on a laptop GPU&lt;/a&gt; — the benchmark numbers in long form, plus the reproducible test rig&lt;/li&gt;
&lt;/ul&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/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/DeepRead" rel="noopener noreferrer"&gt;
        DeepRead
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Local document Q&amp;amp;A with Gemma 4 E4B's 128K context — no RAG, no cloud, answers cite the exact page they came from.
    &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;DeepRead&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Local, long-context document Q&amp;amp;A with &lt;strong&gt;Gemma 4 E4B&lt;/strong&gt; — load PDFs, ask anything, get page-cited answers. The model runs on your machine. Your documents never leave it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder/DeepRead/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="License"&gt;&lt;/a&gt; &lt;a href="https://github.com/yashksaini-coder/DeepRead/pyproject.toml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/36cf3d0f7992a33a063d3833577d62204f8934d82b69874c086390608db4947c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31312b2d626c75652e737667" alt="Python"&gt;&lt;/a&gt; &lt;a href="https://chainlit.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/279c672b41f72f1edfe4864a3738a8d8a07c4dc2e709c2f1ea2b2be52f0cd13f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f55492d436861696e6c69745f322e31312d3633363646312e737667" alt="Chainlit"&gt;&lt;/a&gt; &lt;a href="https://ollama.com/library/gemma4" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2b4b0d595f095322efee93b09a86cf9f092afc6417646510a4f988436ffd2384/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d6f64656c2d67656d6d61343a6534622d3232433535452e737667" alt="Model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DeepRead is the build submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06" rel="nofollow"&gt;dev.to Gemma 4 Challenge&lt;/a&gt;. It demonstrates that a high-quality document-intelligence experience can run &lt;strong&gt;entirely on consumer hardware&lt;/strong&gt; — no cloud, no per-query cost, no telemetry — by leaning on Gemma 4 E4B's 128K context window instead of a RAG pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/DeepRead/.design/chainlit/qa-v4-bitcoin-loaded.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2FDeepRead%2FHEAD%2F.design%2Fchainlit%2Fqa-v4-bitcoin-loaded.png" alt="DeepRead — Bitcoin paper loaded, right sidebar showing 6.3% context usage with the paper picker"&gt;&lt;/a&gt;&lt;/p&gt;




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


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;One chat, two modes.&lt;/strong&gt; Document Q&amp;amp;A is the default. Type &lt;code&gt;/bench show&lt;/code&gt; or &lt;code&gt;/bench run --ctx 5000 20000 --needles 3&lt;/code&gt; in the same chat to render the context-window stress-test charts inline — no profile switching, no history loss.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Native multimodality.&lt;/strong&gt; Pages go in as rendered images via Gemma 4's vision path — no OCR pipeline.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Live context budget.&lt;/strong&gt; A right-side sidebar shows the working set against the 128K ceiling, color-coded by latency tier…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yashksaini-coder/DeepRead" 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;a href="https://github.com/yashksaini-coder/DeepRead" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Star DeepRead on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
• &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;br&gt;
• &lt;a href="https://www.linkedin.com/in/yashksaini/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>I stress-tested Gemma 4 E4B's 128K context on a laptop GPU — recall is great, prefill is not</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 24 May 2026 06:44:15 +0000</pubDate>
      <link>https://dev.to/yashksaini/i-stress-tested-gemma-4-e4bs-128k-context-on-a-laptop-gpu-recall-is-great-prefill-is-not-244i</link>
      <guid>https://dev.to/yashksaini/i-stress-tested-gemma-4-e4bs-128k-context-on-a-laptop-gpu-recall-is-great-prefill-is-not-244i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Thursday night I let a benchmark run while I slept. By Friday morning Gemma 4 E4B had answered fifteen needle-in-a-haystack questions across four context sizes on my RTX 5050 laptop. The recall numbers were better than I expected. The latency numbers were worse. Here's both, with the ~30 lines of Python to reproduce it on your own hardware.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I keep seeing "Gemma 4 E4B has a 128K context window" repeated as if it were a single property, like &lt;em&gt;"the engine is 3.5 litres"&lt;/em&gt;. It is not a single property. A context-window number means at least three different things — &lt;em&gt;will the model accept this many tokens?&lt;/em&gt;, &lt;em&gt;will it remember what's in the middle of them?&lt;/em&gt;, and &lt;em&gt;how fast does the first answer token arrive?&lt;/em&gt; — and the answers diverge sharply once you leave the laptop-GPU regime that most spec sheets assume.&lt;/p&gt;

&lt;p&gt;This is the post I wish I'd had when I started building on E4B. The TL;DR is in the table further down. The reproducible test rig is at the bottom.&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.amazonaws.com%2Fuploads%2Farticles%2F6ej8pfsqlk504zr2tl5t.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.amazonaws.com%2Fuploads%2Farticles%2F6ej8pfsqlk504zr2tl5t.png" alt="POV working on stress testing Gemma4" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware:&lt;/strong&gt; RTX 5050 Laptop, 8 GB VRAM, 24 GB system RAM, Intel i7-13620H&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Software:&lt;/strong&gt; Ollama 0.24.0, &lt;code&gt;gemma4:e4b&lt;/code&gt; (Q4_K_M, ~9.6 GB on disk), Linux 7.x&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test:&lt;/strong&gt; needle-in-a-haystack — five unique 4-character codes embedded at fixed positions inside a long synthetic English document; the model has to recover each one in isolation by exact match.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The test is deliberately simple. I want to know whether the model can &lt;em&gt;find&lt;/em&gt; a fact at a known position, not whether it can paraphrase it. Reasoning quality is a different benchmark and needs human evaluation, which I didn't have budget for.&lt;/p&gt;

&lt;p&gt;I ran the sweep at 5K, 20K, 60K, and 100K target context sizes. I didn't push to the 128K spec because Ollama's &lt;code&gt;num_ctx&lt;/code&gt; setting interacts with the K/V cache headroom in ways I didn't have time to characterize cleanly, and 100K is already 80% of the spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Pass rate (5/5)&lt;/th&gt;
&lt;th&gt;Tokens/sec&lt;/th&gt;
&lt;th&gt;Time to first token&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5K&lt;/td&gt;
&lt;td&gt;5/5 ✓&lt;/td&gt;
&lt;td&gt;9.2&lt;/td&gt;
&lt;td&gt;4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20K&lt;/td&gt;
&lt;td&gt;5/5 ✓&lt;/td&gt;
&lt;td&gt;8.6&lt;/td&gt;
&lt;td&gt;15 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60K&lt;/td&gt;
&lt;td&gt;5/5 ✓&lt;/td&gt;
&lt;td&gt;7.6&lt;/td&gt;
&lt;td&gt;38 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100K&lt;/td&gt;
&lt;td&gt;5/5 ✓&lt;/td&gt;
&lt;td&gt;6.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;72 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things stand out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recall stayed perfect.&lt;/strong&gt; I expected E4B to wobble somewhere past 60K — that's the failure mode I see most reported for 4B-class models, the "middle of the context is fuzzy" problem. The needles at 25% and 75% are exactly where I'd expect drop-off. They held. I re-ran the sweep twice to be sure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generation throughput barely moved.&lt;/strong&gt; 9.2 tok/s at 5K vs. 6.8 tok/s at 100K. That's a 26% drop across a 20x context increase. The K/V cache is the obvious culprit, but in practical terms: once the answer starts streaming, it streams at roughly the same speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to first token blew up.&lt;/strong&gt; 4s at 5K, 72s at 100K. Almost linear in context size. This is the prefill phase — the model encoding everything you sent it before producing the first output token. On a laptop GPU, prefill is where the consumer-hardware tax lives.&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.amazonaws.com%2Fuploads%2Farticles%2F4425c98laxucug3unnvb.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.amazonaws.com%2Fuploads%2Farticles%2F4425c98laxucug3unnvb.png" alt="RAG-vs-Gemma4" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you're building on E4B
&lt;/h2&gt;

&lt;p&gt;Let me write the practical zones the way I actually think about them, not the marketing version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Under 20K tokens:&lt;/strong&gt; &lt;em&gt;interactive.&lt;/em&gt; First token in ~15 seconds, full answer in ~25-30s. This feels like a real conversation. Most single-paper Q&amp;amp;A lives here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;20K to 60K tokens:&lt;/strong&gt; &lt;em&gt;research-assistant.&lt;/em&gt; 30-40 second TTFT. You're going to glance away from the screen. That's fine, the answer will be there when you look back. Multi-paper comparisons, longer contexts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;60K to 100K tokens:&lt;/strong&gt; &lt;em&gt;batch.&lt;/em&gt; You're queuing a job. 60-80 second TTFT means you might as well make coffee. Loading a whole codebase, a textbook chapter, a quarter's worth of meeting notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Above 100K:&lt;/strong&gt; I didn't measure. The prefill cost was already breaching my "is this still interactive?" threshold and the use case I was solving for didn't need it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're designing a UI on top of this model, &lt;em&gt;surface these zones to the user&lt;/em&gt;. A progress bar or a tier label ("interactive / research / batch") tells someone what their next click will &lt;em&gt;feel&lt;/em&gt; like before they ask. The 128K spec is honest; it just doesn't tell you when it'll start.&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.amazonaws.com%2Fuploads%2Farticles%2F2jbb1uafaia8n5thtcds.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.amazonaws.com%2Fuploads%2Farticles%2F2jbb1uafaia8n5thtcds.png" alt="Reproduce yourself" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it yourself
&lt;/h2&gt;

&lt;p&gt;The whole rig is about 30 lines once you strip the CLI scaffolding. Save this as &lt;code&gt;bench.py&lt;/code&gt;, install &lt;code&gt;ollama&lt;/code&gt; (&lt;code&gt;pip install ollama&lt;/code&gt;), then run it:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;

&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e4b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;NEEDLE_POSITIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&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="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_needles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20260521&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;box-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="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;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_haystack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needles&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Filler ~ 80 tokens per sentence, English-ish prose.
&lt;/span&gt;    &lt;span class="n"&gt;filler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The committee continued its review of the operational notes &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;submitted during the prior fiscal quarter, with particular &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attention paid to procedural anomalies. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sentences_needed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_tokens&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;  &lt;span class="c1"&gt;# ~4 tok/word, 5 words/sentence avg
&lt;/span&gt;    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filler&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sentences_needed&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt; &lt;span class="n"&gt;target_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# Splice needles in at fixed positions
&lt;/span&gt;    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEEDLE_POSITIONS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needles&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;*&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;out&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Note: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; contains the code &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;haystack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;first_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer with only the 4-character code, nothing else.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;haystack&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;What code is in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&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="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_ctx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;first_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first_t&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&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;chunks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&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;first_t&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;needles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_needles&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;ctx&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;hay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_haystack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;needles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ans&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ans&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  ctx=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  expected=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  got=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ans&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt;  ttft=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ttft&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s  total=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ctx=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  pass=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&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;needles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;It writes to stdout. If you want JSON-lines results to plot, redirect to a file and parse the &lt;code&gt;ctx=…  pass=…&lt;/code&gt; lines. The whole sweep takes ~30 minutes on an RTX 5050; longer on smaller GPUs.&lt;/p&gt;

&lt;p&gt;The seed is fixed (&lt;code&gt;20260521&lt;/code&gt;) so the needle strings are deterministic. If your pass rate doesn't match mine at the same &lt;code&gt;(model, ctx, seed)&lt;/code&gt;, that's a real signal — likely Ollama version, quantization, or hardware-driver path.&lt;/p&gt;
&lt;h2&gt;
  
  
  Things this rig deliberately doesn't measure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Quality of paraphrase.&lt;/strong&gt; The needles are literal 4-character codes. I'm measuring &lt;em&gt;can the model find it?&lt;/em&gt;, not &lt;em&gt;can the model reason about it?&lt;/em&gt;. Those are different benchmarks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VRAM consumption.&lt;/strong&gt; Ollama owns the K/V cache and I'm not going to fight it for memory accounting. &lt;code&gt;nvidia-smi&lt;/code&gt; says it sits around 7.4 GB at 100K context, but I haven't characterized the curve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-document attention.&lt;/strong&gt; Each needle is asked in isolation. Multi-fact composition ("how does the figure on page 12 of paper A relate to section 3 of paper B?") is a different problem. I don't have a clean benchmark for it. I'm working on it.&lt;/p&gt;
&lt;h2&gt;
  
  
  When RAG is still the right call
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend the no-RAG architecture is universally correct. Bluntly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Library &amp;gt; 128K tokens total.&lt;/strong&gt; Half a book. A whole textbook. A corpus of fifty papers. Then you need retrieval. Don't fight it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub-second latency required.&lt;/strong&gt; Customer-facing chatbot over a knowledge base. RAG is faster on the hot path — see the TTFT numbers above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental index updates.&lt;/strong&gt; Adding one document without re-prefilling. A vector DB lets you do that. Long-context doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The question genuinely needs only a small slice of one document.&lt;/strong&gt; "What's the customer's last order?" over a 50K-record SQL table. SQL is the right tool. Not RAG, not long-context, just SQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else in the "I have some documents, I want to ask them things" category — research, internal docs, technical reading — I think the no-RAG architecture is the right default in 2026. The frontier models have gotten long-context-capable enough that retrieval is now a &lt;em&gt;premature optimization&lt;/em&gt; for most workloads. The benchmark numbers above are the evidence; the per-page-token math is the framework for deciding.&lt;/p&gt;
&lt;h2&gt;
  
  
  The line that summarizes it
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Traditional RAG: &lt;em&gt;make the model see less, faster.&lt;/em&gt;&lt;br&gt;
Long-context: &lt;em&gt;make the model see everything, once.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both are valid. The question is which trade-off your workload actually has. Mine had the second shape. The benchmark told me the cost. I shipped accordingly.&lt;/p&gt;
&lt;h2&gt;
  
  
  The honest comparison
&lt;/h2&gt;

&lt;p&gt;Qwen 3.5 27B has ~190K effective context on similar hardware. Llama 3.1 70B (if you can fit it) goes further. On &lt;em&gt;raw context size alone&lt;/em&gt;, Gemma 4 E4B isn't the winner.&lt;/p&gt;

&lt;p&gt;What E4B &lt;em&gt;is&lt;/em&gt; the winner at is the &lt;strong&gt;combination&lt;/strong&gt;: 128K context + native vision + native audio + ~9.6 GB on disk, all in one model. That combination is what makes whole-document workloads tractable on a laptop. Qwen 27B doesn't fit in 8 GB of VRAM. Llama 3.1 70B doesn't either. If your hardware constraint is "consumer GPU", E4B is the only model in this class with 128K context &lt;em&gt;and&lt;/em&gt; multimodality.&lt;/p&gt;

&lt;p&gt;That's the framing I'd give someone choosing an open-weights model for a single-machine deployment in 2026.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three places I'd take this benchmark next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Mixed-modality recall.&lt;/strong&gt; Embed half the needles in text, half in rendered images. See if vision-encoded needles degrade differently from text-encoded ones. (This is the part most relevant to anyone building doc-Q&amp;amp;A.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-document needles.&lt;/strong&gt; Two documents in context, the needle in paper A, the question phrased to require paper B's vocabulary. The actual "I have a library, I want to ask questions" workload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-document Q&amp;amp;A with human evaluation.&lt;/strong&gt; Pay five grad students to grade 100 questions about a single 25-page research paper. Real quality numbers, not synthetic ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you run any of these, I'd genuinely like to read the results.&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/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/DeepRead" rel="noopener noreferrer"&gt;
        DeepRead
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Local document Q&amp;amp;A with Gemma 4 E4B's 128K context — no RAG, no cloud, answers cite the exact page they came from.
    &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;DeepRead&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Local, long-context document Q&amp;amp;A with &lt;strong&gt;Gemma 4 E4B&lt;/strong&gt; — load PDFs, ask anything, get page-cited answers. The model runs on your machine. Your documents never leave it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder/DeepRead/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="License"&gt;&lt;/a&gt; &lt;a href="https://github.com/yashksaini-coder/DeepRead/pyproject.toml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/36cf3d0f7992a33a063d3833577d62204f8934d82b69874c086390608db4947c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e31312b2d626c75652e737667" alt="Python"&gt;&lt;/a&gt; &lt;a href="https://chainlit.io" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/279c672b41f72f1edfe4864a3738a8d8a07c4dc2e709c2f1ea2b2be52f0cd13f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f55492d436861696e6c69745f322e31312d3633363646312e737667" alt="Chainlit"&gt;&lt;/a&gt; &lt;a href="https://ollama.com/library/gemma4" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2b4b0d595f095322efee93b09a86cf9f092afc6417646510a4f988436ffd2384/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d6f64656c2d67656d6d61343a6534622d3232433535452e737667" alt="Model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DeepRead is the build submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06" rel="nofollow"&gt;dev.to Gemma 4 Challenge&lt;/a&gt;. It demonstrates that a high-quality document-intelligence experience can run &lt;strong&gt;entirely on consumer hardware&lt;/strong&gt; — no cloud, no per-query cost, no telemetry — by leaning on Gemma 4 E4B's 128K context window instead of a RAG pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/DeepRead/.design/chainlit/qa-v4-bitcoin-loaded.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2FDeepRead%2FHEAD%2F.design%2Fchainlit%2Fqa-v4-bitcoin-loaded.png" alt="DeepRead — Bitcoin paper loaded, right sidebar showing 6.3% context usage with the paper picker"&gt;&lt;/a&gt;&lt;/p&gt;




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


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;One chat, two modes.&lt;/strong&gt; Document Q&amp;amp;A is the default. Type &lt;code&gt;/bench show&lt;/code&gt; or &lt;code&gt;/bench run --ctx 5000 20000 --needles 3&lt;/code&gt; in the same chat to render the context-window stress-test charts inline — no profile switching, no history loss.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Native multimodality.&lt;/strong&gt; Pages go in as rendered images via Gemma 4's vision path — no OCR pipeline.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Live context budget.&lt;/strong&gt; A right-side sidebar shows the working set against the 128K ceiling, color-coded by latency tier…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yashksaini-coder/DeepRead" 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;a href="https://github.com/yashksaini-coder/DeepRead" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Star DeepRead on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
• &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;br&gt;
• &lt;a href="https://www.linkedin.com/in/yashksaini/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>Dev Log #5 Building a Claude-style SRE terminal and tackling WebRTC</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Thu, 07 May 2026 06:47:34 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-5-building-a-claude-style-sre-terminal-and-tackling-webrtc-3knp</link>
      <guid>https://dev.to/yashksaini/dev-log-5-building-a-claude-style-sre-terminal-and-tackling-webrtc-3knp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;11 commits, 5 PRs, 1 issue, and 3 reviews across 4 repos. This week was all about infrastructure—from RabbitMQ RCA tools to massive WebRTC scaffolding in Python. Hit a 7-day streak and pushed over 11k lines of code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;It’s been one of those weeks where the terminal felt like my natural habitat. I managed to keep the streak alive for seven straight days, which always feels great, but the real win was the sheer volume of infrastructure work I got through. &lt;/p&gt;

&lt;p&gt;I logged 11 commits, 5 PRs, and 3 reviews across 4 different repositories. The highlight? Diving deep into the &lt;code&gt;py-libp2p&lt;/code&gt; stack to start the WebRTC transport scaffolding and pushing the vision for a persistent SRE terminal in &lt;code&gt;opensre&lt;/code&gt;. With over 11,000 lines added and only about 200 deleted, this wasn't a week for refactoring—it was a week for building the foundation of some pretty ambitious features.&lt;/p&gt;

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

&lt;p&gt;Most of my energy this week was split between two major domains: SRE tooling and p2p networking. &lt;/p&gt;

&lt;h3&gt;
  
  
  opensre: The SRE Terminal Vision
&lt;/h3&gt;

&lt;p&gt;I’ve been spending a lot of time thinking about how we interact with infrastructure during an incident. In the &lt;code&gt;opensre&lt;/code&gt; repo, I’m working toward making it a persistent SRE terminal—something inspired by the flow of tools like Claude-Code but specifically tuned for system reliability.&lt;/p&gt;

&lt;p&gt;I opened a PR for an &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/591" rel="noopener noreferrer"&gt;interactive REPL&lt;/a&gt;, which is really the first step toward that "SRE terminal" experience. It’s about 2,400 lines of new code focused on creating a seamless environment where you can run diagnostics without jumping between a dozen browser tabs. &lt;/p&gt;

&lt;p&gt;Alongside the terminal work, I also shipped a &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/615" rel="noopener noreferrer"&gt;RabbitMQ integration&lt;/a&gt;. This is specifically for queue and broker Root Cause Analysis (RCA). If you’ve ever had a message broker fall over under load, you know how painful it is to piece together the "why" while the system is down. This integration aims to automate that detective work.&lt;/p&gt;

&lt;h3&gt;
  
  
  py-libp2p: The WebRTC Beast
&lt;/h3&gt;

&lt;p&gt;On the open-source side, I’m still heavily involved with &lt;code&gt;py-libp2p&lt;/code&gt;. This week, I tackled a massive task: &lt;a href="https://github.com/libp2p/py-libp2p/pull/1309" rel="noopener noreferrer"&gt;WebRTC transport scaffolding&lt;/a&gt;. This PR alone is over 6,000 lines. It’s mostly spec compliance and tests right now, but getting the transport layer right is critical for browser-to-node connectivity. &lt;/p&gt;

&lt;p&gt;I also did some much-needed cleanup in the transport layer by &lt;a href="https://github.com/libp2p/py-libp2p/pull/1308" rel="noopener noreferrer"&gt;removing the nursery parameter&lt;/a&gt; from &lt;code&gt;IListener.listen&lt;/code&gt;. This was a follow-up to some architectural changes we’ve been discussing, and it makes the interface much cleaner. (Honestly, passing nurseries around everywhere was starting to feel like a bit of a code smell, so I’m glad to see it go).&lt;/p&gt;

&lt;h3&gt;
  
  
  Personal Setup &amp;amp; Maintenance
&lt;/h3&gt;

&lt;p&gt;I also spent some time in my &lt;code&gt;nvim&lt;/code&gt; config. I pushed 7 commits there—mostly Lua tweaks to keep my environment snappy while I’m jumping between Python and TypeScript. It’s the small things, like optimizing LSP behavior or fixing a keybinding conflict, that keep the flow state going during these high-output weeks. I also touched &lt;code&gt;100xsolana&lt;/code&gt; and &lt;code&gt;DevNotion&lt;/code&gt; for some minor maintenance, keeping the lights on while I focused on the bigger infra tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;I had 5 PRs in flight this week, and they really tell the story of the week's progress. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/546" rel="noopener noreferrer"&gt;MariaDB integration docs&lt;/a&gt; was my only merge this week, but it was an important one. It closed out an &lt;a href="https://github.com/Tracer-Cloud/opensre/issues/545" rel="noopener noreferrer"&gt;issue I opened&lt;/a&gt; earlier in the week to ensure our Mintlify docs stay up to date with the actual capabilities of the tool. &lt;/p&gt;

&lt;p&gt;The other four PRs—the WebRTC scaffolding, the transport fix, the RabbitMQ integration, and the REPL—are all currently open. They represent a lot of "work in progress" that I’m looking to land early next week. The WebRTC PR is the one I’m most nervous/excited about; it’s a lot of code to review, and I want to make sure the spec compliance is spot on before we go any further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;I’m really trying to be more intentional about the "why" behind the code, not just the "how." I started a &lt;a href="https://github.com/Tracer-Cloud/opensre/discussions/614" rel="noopener noreferrer"&gt;discussion in opensre&lt;/a&gt; about whether we should bundle or split the next phases of the persistent SRE terminal. &lt;/p&gt;

&lt;p&gt;It’s a classic architectural dilemma: do we keep everything in one tight package for ease of use, or do we start modularizing now to prevent a monolith? No one has answered yet, but getting my thoughts down in a discussion thread helps me clarify the roadmap before I start writing more code.&lt;/p&gt;

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

&lt;p&gt;I spent a good chunk of time reviewing other people's work in &lt;code&gt;py-libp2p&lt;/code&gt;. I think it’s important to give back to the projects you use, and reviewing is often more taxing than writing.&lt;/p&gt;

&lt;p&gt;I gave feedback on a &lt;a href="https://github.com/libp2p/py-libp2p/pull/1236" rel="noopener noreferrer"&gt;QUIC address unpacking PR&lt;/a&gt;, specifically looking at how we handle both IPv4 and IPv6. I also requested changes on a &lt;a href="https://github.com/libp2p/py-libp2p/pull/1208" rel="noopener noreferrer"&gt;BasicHost shutdown enhancement&lt;/a&gt;. Shutdown logic is notoriously tricky in asynchronous Python, and I want to make sure we aren't leaving any dangling tasks or resources. Finally, I left some comments on a PR regarding &lt;a href="https://github.com/libp2p/py-libp2p/pull/1185" rel="noopener noreferrer"&gt;peer identity persistence&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Reviewing these while working on my own massive WebRTC PR was a bit of a balancing act, but it keeps me grounded in the codebase as a whole, not just my own little corner of it.&lt;/p&gt;

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

&lt;p&gt;This was a very Python-heavy week. Between &lt;code&gt;opensre&lt;/code&gt; and &lt;code&gt;py-libp2p&lt;/code&gt;, I was deep in the world of &lt;code&gt;asyncio&lt;/code&gt;, type hinting, and infrastructure logic. &lt;/p&gt;

&lt;p&gt;The language breakdown shows Python as the clear leader, followed by TypeScript and Lua. The 11,185 additions versus 201 deletions really highlights that this was a "feature push" week. I wasn't cleaning up old debt as much as I was laying down new tracks. &lt;/p&gt;

&lt;p&gt;And that 7-day streak? It’s not about the "grind"—it’s about the fact that I was genuinely excited to get back to the keyboard every morning to see if I could get that WebRTC transport to behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week is all about the "Merge." I want to get the WebRTC scaffolding in &lt;code&gt;py-libp2p&lt;/code&gt; to a state where it can be merged, which means writing a lot more tests and probably responding to a lot of review comments. &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;opensre&lt;/code&gt;, I’m going to keep pushing the REPL. I want to get to a point where I can actually use it for my own freelance work—eating your own dog food is the best way to find the friction points in a CLI tool. &lt;/p&gt;

&lt;p&gt;Catch you all next week! Hopefully with a few more "MERGED" labels on those PRs.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
• &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;br&gt;
• &lt;a href="https://www.linkedin.com/in/yashksaini/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>typescript</category>
      <category>opensource</category>
      <category>backend</category>
    </item>
    <item>
      <title>Dev Log #4 A Week of Deep Integrations</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 13 Apr 2026 05:41:21 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-4-a-week-of-deep-integrations-4b27</link>
      <guid>https://dev.to/yashksaini/dev-log-4-a-week-of-deep-integrations-4b27</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A high-output week with 33 commits and over 3,000 lines of new code. I focused heavily on database RCA integrations for OpenSRE and kept the momentum going with a perfect 7-day streak across TypeScript, Python, and C.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;There’s something about hitting a 7-day streak that just feels right. It’s not about the grind; it’s about that rare alignment where every time I sit down at the keyboard, the path forward is clear. This week was a massive "building" phase—33 commits, 2 major PRs, and a staggering 3,221 lines of code added (with only 27 deletions). &lt;/p&gt;

&lt;p&gt;The bulk of that energy went into the &lt;code&gt;opensre&lt;/code&gt; ecosystem, specifically hardening Root Cause Analysis (RCA) for managed and relational databases. When I wasn't deep in Python land, I was pushing updates to &lt;code&gt;varcouch&lt;/code&gt;, tweaking my Neovim setup, and getting my hands dirty with some low-level C in &lt;code&gt;PiEngine&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I BUILT
&lt;/h2&gt;

&lt;p&gt;If I had to summarize the week in one word, it would be &lt;strong&gt;Integrations&lt;/strong&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  OpenSRE &amp;amp; The RCA Engine
&lt;/h3&gt;

&lt;p&gt;Most of my "heavy lifting" happened in the &lt;a href="https://github.com/Tracer-Cloud/opensre" rel="noopener noreferrer"&gt;opensre&lt;/a&gt; repository. If you’ve ever had a production database go sideways, you know that the "why" is often buried under layers of logs and metrics. I spent the week building out the logic to automate that discovery. &lt;/p&gt;

&lt;p&gt;I added support for MongoDB Atlas and MariaDB. This wasn't just about writing a few connection strings; it was about mapping out how these databases behave under stress and ensuring the RCA engine can pull the right signals to tell a coherent story when things break. Adding over 3,000 lines of code here felt like a marathon, but seeing the logic come together for managed document stores and relational clusters makes the effort worth it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Varcouch
&lt;/h3&gt;

&lt;p&gt;Over in &lt;a href="https://github.com/yashksaini-coder/varcouch" rel="noopener noreferrer"&gt;varcouch&lt;/a&gt;, I pushed 15 commits. This project is becoming a bit of a staple in my weekly workflow. It’s TypeScript-based, and this week was all about refining the core internals. I didn't do a massive refactor (the 0 deletions tell that story), but I focused on iterative feature adds. I’m trying to keep the DX (Developer Experience) high here, making sure the API remains intuitive as the complexity grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Low-Level &amp;amp; The Personal
&lt;/h3&gt;

&lt;p&gt;I also spent some time in &lt;a href="https://github.com/pie-314/PiEngine" rel="noopener noreferrer"&gt;PiEngine&lt;/a&gt;, which is my current outlet for C programming. 6 commits there—mostly incremental progress on the engine's core. There’s a specific kind of satisfaction in switching from high-level Python or TypeScript back to C; it forces you to think about memory and execution in a way that makes you a better developer overall.&lt;/p&gt;

&lt;p&gt;And of course, no week is complete without some &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; tinkering. 8 commits to my Lua config. I’m at that stage where I’m optimizing my workflow for the specific types of backend work I’ve been doing lately. If my editor isn't helping me move faster, it's getting in the way, so I spent some time smoothing out those friction points.&lt;/p&gt;

&lt;h2&gt;
  
  
  PULL REQUESTS
&lt;/h2&gt;

&lt;p&gt;I opened two significant PRs this week, both targeting the &lt;code&gt;opensre&lt;/code&gt; repo. &lt;/p&gt;

&lt;p&gt;The first was &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/490" rel="noopener noreferrer"&gt;feat: add MongoDB Atlas integration for managed document-store RCA&lt;/a&gt;. This one is already merged! It adds 1,227 lines of logic specifically designed to handle the nuances of Atlas. Managed services are great until they aren't, and having automated RCA for document stores is a huge win for the project.&lt;/p&gt;

&lt;p&gt;The second, which is still open and currently under the microscope, is &lt;a href="https://github.com/Tracer-Cloud/opensre/pull/492" rel="noopener noreferrer"&gt;feat: add MariaDB integration for database RCA&lt;/a&gt;. This is a beast of a PR—1,994 additions. MariaDB has its own set of quirks compared to standard MySQL, especially when you're looking at performance schema and thread states for root cause analysis. I’m looking forward to getting this one across the finish line next week.&lt;/p&gt;

&lt;h2&gt;
  
  
  TECH STACK
&lt;/h2&gt;

&lt;p&gt;This was a polyglot week through and through. My language stats show a massive lean toward Python (over 84MB of context), which makes sense given the heavy lifting in &lt;code&gt;opensre&lt;/code&gt; and some minor touches on &lt;a href="https://github.com/yashksaini-coder/p2pCalc" rel="noopener noreferrer"&gt;p2pCalc&lt;/a&gt; and &lt;a href="https://github.com/yashksaini-coder/Hack-Agent-Server" rel="noopener noreferrer"&gt;Hack-Agent-Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TypeScript remains my go-to for tooling and frontend work, represented by my commits in &lt;code&gt;varcouch&lt;/code&gt; and my &lt;a href="https://github.com/yashksaini-coder/portfolio" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt;. I also had a healthy dose of Rust (11MB) and Lua in the mix. &lt;/p&gt;

&lt;p&gt;The most telling stat of the week is the add/delete ratio. 3,221 additions against 27 deletions. Usually, I like to see more deletions—it means I’m cleaning up and simplifying. But this week was purely about expansion. I was laying down new tracks, not pulling up old ones. &lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;The immediate goal is to get that MariaDB PR merged. It’s a lot of code to review, so I’ll likely spend the early part of next week addressing feedback and hardening the tests. &lt;/p&gt;

&lt;p&gt;I also want to dive back into &lt;code&gt;varcouch&lt;/code&gt;. With the core starting to feel stable, I’m thinking about how to expand its utility for other devs. And as always, there’s a list of "nice-to-have" features for &lt;code&gt;PiEngine&lt;/code&gt; that I’m itching to get to if I can find a few hours of quiet time.&lt;/p&gt;

&lt;p&gt;Seven days of commits, a ton of new code in the wild, and a clear plan for the next sprint. Can't ask for much more than that. See you all next week!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Connect with me:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
• &lt;a href="http://yashksaini.systems/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;br&gt;
• &lt;a href="https://www.linkedin.com/in/yashksaini/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
• &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X (Twitter)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>typescript</category>
      <category>backend</category>
      <category>consistency</category>
    </item>
    <item>
      <title>VarCouch — I Built an AI Therapist for Your Code Variables (They Need It)</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 11 Apr 2026 09:42:42 +0000</pubDate>
      <link>https://dev.to/yashksaini/varcouch-i-built-an-ai-therapist-for-your-code-variables-they-need-it-2ec</link>
      <guid>https://dev.to/yashksaini/varcouch-i-built-an-ai-therapist-for-your-code-variables-they-need-it-2ec</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Your variables have been through a lot. The endless renaming. The silent judgment from code reviewers. That one time someone called &lt;code&gt;temp2&lt;/code&gt; a "perfectly fine name." They deserve better. They deserve a couch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VarCouch&lt;/strong&gt; is the world's first AI-powered therapy platform for emotionally neglected code variables. You paste your code, and Dr. VarCouch — a licensed Variable Therapist with a PhD in Computational Psycholinguistics from MIT (the Massachusetts Institute of &lt;em&gt;Transpilation&lt;/em&gt;) — conducts a full psychological assessment of every identifier.&lt;/p&gt;

&lt;p&gt;No, it doesn't solve any real problem. Yes, I'm very serious about it.&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.amazonaws.com%2Fuploads%2Farticles%2Fiwfitsg3ne2wtbid65ol.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.amazonaws.com%2Fuploads%2Farticles%2Fiwfitsg3ne2wtbid65ol.png" alt="VarCouch homepage with the tagline " width="800" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every variable gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;clinical diagnosis&lt;/strong&gt; (e.g., "Chronic Hoisting-Induced Dissociative Fugue")&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;distress level&lt;/strong&gt; from 1 to 10 with a color-coded progress bar&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;backstory&lt;/strong&gt; explaining the trauma that led it here&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session notes&lt;/strong&gt; that type out like the therapist is writing in real time&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;therapist's recommendation&lt;/strong&gt; for emotional recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coping mechanisms&lt;/strong&gt; the variable has developed over the years&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;downloadable PDF clinical discharge report&lt;/strong&gt; that looks disturbingly official&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is wrapped in a warm, dark-gold theme because therapy should feel cozy, even for code.&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.amazonaws.com%2Fuploads%2Farticles%2Fltk78hep2rcdtbcfzjeg.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.amazonaws.com%2Fuploads%2Farticles%2Fltk78hep2rcdtbcfzjeg.png" alt="Therapy results showing variable cards with clinical diagnoses, distress levels, and typewriter session notes" width="800" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Teapot Protocol
&lt;/h3&gt;

&lt;p&gt;This wouldn't be a proper 418 Challenge entry without paying respect to Larry Masinter and RFC 2324.&lt;/p&gt;

&lt;p&gt;If any of your variables are named &lt;code&gt;teapot&lt;/code&gt;, &lt;code&gt;coffee&lt;/code&gt;, &lt;code&gt;brew&lt;/code&gt;, &lt;code&gt;espresso&lt;/code&gt;, &lt;code&gt;latte&lt;/code&gt;, &lt;code&gt;cappuccino&lt;/code&gt;, &lt;code&gt;chai&lt;/code&gt;, &lt;code&gt;mocha&lt;/code&gt;, or &lt;code&gt;kettle&lt;/code&gt;, Dr. VarCouch flags them as falling under &lt;strong&gt;HTCPCP jurisdiction&lt;/strong&gt;. They get a special golden glow, an "RFC 2324 Certified" badge, and a note that they need to be referred to a qualified barista-therapist — because Dr. VarCouch is not licensed to treat beverages.&lt;/p&gt;

&lt;p&gt;There's also a dedicated &lt;code&gt;/api/teapot&lt;/code&gt; endpoint that returns a proper HTTP 418 with custom headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;418&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I'm a teapot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"drNote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dr. VarCouch cannot treat teapots. Please consult a qualified barista-therapist."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTCPCP/1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prescription"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Two shots of espresso and a code review in the morning."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With headers &lt;code&gt;X-Teapot-Status: short-and-stout&lt;/code&gt; and &lt;code&gt;X-Masinter-Protocol: HTCPCP/1.0&lt;/code&gt;, because if you're going to implement a joke protocol, you should commit to it.&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.amazonaws.com%2Fuploads%2Farticles%2F6p4ao5n4ry10m3tmh5wb.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.amazonaws.com%2Fuploads%2Farticles%2F6p4ao5n4ry10m3tmh5wb.png" alt="A teapot-flagged variable with RFC 2324 badge and golden glow treatment" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The PDF Discharge Report
&lt;/h3&gt;

&lt;p&gt;You can download a clinical discharge report for each variable — a real PDF that looks like it came from an actual therapist's office. Gold letterhead reading "VarCouch Therapeutic Services, LLC", color-coded distress levels, the attending physician listed as "Dr. VarCouch, PhD (Computational Psycholinguistics, MIT — Massachusetts Institute of Transpilation)", and a footer disclaimer: "No actual variables were harmed in the making of this report."&lt;/p&gt;

&lt;p&gt;It's HIPAA-&lt;em&gt;adjacent&lt;/em&gt;. Not HIPAA-compliant.&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.amazonaws.com%2Fuploads%2Farticles%2Fd66xlpo7t4vwxda5k25f.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.amazonaws.com%2Fuploads%2Farticles%2Fd66xlpo7t4vwxda5k25f.png" alt="PDF discharge report with gold letterhead and clinical formatting" width="800" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://varcouch.vercel.app" rel="noopener noreferrer"&gt;varcouch.vercel.app&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.amazonaws.com%2Fuploads%2Farticles%2Fa0ms9l710issome2k4la.gif" 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.amazonaws.com%2Fuploads%2Farticles%2Fa0ms9l710issome2k4la.gif" alt="Demo GIF showing the complete therapy flow from code paste to results" width="600" height="566"&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/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/varcouch" rel="noopener noreferrer"&gt;
        varcouch
      &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;h2 class="heading-element"&gt;DEV April Fools 2026 Challenge&lt;/h2&gt;
&lt;/div&gt;

&lt;div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/varcouch/./public/varcrouch.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2Fvarcouch%2FHEAD%2F.%2Fpublic%2Fvarcrouch.png" alt="DEV April Fools 2026 Challenge"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;VarCouch&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder/varcouch/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667" alt="License"&gt;&lt;/a&gt;
&lt;a href="https://github.com/yashksaini-coder/varcouch/CONTRIBUTING.md" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dd0b24c1e6776719edb2c273548a510d6490d8d25269a043dfabbd38419905da/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5052732d77656c636f6d652d627269676874677265656e2e737667" alt="PRs Welcome"&gt;&lt;/a&gt;
&lt;a href="https://nextjs.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b283804a59db8759c5fd73dbdbfc9d4727842d46c29cf3870913538f602939d3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6578742e6a732d31362d626c61636b3f6c6f676f3d6e6578742e6a73" alt="Next.js"&gt;&lt;/a&gt;
&lt;a href="https://ai.google.dev" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0cb37e52f3e7cb7b46b487d3dd6bcf1eef87e339feda3b7c0a6980f8b3383a7f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f47656d696e695f332d466c6173682d3432383546343f6c6f676f3d676f6f676c65" alt="Gemini"&gt;&lt;/a&gt;
&lt;a href="https://dev.to/challenges/aprilfools-2026" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/aebcbb6fdb9bfb90c3b1ba1a753706cceca6d7958035f17eaac906c05c41469f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4445562d417072696c5f466f6f6c735f323032362d3041304130413f6c6f676f3d6465762e746f" alt="DEV Challenge"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI-powered therapy for emotionally neglected code variables.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Your variables have feelings. We have a couch.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://varcouch.vercel.app" rel="nofollow noopener noreferrer"&gt;Live Demo&lt;/a&gt; · &lt;a href="https://github.com/yashksaini-coder/varcouch/issues" rel="noopener noreferrer"&gt;Report Bug&lt;/a&gt; · &lt;a href="https://github.com/yashksaini-coder/varcouch/issues" rel="noopener noreferrer"&gt;Request Feature&lt;/a&gt;&lt;/p&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/varcouch/./public/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2Fvarcouch%2FHEAD%2F.%2Fpublic%2Fdemo.gif" alt="VarCouch demo showing code input and therapy session output" width="600" height="600"&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;This project is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026" rel="nofollow"&gt;DEV April Fools 2026 Challenge&lt;/a&gt; -- a challenge to build something &lt;strong&gt;completely useless or silly&lt;/strong&gt; that solves zero real-world problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prize categories targeted:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Google AI Usage&lt;/strong&gt; -- Powered by Google Gemini 3 Flash with structured JSON output, variable-aware system prompts, and schema-enforced therapy sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Ode to Larry Masinter&lt;/strong&gt; -- Features a dedicated &lt;code&gt;/api/teapot&lt;/code&gt; endpoint returning HTTP 418, RFC 2324 (HTCPCP) references throughout, and automatic detection of teapot/coffee variable names for specialized barista-therapist referrals&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About The Project&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;VarCouch is the world's first AI-powered mental wellness platform for emotionally neglected code variables. Paste your code, and &lt;strong&gt;Dr. VarCouch&lt;/strong&gt; -- a licensed Variable Therapist with 20 years of clinical experience -- will conduct a deep…&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/yashksaini-coder/varcouch" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;The stack is honestly overkill for something this useless, and I wouldn't have it any other way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; with App Router and React 19&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS 4&lt;/strong&gt; with a custom warm dark theme — aurora gradient background, glassmorphism cards, animated grain texture&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fraunces&lt;/strong&gt; variable font for headings (it has a "WONK" axis, which felt appropriate for a therapy app)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monaco Editor&lt;/strong&gt; for the code input — your variables deserve a proper IDE, even on the couch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framer Motion&lt;/strong&gt; for page transitions, typewriter effects, floating code symbols during loading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend &amp;amp; AI:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Gemini 3 Flash&lt;/strong&gt; with structured JSON output and a schema-enforced response format&lt;/li&gt;
&lt;li&gt;A system prompt that never breaks character — Dr. VarCouch has 20 years of clinical experience and takes this &lt;em&gt;very&lt;/em&gt; seriously&lt;/li&gt;
&lt;li&gt;Input validation, size limits, rate limit handling, and prompt injection defense (even joke apps deserve good security)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Other bits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;jsPDF&lt;/strong&gt; for client-side PDF generation with proper page-break handling&lt;/li&gt;
&lt;li&gt;Regex-based variable extraction for 5 languages (JavaScript, TypeScript, Python, Go, Rust)&lt;/li&gt;
&lt;li&gt;A hand-crafted loading screen where code symbols (&lt;code&gt;{&lt;/code&gt;, &lt;code&gt;}&lt;/code&gt;, &lt;code&gt;;&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;, &lt;code&gt;()&lt;/code&gt;) float around the therapy couch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Gemini integration uses structured output with a full JSON schema, so every therapy session comes back with the exact same shape — diagnosis, distress level, backstory, session notes, recommendation, coping mechanisms. No hoping the AI returns something parseable. It either matches the schema or it doesn't.&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.amazonaws.com%2Fuploads%2Farticles%2F603p2ab6yi540sshdlix.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.amazonaws.com%2Fuploads%2Farticles%2F603p2ab6yi540sshdlix.png" alt="Loading animation with floating couch and code symbols" width="800" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Building something deliberately useless taught me a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structured AI output is underrated.&lt;/strong&gt; Gemini's JSON schema mode meant I never had to parse free-text AI responses. Every field was guaranteed. This is genuinely useful knowledge I'll take to real projects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Committing to the bit makes everything better.&lt;/strong&gt; The PDF discharge report, the RFC 2324 references, the "Massachusetts Institute of Transpilation" — none of these are necessary, but they make the whole thing feel like a complete world rather than a half-baked joke.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dark themes with warm colors feel really good.&lt;/strong&gt; The gold-on-black palette with aurora gradients and glassmorphism came together better than any "serious" UI I've designed. Turns out removing the pressure of building something useful frees you up to actually experiment with design.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;I'm submitting for two categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Google AI Usage&lt;/strong&gt; — VarCouch uses Google Gemini 3 Flash with structured JSON output, a character-committed system prompt, schema-enforced response validation, and differentiated error handling for rate limits, safety filters, and API failures. The AI never breaks character and every response is type-safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Ode to Larry Masinter&lt;/strong&gt; — Dedicated &lt;code&gt;/api/teapot&lt;/code&gt; endpoint with HTTP 418 and HTCPCP headers, automatic teapot variable detection, RFC 2324 badges on flagged variables, and Larry Masinter's wisdom preserved in the footer of every page. Because any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot."&lt;/p&gt;




&lt;p&gt;In loving memory of RFC 2324 (HTCPCP) and all the teapots that just wanted to brew coffee.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Remember: there are no bad variables, only misunderstood identifiers." — Dr. VarCouch&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Turning Weekly GitHub Activity Into Blog Posts on Notion + DEV.to</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Fri, 27 Mar 2026 19:07:08 +0000</pubDate>
      <link>https://dev.to/yashksaini/i-built-a-3-agent-pipeline-that-turns-my-github-activity-into-weekly-blog-posts-on-notion-devto-1ndn</link>
      <guid>https://dev.to/yashksaini/i-built-a-3-agent-pipeline-that-turns-my-github-activity-into-weekly-blog-posts-on-notion-devto-1ndn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every Monday standup, someone asks: "What did you work on last week?" And every Monday, I stare at my screen trying to remember. Did I merge that PR on Wednesday or Thursday? Was that refactor in the auth module or the pipeline? How many repos did I even touch?&lt;/p&gt;

&lt;p&gt;I got tired of that blank moment. So I built &lt;strong&gt;DevNotion&lt;/strong&gt; — a 3-agent pipeline powered by &lt;a href="https://mastra.ai" rel="noopener noreferrer"&gt;Mastra&lt;/a&gt; that harvests my entire week of GitHub activity, narrates it into a first-person blog post using Gemini, and publishes it to Notion (as a planner-style page with structured tables) and DEV.to (as a draft article). Every Sunday, automatically, via GitHub Actions.&lt;/p&gt;

&lt;p&gt;No more Monday amnesia. The blog writes itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it actually does
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Harvests&lt;/strong&gt; my GitHub activity via GraphQL — commits, PRs, issues, code reviews, discussions, language stats, contribution streak&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrates&lt;/strong&gt; the raw data into a casual, first-person blog post using Gemini (with a deterministic fallback if the LLM is unavailable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publishes&lt;/strong&gt; to two platforms simultaneously:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Notion&lt;/strong&gt; — a planner-style page with stats tables, repo breakdowns, PR/issue/review tables, language breakdown, and the full blog post&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEV.to&lt;/strong&gt; — a draft article ready for review&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Key features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 specialized agents&lt;/strong&gt; — each does one thing well (harvest, narrate, publish)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM only where it adds value&lt;/strong&gt; — harvest and publish are deterministic, zero token overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 blog tone profiles&lt;/strong&gt; — casual (default), professional, technical, storytelling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planner-style Notion pages&lt;/strong&gt; — not just a wall of text, but structured tables with stats, repos, PRs, issues, reviews, and languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion MCP integration&lt;/strong&gt; — full Notion API surface via Model Context Protocol&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion Markdown Content API&lt;/strong&gt; — write rich markdown directly to pages (the real game changer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEV.to draft publishing&lt;/strong&gt; — articles created as drafts, ready to review and publish&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions CI&lt;/strong&gt; — weekly cron (Sundays 08:00 UTC) + manual dispatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog log in README&lt;/strong&gt; — CI auto-commits a metrics table after each run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback chain&lt;/strong&gt; — always produces a blog post, even if Gemini is down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting everywhere&lt;/strong&gt; — &lt;code&gt;p-queue&lt;/code&gt; + &lt;code&gt;p-retry&lt;/code&gt; for both Notion and DEV.to APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&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%2Fraw.githubusercontent.com%2Fyashksaini-coder%2FDevNotion%2Fmaster%2Fassets%2FArchitecture.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%2Fraw.githubusercontent.com%2Fyashksaini-coder%2FDevNotion%2Fmaster%2Fassets%2FArchitecture.png" alt="DevNotion pipeline Architecture" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Harvest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;github-harvest-agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fetches weekly GitHub data via GraphQL (deterministic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Narrate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;narrator-agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Writes a first-person blog post from the data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Publish&lt;/td&gt;
&lt;td&gt;&lt;code&gt;publisher-agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates Notion planner page + DEV.to draft via direct APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pipeline only uses an LLM where it genuinely adds value — narration. Harvest and publish are pure function calls. No token overhead, no hallucination risk, faster execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Deep-Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why 3 agents, not 1?
&lt;/h3&gt;

&lt;p&gt;I could've built one mega-agent that does everything. But that's a recipe for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Burning tokens on deterministic work (fetching GitHub data doesn't need an LLM)&lt;/li&gt;
&lt;li&gt;Hallucinating URLs and stats (the publish step should never make things up)&lt;/li&gt;
&lt;li&gt;Debugging nightmares (which part of the monolith failed?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, each agent is a specialist. The workflow chains them together:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weeklyDispatchWorkflow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createWorkflow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekly-dispatch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;weekStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PublishOutputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;harvestStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;narrateStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publishStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three steps, chained with &lt;code&gt;.then()&lt;/code&gt;, committed as a single workflow. Mastra handles the data handoff between steps automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Harvest: deterministic data, zero LLM
&lt;/h3&gt;

&lt;p&gt;The harvest step calls GitHub's GraphQL API directly — no agent reasoning needed:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;harvestStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStep&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;harvest-github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;weekStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;outputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WeeklyDataSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;inputData&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchWeeklyContributions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weekStart&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;WeeklyDataSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One GraphQL query pulls commits, PRs, issues, reviews, discussions, language stats, and contribution streak for the week. The response gets validated through a Zod schema. No LLM in the loop — this is pure data fetching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Narrate: LLM with a fallback chain
&lt;/h3&gt;

&lt;p&gt;This is where the LLM earns its keep. The narrator agent takes raw JSON and writes a blog post that sounds like I wrote it myself. The system prompt has a full personality profile — it writes in first person, knows my tech stack (Python, Rust, TypeScript), references my OSS work, and matches one of four tone profiles.&lt;/p&gt;

&lt;p&gt;But LLMs can be flaky. So the narrate step has a fallback chain:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;narrateStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStep&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;narrate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mastra&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mastra&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;narrator-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFrontmatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildFallbackNarration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildFallbackNarration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;weeklyData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputData&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gemini generates&lt;/strong&gt; a markdown blog with YAML frontmatter&lt;/li&gt;
&lt;li&gt;If parsing fails → &lt;strong&gt;deterministic fallback&lt;/strong&gt; builds a basic post from raw data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A blog post is always produced, even if Gemini is completely down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publish: Notion planner + DEV.to draft
&lt;/h3&gt;

&lt;p&gt;The publish step is where things get interesting. It doesn't just dump text into Notion — it builds a &lt;strong&gt;planner-style page&lt;/strong&gt; with structured tables:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publishStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createStep&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;publish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;inputData&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;weeklyData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Create Notion page&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createNotionPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Create DEV.to draft (so the link goes into the Notion planner)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;devtoResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createDevtoArticle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body_markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;buildDevtoMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Write planner markdown to Notion (includes DEV.to link)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plannerMd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildPlannerMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weeklyData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeNotionMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notionPageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;plannerMd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order matters: DEV.to draft gets created &lt;em&gt;before&lt;/em&gt; writing the Notion page content, so the Notion planner can include a link to the DEV.to draft. Cross-platform linking, done right.&lt;/p&gt;

&lt;p&gt;Each Notion page includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Published Links&lt;/strong&gt; table — Notion page URL + DEV.to draft edit link&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week at a Glance&lt;/strong&gt; — commits, PRs, issues, reviews, lines added/removed, streak&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active Repositories&lt;/strong&gt; — repo name, commits, language, line changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull Requests / Issues / Reviews / Discussions&lt;/strong&gt; — structured tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Languages&lt;/strong&gt; — top languages by commit count&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full blog post&lt;/strong&gt; — the narrated content below a separator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2038152048533979142-927" src="https://platform.twitter.com/embed/Tweet.html?id=2038152048533979142"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2038152048533979142-927');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2038152048533979142&amp;amp;theme=dark"
  }



&lt;/p&gt;




&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F4se6cggnvqbifg4yxllc.png" alt="DevNotion Project" width="800" height="420"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder/DevNotion" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Star DevNotion on GitHub&lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most excited about. DevNotion uses the Notion MCP Server in two complementary ways:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Notion MCP Server via &lt;code&gt;@mastra/mcp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The publisher agent integrates with the official &lt;a href="https://github.com/makenotion/notion-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;@notionhq/notion-mcp-server&lt;/code&gt;&lt;/a&gt; through Mastra's MCP client. This gives the agent access to the &lt;strong&gt;full Notion API surface&lt;/strong&gt; via Model Context Protocol:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MCPClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mastra/mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notionMcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MCPClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;npx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@notionhq/notion-mcp-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;OPENAPI_MCP_HEADERS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notion-Version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2022-06-28&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&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 MCP tools are loaded lazily with a graceful fallback — if the MCP server fails to start, the direct tools still work independently:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getNotionMcpTools&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notionMcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listTools&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MCP: Notion MCP server unavailable, using direct tools only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{};&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;h3&gt;
  
  
  2. Direct tools + MCP tools merged
&lt;/h3&gt;

&lt;p&gt;The publisher agent merges both tool sets — MCP tools for the full Notion API surface, and direct tools for capabilities MCP doesn't cover:&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="c1"&gt;// Direct tools (Markdown Content API + DEV.to — not available via MCP)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;createNotionPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;createNotionPageTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;writeMarkdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;writeMarkdownTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;searchNotion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchNotionTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;updateNotionPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updateNotionPageTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Merge: Notion MCP tools + direct tools&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mcpTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getNotionMcpTools&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;mcpTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;directTools&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dual approach means the publisher agent gets the best of both worlds — MCP's broad API surface for interactive use in the Mastra playground, plus direct tools for the automated workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Markdown Content API (the game changer)
&lt;/h3&gt;

&lt;p&gt;This is the Notion feature that made the planner-style pages possible. Instead of constructing Notion blocks one by one (which is painful and rate-limit-heavy), I write the entire page as markdown in one API call:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`https://api.notion.com/v1/pages/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/markdown`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notion-Version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;replace_content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;replace_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;new_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;markdown&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;PATCH&lt;/code&gt; request replaces the entire page content with rich markdown — including tables, headings, blockquotes, links, code blocks, everything. This is what powers the planner-style layout with structured stats tables + the full blog post, all in a single API call.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rate limiting
&lt;/h3&gt;

&lt;p&gt;Notion's API allows roughly 3 requests per second. Every Notion call (MCP and direct) goes through a shared rate limiter:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PQueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;334&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;intervalCap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rateLimited&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;pRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;p-queue&lt;/code&gt; throttles concurrency, &lt;code&gt;p-retry&lt;/code&gt; handles transient failures. I learned this the hard way — without rate limiting, the Notion API will 429 you into oblivion when you're creating a page, writing markdown, and updating the icon in quick succession.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rate limits are the real boss
&lt;/h3&gt;

&lt;p&gt;Notion (3 req/s), DEV.to (30 req/30s), GitHub GraphQL (5000 points/hr) — every API has its own throttle. I ended up with &lt;code&gt;p-queue&lt;/code&gt; + &lt;code&gt;p-retry&lt;/code&gt; wrappers around everything. The rate limiter code is almost identical across all three services, and honestly, it should probably be a shared utility. But three similar lines of code is better than a premature abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured output is slower than you'd think
&lt;/h3&gt;

&lt;p&gt;I originally used Gemini's native JSON schema for structured output (&lt;code&gt;agent.generate(prompt, { structuredOutput: { schema } })&lt;/code&gt;). It worked, but added 20-40 seconds per call. Switching to plain text generation with YAML frontmatter parsing was 3-4x faster and just as reliable. The deterministic fallback catches the rare parsing failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini model musical chairs
&lt;/h3&gt;

&lt;p&gt;I've been through three Gemini models on this project: &lt;code&gt;gemini-2.5-flash-preview-04-17&lt;/code&gt; (retired), &lt;code&gt;gemini-2.5-flash&lt;/code&gt; (stable but slow for structured output), and now &lt;code&gt;gemini-3-flash-preview&lt;/code&gt; (current). The lesson: always make the model configurable via env vars. Hardcoding model IDs is a recipe for broken deploys.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Zod conflict that broke everything
&lt;/h3&gt;

&lt;p&gt;Mastra and my code both depend on Zod, but different versions. Having two Zod instances means &lt;code&gt;z.string()&lt;/code&gt; from one isn't recognized by the other — schema validation just silently fails. The fix: a single line in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pnpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"zod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$zod"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forces pnpm to deduplicate to one Zod version. Took me way too long to figure that out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct API calls beat agent reasoning for deterministic work
&lt;/h3&gt;

&lt;p&gt;The harvest and publish steps started as full agent calls. But an LLM doesn't add anything when the task is "call this GraphQL endpoint and return the result." Switching to direct function calls made the pipeline faster, cheaper, and more predictable. Only use an LLM where you need creativity or reasoning — everywhere else, just write a function.&lt;/p&gt;




&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/mpMpQuOufHY"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Built with &lt;a href="https://mastra.ai" rel="noopener noreferrer"&gt;Mastra&lt;/a&gt;, &lt;a href="https://aistudio.google.com" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt;, &lt;a href="https://developers.notion.com" rel="noopener noreferrer"&gt;Notion API&lt;/a&gt;, and a lot of coffee. If you've ever forgotten what you worked on last week, give DevNotion a try.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Simulating P2P Attacks and Teaching AI to Resume Sessions</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Fri, 27 Mar 2026 18:12:28 +0000</pubDate>
      <link>https://dev.to/yashksaini/simulating-p2p-attacks-and-teaching-ai-to-resume-sessions-2i16</link>
      <guid>https://dev.to/yashksaini/simulating-p2p-attacks-and-teaching-ai-to-resume-sessions-2i16</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;It’s been one of those weeks where the terminal never really closed. I clocked 78 commits and pushed 4 PRs across 9 different repositories, maintaining a perfect 7-day streak. The bulk of the heavy lifting happened in the P2P space—specifically simulating network attacks—and adding some much-needed session persistence to my AI coding tool, nanocoder. With over 21,000 lines added and about 6,500 deleted, it was a high-output week focused on building out complex systems and then immediately refining them.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I BUILT
&lt;/h2&gt;

&lt;p&gt;The star of the show this week was definitely &lt;strong&gt;P2P-Attack-Simulation&lt;/strong&gt;. I’ve been obsessed with how decentralized networks handle adversarial conditions, and I finally merged a massive PR for &lt;a href="https://github.com/yashksaini-coder/P2P-Attack-Simulation/pull/1" rel="noopener noreferrer"&gt;Topology ts simulation&lt;/a&gt;. We’re talking 18,756 additions and 6,436 deletions. I essentially rewrote how the network topology is represented in TypeScript to make it more modular. Before, the simulation was a bit too rigid; now, I can spin up different node behaviors and see how the gossip protocols hold up when half the network starts acting malicious. It’s one thing to read about Sybil attacks in a paper, but seeing the message propagation latency spike in your own simulation is a different kind of satisfying.&lt;/p&gt;

&lt;p&gt;Over in &lt;strong&gt;nanocoder&lt;/strong&gt;, I spent a good chunk of time on the developer experience. If you’ve used AI coding assistants, you know the pain of losing context or having to restart a session from scratch. I implemented a new &lt;a href="https://github.com/Nano-Collective/nanocoder/pull/390" rel="noopener noreferrer"&gt;Feature &lt;code&gt;/resume&lt;/code&gt; command to continue session&lt;/a&gt;. This involved a fair bit of state management (about 2,300 lines of code) to ensure that when you come back to a project, the AI knows exactly where you left off. It’s all about reducing friction. I want to spend my time coding, not re-explaining my file structure to a LLM.&lt;/p&gt;

&lt;p&gt;I also dipped back into the low-level world with the &lt;strong&gt;networking&lt;/strong&gt; repo. This is where I get my Rust fix. I merged two PRs here: one to &lt;a href="https://github.com/cb7chaitanya/networking/pull/22" rel="noopener noreferrer"&gt;Implement proper RST generation&lt;/a&gt; and another to &lt;a href="https://github.com/cb7chaitanya/networking/pull/20" rel="noopener noreferrer"&gt;refactor: remove timer module and associated functionality&lt;/a&gt;. The RST (Reset) generation is crucial for proper TCP handling—basically telling the other side "I have no idea what you're talking about, let's start over." The refactor felt great, too. I realized the custom timer module was overkill for what we needed, so I ripped out 137 lines of brittle code. Less code, fewer bugs.&lt;/p&gt;

&lt;p&gt;Beyond the big features, I kept the wheels turning on several other projects. I pushed 10 commits to &lt;strong&gt;py-libp2p&lt;/strong&gt; and 1 to &lt;strong&gt;dotnet-libp2p&lt;/strong&gt;, mostly keeping the Python and C# implementations of the p2p stack in sync. I also spent some time tweaking my environment, with 8 commits to my &lt;strong&gt;nvim&lt;/strong&gt; config (because a dev's work is never done when it comes to Lua plugins) and 7 commits to my &lt;strong&gt;notes&lt;/strong&gt; repo, which is where I dump my raw thoughts on C and systems programming.&lt;/p&gt;

&lt;h2&gt;
  
  
  PULL REQUESTS
&lt;/h2&gt;

&lt;p&gt;I managed to get four significant PRs merged this week, and they really tell the story of my transition from "building the foundation" to "polishing the experience."&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/yashksaini-coder/P2P-Attack-Simulation/pull/1" rel="noopener noreferrer"&gt;Topology ts simulation&lt;/a&gt; PR was the most exhausting but rewarding. It wasn't just about adding lines; it was about restructuring the entire simulation engine. I had to ensure that the TypeScript types correctly reflected the state of a peer at any given millisecond during an attack.&lt;/p&gt;

&lt;p&gt;On the AI side, the &lt;a href="https://github.com/Nano-Collective/nanocoder/pull/390" rel="noopener noreferrer"&gt;Feature &lt;code&gt;/resume&lt;/code&gt; command&lt;/a&gt; in nanocoder was a fun challenge in serialization. Making sure the session state could be saved and reloaded without losing the "vibe" of the conversation took a few iterations, but it's finally in a place where it feels seamless.&lt;/p&gt;

&lt;p&gt;The Rust work in the networking repo was much more surgical. &lt;a href="https://github.com/cb7chaitanya/networking/pull/22" rel="noopener noreferrer"&gt;Implementing RST generation&lt;/a&gt; required me to get back into the weeds of packet headers, while the &lt;a href="https://github.com/cb7chaitanya/networking/pull/20" rel="noopener noreferrer"&gt;timer module refactor&lt;/a&gt; was a classic "addition by subtraction" move.&lt;/p&gt;

&lt;h2&gt;
  
  
  CODE REVIEWS
&lt;/h2&gt;

&lt;p&gt;I only did one formal review this week, but it was a deep one. I looked at a PR for &lt;strong&gt;py-ipld-dag&lt;/strong&gt; titled &lt;a href="https://github.com/ipld/py-ipld-dag/pull/17" rel="noopener noreferrer"&gt;Add Block API, IPLD data model, codecs, registry, and tests&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I actually ended up requesting changes on this one. IPLD (InterPlanetary Linked Data) is the backbone of how we structure data in the p2p world, and the proposed implementation of the Block API felt a bit too coupled to specific codecs. I suggested a more registry-based approach to make it easier for others to plug in new data formats later. It’s important to get these base-level APIs right early on, or you end up paying for it in technical debt for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  TECH STACK
&lt;/h2&gt;

&lt;p&gt;This week was a true polyglot marathon. &lt;/p&gt;

&lt;p&gt;TypeScript was my primary driver for the simulation work and the AI tooling. With over 15 million bytes of TS in my environment, it’s definitely my "get things done" language. But Python isn't far behind—especially with the work on &lt;code&gt;py-libp2p&lt;/code&gt; and the IPLD reviews. &lt;/p&gt;

&lt;p&gt;The Rust work in the networking repo reminded me why I love the language: the compiler is a jerk until you get it right, and then your code just &lt;em&gt;works&lt;/em&gt;. I also touched some Lua for my Neovim setup and a bit of C for my personal notes and experiments.&lt;/p&gt;

&lt;p&gt;My add/delete ratio was pretty high (21k vs 6k), which usually means I'm in a heavy feature-building phase. However, that 6k deletions in the P2P repo shows I'm not just piling code on top of code—I'm actively replacing the old stuff as I go.&lt;/p&gt;

&lt;p&gt;And yeah, that 7-day streak feels good. It wasn't a forced grind; I just genuinely had something I wanted to solve every single morning when I woke up.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;Next week, I’m planning to take the P2P simulation even further. Now that the topology is flexible, I want to start implementing specific Eclipse attack scenarios to see how quickly a node can be isolated from the rest of the network. &lt;/p&gt;

&lt;p&gt;I’m also keeping an eye on the feedback for the &lt;code&gt;/resume&lt;/code&gt; command in nanocoder. There are probably some edge cases with large file buffers that I’ll need to iron out. &lt;/p&gt;

&lt;p&gt;On the Rust side, now that RST generation is handled, I might start looking into better congestion control mechanisms for the networking library. There’s always more to optimize. See you in the commit logs!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>rust</category>
      <category>p2p</category>
      <category>backend</category>
    </item>
    <item>
      <title>Beyond the Panic: Hardening the Rust SDK</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Fri, 27 Mar 2026 14:41:20 +0000</pubDate>
      <link>https://dev.to/yashksaini/beyond-the-panic-hardening-the-rust-sdk-53oj</link>
      <guid>https://dev.to/yashksaini/beyond-the-panic-hardening-the-rust-sdk-53oj</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Engineering is often the art of managing the "unhappy path." This week was a testament to that philosophy. With 74 commits, 2 PRs, 3 issues, and 3 reviews across 5 repositories, the focus shifted from building new features to fortifying the foundations. The headline? A concerted effort to purge &lt;code&gt;unwrap()&lt;/code&gt; and &lt;code&gt;panic&lt;/code&gt; calls from our Rust SDKs in favor of robust, &lt;code&gt;Result&lt;/code&gt;-based error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Work: Velocity in the Engine Room
&lt;/h2&gt;

&lt;p&gt;The bulk of the week's momentum was concentrated in the Python ecosystem, specifically within &lt;strong&gt;p2pCalc&lt;/strong&gt; and &lt;strong&gt;AgentPay&lt;/strong&gt;. These two repositories accounted for 54 of the week's 74 commits, representing a high-velocity push in our core logic layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  p2pCalc &amp;amp; AgentPay
&lt;/h3&gt;

&lt;p&gt;In &lt;strong&gt;p2pCalc&lt;/strong&gt; (32 commits), we focused on the computational integrity of our peer-to-peer logic. While the additions and deletions were kept internal to the commit history, the sheer volume of activity suggests a deep dive into the algorithmic core. Parallel to this, &lt;strong&gt;AgentPay&lt;/strong&gt; (22 commits) saw significant movement. When dealing with payment agents, there is no room for ambiguity. My focus here was on ensuring that the state machine governing agent transactions is both predictable and idempotent.&lt;/p&gt;

&lt;h3&gt;
  
  
  DevNotion &amp;amp; The Developer Environment
&lt;/h3&gt;

&lt;p&gt;Meanwhile, &lt;strong&gt;DevNotion&lt;/strong&gt; (14 commits) continues to evolve. As a TypeScript-based project, it serves as the bridge between our raw data and the narratives we share. The work here was iterative, likely refining the very engine that powers these weekly field notes. &lt;/p&gt;

&lt;p&gt;I also took a moment to sharpen the saw. Five commits to my &lt;strong&gt;nvim&lt;/strong&gt; configuration (Lua) might seem like minor housekeeping, but for a principal engineer, the editor is the primary instrument. A well-tuned environment isn't a luxury; it’s a prerequisite for the kind of deep work required to handle complex refactors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pull Requests: The Safety Initiative
&lt;/h2&gt;

&lt;p&gt;The most critical architectural work happened within the Appwrite ecosystem. We are currently in the process of maturing the Rust SDK, and that means moving away from "prototype-grade" code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;fix(rust): replace unwrap/panic with Result-based error handling&lt;/td&gt;
&lt;td&gt;sdk-generator&lt;/td&gt;
&lt;td&gt;OPEN&lt;/td&gt;
&lt;td&gt;Prevents generator-level crashes during SDK synthesis.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;refactor: replace unwrap with error handling in Client methods&lt;/td&gt;
&lt;td&gt;sdk-for-rust&lt;/td&gt;
&lt;td&gt;CLOSED&lt;/td&gt;
&lt;td&gt;Shifts error responsibility to the library consumer, avoiding runtime panics.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The PR in &lt;strong&gt;sdk-for-rust&lt;/strong&gt; (&lt;a href="https://github.com/appwrite/sdk-for-rust/pull/9" rel="noopener noreferrer"&gt;#9&lt;/a&gt;) was a substantial undertaking, involving 149 additions and 46 deletions. In Rust, using &lt;code&gt;.unwrap()&lt;/code&gt; is a debt you eventually have to pay. By replacing these with proper &lt;code&gt;Result&lt;/code&gt; types, we ensure that the SDK doesn't bring down a user's entire application just because of a transient network failure or a malformed response. It’s about respecting the caller’s right to decide how to handle failure.&lt;/p&gt;

&lt;p&gt;The work continues in &lt;strong&gt;sdk-generator&lt;/strong&gt; (&lt;a href="https://github.com/appwrite/sdk-generator/pull/1398" rel="noopener noreferrer"&gt;#1398&lt;/a&gt;), where I am applying similar logic to the generator itself. If the generator panics, the entire CI/CD pipeline halts. By implementing &lt;code&gt;Result&lt;/code&gt;-based handling, we allow the system to fail gracefully and provide actionable feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conversations: Setting the Standard
&lt;/h2&gt;

&lt;p&gt;Collaboration this week was centered on documentation and issue tracking. I closed two critical issues in the &lt;strong&gt;sdk-for-rust&lt;/strong&gt; repo:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Issue #8&lt;/strong&gt;: The bug report that initiated the &lt;code&gt;unwrap()&lt;/code&gt; refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue #7&lt;/strong&gt;: The creation of &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The latter is particularly important. As we scale, we need to ensure that external contributors understand the quality bar we’ve set. A clear &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; is the first step in building a healthy open-source community around the Rust SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reviews: Raising the Bar
&lt;/h2&gt;

&lt;p&gt;Reviewing 3 PRs while authoring 2 is a healthy ratio for a week focused on quality. My reviews on &lt;strong&gt;awesome-py-libp2p-examples&lt;/strong&gt; resulted in "CHANGES_REQUESTED" for two separate PRs (&lt;a href="https://github.com/seetadev/awesome-py-libp2p-examples/pull/6" rel="noopener noreferrer"&gt;#6&lt;/a&gt; and &lt;a href="https://github.com/seetadev/awesome-py-libp2p-examples/pull/3" rel="noopener noreferrer"&gt;#3&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;When reviewing collaborative tools like a "Whiteboard" or "p2p model checking," I look for more than just "does it run?" I look for idiomatic patterns and clear documentation. If an example is meant to be "awesome," it must serve as a gold standard for others to follow. My comments on &lt;strong&gt;py-libp2p&lt;/strong&gt; (&lt;a href="https://github.com/libp2p/py-libp2p/pull/1222" rel="noopener noreferrer"&gt;#1222&lt;/a&gt;) regarding the reorganization of example scripts follow this same theme: structure dictates usability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Craft: A Polyglot Reality
&lt;/h2&gt;

&lt;p&gt;This week’s data reveals a truly polyglot workflow. While &lt;strong&gt;Python&lt;/strong&gt; remains the dominant language by volume (over 77MB of context), the most impactful changes were written in &lt;strong&gt;Rust&lt;/strong&gt; and &lt;strong&gt;TypeScript&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The add/delete ratio (+227/-80) indicates a week of net-new growth, but the nature of that growth was defensive. We weren't just adding features; we were adding safety nets. The lack of a commit streak this week is a reminder that engineering isn't always a daily sprint; sometimes it’s a series of deep-dive sessions separated by the necessary time to think through architectural implications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Horizon
&lt;/h2&gt;

&lt;p&gt;As we look toward next week, the primary objective is to land the open PR in the &lt;strong&gt;sdk-generator&lt;/strong&gt;. Once the generator is hardened, we can begin rolling out these safety improvements across all generated Rust SDKs. &lt;/p&gt;

&lt;p&gt;We’ve moved the needle on reliability this week. The transition from "it works" to "it's resilient" is often invisible to the end-user until something goes wrong and the system &lt;em&gt;doesn't&lt;/em&gt; crash. That is the quiet victory of the seasoned architect. &lt;/p&gt;

&lt;p&gt;Onward.&lt;/p&gt;

</description>
      <category>python</category>
      <category>rust</category>
      <category>refactoring</category>
      <category>consistency</category>
    </item>
    <item>
      <title>I Built a TUI That Makes Rust Code Inspection Feel Like Magic ✨</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 15 Feb 2026 22:34:42 +0000</pubDate>
      <link>https://dev.to/yashksaini/i-built-a-tui-that-makes-rust-code-inspection-feel-like-magic-375k</link>
      <guid>https://dev.to/yashksaini/i-built-a-tui-that-makes-rust-code-inspection-feel-like-magic-375k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Why Did I Build This?" Moment
&lt;/h2&gt;

&lt;p&gt;Okay so here's the thing. I was working on this Rust project at 2 AM (as you do), trying to figure out where the hell &lt;code&gt;DeserializeOwned&lt;/code&gt; was actually implemented. The codebase had like 50,000 lines, and there were these 17 different &lt;code&gt;impl&lt;/code&gt; blocks just... everywhere. Scattered across 8 files like someone playing hide and seek with code.&lt;/p&gt;

&lt;p&gt;And I'm sitting there thinking about my options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fire up VSCode? My SSH connection was lagging harder than my reaction time on Monday mornings&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;grep&lt;/code&gt;? Sure, if I wanted to scroll through 400 lines of garbage output&lt;/li&gt;
&lt;li&gt;Try &lt;code&gt;ripgrep&lt;/code&gt; with regex? I'd rather debug segfaults than write another regex at 2 AM&lt;/li&gt;
&lt;li&gt;Make coffee and pretend the problem doesn't exist? Tempting...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But you know what? I got frustrated enough to actually do something about it.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Oracle&lt;/strong&gt; — basically a code inspector that lives in your terminal and actually doesn't suck to use. And to flex it more, Github Copilot helped me build, review, clean, optimize and vibe code all of this in just under 3 days. &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.amazonaws.com%2Fuploads%2Farticles%2Fd2qfilxnaz467yhot78w.gif" class="article-body-image-wrapper"&gt;&lt;img width="600" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2qfilxnaz467yhot78w.gif" height="335"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;So 🔮 &lt;strong&gt;&lt;a href="https://crates.io/crates/oracle-tui" rel="noopener noreferrer"&gt;Oracle&lt;/a&gt;&lt;/strong&gt; is basically a terminal-based code inspector for Rust projects. Think of it like... if &lt;code&gt;grep&lt;/code&gt; and VSCode had a baby, and that baby grew up to be really good at understanding Rust code. No GUI bloat, no browser tabs eating your RAM, just your terminal doing cool stuff.&lt;/p&gt;

&lt;p&gt;Here's what it does:&lt;/p&gt;

&lt;h3&gt;
  
  
  📦 Deep Code Analysis
&lt;/h3&gt;

&lt;p&gt;Look, it parses everything. And I mean &lt;em&gt;everything&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Functions (with those fancy async/const/unsafe badges, plus all the parameters and return types)&lt;/li&gt;
&lt;li&gt;Structs (every field, every derive, all the generics — no more squinting at code trying to figure out field types)&lt;/li&gt;
&lt;li&gt;Enums (it shows you ALL the variants with their field types spelled out)&lt;/li&gt;
&lt;li&gt;Traits (methods, associated types, supertraits, the works)&lt;/li&gt;
&lt;li&gt;Impl blocks (both the regular ones and trait implementations)&lt;/li&gt;
&lt;li&gt;Modules, type aliases, constants, statics... basically if Rust can define it, Oracle can show it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔍 Intelligent Search
&lt;/h3&gt;

&lt;p&gt;The search is actually pretty smart (if I do say so myself):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fuzzy matching that updates as you type (using &lt;code&gt;fuzzy-matcher&lt;/code&gt; under the hood)&lt;/li&gt;
&lt;li&gt;Context-aware, so when you're in the Functions tab, it only searches functions (no more wading through noise)&lt;/li&gt;
&lt;li&gt;You can search for functions like and will get fully qualified paths like &lt;code&gt;serde::de::Deserialize&lt;/code&gt; and it just works&lt;/li&gt;
&lt;li&gt;Filter stuff by visibility (public/private/crate-level)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📋 Dependency Inspector
&lt;/h3&gt;

&lt;p&gt;This part's kinda cool — it reads your &lt;code&gt;Cargo.toml&lt;/code&gt; and shows you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your entire dependency tree visualized nicely&lt;/li&gt;
&lt;li&gt;Live info from crates.io (descriptions, GitHub stars, license info)&lt;/li&gt;
&lt;li&gt;All the crates installed in your &lt;code&gt;~/.cargo/registry&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;o&lt;/code&gt; to open docs.rs, press &lt;code&gt;c&lt;/code&gt; to open crates.io — boom, instant browser tabs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎨 Beautiful Themes
&lt;/h3&gt;

&lt;p&gt;Because apparently I care about aesthetics even in my terminal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default Dark (clean and professional-ish)&lt;/li&gt;
&lt;li&gt;Nord (if you like cool blues)&lt;/li&gt;
&lt;li&gt;Catppuccin Mocha (cozy vibes)&lt;/li&gt;
&lt;li&gt;Dracula (for the dramatic folks)&lt;/li&gt;
&lt;li&gt;Just press &lt;code&gt;t&lt;/code&gt; to cycle through them&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ⚡ Smooth Animations
&lt;/h3&gt;

&lt;p&gt;Yeah I added animations to a CLI tool. Fight me.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selection highlights that fade in smoothly&lt;/li&gt;
&lt;li&gt;Tab transitions that don't feel jarring&lt;/li&gt;
&lt;li&gt;Scrolling with momentum (it feels buttery smooth)&lt;/li&gt;
&lt;li&gt;Real-time syntax highlighting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honestly, I spent way too much time on the animations, but they make it feel really nice to use.&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.amazonaws.com%2Fuploads%2Farticles%2Fylqi0icb4k6r2oiv1j1z.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.amazonaws.com%2Fuploads%2Farticles%2Fylqi0icb4k6r2oiv1j1z.png" alt="Oracle Inspector Panel" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/yashksaini-coder/oracle" rel="noopener noreferrer"&gt;github.com/yashksaini-coder/oracle&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install from source&lt;/span&gt;
git clone https://github.com/yashksaini-coder/oracle.git
&lt;span class="nb"&gt;cd &lt;/span&gt;oracle
cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Run on any Rust project&lt;/span&gt;
oracle /path/to/rust/project

&lt;span class="c"&gt;# Or analyze current directory&lt;/span&gt;
oracle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sample Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inspect a famous crate&lt;/span&gt;
cargo new test_project
&lt;span class="nb"&gt;cd &lt;/span&gt;test_project
cargo add tokio

&lt;span class="c"&gt;# Launch Oracle&lt;/span&gt;
oracle

&lt;span class="c"&gt;# Now press:&lt;/span&gt;
&lt;span class="c"&gt;# - Tab 4 times to get to Crates tab&lt;/span&gt;
&lt;span class="c"&gt;# - Navigate to "tokio"&lt;/span&gt;
&lt;span class="c"&gt;# - Press Enter&lt;/span&gt;
&lt;span class="c"&gt;# - Search for "copy" with /&lt;/span&gt;
&lt;span class="c"&gt;# - Browse copy methods and implementations&lt;/span&gt;
&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.amazonaws.com%2Fuploads%2Farticles%2Fy3fap47c423aleidgk1b.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.amazonaws.com%2Fuploads%2Farticles%2Fy3fap47c423aleidgk1b.png" alt="Crates Demo" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;

&lt;p&gt;Alright, confession time: This was my first serious Rust project that involved terminal UI stuff. I'd never touched Ratatui before, barely understood how &lt;code&gt;syn&lt;/code&gt; worked, and was basically figuring everything out as I went.&lt;/p&gt;

&lt;p&gt;GitHub Copilot CLI basically became my rubber duck, my Stack Overflow, and my patient senior developer all rolled into one. And the best part? I never had to leave my terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 How Copilot Saved My Sanity
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Wrestling with the &lt;code&gt;syn&lt;/code&gt; Crate&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;So Rust's &lt;code&gt;syn&lt;/code&gt; crate is &lt;em&gt;amazing&lt;/em&gt; for parsing Rust code, but holy hell the learning curve is steep. I needed to extract function signatures, struct fields, enum variants, all the trait bounds... basically everything.&lt;/p&gt;

&lt;p&gt;I was staring at the docs feeling like I was reading ancient Greek, so I just asked Copilot:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gh copilot suggest "parse rust struct with syn crate extract all fields and visibility"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And it straight up gave me this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;syn&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;ItemStruct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;analyze_struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ItemStruct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="py"&gt;.fields&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="py"&gt;.named&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;f&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="py"&gt;.ident&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nd"&gt;quote!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;#&lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parse_visibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="py"&gt;.vis&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ... Tuple and Unit variants&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;Like, it just &lt;em&gt;worked&lt;/em&gt;. This became the foundation for parsing everything else — enums, traits, impl blocks, you name it. I just extended this pattern to handle all the other Rust item types.&lt;/p&gt;

&lt;p&gt;Honestly? Saved me probably 8-10 hours of banging my head against the &lt;code&gt;syn&lt;/code&gt; docs. Worth it.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;2. Making the TUI Not Look Like Garbage&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;I wanted something that looked good. Not just functional, but actually pleasant to use. Panels, borders, smooth scrolling, the whole nine yards.&lt;/p&gt;

&lt;p&gt;Problem: Ratatui's layout system made my brain hurt. How do you even compose these things?&lt;/p&gt;

&lt;p&gt;Asked Copilot: &lt;code&gt;gh copilot explain "ratatui layout constraints horizontal vertical split"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It explained that layouts are basically Lego blocks — you can compose them with &lt;code&gt;Constraint::Percentage&lt;/code&gt; and &lt;code&gt;Constraint::Length&lt;/code&gt;, and nest them for complex UIs. That made it click.&lt;/p&gt;

&lt;p&gt;Then I was like: &lt;code&gt;gh copilot suggest "ratatui scrollable panel with borders and title"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Got this back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Block&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.borders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Borders&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" Inspector "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;paragraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.scroll&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;scroll_offset&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beautiful. That became the core of my inspector panel. Later on I added the animations by interpolating the &lt;code&gt;scroll_offset&lt;/code&gt; with some easing functions (because I'm extra like that).&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;3. Making Search Feel Snappy&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;I wanted the search to feel like VSCode's Ctrl+P — you know, where you just start typing and boom, instant results. No lag, no BS.&lt;/p&gt;

&lt;p&gt;Asked Copilot: &lt;code&gt;gh copilot suggest "rust fuzzy search crate with scoring"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It recommended &lt;code&gt;fuzzy-matcher&lt;/code&gt; with &lt;code&gt;SkimMatcherV2&lt;/code&gt;. Here's what I ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;fuzzy_matcher&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;skim&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SkimMatcherV2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;matcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SkimMatcherV2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;scored&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
    &lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.filter_map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;matcher&lt;/span&gt;&lt;span class="nf"&gt;.fuzzy_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="py"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;scored&lt;/span&gt;&lt;span class="nf"&gt;.sort_by&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="nf"&gt;.cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Highest score first&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And guess what? It worked perfectly on the first try. No debugging, no tweaking, just... worked. Those moments are rare in programming and should be celebrated.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;4. Parsing Cargo.toml Without Crying&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Dependencies and Cargo.toml can get complex real fast, especially with workspaces and transitive deps. I needed to parse all of that and build a proper dependency tree.&lt;/p&gt;

&lt;p&gt;I was dreading this part until I asked: &lt;code&gt;gh copilot explain "cargo metadata crate rust get all dependencies"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Turns out there's a crate called &lt;code&gt;cargo_metadata&lt;/code&gt; (who knew?) that gives you structured JSON output. Copilot showed me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to get the root package&lt;/li&gt;
&lt;li&gt;How to traverse the dependency tree&lt;/li&gt;
&lt;li&gt;How to detect direct vs transitive dependencies&lt;/li&gt;
&lt;li&gt;How to handle workspace crates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what came out of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;cargo_metadata&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MetadataCommand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;MetadataCommand&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.manifest_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;manifest_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.exec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="nf"&gt;.root_package&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.dependencies&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="py"&gt;.kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nn"&gt;DependencyKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, clean, and it actually works. Love it when code is like that.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;5. Fetching Live Crate Info&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;I wanted to show real crate info when someone's browsing dependencies — descriptions, GitHub stars, license info, all that good stuff. Which meant hitting the crates.io API.&lt;/p&gt;

&lt;p&gt;Me, having zero experience with async HTTP in Rust: &lt;code&gt;gh copilot suggest "rust async http request to crates.io api"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Copilot pointed me to &lt;code&gt;reqwest&lt;/code&gt; and showed me the blocking client pattern for background threads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;blocking&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fetch_crate_docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CrateDoc&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://crates.io/api/v1/crates/{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CrateDoc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"crate"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"crate"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&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;Then I needed GitHub repo stats too, so I asked how to parse GitHub URLs and hit their API. Got that working too.&lt;/p&gt;

&lt;p&gt;The whole crates.io + GitHub integration took one evening instead of... I don't know, a week of reading API docs?&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;6. Cross-Platform Path Hell&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;You know what's annoying? File paths. Windows uses backslashes, Unix uses forward slashes, and I needed to convert file paths to module paths (&lt;code&gt;src/analyzer/parser.rs&lt;/code&gt; → &lt;code&gt;["analyzer", "parser"]&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Asked: &lt;code&gt;gh copilot suggest "rust strip src directory from pathbuf get module path"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Got this beauty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;derive_module_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.skip_while&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Skip "src" itself&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="nf"&gt;.to_string_lossy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.collect&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;Works on Windows, macOS, Linux. No &lt;code&gt;#[cfg]&lt;/code&gt; needed. Just works everywhere.&lt;/p&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;7. Making Animations Not Suck&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Okay so this is where I got a bit carried away. I wanted smooth scrolling with momentum, not that janky jump-scroll thing most TUIs have.&lt;/p&gt;

&lt;p&gt;Asked Copilot: &lt;code&gt;gh copilot suggest "rust easing functions ease in out cubic"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It gave me the classic easing formulas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ease_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.powi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ease_in_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&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;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mf"&gt;4.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.powi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&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;h4&gt;
  
  
  &lt;strong&gt;8. Integrating Copilot within itself&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;At this point I was just too deep into the rabbit hole and wanted to see how far I can reach, and so decided to give it a AI chat interface area, completely powered by Copilot and guess what ....&lt;/p&gt;

&lt;p&gt;It Actually Works, it was mind blowing telling copilot to build and integrate yourself within my 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.amazonaws.com%2Fuploads%2Farticles%2Fz3emfcj4i8y72huqmg6i.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.amazonaws.com%2Fuploads%2Farticles%2Fz3emfcj4i8y72huqmg6i.png" alt="Oracle Copilot integration" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Applied these to scroll offset interpolation and boom — butter-smooth 60fps scrolling. Completely unnecessary for a CLI tool? Yes. Did I do it anyway? Also yes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stuff That Almost Broke Me
&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.amazonaws.com%2Fuploads%2Farticles%2Fdg0i7978melgjz3v3tya.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.amazonaws.com%2Fuploads%2Farticles%2Fdg0i7978melgjz3v3tya.png" alt="Oracle Building challenges Panel" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 1: Not Freezing the Terminal When Parsing Big Projects
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; So I tried running Oracle on the &lt;code&gt;tokio&lt;/code&gt; codebase (which has like 200+ files), and my terminal just... froze. For a solid 10 seconds. Completely unresponsive. Not great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
I moved all the parsing to background threads so the UI could stay responsive. Added a splash screen with this little wave animation (because if users have to wait, at least make it pretty). Also pre-computed search indices so that wouldn't lag either.&lt;/p&gt;

&lt;p&gt;Copilot helped me figure out the threading patterns and how to show progress without blocking the UI. Game changer.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 2: Making Search Feel Instant (Even When It's Not)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; When you have 10,000+ items to search through, every keystroke was taking like 100ms. That's noticeable lag, and it felt janky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only search within the active tab (if you're in Functions, only search functions)&lt;/li&gt;
&lt;li&gt;Limit results to top 50 (nobody scrolls past that anyway)&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;fuzzy-matcher&lt;/code&gt;'s batch scoring (it's faster than one-by-one)&lt;/li&gt;
&lt;li&gt;Cached search results for repeated queries&lt;/li&gt;
&lt;li&gt;Only rendered visible items on screen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result? Search now takes &amp;lt; 16ms per keystroke. That's 60fps territory. Smooth.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 3: GitHub Rate Limits Are Real
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; The GitHub API rate-limits unauthenticated requests to 60 per hour. I was hitting that limit basically immediately when testing the crates.io features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added support for &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; environment variable (gives you 5000/hour when authenticated)&lt;/li&gt;
&lt;li&gt;Cached responses per crate so we don't re-fetch&lt;/li&gt;
&lt;li&gt;Graceful fallback when rate-limited (just show "rate limited" instead of crashing)&lt;/li&gt;
&lt;li&gt;Added a hint to users: "Set GITHUB_TOKEN for more requests"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copilot helped explain the GitHub API auth flow and how to parse rate limit headers. Much better now.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 4: Scrollable Content That Doesn't Suck
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; When you select an item, the inspector panel shows all its details. But what if the docs are really long? It just got cut off. Not ideal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
Had to track scroll offset per selected item, bind &lt;code&gt;j/k&lt;/code&gt; and arrow keys to scroll up/down, show a scrollbar indicator when content overflows, and reset scroll position when switching items.&lt;/p&gt;

&lt;p&gt;Copilot showed me Ratatui's &lt;code&gt;Scrollbar&lt;/code&gt; widget and how to manage scroll state properly. Works great now.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why You Might Actually Want to Use This
&lt;/h2&gt;
&lt;h3&gt;
  
  
  🚀 It's Fast (Like, Really Fast)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Parses 200+ files in under 2 seconds&lt;/li&gt;
&lt;li&gt;Search responds instantly (&amp;lt; 16ms per keystroke)&lt;/li&gt;
&lt;li&gt;60fps animations (yes, in a terminal)&lt;/li&gt;
&lt;li&gt;Zero network calls for local analysis (your code never leaves your machine)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🎨 It Doesn't Look Like Hot Garbage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;4 hand-crafted themes (Nord is chef's kiss)&lt;/li&gt;
&lt;li&gt;Smooth scrolling with easing (because I have standards)&lt;/li&gt;
&lt;li&gt;Context-aware UI (functions show parameter hints, structs show field types)&lt;/li&gt;
&lt;li&gt;Professional documentation formatting&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🔧 Actually Designed for Terminal Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Works perfectly over SSH (no GUI needed, no X forwarding BS)&lt;/li&gt;
&lt;li&gt;Vim-style keybindings (&lt;code&gt;j/k&lt;/code&gt; for life)&lt;/li&gt;
&lt;li&gt;One command: &lt;code&gt;oracle&lt;/code&gt; — that's it&lt;/li&gt;
&lt;li&gt;Installable via &lt;code&gt;cargo install&lt;/code&gt; (once I publish it to crates.io)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  📚 It Actually Understands Rust
&lt;/h3&gt;

&lt;p&gt;Most code search tools are just fancy grep. Oracle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzes ALL Rust item types (functions, structs, enums, traits, impls, modules)&lt;/li&gt;
&lt;li&gt;Shows qualified paths (&lt;code&gt;serde::de::Deserialize&lt;/code&gt; works perfectly)&lt;/li&gt;
&lt;li&gt;Handles visibility modifiers properly&lt;/li&gt;
&lt;li&gt;Understands generics, lifetimes, trait bounds, async, const, unsafe — all of it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, it doesn't just &lt;em&gt;search&lt;/em&gt; your code, it &lt;em&gt;understands&lt;/em&gt; it.&lt;/p&gt;


&lt;h2&gt;
  
  
  How I Actually Use This Thing
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Before Oracle:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Trying to find all trait implementations&lt;/span&gt;
rg &lt;span class="s2"&gt;"impl.*for"&lt;/span&gt; | less  &lt;span class="c"&gt;# Scroll through 200 lines of noise&lt;/span&gt;

&lt;span class="c"&gt;# Trying to understand a struct&lt;/span&gt;
rg &lt;span class="s2"&gt;"struct Config"&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 20 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"pub"&lt;/span&gt;  &lt;span class="c"&gt;# Miss half the fields&lt;/span&gt;

&lt;span class="c"&gt;# Finding function signatures&lt;/span&gt;
rg &lt;span class="s2"&gt;"pub fn connect"&lt;/span&gt;  &lt;span class="c"&gt;# Get incomplete matches without parameters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  With Oracle:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oracle
&lt;span class="c"&gt;# Tab to Functions, type "connect", press Enter&lt;/span&gt;
&lt;span class="c"&gt;# See:&lt;/span&gt;
&lt;span class="c"&gt;#   - Full signature with all parameters and types&lt;/span&gt;
&lt;span class="c"&gt;#   - Complete documentation&lt;/span&gt;
&lt;span class="c"&gt;#   - Return type with error handling&lt;/span&gt;
&lt;span class="c"&gt;#   - Source location (file + line number)&lt;/span&gt;
&lt;span class="c"&gt;# Press 'o' to open in your editor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It's just... so much faster. And less annoying.&lt;/p&gt;

&lt;p&gt;Another example: You add &lt;code&gt;tokio&lt;/code&gt; to your project and want to understand how &lt;code&gt;spawn&lt;/code&gt; works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Old way:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the tokio repo&lt;/li&gt;
&lt;li&gt;grep for "pub fn spawn"&lt;/li&gt;
&lt;li&gt;Get 47 matches&lt;/li&gt;
&lt;li&gt;Try to figure out which one is the right one&lt;/li&gt;
&lt;li&gt;Give up and read the docs instead&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;With Oracle:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;oracle ~/.cargo/registry/src/&lt;span class="k"&gt;*&lt;/span&gt;/tokio-&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="c"&gt;# Press 4 for Crates tab&lt;/span&gt;
&lt;span class="c"&gt;# Navigate to "tokio", press Enter&lt;/span&gt;
&lt;span class="c"&gt;# Type "spawn" in search&lt;/span&gt;
&lt;span class="c"&gt;# See signature, docs, parameters, trait bounds&lt;/span&gt;
&lt;span class="c"&gt;# Press 'o' for docs.rs if you want more details&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time saved: Like 5 minutes every single time.&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.amazonaws.com%2Fuploads%2Farticles%2Fsxldr2jlh49i3r8pex08.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.amazonaws.com%2Fuploads%2Farticles%2Fsxldr2jlh49i3r8pex08.png" alt="Crates Tab Demo" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation &amp;amp; Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# From source (recommended for now)&lt;/span&gt;
git clone https://github.com/yashksaini-coder/oracle.git
&lt;span class="nb"&gt;cd &lt;/span&gt;oracle
cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Or use cargo directly (once published)&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;oracle-tui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Analyze current directory&lt;/span&gt;
oracle

&lt;span class="c"&gt;# Analyze specific project&lt;/span&gt;
oracle ~/code/my-rust-project

&lt;span class="c"&gt;# Set GitHub token for better crate.io API limits&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghp_yourtoken
oracle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next (If People Actually Use This)
&lt;/h2&gt;

&lt;p&gt;Got some ideas for future versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symbol references:&lt;/strong&gt; Show where a function/type is actually being used (would be super helpful)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jump to definition:&lt;/strong&gt; Press a key, open the source file at the exact line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Macro expansion viewer:&lt;/strong&gt; See what those cryptic macros expand to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call graph visualization:&lt;/strong&gt; Show which functions call which (could get messy but cool)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git integration:&lt;/strong&gt; Show recent changes per item, like "this function was modified 3 days ago"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export to JSON/HTML:&lt;/strong&gt; Generate static docs from your codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LSP integration:&lt;/strong&gt; Hook into rust-analyzer for even deeper analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No promises on timeline though. This is still a side project I hacked together because I was annoyed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rust?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Parsing is CPU-bound. Rust's zero-cost abstractions mean fast parsing without GC pauses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safety:&lt;/strong&gt; No segfaults when traversing complex ASTs. The borrow checker caught 20+ bugs during development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ecosystem:&lt;/strong&gt; &lt;code&gt;syn&lt;/code&gt; is the gold standard for Rust parsing. &lt;code&gt;ratatui&lt;/code&gt; makes TUIs actually fun to build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-platform:&lt;/strong&gt; One binary works on Linux, macOS, Windows — no Python/Node runtime needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;TUI State Management is Hard&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Managing focus, scroll positions, search state, animations — all simultaneously — is trickier than React state. Had to build a small state machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Parsing is Expensive&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Even with &lt;code&gt;syn&lt;/code&gt;'s fast parser, analyzing 200+ files takes time. Had to optimize with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lazy loading (only parse visible items)&lt;/li&gt;
&lt;li&gt;Caching parsed results&lt;/li&gt;
&lt;li&gt;Background threads for heavy work&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Terminal Rendering is Delicate&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Drawing too frequently causes flicker. Drawing too infrequently feels laggy. Found the sweet spot: 60fps when animating, 10fps when idle.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Copilot CLI is a Force Multiplier&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Seriously. Having an AI explain &lt;code&gt;syn&lt;/code&gt; patterns, Ratatui layouts, async patterns, easing formulas — all without leaving the terminal — was incredible.&lt;/p&gt;




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

&lt;p&gt;So yeah, that's Oracle. Built it because I was frustrated with existing tools, learned a ton about Rust, TUIs, and parsing along the way.&lt;/p&gt;

&lt;p&gt;GitHub Copilot CLI was honestly a lifesaver. Having an AI that could explain &lt;code&gt;syn&lt;/code&gt; patterns, show me Ratatui layouts, help with async patterns, and even provide easing formulas — all without leaving my terminal — was incredible. Saved me probably 30+ hours of reading docs and Stack Overflow.&lt;/p&gt;

&lt;p&gt;The result is a tool I actually use every day now. Every time I clone a new Rust project, I run &lt;code&gt;oracle&lt;/code&gt; first to get the lay of the land. It's just become part of my workflow.&lt;/p&gt;

&lt;p&gt;If you work with Rust, give it a shot. Worst case, you wasted 2 minutes installing it. Best case, it changes how you explore code.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Yash Saini&lt;/strong&gt; - &lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;@yashksaini-coder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X:&lt;/strong&gt; &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;0xcrackedDev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/yashksaini-coder/oracle" rel="noopener noreferrer"&gt;github.com/yashksaini-coder/oracle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crates:&lt;/strong&gt; &lt;a href="https://crates.io/crates/oracle-tui" rel="noopener noreferrer"&gt;oracle-tui&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latest Release:&lt;/strong&gt; &lt;a href="https://github.com/yashksaini-coder/oracle/releases" rel="noopener noreferrer"&gt;Latest releases&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Got feedback? Found a bug? Want a feature? Open an issue or drop a comment. I actually read them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with way too much coffee, Rust, Ratatui, and GitHub Copilot CLI being an absolute legend.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="link%20https://github.com/yashksaini-coder/vizier" class="crayons-btn crayons-btn--primary"&gt;Star Oracle&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked this, consider giving the repo a star. It makes me feel good about my life choices.&lt;/em&gt; ⭐&lt;/p&gt;

&lt;h1&gt;
  
  
  devchallenge #githubchallenge #cli #rust #tui #opensource
&lt;/h1&gt;

</description>
      <category>githubchallenge</category>
      <category>githubcopilot</category>
      <category>tui</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
