<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://sheetsj.com/">
  <title>Jeff Sheets - Uncommented Bytes</title>
  <subtitle>Tech briefs and notes by Jeff Sheets</subtitle>
  <link href="https://sheetsj.com/feed.xml" rel="self" />
  <link href="https://sheetsj.com/blog/" />
  
  <updated>2026-02-23T15:00:00Z</updated>
  <id>https://sheetsj.com/</id>
  <author>
    <name>Jeff Sheets</name>
    <email>jeffsheets@gmail.com</email>
  </author>
  <entry>
    <title>FishFryday: 17 Years of Omaha Fridays</title>
    <link href="https://sheetsj.com/blog/fishfryday-17-years-of-omaha-fridays/" />
    <updated>2026-02-23T15:00:00Z</updated>
    <id>https://sheetsj.com/blog/fishfryday-17-years-of-omaha-fridays/</id>
    <content
      xml:lang=""
      type="html"
      >&lt;p&gt;Just casually (and slightly embarrassingly) announcing the geekiest project page I’ve ever built: the &lt;a href=&quot;https://sheetsj.com/fishfry/&quot;&gt;Omaha Fish Fry Tracker&lt;/a&gt; 🐟&lt;/p&gt;
&lt;p&gt;Fish fries in Omaha are like &lt;a href=&quot;https://sarahbakerhansen.com/omaha-fish-frys/&quot; rel=&quot;noopener&quot;&gt;a big deal&lt;/a&gt;, and amazingly fun and quirky and community at its best.&lt;/p&gt;
&lt;p&gt;Like the best fish and chips, sometimes baked, fried shrimp, meat wheel raffles, pickle cards (not pull-tabs cause I’m not in Minnesota and in Nebraska we call them pickles), cover bands, and maybe (ok definitely) a beer or margarita or two. And long lines (like hanging out with friends hours long). And you go home smelling like a fryer. But man… it’s the best!&lt;/p&gt;
&lt;p&gt;Somehow there’s no good Omaha fish fry page to link to – but &lt;a href=&quot;https://duckduckgo.com/?ia=images&amp;amp;t=h_&amp;amp;q=omaha+fish+fries&amp;amp;chip-select=search&amp;amp;iax=images&quot; rel=&quot;noopener&quot;&gt;viewing the duckduckgo images&lt;/a&gt; is a pretty great summary LOL&lt;/p&gt;
&lt;p&gt;I don’t know why I started keeping track, but I’m super happy I did 😃&lt;/p&gt;
</content
    >
  </entry>
  <entry>
    <title>HuskerFinder: Claude Code Shipped my ~20 year old idea</title>
    <link href="https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/" />
    <updated>2025-10-27T15:00:00Z</updated>
    <id>https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/</id>
    <content
      xml:lang=""
      type="html"
      >&lt;p&gt;I’ve had this idea in my head for maybe ~17 years. I remember talking about it when iPhone’s first started shipping custom apps in the app store. Build a simple website (or app) that tells us Husker fans what radio station is broadcasting the game near wherever I happen to be. That’s it. Super simple concept.&lt;/p&gt;
&lt;p&gt;Around 10 years ago I started building it as a static website. Figuring out the location distance logistics. Got a basic page going, scraped some station data. Then I’d come back to it every few years, tinker around, add a feature or two, but never felt like it was ready to ship. It always felt incomplete or unpolished or just not quite there yet.&lt;/p&gt;
&lt;p&gt;Fast-forward to a few weeks ago and &lt;a href=&quot;https://huskerfinder.sheetsj.com/&quot; rel=&quot;noopener&quot;&gt;HuskerFinder&lt;/a&gt; is live. What changed? Claude Code.&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://sheetsj.com/assets/images/huskerfinder-320w.webp 320w, https://sheetsj.com/assets/images/huskerfinder-570w.webp 570w, https://sheetsj.com/assets/images/huskerfinder-820w.webp 820w, https://sheetsj.com/assets/images/huskerfinder-1200w.webp 1200w, https://sheetsj.com/assets/images/huskerfinder-1600w.webp 1600w&quot; sizes=&quot;(min-width: 55rem) 820px, 100vw&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://sheetsj.com/assets/images/huskerfinder-320w.jpeg 320w, https://sheetsj.com/assets/images/huskerfinder-570w.jpeg 570w, https://sheetsj.com/assets/images/huskerfinder-820w.jpeg 820w, https://sheetsj.com/assets/images/huskerfinder-1200w.jpeg 1200w, https://sheetsj.com/assets/images/huskerfinder-1600w.jpeg 1600w&quot; sizes=&quot;(min-width: 55rem) 820px, 100vw&quot; /&gt;&lt;img src=&quot;https://sheetsj.com/assets/images/huskerfinder-320w.jpeg&quot; width=&quot;1600&quot; height=&quot;925&quot; alt=&quot;HuskerFinder showing nearby radio stations on an interactive map&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/picture&gt;&lt;/figure&gt;
&lt;h2 id=&quot;the-web-authors-block-problem&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/#the-web-authors-block-problem&quot;&gt;The Web Author’s Block Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s the thing - &lt;a href=&quot;https://sheetsj.com/about/&quot;&gt;I&lt;/a&gt; &lt;a href=&quot;https://twitter.sheetsj.com/1185612834991988737/&quot; rel=&quot;noopener&quot;&gt;love&lt;/a&gt; &lt;a href=&quot;https://twitter.sheetsj.com/1374171248813608968/&quot; rel=&quot;noopener&quot;&gt;building&lt;/a&gt; &lt;a href=&quot;https://gaspumpr.com/&quot; rel=&quot;noopener&quot;&gt;stuff&lt;/a&gt;. But on weekends and evenings after work these days…? I’m tired. My brain is fried from a full day of coding and meetings and architecture decisions. I want to spend hours with my family, and dogs. Especially the dogs 🐶🐶&lt;/p&gt;
&lt;figure style=&quot;max-width: 300px;&quot;&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://sheetsj.com/assets/images/my-dogs-320w.webp 320w, https://sheetsj.com/assets/images/my-dogs-570w.webp 570w, https://sheetsj.com/assets/images/my-dogs-820w.webp 820w, https://sheetsj.com/assets/images/my-dogs-1200w.webp 1200w&quot; sizes=&quot;&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://sheetsj.com/assets/images/my-dogs-320w.jpeg 320w, https://sheetsj.com/assets/images/my-dogs-570w.jpeg 570w, https://sheetsj.com/assets/images/my-dogs-820w.jpeg 820w, https://sheetsj.com/assets/images/my-dogs-1200w.jpeg 1200w&quot; sizes=&quot;&quot; /&gt;&lt;img src=&quot;https://sheetsj.com/assets/images/my-dogs-320w.jpeg&quot; width=&quot;1200&quot; height=&quot;1600&quot; alt=&quot;My dogs - Scout and Finley&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/picture&gt;&lt;figcaption class=&quot;cluster font-display&quot;&gt;&lt;p&gt;Finley and Scout are the best&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;So projects like HuskerFinder would sit there. I’d think about them, plan them out in my head, but not much more. It’s like writer’s block but for web development. Web author’s block.&lt;/p&gt;
&lt;h2 id=&quot;switching-roles-from-developer-to-product-owner&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/#switching-roles-from-developer-to-product-owner&quot;&gt;Switching Roles: From Developer to Product Owner&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Claude Code flipped my mindset. Instead of being the engineer who has to implement everything, I became the product owner and architect. I tell Claude Code what I want, review what it builds, guide the direction, make decisions.&lt;/p&gt;
&lt;p&gt;Here’s how I structure my screen for IntelliJ and Claude Code (I’m not a fan of the IntelliJ CC plugin itself, so I run a separate terminal):&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://sheetsj.com/assets/images/cc-split-screen-320w.webp 320w, https://sheetsj.com/assets/images/cc-split-screen-570w.webp 570w, https://sheetsj.com/assets/images/cc-split-screen-820w.webp 820w, https://sheetsj.com/assets/images/cc-split-screen-1200w.webp 1200w, https://sheetsj.com/assets/images/cc-split-screen-1600w.webp 1600w&quot; sizes=&quot;(min-width: 55rem) 820px, 100vw&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://sheetsj.com/assets/images/cc-split-screen-320w.jpeg 320w, https://sheetsj.com/assets/images/cc-split-screen-570w.jpeg 570w, https://sheetsj.com/assets/images/cc-split-screen-820w.jpeg 820w, https://sheetsj.com/assets/images/cc-split-screen-1200w.jpeg 1200w, https://sheetsj.com/assets/images/cc-split-screen-1600w.jpeg 1600w&quot; sizes=&quot;(min-width: 55rem) 820px, 100vw&quot; /&gt;&lt;img src=&quot;https://sheetsj.com/assets/images/cc-split-screen-320w.jpeg&quot; width=&quot;1600&quot; height=&quot;896&quot; alt=&quot;Split screen showing IntelliJ on the left with code, Claude Code terminal on the right&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/picture&gt;&lt;/figure&gt;
&lt;p&gt;Describe a feature in plain English. Claude Code implements it. Review the changes in IntelliJ, test it out, then either accept it or provide feedback for refinement. Rinse and repeat.&lt;/p&gt;
&lt;p&gt;The mental load difference is huge. Instead of “okay I need to set up Leaflet, figure out the marker API, handle the popup state, wire up the click handlers…” I just say “add an interactive map that shows station locations and my current position.” Done.&lt;/p&gt;
&lt;h2 id=&quot;the-aha-moment&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/#the-aha-moment&quot;&gt;The Aha Moment&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The map view was my aha moment. Dealing with map libraries, figuring out the right one to use, learning the API, positioning markers, handling interactions… ugh.&lt;/p&gt;
&lt;p&gt;I mentioned to Claude Code. Within seconds it had added Leaflet.js, set up the map component, added markers for all the stations with custom icons color-coded by sport, implemented popups with station details, and added a user location marker.&lt;/p&gt;
&lt;p&gt;This wasn’t just a coding assistant that autocompletes functions. This was me being able to focus on product decisions while Claude Code handled the technical grunt work.&lt;/p&gt;
&lt;h2 id=&quot;vibe-engineering-greater-vibe-coding&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/#vibe-engineering-greater-vibe-coding&quot;&gt;Vibe Engineering &amp;gt; Vibe Coding&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To be clear, I’m not just prompting and shipping blindly. I still review every change. I still make architectural decisions. I still catch bugs and edge cases. I still write some code myself when I want to.&lt;/p&gt;
&lt;p&gt;Now though I’m spending my mental energy on the interesting problems - the product decisions, the user experience, the vision - not on “how do I calculate the distance between two lat/long coordinates”.&lt;/p&gt;
&lt;p&gt;I’m automating the rough edges that I don’t want to mentally juggle at the moment. I’m conducting the orchestra.&lt;/p&gt;
&lt;h2 id=&quot;static-sites-ftw&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/#static-sites-ftw&quot;&gt;Static Sites FTW&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This project is a static site. Just HTML, CSS, and vanilla JavaScript. No backend servers, no databases, no complex deployment pipelines.&lt;/p&gt;
&lt;p&gt;This is the sweet spot for GenAI development:&lt;/p&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;&lt;strong&gt;No backend to manage&lt;/strong&gt; - No servers to configure, no APIs to secure, no database migrations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy deployment&lt;/strong&gt; - Push to GitHub, let GitHub Pages handle the rest. Done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEO-friendly&lt;/strong&gt; - Search engines love static HTML. No client-side rendering gymnastics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Free hosting&lt;/strong&gt; - GitHub Pages, Cloudflare Pages, etc… all free for static sites&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast&lt;/strong&gt; - No server round trips, just cached HTML and assets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Static sites let you focus on the product, not the infrastructure.&lt;/p&gt;
&lt;p&gt;Claude Code excels at this. “Add a map view” works great when you don’t also need to wire up backend endpoints, authentication, and data persistence. It’s just DOM manipulation and client-side logic. Ship it.&lt;/p&gt;
&lt;h2 id=&quot;shipped&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/huskerfinder-claude-code-shipped-my-20-year-old-idea/#shipped&quot;&gt;Shipped&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So yeah, 20-year-old idea, started building it 10 years ago, finally shipped it in a few weeks once I had Claude Code. The difference wasn’t the technology stack or the complexity of the problem. The difference was having a tool that let me work the way I wanted to work - as a product owner and architect - instead of grinding through implementation details when I’m already mentally exhausted.&lt;/p&gt;
&lt;p&gt;Could I have structured the js data better? Could I have used CSS variables more semantically? So many things, sure. But why twiddle when we can ship.&lt;/p&gt;
&lt;p&gt;And hey, if you’re ever driving through Nebraska (or apparently Kansas or South Dakota too) wondering which station has the 🏈🏐🏀 game, I’ve got an app for that now: &lt;a href=&quot;https://huskerfinder.sheetsj.com/&quot; rel=&quot;noopener&quot;&gt;huskerfinder.sheetsj.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go Big Red!&lt;/p&gt;
&lt;figure&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://sheetsj.com/assets/images/hf-full-map-view-320w.webp 320w, https://sheetsj.com/assets/images/hf-full-map-view-570w.webp 570w, https://sheetsj.com/assets/images/hf-full-map-view-820w.webp 820w, https://sheetsj.com/assets/images/hf-full-map-view-1200w.webp 1200w, https://sheetsj.com/assets/images/hf-full-map-view-1600w.webp 1600w&quot; sizes=&quot;(min-width: 55rem) 820px, 100vw&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://sheetsj.com/assets/images/hf-full-map-view-320w.jpeg 320w, https://sheetsj.com/assets/images/hf-full-map-view-570w.jpeg 570w, https://sheetsj.com/assets/images/hf-full-map-view-820w.jpeg 820w, https://sheetsj.com/assets/images/hf-full-map-view-1200w.jpeg 1200w, https://sheetsj.com/assets/images/hf-full-map-view-1600w.jpeg 1600w&quot; sizes=&quot;(min-width: 55rem) 820px, 100vw&quot; /&gt;&lt;img src=&quot;https://sheetsj.com/assets/images/hf-full-map-view-320w.jpeg&quot; width=&quot;1600&quot; height=&quot;912&quot; alt=&quot;HuskerFinder full map view&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; /&gt;&lt;/picture&gt;&lt;/figure&gt;
</content
    >
  </entry>
  <entry>
    <title>FROM Excel TO SQL: Claude Code Magic</title>
    <link href="https://sheetsj.com/blog/from-excel-to-sql-claude-code-magic/" />
    <updated>2025-09-30T15:00:00Z</updated>
    <id>https://sheetsj.com/blog/from-excel-to-sql-claude-code-magic/</id>
    <content
      xml:lang=""
      type="html"
      >&lt;blockquote&gt;
&lt;p&gt;The Excel to SQL converter you didn’t know you needed&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So I’ve been using &lt;a href=&quot;https://claude.com/product/claude-code&quot; rel=&quot;noopener&quot;&gt;Claude Code&lt;/a&gt; for SQL generation lately and it’s honestly magical.&lt;/p&gt;
&lt;p&gt;My 🤯 moment: I can copy/paste a bunch of Excel rows directly into the terminal - item numbers, descriptions, whatever - and Claude Code just &lt;strong&gt;gets it&lt;/strong&gt;. Even a screenshot works.&lt;/p&gt;
&lt;p&gt;Like, I’ll paste in something messy where there are bold category headers above groups of items, and Claude Code infers the relationships. It’ll generate proper SQL INSERT statements complete with:&lt;/p&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;UUID generation for primary keys&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createdAt&lt;/code&gt; and &lt;code&gt;updatedAt&lt;/code&gt; timestamps&lt;/li&gt;
&lt;li&gt;Parent/child relationships from context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I can even say things like “use parentNumber 150 for all rows” and it’ll write the subquery to grab the right &lt;code&gt;parentId&lt;/code&gt; from the Parent table.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; items &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; itemNumber&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; categoryId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parentId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; createdAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; updatedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
  gen_random_uuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;12345&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&#39;Widget Description&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; categories &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Hardware&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; parents &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; parentNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fact that it can parse unstructured Excel data and turn it into structured SQL with proper foreign key relationships? This is &lt;em&gt;the kind of AI assistance that actually saves real time!!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hachyderm.io/@jeffsheets/115290946618395664&quot; rel=&quot;noopener&quot;&gt;Posted about this on Mastodon too&lt;/a&gt; because, social media, i guess.&lt;/p&gt;
&lt;p&gt;Claude Code is my new fave.&lt;/p&gt;
</content
    >
  </entry>
  <entry>
    <title>TIL: How NPM Lockfiles Actually Work</title>
    <link href="https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/" />
    <updated>2025-09-29T15:00:00Z</updated>
    <id>https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/</id>
    <content
      xml:lang=""
      type="html"
      >&lt;blockquote&gt;
&lt;p&gt;And why your CI/CD should use npm ci&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;TIL that I’ve been misunderstanding how npm lockfiles work!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I always thought &lt;code&gt;npm install&lt;/code&gt; would automatically update to the latest versions within your semver ranges, but that’s not quite right. Though it was plausibly right for old versions of npm but changed a few years ago…&lt;/p&gt;
&lt;h2 id=&quot;how-npm-install-actually-works&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#how-npm-install-actually-works&quot;&gt;How NPM Install &lt;em&gt;Actually&lt;/em&gt; Works&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s the key insight I missed: &lt;code&gt;npm install&lt;/code&gt; will &lt;strong&gt;only&lt;/strong&gt; update packages if your &lt;code&gt;package.json&lt;/code&gt; doesn’t match your &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So if you have:&lt;/p&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt; says: &lt;code&gt;&amp;quot;react&amp;quot;: &amp;quot;^18.0.0&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;package-lock.json&lt;/code&gt; has: &lt;code&gt;&amp;quot;react&amp;quot;: &amp;quot;18.2.0&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then &lt;code&gt;npm install&lt;/code&gt; will use &lt;code&gt;18.2.0&lt;/code&gt; from the lockfile, not go fetch &lt;code&gt;18.3.1&lt;/code&gt; (or whatever the latest 18.x version is).&lt;/p&gt;
&lt;p&gt;The problems only happen when:&lt;/p&gt;
&lt;ol class=&quot;list&quot;&gt;
&lt;li&gt;Someone modifies &lt;code&gt;package.json&lt;/code&gt; but doesn’t commit the updated lockfile&lt;/li&gt;
&lt;li&gt;The lockfile is missing entirely&lt;/li&gt;
&lt;li&gt;There’s a mismatch between the two files&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;🚨-the-hidden-problem-ci/cd-misconfigurations&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#%F0%9F%9A%A8-the-hidden-problem-ci/cd-misconfigurations&quot;&gt;🚨 The Hidden Problem: CI/CD Misconfigurations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a consultant, I’ve seen this pattern &lt;strong&gt;way too many times&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jenkins pipeline or GitHub Actions doing:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Instead of:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; ci
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a &lt;strong&gt;huge&lt;/strong&gt; difference!:&lt;/p&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;&lt;code&gt;npm install&lt;/code&gt; will try to “fix” any mismatches between package.json and lockfile&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm ci&lt;/code&gt; will &lt;strong&gt;fail fast&lt;/strong&gt; if there are any mismatches&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npm ci&lt;/code&gt; is safest because it uses the lockfile exactly as-is, ensuring reproducible builds&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;war-stories&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#war-stories&quot;&gt;War Stories&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ve debugged so many “works on my machine” issues that trace back to:&lt;/p&gt;
&lt;ol class=&quot;list&quot;&gt;
&lt;li&gt;Add a package locally: &lt;code&gt;npm install --save lodash&lt;/code&gt; (or commited package.json by hand editing and not with the cli npm install command)&lt;/li&gt;
&lt;li&gt;commits &lt;code&gt;package.json&lt;/code&gt; but missed committing &lt;code&gt;package-lock.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD&lt;/strong&gt; runs &lt;code&gt;npm install&lt;/code&gt; and gets a different version of lodash than we did&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production&lt;/strong&gt; breaks because of subtle differences 😬&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;ol class=&quot;list&quot;&gt;
&lt;li&gt;&lt;strong&gt;CI/CD&lt;/strong&gt; uses &lt;code&gt;npm install&lt;/code&gt; instead of &lt;code&gt;npm ci&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New vulnerability&lt;/strong&gt; gets published in a dependency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD&lt;/strong&gt; automatically pulls the latest “safe” version with the vulnerability because lockfile was out of sync&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security incident&lt;/strong&gt; happens&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;my-workflow-today&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#my-workflow-today&quot;&gt;My workflow today&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;initial-project-clone&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#initial-project-clone&quot;&gt;Initial project clone:&lt;/a&gt;&lt;/h3&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;Use &lt;code&gt;npm ci&lt;/code&gt; to verify lockfile is correct and modules are expected&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;for-local-dev&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#for-local-dev&quot;&gt;For Local Dev:&lt;/a&gt;&lt;/h3&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;Use &lt;code&gt;npm ci&lt;/code&gt; except if I know I want to modify dependencies, out of habit&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;npm install --save&lt;/code&gt; if I want to add/update a specific package&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always commit both&lt;/strong&gt; &lt;code&gt;package.json&lt;/code&gt; AND &lt;code&gt;package-lock.json&lt;/code&gt; together&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;for-ci/cd-pipelines&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#for-ci/cd-pipelines&quot;&gt;For CI/CD Pipelines:&lt;/a&gt;&lt;/h3&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;&lt;strong&gt;Always use &lt;code&gt;npm ci&lt;/code&gt;&lt;/strong&gt; instead of &lt;code&gt;npm install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;This guarantees your build uses exactly what’s in the lockfile&lt;/li&gt;
&lt;li&gt;It fails fast if someone forgot to commit the lockfile&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;🤝-building-on-great-ideas&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#%F0%9F%A4%9D-building-on-great-ideas&quot;&gt;🤝 Building on Great Ideas&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The recent &lt;a href=&quot;https://socket.dev/blog/ongoing-supply-chain-attack-targets-crowdstrike-npm-packages&quot; rel=&quot;noopener&quot;&gt;CrowdStrike-themed npm supply chain attack&lt;/a&gt; is a perfect example of why lockfiles matter in the npm world today. But there are other ideas on how to improve it all:&lt;/p&gt;
&lt;p&gt;This whole learning journey was sparked by some timely blogs. &lt;a href=&quot;https://blog.jim-nielsen.com/2025/run-software-on-software-youve-never-run/&quot; rel=&quot;noopener&quot;&gt;Jim Nielsen’s post&lt;/a&gt; about running software combinations that have never been tested together really resonated. And &lt;a href=&quot;https://tonsky.me/blog/lockfiles/&quot; rel=&quot;noopener&quot;&gt;Niki@tonsky’s deep dive&lt;/a&gt; into how Maven/Gradle have worked for years &lt;strong&gt;without&lt;/strong&gt; lockfiles in Java land.&lt;/p&gt;
&lt;p&gt;The Maven approach of deterministic dependency resolution is arguably cleaner than the NPM lockfile system. But until the npm ecosystem evolves, we need to use the tools we have &lt;strong&gt;correctly&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;🚀-tl;dr-for-your-ci/cd&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/til-how-npm-lockfiles-actually-work/#%F0%9F%9A%80-tl;dr-for-your-ci/cd&quot;&gt;🚀 TL;DR for your CI/CD&lt;/a&gt;&lt;/h2&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;Use &lt;code&gt;npm ci&lt;/code&gt; instead of &lt;code&gt;npm install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Always commit both &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;lockfiles are a necessary evil (even if they’re confusing)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m sure I’m wrong in here somewhere, but at least now I documented how &lt;em&gt;i think&lt;/em&gt; it all works so I can re-read this later when I learn I’m wrong again 😅&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Side note: &lt;a href=&quot;https://hachyderm.io/@jeffsheets/115225375335762223&quot; rel=&quot;noopener&quot;&gt;My original confused idea of npm ci - on Mastodon&lt;/a&gt;&lt;/p&gt;
</content
    >
  </entry>
  <entry>
    <title>Where is the GenAI Deployment Shovelware?</title>
    <link href="https://sheetsj.com/blog/where-is-the-genai-deployment-shovelware/" />
    <updated>2025-09-10T14:00:00Z</updated>
    <id>https://sheetsj.com/blog/where-is-the-genai-deployment-shovelware/</id>
    <content
      xml:lang=""
      type="html"
      >&lt;p&gt;Mike Judge recently wrote a &lt;a href=&quot;https://mikelovesrobots.substack.com/p/wheres-the-shovelware-why-ai-coding&quot; rel=&quot;noopener&quot;&gt;viral post asking “Where’s the shovelware?”&lt;/a&gt; - basically arguing that if AI coding tools were really making developers extraordinarily productive, we’d be drowning in new apps by now. He put together compelling data showing flat growth across every software category despite widespread AI tool adoption.&lt;/p&gt;
&lt;p&gt;Mike’s frustration is palpable and his data is solid. He captures this perfectly:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I wish I could make every dumb coding idea I ever had a reality. I wish I could make a fretboard learning app on Monday, a Korean trainer on Wednesday, and a video game on Saturday. I’d release them all. I’d drown the world in a flood of shovelware like the world had never seen. Well, I would — if it worked.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But here’s the thing - I think Mike’s partly right that the AI tools aren’t delivering the promised productivity gains for experienced developers. However, there’s another angle he doesn’t explore: &lt;strong&gt;the people who ARE getting value from AI coding have no easy way to deploy their creations.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-missing-piece-simple-backend-db-hosting&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/where-is-the-genai-deployment-shovelware/#the-missing-piece-simple-backend-db-hosting&quot;&gt;The Missing Piece: Simple Backend + DB Hosting&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The shovelware won’t appear until one of the vibe-coding services offers a free hosted tier for backend + database. Imagine Claude Code + the original Heroku experience! Right now GenAI is limited to prototypes and local apps.&lt;/p&gt;
&lt;p&gt;I really miss how Heroku used to work with their free Hobby plans. It was &lt;em&gt;so&lt;/em&gt; easy to spin up a Grails app for free with a tiny DB 10+ years ago. Just &lt;code&gt;git push heroku main&lt;/code&gt; and boom - your app was live on the internet.&lt;/p&gt;
&lt;h2 id=&quot;the-current-landscape-is-messy&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/where-is-the-genai-deployment-shovelware/#the-current-landscape-is-messy&quot;&gt;The Current Landscape is Messy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s what I see when helping non-technical folks deploy their AI-generated ideas:&lt;/p&gt;
&lt;ul class=&quot;list&quot;&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://render.com/&quot; rel=&quot;noopener&quot;&gt;Render&lt;/a&gt;&lt;/strong&gt; looks okay and is probably where I’d start today&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://supabase.com/&quot; rel=&quot;noopener&quot;&gt;Supabase&lt;/a&gt;&lt;/strong&gt; is decent for free DB and auth, but the row-level-security required to connect directly from a web app feels flakey and bug-prone&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS&lt;/strong&gt; is a complete headache for non-techies (and honestly, most techies too)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vercel/Netlify&lt;/strong&gt; are great for static sites but don’t solve the backend + database story in a simple vibe-coding manageable way IMO&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The promise of serverless never really delivered for the “relational DB + backend + frontend PoC app created by business/designers with no devops experience” use case.&lt;/p&gt;
&lt;h2 id=&quot;what-we-actually-need&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/where-is-the-genai-deployment-shovelware/#what-we-actually-need&quot;&gt;What We Actually Need&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s the scenario I’ve been seeing: You’re a business analyst who just convinced Claude to build you a resource management app. It works perfectly locally. Now what?&lt;/p&gt;
&lt;p&gt;You should be able to:&lt;/p&gt;
&lt;ol class=&quot;list&quot;&gt;
&lt;li&gt;Push to a git repo&lt;/li&gt;
&lt;li&gt;Connect that repo to a hosting service&lt;/li&gt;
&lt;li&gt;Get a live URL with a real database&lt;/li&gt;
&lt;li&gt;All for free (at least for small/prototype apps)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No Docker. No environment variables. No “contact your system administrator.” Just… it works.&lt;/p&gt;
&lt;h2 id=&quot;the-genai-deployment-gap&quot;&gt;&lt;a class=&quot;heading-anchor&quot; href=&quot;https://sheetsj.com/blog/where-is-the-genai-deployment-shovelware/#the-genai-deployment-gap&quot;&gt;The GenAI Deployment Gap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is one reason we’re not drowning in AI-generated crud apps yet. The technical folks are building amazing local demos and prototypes. The business folks and designers have brilliant ideas that Claude/Cursor can actually implement. But there’s this giant chasm in the middle where deployment lives.&lt;/p&gt;
&lt;p&gt;Once someone bridges that gap with truly simple, free/cheap hosting that includes a real database? &lt;em&gt;Then&lt;/em&gt; we’ll see the shovelware flood. And honestly? I’m kind of looking forward to it. Some of those “quickly built” ideas might just change everything. And I won’t be surprised if Cursor, Claude, CoPilot, have something in the works already…&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need help shipping your AI-generated app?&lt;/strong&gt; Got a brilliant vibe-coded creation that’s stuck living on your localhost? Drowning in deployment complexities when you try to share it with the world? That’s exactly the kind of technical gap we help bridge at &lt;a href=&quot;https://devobsessed.com/&quot; rel=&quot;noopener&quot;&gt;DevObsessed&lt;/a&gt;. Whether you’re trying to get your AI-generated prototype live or scale it into something enterprise-ready, we’d love to help you ship it.&lt;/p&gt;
&lt;/blockquote&gt;
</content
    >
  </entry>
</feed>
