<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Jonathan Contreras - Blog</title>
    <link>https://www.jonathancontreras.com/blog</link>
    <description>Full-stack development insights, tutorials, and thoughts from Jonathan Contreras</description>
    <language>en-us</language>
    <lastBuildDate>Sun, 16 Aug 2026 14:34:03 GMT</lastBuildDate>
    <atom:link href="https://www.jonathancontreras.com/blog/rss.xml" rel="self" type="application/rss+xml"/>
    
    <item>
      <title><![CDATA[I Keep Choosing the Less Impressive Model]]></title>
      <link>https://www.jonathancontreras.com/blog/honest-ai</link>
      <guid isPermaLink="true">https://www.jonathancontreras.com/blog/honest-ai</guid>
      <description><![CDATA[Two projects where I constrained the model toward honesty at the cost of fluency, and one where I still haven't. What each decision looked like in code, and what it cost.]]></description>
      <content:encoded><![CDATA[<p>gemma3:4b looked at a diff where I had renamed one file and changed nothing inside it, and wrote <code>refactor: update authentication module</code>. Nothing was refactored. Nothing was updated. But it read like a commit message — right register, right verb, right shape — and if I hadn't been watching it would have gone into the log and stayed there.</p>
<p>That's the failure mode I've spent the last year building against: a model that is wrong in a way the user can't detect. Not wrong loudly. Wrong smoothly, in well-formed language, in exactly the tone you were expecting. Three times, across three different projects, I hit that wall and made the same call — constrain the feature toward honesty, even when the output got less fluent, slower, or less fun to demo. Here's what each of those decisions looked like in code, and what it cost me.</p>
<h2>The commit tool that got worse on purpose</h2>
<p>Verbatim Commit is a small CLI that reads your staged diff and writes the commit message. The first version ran gemma3:4b through Ollama, because it's fast, it's local, and 4B is plenty for a sentence and a half.</p>
<p>It was plenty for a sentence and a half. It was not enough to be honest about a diff. It labeled renames as updates. It described the intent behind a change it had no way of knowing. Given a diff that touched three files, it would sometimes narrate a fourth. The messages were fluent, conventional-commits-shaped, and quietly fictional.</p>
<p>For most generated text you can shrug at that. Not here. A commit message is a permanent artifact that other people read as fact. Six months from now someone bisects a regression, reads <code>fix: handle null user session</code>, and trusts it. If the model made that up, I haven't saved anyone time — I've poisoned the record and made it harder to find the bug than if the message just said <code>wip</code>.</p>
<p>Two fixes. First, the prompt stopped asking for a good commit message and started asking for a constrained one: describe only what the diff shows, use the change type the diff actually supports, never state a rationale that isn't visible in the code. Second — the part I sat on for a while — I moved the default model to qwen2.5-coder:7b. And I kept the verify mode as a git hook, so the check runs whether or not you used the tool to generate the message.</p>
<p>I measured the cost rather than assert it, and the number I was ready to report is not the number I got. On speed, roughly what you'd expect: the 7b takes about 4.0 seconds at p50 against 2.5 for the 4b, and its slow tail is meaningfully worse — 38% of runs cross five seconds, where the 4b crosses it 9% of the time. Some of that gap is qwen writing more rather than computing slower; normalized per character it's still about 21% slower, so the direction holds.</p>
<p>The memory cost I was about to concede doesn't exist. As the CLI actually loads them, the 7b uses roughly a gigabyte <em>less</em> resident memory than the 4b, because gemma3:4b silently claims a 128k context window and qwen only asks for 32k. Force both to the same context and the 7b costs about 0.35 GB more — real, but a fraction of the figure I'd have written down from memory.</p>
<p>I'm leaving that in rather than quietly correcting the sentence, because it's the same failure this whole post is about. I had a confident, plausible, well-shaped claim about a tradeoff I'd made. I had no measurement behind it. And it was wrong in the direction that made the decision look more principled than it was. On a thin laptop the 4b is still the nicer thing to live with — for latency, which I'd have gotten right, and not for memory, which I'd have gotten backwards.</p>
<p>Worth separating two things I could easily let blur together: the default and what I actually run. Day to day I use gemma4:12b-mlx, on a machine that can afford it. That's not what ships. A default has to be right for someone I'll never meet, on hardware I can't see, who will never open the config file — which is a stricter question than what's best on my laptop, and the reason the answer isn't just "the biggest model I can run."</p>
<p>I took the trade anyway, because a slower tool that tells the truth about your diff beats a fast one whose output you have to read against the diff to trust. If I have to check it, it hasn't done the job.</p>
<h2>The score I refused to let the model guess</h2>
<p>Resuzen shows you a match score between your résumé and a job description. The easy implementation is four lines: paste both into the context, ask the model to rate the fit out of 100, render the number.</p>
<p>I built that version. It works, in the sense that it returns a number. It fails in the sense that the number is the product. Ask twice and you get 72 and 78. Ask nicely and you get a better score. And the direction of the drift is not random — a model asked to evaluate someone's work, with that person's work sitting in the context, flatters. That's not a scoring feature. That's a feature that talks somebody into applying for a job they're not ready for, and charges them for the privilege.</p>
<p>So I split it. The model does the part it's actually good at: reading a job description written in human prose and pulling out the skills, tools, and requirements it's really asking for — including the ones phrased three different ways in three different bullets. That's fuzzy linguistic work and code is bad at it. Then the coverage calculation happens in code. Deterministic set comparison, weighted, same inputs in and same number out, every single time.</p>
<p>The split is the whole insight, and it generalizes further than résumés: <em>use the model for the fuzzy part, use code for the part that has to be reproducible.</em> Anything a user will treat as a measurement should be computed, not generated.</p>
<p>The side effect showed up in interview prep. Because the pipeline knows exactly which requirements the résumé doesn't cover — not "the model felt it was a 78" but a concrete list — it can name the gaps and drill you on them. A model asked to score would have smoothed those gaps over. The deterministic version can't. It's the least flattering feature I've built and the most useful one.</p>
<h2>The one I haven't fixed yet</h2>
<p>The third project is where the rule stops being a story about good decisions.</p>
<p>Fixr answers vehicle maintenance questions. Users ask it when a part is due for replacement and what a repair should run them, and then they spend money based on the answer. Of the three projects here, it has the highest stakes and the weakest safeguards, and I'd rather say that in public than let a case study imply otherwise.</p>
<p>Here is what actually happens when you ask Fixy a question. The message goes to a single Gemini call, the response comes back, and it renders. Nothing sits between the model's text and your screen. No source is consulted, no claim is scored, and there's no branch anywhere that drops a number the system can't stand behind. The prompt asks for a typical cost and a recommended interval in every answer, which means the most checkable thing in the response is also the thing with nothing behind it.</p>
<p>What I do have is prompt instruction — lines telling the model to add verification language and not to guess at specs it isn't sure about. That's the model policing itself, and I want to be precise about what that's worth. An unenforced instruction is a preference. It is not a constraint, and I shouldn't have been counting it as one.</p>
<p>The part that's harder to write: I made this worse on purpose, once. An early version of the prompt said that when the model was unsure about vehicle-specific details, it should point the user to their owner's manual or a mechanic. I replaced that with an instruction to provide specific specs when confident, plus a hedge for critical values like torque and fluid capacities. The reasoning was real — generic advice is available anywhere, and vague answers were the top complaint. But look at the direction. I made the product more confident before I made it more verifiable, and I solved a vagueness problem by removing the caution rather than by adding a way to check.</p>
<p>There's a disclaimer, and it only appears on the empty state, before you've asked anything. The moment an answer shows up, it's gone. A disclaimer that isn't next to the claim isn't doing the job I told myself it was doing.</p>
<p>The frustrating part is that the checking source already exists in the repo. There's a maintenance schedule table with real intervals and cost ranges, and it works fine — it just feeds a separate, non-AI suggestions path that does plain arithmetic against your service records. The chat action never imports it.</p>
<p>Wiring it up isn't complicated, and I can describe the version I'd build. Fixy would return its numbers as structured data alongside the prose instead of burying them in a sentence. Code would compare those numbers against the table and render one of three states next to the claim: quiet when it's in range, both figures shown when it isn't, and marked unverified when the table has no entry for that service. The disclaimer would move from the empty state, where it currently sits, to sitting under every answer that carries a number.</p>
<p>I'm not building it this quarter. I want to be straight about that rather than let a description of the plan read like a description of the product. And even the version above is weaker than it sounds: the table is generic by service type, not per-vehicle, so it can catch a $600 oil change but it can't tell you whether an interval is right for your car. Actual per-vehicle verification needs manufacturer data I don't have access to. What's realistically on the table is visible uncertainty, not verified answers, and it's still sitting behind other work.</p>
<p>I've made this call once before, somewhere the stakes were public instead of personal. I built a public crime statistics page that pulled from two government sources. They used different schemas, and for the same reporting window they disagreed on the counts. The clean version picks the source you trust more, or splits the difference, and ships one confident number. What went out instead showed the disagreement — both figures, both sources, right there on the page. It reads worse. It's harder to screenshot. But a reader has no way to audit the underlying data, and the alternative was handing them my judgment call dressed up as a fact.</p>
<p>The rule I've been describing is easy to follow while you're building something. It's much harder once the thing has shipped, works, and nobody is complaining.</p>
<h2>The pattern</h2>
<p>Written out, the rule I keep landing on is boring, which is probably a good sign:</p>
<ol>
<li>Decide which part of the feature has to be reproducible, and move that part into code.</li>
<li>Let the model do the linguistic work — parsing messy human text, summarizing, rephrasing — because that's what it's genuinely better at than code.</li>
<li>If the model can be wrong in a way the user can't detect, add a verification layer or don't ship the feature.</li>
<li>Given two defaults, prefer the one that fails visibly over the one that fails fluently.</li>
</ol>
<p>Now the part that makes this a position rather than a brag: this rule costs me, every time, and the costs are not hypothetical.</p>
<p>The honest defaults are slower and hungrier. The honest score is lower than the flattering one, which is a worse first impression on a product where the first impression is most of the funnel. The verified answer sometimes comes back as "I can't confirm that," which is the least impressive thing a demo can do — and I have watched people's faces when it happens. A tool that says "I don't know" in a room full of tools that say something is not the one that gets remembered.</p>
<p>I should be clear about what I can't show you. In all three cases, I'm the one who decided the honest version was better. No user asked for a lower match score. Nobody has written in to say a commit message misled them six months later. I don't have a retention curve proving people prefer a tool that sometimes says it can't confirm something. If you came here for evidence that honest defaults win on the metrics, I don't have it, and I'd be a little suspicious of anyone who claims it this cleanly. What I have is a standard I'm not willing to trade away, and three repositories where you can check whether I actually held to it. Those are different claims, and I'd rather not blur them together.</p>
<p>I take the trade because of the asymmetry. When a fluent-but-wrong feature fails, it fails silently, at the exact moment the user has decided to trust it — the commit six months later, the application to the wrong job, the repair that costs more than the car is worth. When an honest feature fails, it fails in front of you, while you can still do something about it.</p>
<h2>The standard</h2>
<p>I'd rather ship something that makes me look slightly less clever and is right. That's the whole position. It's not a philosophy of AI, it's a build rule, and the only way to argue it is to show the code where it applied.</p>
<p>So: <a href="https://www.jonathancontreras.com/projects/verbatim-commit">Verbatim Commit</a>, <a href="https://www.jonathancontreras.com/projects/resuzen">Resuzen</a>, and <a href="https://www.jonathancontreras.com/projects/fixr">Fixr</a> — two where I applied the rule and one where I'm still owing it. Go check my work. That's the point of writing it down.</p>]]></content:encoded>
      <pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate>
      <dc:creator><![CDATA[Jonathan Contreras]]></dc:creator>
      <category>AI</category>
      <category>LLM</category>
      <category>Product</category>
      <category>Engineering</category>
    </item>
    <item>
      <title><![CDATA[From Piano to Programming: How I Ended Up in Computer Science]]></title>
      <link>https://www.jonathancontreras.com/blog/from-piano-to-programming</link>
      <guid isPermaLink="true">https://www.jonathancontreras.com/blog/from-piano-to-programming</guid>
      <description><![CDATA[I was never the kid who dreamed of writing code. I was the kid practicing scales and chord progressions, convinced music was my path — until college applications made me question everything.]]></description>
      <content:encoded><![CDATA[<p>I was never the kid who dreamed of writing code.</p>
<p>I was the kid at the keyboard late at night, teaching myself songs by ear, lost in it. No formal lessons, no classical training — just me figuring it out one chord at a time.</p>
<p>I still am that kid, actually. These days I play piano at church and serve as the music director. Music never left — it just stopped being the only thing.</p>
<p>So how did I end up a full-stack developer and CS student? Honestly — one class I can barely remember signing up for, a professor who refused to let me settle, and a slow realization that I didn't have to choose.</p>
<hr>
<h2>Music Was More Than a Hobby</h2>
<p>People sometimes assume that because I didn't pursue music professionally, it was just a phase. It wasn't.</p>
<p>Music was how I communicated. Not in a metaphorical, romanticized way — in a real, practical one. There were things I couldn't put into words that I could work through at a piano. Moods, tensions, ideas that hadn't fully formed yet. Playing was how I processed the world. It was part of my identity in a way that went deeper than "I enjoy this."</p>
<p>Being self-taught mattered too. I didn't learn music the way schools teach it — theory first, sheet music, scales in isolation. I learned it backwards, by feel. I'd hear something, want to understand it, and pull it apart until I could recreate it. That instinct — <em>hear something interesting, figure out how it works, make it yours</em> — would show up again later in ways I didn't expect.</p>
<p>When senior year came around, music was still the plan. I had a picture of what that future looked like — studying music, maybe becoming a teacher, passing on what I'd taught myself to someone else. There was something appealing about that full circle: self-taught kid figures it out, then helps the next one do the same.</p>
<p>And then I hit a wall: auditions.</p>
<p>Every program wanted classical training. Formal repertoire. The kind of foundation you build over years of structured lessons. I didn't have that. I had real passion and real hours, but I hadn't come through the right channel. It felt like a door that was technically open but not built for someone like me — not because I wasn't good at music, but because the system wasn't designed to evaluate what I actually had.</p>
<p>I wasn't bitter. But I had to accept that the path I'd imagined wasn't going to look the way I'd imagined it.</p>
<hr>
<h2>The Class I Almost Don't Remember Taking</h2>
<p>Somewhere in high school — a friend mentioned it, or maybe my counselor, I genuinely can't remember — I ended up in a CS class.</p>
<p>It wasn't a dramatic decision. It was just the next thing on the schedule.</p>
<p>But something clicked in a way I didn't anticipate. Not immediately, not in a life-changing-moment kind of way. More like: <em>this is interesting. I want to keep pulling on this thread.</em></p>
<p>The project that sticks with me most from that class: we built something that used camera recognition to control the Google dinosaur game with your hand. No keyboard, no mouse — just your hand in front of a webcam, and the little dinosaur jumping on command.</p>
<p>It sounds simple now. But at the time, the fact that I had <em>made</em> that happen — that I'd written the logic that connected a camera feed to a game response — felt genuinely wild. It was the first time I understood that code wasn't just solving textbook problems. It was building things that did things in the real world.</p>
<p>I didn't walk out of that class thinking <em>this is my calling.</em> I walked out thinking <em>that was really cool. What else can I make?</em></p>
<hr>
<h2>College, a Near-Quit, and Dr. Zaki</h2>
<p>When it came time to pick a major, I chose CS. Not out of passion, not out of certainty — more like: <em>music school isn't the right fit, I liked that class, let's see where this goes.</em></p>
<p>Freshman year was an adjustment. I was figuring out if this was actually my thing, or just the path of least resistance I'd stumbled into. For a while, I wasn't sure.</p>
<p>Second semester, I had Dr. Zaki.</p>
<p>I don't know how to overstate how much that mattered. She wasn't just a professor — she became a mentor. She pushed me in the way that only someone who actually believes in you can. When I wanted to take the easy way out, she didn't let me. When I was ready to quit, she saw something in me that I wasn't seeing in myself yet and held me to a higher standard because of it.</p>
<p>There's a specific kind of impact a person like that has — not just on your grade or your semester, but on the story you tell yourself about what you're capable of. Before Dr. Zaki, CS was something I was trying. After, it was something I was committed to.</p>
<p>I started building things. Small projects at first, then bigger ones. I started seeing how much you could actually <em>create</em> with code — not just solve problems in the abstract, but build real products that people interact with.</p>
<p>It started to feel the way music used to feel.</p>
<p>The self-taught instinct I'd built at the piano translated directly. The pattern recognition — hearing a song and pulling apart how it worked, how the pieces related to each other — showed up again in how I approached systems and code. It wasn't a completely different world. It was a different instrument.</p>
<hr>
<h2>What I've Taken From It</h2>
<p>The most honest version of this story is: I didn't know CS was my thing until I was already deep in it. And I think that's okay.</p>
<p>I'm a self-taught kid who loved music, hit a wall, wandered into a class he barely remembers signing up for, almost quit, and then met someone who believed in him before he believed in himself.</p>
<p>And here's the part I want to be clear about: I never gave up the first thing to find the second one. Music is still a massive part of my life. I play piano every week at church. I serve as the music director. That identity didn't go anywhere — it just made room for something new alongside it.</p>
<p>I think that's actually the real lesson here. The audition wall felt like a door closing. It wasn't. It was just a redirect. The self-taught instinct, the way I hear patterns and pull them apart to understand them — that's as alive in how I write code as it ever was at the piano. I didn't leave music behind. I brought it with me.</p>
<p>If you came to tech through an unexpected door — or if you're still standing outside it, not sure if it's for you — that's a valid way to get here. Not everyone has a dramatic origin story. Sometimes it's a dinosaur game and a professor who wouldn't let you settle.</p>
<p>And sometimes the thing you thought you were leaving is still exactly where you left it, waiting for Sunday morning.</p>]]></content:encoded>
      <pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate>
      <dc:creator><![CDATA[Jonathan Contreras]]></dc:creator>
      <category>Personal</category>
      <category>Career</category>
      <category>Learning</category>
    </item>
    <item>
      <title><![CDATA[My Tech Stack for 2026 — and Why]]></title>
      <link>https://www.jonathancontreras.com/blog/my-tech-stack-for-2026</link>
      <guid isPermaLink="true">https://www.jonathancontreras.com/blog/my-tech-stack-for-2026</guid>
      <description><![CDATA[A breakdown of the tools, frameworks, and technologies I'm using in 2026 as an engineer building real products. Plus what I'm actively learning. Written February 2026, with a note on what has changed since.]]></description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>Editor's note, August 2026.</strong> This was written in February, while I was still
finishing my CS degree — I graduated from HCU in May and start an M.S. in Applied
Computing at OU this month. The stack below is otherwise current. Where it
describes me as a student, read it as a snapshot of that February.</p>
</blockquote>
<p>Okay so I'll be real — writing a "tech stack" post always felt a little self-indulgent to me. Like, who cares what tools I use? But the more I thought about it, the more I realized: I actually get a ton out of reading other developers' stack breakdowns. Especially people who are in similar situations. So here's mine.</p>
<p>Right now I'm in a weird but exciting in-between: finishing my CS degree at Houston Christian University, building real products at Belsade Tech (an AI startup), applying to software engineering roles, and shipping side projects on top of all of that. My stack has to work for all of those contexts at once — academic credibility, startup speed, and job-market relevance. That tension is actually what shapes every decision below.</p>
<p>Here's everything I'm leaning on in 2026, why it made the cut, and what I'm still picking up along the way.</p>
<h2>01 — Frontend: React + Next.js stays king</h2>
<p>I've tried to quit Next.js twice. I keep coming back. The combination of server components, file-based routing, and the ecosystem around it remains the most productive way I know to ship polished web UIs quickly. My portfolio (jonathancontreras.com), the CampusConnect student hub I'm building and every other project I work on all run Next.js.</p>
<p>For styling I'm all-in on <strong>Tailwind CSS</strong>. Not because it's trendy — because it keeps me in the component file instead of context-switching to a separate stylesheet. I pair it with <strong>shadcn/ui</strong> for accessible primitives and pull from <strong>Framer Motion</strong> when a project needs something with personality.</p>
<p><strong>Stack:</strong> React 19, Next.js 16, Tailwind CSS, shadcn/ui</p>
<h2>02 — Backend: Supabase &#x26; Convex day-to-day, Spring Boot on the horizon</h2>
<p>My default backends are <strong>Supabase</strong> and <strong>Convex</strong>, and honestly they cover most of what I need.</p>
<p>Supabase is my go-to when the project is relational at heart. You get a real PostgreSQL database, row-level security, auth, and storage out of the box. It feels like cheating until you realize it's just a well-designed product. Convex I reach for when a feature needs real-time reactivity — it handles the sync layer so you don't have to think about it, which is a huge deal when you're building collaborative or live-updating features.</p>
<p>For scripting, data work, and AI integrations, I default to <strong>Python</strong>. My NLP coursework and machine learning projects runs Python.</p>
<p>Now — <strong>Spring Boot</strong>. I'm being upfront here: I recently started learning it. I'm picking it up specifically because I'm applying to Sysco, and enterprise Java is very much the world they operate in. It's been a genuinely interesting shift — dependency injection, JPA, thinking in types, understanding how a mature framework is structured. I'm early in it, but I'm taking it seriously and building with it as I go.</p>
<p><strong>How I decide which backend to reach for:</strong></p>
<ul>
<li>Day-to-day projects and prototypes — Supabase or Convex depending on whether I need real-time.</li>
<li>Anything enterprise-facing or resume-worthy — Spring Boot, even if it means taking more time to learn as I build.</li>
</ul>
<p><strong>Stack:</strong> Supabase, Convex, Python 3.12, Spring Boot 3 (in progress)</p>
<h2>03 — AI &#x26; Integration: Building with AI APIs, not just using them</h2>
<p>With the way technology is shifting, I have been spending time evaluating AI models for agentic applications. Right now <strong>Claude (Anthropic)</strong> is my go-to for anything requiring nuanced reasoning and tool use. The API is clean, the context windows are generous, and the models genuinely follow complex instructions — which matters when you're building AI-powered workflows, not just chatbots. I default to claude too since I have my subscription, so it feels like a no brainer.</p>
<p>For the FIXR vehicle maintenance app, we're prototyping features that need structured output and reliable function calling. Since at the moment we are not bring in any revenue, we decided to go with Gemini 2.5-flash since its very cost effective. I also reach for <strong>Hugging Face models</strong> when I need something self-hosted or need to experiment for coursework.</p>
<p><strong>Stack:</strong> Anthropic Claude API, Gemini API, Hugging Face, scikit-learn / NLTK, R (tidyverse)</p>
<h2>04 — Infrastructure &#x26; Tooling: The invisible layer that keeps everything running</h2>
<p>My home network runs a <strong>Tailscale mesh</strong> with a <strong>Pi-hole DNS sinkhole</strong>. This setup taught me more about networking, DNS resolution, and zero-trust architecture than any class. Everything I touch is version-controlled on <strong>GitHub</strong> — I run GitHub workshops at HCU, so I eat my own cooking here.</p>
<p>For deployment, I'm using <strong>Vercel</strong> for Next.js frontends (it's still the easiest path from push to prod). <strong>Docker</strong> has become non-negotiable for keeping local dev and production parity sane.</p>
<p><strong>Stack:</strong> GitHub, Docker, Tailscale, Vercel</p>
<h2>05 — What I want to learn in 2026</h2>
<p>Honestly, writing this post made me realize how much I still want to learn. That's not a bad thing — it means I'm paying attention. Here's what's on my radar for the rest of 2026.</p>
<h3>Kubernetes &#x26; container orchestration</h3>
<p>I know Docker. I use it fairly regularly. But I've been avoiding Kubernetes because it felt like overkill for what I was building. That's starting to change — especially as Belsade's services grow and I want to be a stronger candidate for roles that touch distributed systems.</p>
<h3>LLM fine-tuning &#x26; RAG pipelines</h3>
<p>Working with AI APIs is one thing. Understanding what's happening under the hood — how to fine-tune a model for a specific domain, how to build a reliable RAG pipeline, how to evaluate outputs at scale — is a completely different skillset. Given where Belsade is headed, this one isn't optional for me.</p>
<h3>TypeScript, properly</h3>
<p>I use TypeScript. But I'll admit — I've been using it like JavaScript with a seatbelt. This year I want to actually lean into generics, utility types, and strict mode without fighting it. It's one of those things where the investment compounds over time.</p>
<h2>06 — Final thought: Build things. Break things. Ship things.</h2>
<p>Every tool on this list earned its spot by helping me ship something real — a startup feature, a church site, a campus project, a homework submission that actually ran. If it hasn't shipped, it doesn't count.</p>
<p>What does your stack look like? I'm always curious what other developers in similar situations are choosing — especially if you're also doing the school + startup + job hunt juggle. Reach out on the contact page. I genuinely want to know.</p>]]></content:encoded>
      <pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate>
      <dc:creator><![CDATA[Jonathan Contreras]]></dc:creator>
      <category>React</category>
      <category>Next.js</category>
      <category>Supabase</category>
      <category>Convex</category>
      <category>Python</category>
      <category>Spring Boot</category>
      <category>AI/ML</category>
      <category>DevOps</category>
      <category>Tailscale</category>
      <category>Learning</category>
    </item>
  </channel>
</rss>