<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed"/>
    <language>en</language>
    <item>
      <title>Pascal Programming</title>
      <dc:creator>Bek Brace</dc:creator>
      <pubDate>Sat, 01 Aug 2026 08:04:47 +0000</pubDate>
      <link>https://dev.to/bekbrace/pascal-programming-146</link>
      <guid>https://dev.to/bekbrace/pascal-programming-146</guid>
      <description>&lt;p&gt;One small confession before we begin.&lt;/p&gt;

&lt;p&gt;The first programming language I ever used was &lt;strong&gt;not Pascal&lt;/strong&gt;. It was the built-in &lt;strong&gt;BASIC&lt;/strong&gt; on the Atari 800XL home computer. At the time, in Egypt, that was what was available to many of us—mostly home computers imported from Saudi Arabia and Kuwait, including the famous &lt;strong&gt;SAKHR&lt;/strong&gt; Arabic computers.&lt;/p&gt;

&lt;p&gt;Surprisingly, I wasn't introduced to Pascal until I was &lt;strong&gt;39 years old&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Perhaps it was my own ignorance, or perhaps my attention had been captured by newer systems programming languages like &lt;strong&gt;Rust&lt;/strong&gt;, &lt;strong&gt;Zig&lt;/strong&gt;, and &lt;strong&gt;Odin&lt;/strong&gt;. Whatever the reason, I now feel I discovered Pascal far later than I should have.&lt;/p&gt;

&lt;p&gt;The more I write in Pascal, the more I appreciate its elegance. Its syntax is remarkably clean, easy to read, and easy to learn. In many ways, it almost reads like plain English—you feel as though you're simply having a conversation with the computer. That clarity is one of the reasons Pascal has remained a beloved teaching language for decades.&lt;/p&gt;

&lt;p&gt;After spending some time reading about the language and its history, I found that the modern Pascal ecosystem can be summarized as follows:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Free Pascal Compiler (FPC)
&lt;/h3&gt;

&lt;p&gt;This is the compiler I currently use. It is free, open source, and actively maintained. FPC supports Windows, Linux, macOS, BSD, ARM, and many other platforms, making it one of the most versatile Pascal compilers available today.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lazarus
&lt;/h3&gt;

&lt;p&gt;Lazarus is the most popular free IDE for Free Pascal.&lt;/p&gt;

&lt;p&gt;Think of it as the Pascal equivalent of Visual Studio for C# or Code::Blocks for C (although I'm not sure how many people still use Code::Blocks in 2026!). It provides a modern editor, debugger, project manager, visual form designer, and everything you need to build Pascal applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Delphi (Object Pascal)
&lt;/h3&gt;

&lt;p&gt;Delphi is the commercial branch of Pascal and the direct successor to the legendary Turbo Pascal. It includes both an IDE and a compiler and is still widely used for developing professional desktop and business applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Turbo Pascal
&lt;/h3&gt;

&lt;p&gt;The classic Borland compiler that introduced an entire generation to programming.&lt;/p&gt;

&lt;p&gt;If you programmed during the DOS era, chances are you've seen its iconic blue IDE. It was famous for its incredibly fast compiler and became one of the most influential development environments of the 1980s and 1990s.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Pascal
                       │
        ┌──────────────┴──────────────┐
        │                             │
 Classic Pascal                 Object Pascal
        │                             │
        │                        Delphi
        │                           │
        │                           ├── VCL
        │                           └── FireMonkey
        │
        └─────────────┐
                      │
             Free Pascal (FPC)
                      │
          ┌───────────┴───────────┐
          │                       │
      Lazarus IDE          Castle Game Engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've ever been curious about Pascal, I've put together a short introductory video covering its history, the different Pascal ecosystems, and how to get started with Free Pascal and Lazarus.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/BybCkWf0tAI"&gt;
  &lt;/iframe&gt;
&lt;br&gt;
Thank you for taking the time to read this and watch the video.&lt;/p&gt;

&lt;p&gt;I'd also love to hear about your own experience. Did you learn Pascal at school or university? Was it your first programming language, or did you discover it later in your career? Share your memories in the comments!&lt;/p&gt;

</description>
      <category>pascal</category>
      <category>vintage</category>
      <category>programming</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Offline Table Builder: Build Tables Without an Internet Connection</title>
      <dc:creator>Olayiwola Akinnagbe</dc:creator>
      <pubDate>Sat, 01 Aug 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/olayiwola_akinnagbe/offline-table-builder-build-tables-without-an-internet-connection-4igg</link>
      <guid>https://dev.to/olayiwola_akinnagbe/offline-table-builder-build-tables-without-an-internet-connection-4igg</guid>
      <description>&lt;p&gt;Most web tools stop working the moment you lose your internet&lt;br&gt;
connection. For a table builder used on a train, in a conference&lt;br&gt;
room with spotty wifi, or on a flight, this is a real problem.&lt;/p&gt;

&lt;p&gt;Progressive Web Apps solve this through service workers, a background&lt;br&gt;
script that caches the application assets and intercepts network&lt;br&gt;
requests. When you open a PWA without internet access, the service&lt;br&gt;
worker serves the cached version of the app. The tool works exactly&lt;br&gt;
as it does online.&lt;/p&gt;

&lt;p&gt;Tablesmit is a PWA. The first time you visit tablesmit.com, the&lt;br&gt;
service worker caches the application. After that, you can open it&lt;br&gt;
without internet access and every feature works: editing, formatting,&lt;br&gt;
all export formats.&lt;/p&gt;

&lt;p&gt;The exports (PDF, Excel, LaTeX, CSV, PNG, JPEG) all run client-side&lt;br&gt;
using jsPDF, ExcelJS, and html2canvas. None of them require a server&lt;br&gt;
call. The export file is generated in your browser and downloaded to&lt;br&gt;
your device.&lt;/p&gt;

&lt;p&gt;To install Tablesmit as a PWA: on Chrome or Edge, look for the install&lt;br&gt;
icon in the address bar. On Safari on iOS, use Share → Add to Home Screen.&lt;br&gt;
The installed app opens like a native application and works offline&lt;br&gt;
without opening a browser first.&lt;/p&gt;

&lt;p&gt;The offline capability also means your table data never leaves your&lt;br&gt;
device. There is no server, no database, no sync. The table you build&lt;br&gt;
exists only in your browser session until you export it.&lt;/p&gt;

&lt;p&gt;Free, no account, MIT licensed. tablesmit.com&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post originally appeared on the Tablesmit Blog at &lt;a href="https://tablesmit.com/blog/offline-table-builder/" rel="noopener noreferrer"&gt;tablesmit.com/blog/offline-table-builder&lt;/a&gt;. Tablesmit is a free, open source table builder. Export to PDF, Excel, LaTeX, CSV, PNG. No account required. Try it at &lt;a href="https://tablesmit.com" rel="noopener noreferrer"&gt;tablesmit.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>tools</category>
      <category>offline</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fixing a Search Race Condition in npmx</title>
      <dc:creator>Anil Loutombam</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:55:07 +0000</pubDate>
      <link>https://dev.to/anilloutombam/fixing-a-search-race-condition-in-npmx-4la3</link>
      <guid>https://dev.to/anilloutombam/fixing-a-search-race-condition-in-npmx-4la3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev" rel="noopener noreferrer"&gt;npmx&lt;/a&gt; is a modern browser for the npm registry. It provides package search, version timelines, dependency information, comparisons, and other tools for exploring npm packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;I worked on a search bug reported in &lt;a href="https://github.com/npmx-dev/npmx.dev/issues/2617" rel="noopener noreferrer"&gt;issue #2617&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When the saved page size was 50 or higher and instant search was disabled, the first search could show no package results. The organization suggestion still appeared, and reloading the same search page made all the package results appear.&lt;/p&gt;

&lt;p&gt;That made it clear that the packages existed and the request was working, but something was going wrong while the search page was being initialized.&lt;/p&gt;

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

&lt;p&gt;Pull request: &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3109" rel="noopener noreferrer"&gt;npmx-dev/npmx.dev#3109&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix waits for an initial search that is already in progress:&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;asyncData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dedupe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;defer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;The page-size preference is stored locally. During the first navigation to the search page, the search could start with the default size of 25. The saved preference would then load and change the requested size to 50 while that search was still pending.&lt;/p&gt;

&lt;p&gt;This triggered &lt;code&gt;fetchMore&lt;/code&gt; before the initial response was ready. It could read the temporary empty response and store it in the search cache. The completed response arrived afterward, but the empty cached value took priority, so the page displayed no packages.&lt;/p&gt;

&lt;p&gt;I updated &lt;code&gt;fetchMore&lt;/code&gt; to wait for the pending search before reading its result. I used &lt;code&gt;dedupe: 'defer'&lt;/code&gt; so it joins the request already in progress instead of cancelling it and starting another one.&lt;/p&gt;

&lt;p&gt;I also added a regression test that changes the requested page size while the initial Algolia search is pending. The test verifies that the completed package results are preserved.&lt;/p&gt;

&lt;p&gt;After the fix, the package results appear on the first search without requiring a reload.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I Was About to Spend Money on Ads. So I Made an AI Agent Test My Product for 29 Hours.</title>
      <dc:creator>Kevin Kan</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:54:42 +0000</pubDate>
      <link>https://dev.to/kansm/i-was-about-to-spend-money-on-ads-so-i-made-an-ai-agent-test-my-product-for-29-hours-mhc</link>
      <guid>https://dev.to/kansm/i-was-about-to-spend-money-on-ads-so-i-made-an-ai-agent-test-my-product-for-29-hours-mhc</guid>
      <description>&lt;p&gt;&lt;strong&gt;872 million tokens processed — 97.63% of them cached inputs. 5,222 tool calls. 88 issues closed. Zero critical bugs, and several high-severity ones, in a product I had been QA-ing the whole time I built it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note before we start.&lt;/strong&gt; There's nothing exotic in this setup — no Hermes harness, no custom framework. Just Codex and a long prompt. I'm sure a properly configured Hermes run would measure all of this far more rigorously. I just don't think many people have gotten there yet, and I wanted to show what's already reachable without it.&lt;/p&gt;




&lt;p&gt;This didn't start as an experiment. It started as nerves.&lt;/p&gt;

&lt;p&gt;I was getting ready to run paid ads. Once you start buying traffic, every broken screen costs money — you're paying to deliver strangers to your worst bug. And I had a specific fear: my product runs in multiple languages, and I am not equally fluent in all of them. If something quietly breaks in one locale, I might not notice for weeks. The ads wouldn't stop running. They'd just keep working perfectly, delivering people to something broken.&lt;/p&gt;

&lt;p&gt;So instead of clicking around for an afternoon and calling it good, I handed the whole thing to Codex and told it not to stop until it ran out of things to break.&lt;/p&gt;

&lt;p&gt;It ran for just under 30 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The instructions
&lt;/h2&gt;

&lt;p&gt;The prompt mattered more than I expected, so here it is, cleaned up. Most of the specificity in it exists because vague instructions produce vague testing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Objective&lt;/strong&gt; I'm about to start paid advertising. Before I pay to send strangers to this product, find what they'll hit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ground rules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Depth&lt;/strong&gt; — the happy path is the starting line, not the finish: repeat and double-click and fire concurrent requests; interrupt mid-flow with a refresh, a navigation, a sign-out, a return; switch language mid-session and check that state survives; kill a step deliberately and verify both recovery and usage accounting; mobile, tablet, desktop; keyboard-only, focus, labels, contrast; and check privacy boundaries — can any account see anything it shouldn't?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For every issue&lt;/strong&gt; Record: what a user would see, exact reproduction steps, root cause in the code, severity (Critical / High / Medium / Low), the fix, the regression test that now covers it, verification after deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Process:&lt;/strong&gt; Read docs/ first. Fix as you go in small, traceable commits. Deploy to main and re-verify in the live environment. A fix isn't done until it has been checked in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt; A separate report per language, plus one consolidated severity-ranked summary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rule 4 and rule 5 are there because of what happened during the run. I'll get to that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;Deliberately boring:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observe, Reproduce, Investigate, Fix, Test, Verify, Repeat.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The rule that made the difference was refusing to trust a single pass. A screen working once proves almost nothing. So every flow got revisited after refreshes, sign-outs, restarts, language switches, repeated requests, deliberate failures, and finally, after deployment.&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;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total elapsed time&lt;/td&gt;
&lt;td&gt;29h 45m 49s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recorded agent work time&lt;/td&gt;
&lt;td&gt;24h 57m 59s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Major work turns&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total tool calls&lt;/td&gt;
&lt;td&gt;5,222&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser-control calls&lt;/td&gt;
&lt;td&gt;2,408&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shell / Git / test / deploy calls&lt;/td&gt;
&lt;td&gt;2,570&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring and wait calls&lt;/td&gt;
&lt;td&gt;134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Images inspected&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF analyses&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Issues recorded and addressed&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total commits&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targeted fix commits&lt;/td&gt;
&lt;td&gt;111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated web checks passed&lt;/td&gt;
&lt;td&gt;829&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated hook checks passed&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Type checking, linting, builds, and the final test suites all passed before I called it finished. And "finished" didn't mean the last commit, it meant deployed, re-checked in the live environment, and synced back to main.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yes, it processed 872 million tokens
&lt;/h2&gt;

&lt;p&gt;Here's the headline number, and here's the asterisk, right next to it where it belongs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token type&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total processed tokens&lt;/td&gt;
&lt;td&gt;872,027,184&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input tokens&lt;/td&gt;
&lt;td&gt;870,333,094&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cached input tokens&lt;/td&gt;
&lt;td&gt;849,674,240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-cached input tokens&lt;/td&gt;
&lt;td&gt;20,658,854&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output tokens&lt;/td&gt;
&lt;td&gt;1,694,090&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning tokens&lt;/td&gt;
&lt;td&gt;512,615&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-cached input + output&lt;/td&gt;
&lt;td&gt;22,352,944&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;872 million&lt;/strong&gt; is a genuinely fun number to say out loud. It is also 97.63% cached input.&lt;/p&gt;

&lt;p&gt;That's what a long-running session looks like from the inside: the same codebase, the same reports, the same accumulated conversation, re-processed on every turn. The cumulative counter faithfully counts all of it. It does not mean 872 million &lt;em&gt;new&lt;/em&gt; tokens were introduced or billed as fresh usage.&lt;/p&gt;

&lt;p&gt;If you want the number that describes actual new context and generated work, it's roughly &lt;strong&gt;22.35 million&lt;/strong&gt;, non-cached input plus output.&lt;/p&gt;

&lt;p&gt;I'm publishing both, because a large AI usage figure only means something when the caching structure sits beside it. Total volume without the ratio isn't measurement, it's marketing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I had to step in
&lt;/h2&gt;

&lt;p&gt;Two days is long enough that I checked in on it periodically, not reviewing every commit, just watching screens go by and asking myself whether what I was seeing looked right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first catch was mine.&lt;/strong&gt; In every language but one, a particular record displayed correctly. In that one language, it didn't. The agent had looked at that screen, registered it as acceptable, and moved on. I flagged it manually. Once pointed at directly, it was diagnosed and fixed quickly. The fix was never the hard part. Noticing was.&lt;/p&gt;

&lt;p&gt;That's the exact failure mode you should expect from an agent working alone: it is very good at "does this throw an error," and much weaker at "does this look wrong compared to the eleven other times I saw something similar." Rule 4 in the prompt above exists because of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second catch came after the run was already declared complete.&lt;/strong&gt; Going back through the results myself, I found a PostHog event that wasn't firing correctly, the one that records a completed payment.&lt;/p&gt;

&lt;p&gt;Think about what that means in context. I was doing all of this to prepare for an ad campaign. The number I would have used to judge whether the ads were working was broken. No screen looked wrong. No test failed. Nothing threw. It would simply have been quietly incorrect forever, on the single metric that mattered most for the thing I was about to spend money on.&lt;/p&gt;

&lt;p&gt;That's rule 5. Verify past the screen. A green checkmark that didn't emit its event is not a pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;88 tracked issues, 111 targeted fix commits. The mismatch is the interesting part: one observed problem usually needed several independent changes. A single inconsistency could touch persisted data, restored UI state, translated output, failure recovery, &lt;em&gt;and&lt;/em&gt; test coverage. Rather than folding that into a few fat commits, everything was split into small, traceable units.&lt;/p&gt;

&lt;p&gt;The fixes clustered into a few themes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More reliable state restoration when returning to a workflow&lt;/li&gt;
&lt;li&gt;More consistent handling of repeated or concurrent actions&lt;/li&gt;
&lt;li&gt;Better recovery when an operation dies partway through&lt;/li&gt;
&lt;li&gt;Stronger file and document boundary handling&lt;/li&gt;
&lt;li&gt;Fewer layout breaks across languages and screen sizes&lt;/li&gt;
&lt;li&gt;Clearer protection against unintended internal-data exposure&lt;/li&gt;
&lt;li&gt;New automated tests on paths that previously had none&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The volume of changed code isn't the point. The point is that every fix traces back to an observed behavior, a reproducible case, and a verification step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually bothered me
&lt;/h2&gt;

&lt;p&gt;No critical bugs. Good.&lt;/p&gt;

&lt;p&gt;But there were high-severity ones. Plural. In a product where I had been running QA continuously &lt;em&gt;while&lt;/em&gt; building it.&lt;/p&gt;

&lt;p&gt;That's the finding I keep coming back to, and it's more useful than any of the numbers above. I hadn't been careless. I'd been checking as I went. And a hard 30-hour pass still surfaced multiple high-severity problems that ordinary development QA had walked straight past.&lt;/p&gt;

&lt;p&gt;Then the stress campaign itself finished, and I found two more things by looking with my own eyes.&lt;/p&gt;

&lt;p&gt;There's a popular idea right now that AI just handles this, that you point a good model at a codebase and quality is a solved problem. This run is strong evidence for how far agents have come, and it's equally strong evidence against that idea. Development-time QA missed things. A 29-hour adversarial campaign caught most of them and still missed the ones a human caught by paying attention. So the honest conclusion isn't "now it's clean." The honest conclusion is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There are almost certainly still latent bugs in there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll run this again. Right now I believe there is nothing high-severity left. &lt;em&gt;Believe&lt;/em&gt;, not &lt;em&gt;know&lt;/em&gt;. That distinction is the entire point of writing this up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone doing this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Continuity is the capability, not intelligence.&lt;/strong&gt; A one-off prompt fixes a visible bug. An agent that stays with the product discovers how that bug behaves once you add retries, localization, generated files, and a user who wanders back three steps later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser access and code access are far stronger together.&lt;/strong&gt; Browser automation tells you something failed. Source access tells you what might fail. Only both together let it see the symptom, trace the implementation, apply the fix, and walk the same journey again to confirm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A fix without a regression test isn't finished.&lt;/strong&gt; The screen looking better is not evidence. Where practical, every corrected behavior got a test so a future change can't quietly undo it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forbid "probably fine" explicitly.&lt;/strong&gt; Agents skip small anomalies for the same reason tired humans do, nothing was on fire. Write the rule into the prompt: an inconsistency across languages is a finding, not a mood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify beyond the UI, especially analytics.&lt;/strong&gt; Events, emails, generated files, and stored state are where silent failures live. These are exactly the bugs that survive a successful test run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay in the loop.&lt;/strong&gt; The agent extended my reach enormously. It did not take over ownership. Both times something important slipped through, the thing that caught it was a person looking at the screen and thinking &lt;em&gt;hmm, that's odd.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The question worth changing
&lt;/h2&gt;

&lt;p&gt;The most useful outcome here wasn't any single fix. It was watching an agent hold an entire product-stabilization cycle, navigating, inspecting evidence, editing code, running tests, managing changes, waiting on external processes, and coming back to verify, for 30 straight hours.&lt;/p&gt;

&lt;p&gt;Which means we've been asking coding agents a slightly small question. Not only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can you build this feature?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But also:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can you keep testing this product, investigate what breaks, fix the root causes, prove the fixes hold, and keep going until the validation criteria are met?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a completely different job description. And right now it may be the most practical one we have, as long as somebody is still watching.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://blog.kanapp.net/posts/i-was-about-to-spend-money-on-ads-so-i-made-an-ai-agent-test-my-product-for-29-hours" rel="noopener noreferrer"&gt;Kanapp Notes&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>buildinpublic</category>
      <category>devops</category>
    </item>
    <item>
      <title>Complex Requirements Are Not the Biggest Problem Anymore: Why Workflow Quality Matters More in the AI Era</title>
      <dc:creator>Awaliyatul Hikmah</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:50:01 +0000</pubDate>
      <link>https://dev.to/ahikmah/complex-requirements-are-not-the-biggest-problem-anymore-why-workflow-quality-matters-more-in-the-33oi</link>
      <guid>https://dev.to/ahikmah/complex-requirements-are-not-the-biggest-problem-anymore-why-workflow-quality-matters-more-in-the-33oi</guid>
      <description>&lt;p&gt;&lt;em&gt;How we used AI to make CI stricter, more observable, and easier to improve across repositories.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Having worked across both implementation and delivery, I’ve learned that complex requirements are not necessarily the biggest problem in the current AI era.&lt;/p&gt;

&lt;p&gt;AI can help us break down requirements, explore unfamiliar repositories, and generate implementation ideas. That makes the implementation step much easier.&lt;/p&gt;

&lt;p&gt;But implementation is only one part of delivery. The feature still has to move through a real engineering system.&lt;/p&gt;

&lt;p&gt;It has to fit the repositories, CI rules, review process, and deployment path that deliver it.&lt;/p&gt;

&lt;p&gt;As implementation gets faster, the surrounding engineering system becomes more important. The real challenge is making sure every change is tested, reviewed against the requirements, and safe to merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond the Fear of Replacement
&lt;/h2&gt;

&lt;p&gt;There is a lot of fear around AI replacing developers.&lt;/p&gt;

&lt;p&gt;Some of that concern is reasonable. Entry-level opportunities may become more limited when routine implementation tasks can be generated, tested, and reviewed with AI assistance.&lt;/p&gt;

&lt;p&gt;That is an important challenge, especially for people who are trying to get their first opportunity in software development.&lt;/p&gt;

&lt;p&gt;But I do not see this as the end of the developer role.&lt;/p&gt;

&lt;p&gt;I see it as one of the most exciting periods to work in software.&lt;/p&gt;

&lt;p&gt;The value is shifting.&lt;/p&gt;

&lt;p&gt;Writing routine code is becoming less differentiating. Understanding a problem, asking the right questions, validating behavior, making trade-offs, and taking responsibility for the result are becoming more important.&lt;/p&gt;

&lt;p&gt;AI can produce an implementation, but it does not automatically understand the business context, the hidden constraint, the operational risk, or whether the solution is appropriate for the team.&lt;/p&gt;

&lt;p&gt;That is why workflow quality matters.&lt;/p&gt;

&lt;p&gt;The developers who benefit most are not necessarily the ones who ask AI to generate the most code. They are the ones who can use AI inside a disciplined process: define the requirement, inspect the evidence, test the result, review the risk, and improve the system around it.&lt;/p&gt;

&lt;p&gt;For junior developers, the path may change, but it does not disappear. Fundamentals, debugging, communication, product understanding, and the ability to work effectively with AI will matter even more.&lt;/p&gt;

&lt;p&gt;That shift in perspective changed how we approached our CI. We did not start by asking how to make it run faster. We asked how to make it catch more of the right problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Work Was Strengthening the CI Workflow
&lt;/h2&gt;

&lt;p&gt;Recently, we worked on a GitLab CI workflow with several responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Move linked issues to &lt;code&gt;Status::In Review&lt;/code&gt; when a merge request is created.&lt;/li&gt;
&lt;li&gt;Move issues to &lt;code&gt;Status::Done&lt;/code&gt; after the merge.&lt;/li&gt;
&lt;li&gt;Run an AI review for the entire merge request.&lt;/li&gt;
&lt;li&gt;Block the pipeline when the AI review confirms a critical or high-severity finding.&lt;/li&gt;
&lt;li&gt;Leave a comment when the merge request does not contain a closing reference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part was not simply adding more CI jobs.&lt;/p&gt;

&lt;p&gt;We used AI to inspect the workflow itself and ask better questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does &lt;code&gt;Status::In Review&lt;/code&gt; prove that the merge request is actually connected to a ticket?&lt;/li&gt;
&lt;li&gt;Does &lt;code&gt;Status::Done&lt;/code&gt; run only after a real merge to the default branch?&lt;/li&gt;
&lt;li&gt;Can an AI finding block a merge without concrete evidence?&lt;/li&gt;
&lt;li&gt;What happens when a merge request is missing a closing reference?&lt;/li&gt;
&lt;li&gt;Which checks are generic policy, and which checks belong to one repository?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions changed the CI from a collection of commands into a quality control system.&lt;/p&gt;

&lt;p&gt;The goal was not to make CI more complicated. It was to make each decision more explicit: what is checked automatically, what evidence is required, and what should block the merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AI Is Actually Used in the Pipeline
&lt;/h2&gt;

&lt;p&gt;The AI review is not a separate tool that developers have to run manually. It is a GitLab CI job that runs when a merge request targets the default branch.&lt;/p&gt;

&lt;p&gt;The setup has four simple parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A pinned AI CLI is installed in the CI job.&lt;/li&gt;
&lt;li&gt;A small repository script collects the merge request context.&lt;/li&gt;
&lt;li&gt;The AI returns a structured JSON report.&lt;/li&gt;
&lt;li&gt;A separate job evaluates that report and decides whether the pipeline passes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The job uses a pinned version of the AI CLI instead of installing whatever happens to be latest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ai_review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:22-bookworm-slim&lt;/span&gt;
  &lt;span class="na"&gt;before_script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm install --global @openai/codex@&amp;lt;pinned-version&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python scripts/ai_review.py review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the AI is accessed through the OpenAI Codex CLI. The exact CLI can vary, but the important parts are that the version is explicit and the job runs in an isolated CI environment.&lt;/p&gt;

&lt;p&gt;The repository script does not send the entire repository blindly to the model. It collects only the context needed for the review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the merge request description;&lt;/li&gt;
&lt;li&gt;linked closing issues and their requirements;&lt;/li&gt;
&lt;li&gt;the changed files and diff;&lt;/li&gt;
&lt;li&gt;relevant file content around the changes;&lt;/li&gt;
&lt;li&gt;the current commit SHA and target branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prompt also defines what the AI is allowed to conclude. For example, a requirement is marked as &lt;code&gt;Covered&lt;/code&gt;, &lt;code&gt;Partial&lt;/code&gt;, &lt;code&gt;Missing&lt;/code&gt;, or &lt;code&gt;Not verifiable&lt;/code&gt;. A finding must include its category, severity, affected path, evidence, impact, and recommendation.&lt;/p&gt;

&lt;p&gt;The response is validated against a JSON schema before it is used. The report is then saved as a CI artifact and posted as a merge request comment. This makes the reasoning visible and gives the next job a stable input.&lt;/p&gt;

&lt;p&gt;The credentials are separated by responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an AI access token allows the review job to call the AI tool;&lt;/li&gt;
&lt;li&gt;a GitLab automation token allows the workflow to read merge request data and update comments or labels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are managed as masked group-level CI variables. The review job uses the AI token. The gate job does not need to call the AI, so it consumes the report artifact and removes the AI token from its environment.&lt;/p&gt;

&lt;p&gt;This separation is useful for two reasons. It limits where sensitive credentials are exposed, and it prevents the final gate from making another AI request whose result could differ from the report that was reviewed.&lt;/p&gt;

&lt;p&gt;In practice, the flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Merge request
    -&amp;gt; collect GitLab context
    -&amp;gt; AI review with structured output
    -&amp;gt; report.json artifact and comment
    -&amp;gt; deterministic gate
    -&amp;gt; pass or block the pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI is therefore used for analysis and explanation. GitLab CI remains responsible for validating the output, checking the commit, updating the merge request, and enforcing the final policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Package the Improved Workflow, Not the Repository
&lt;/h2&gt;

&lt;p&gt;The shared component was a useful result, but it was not the main improvement. The main improvement was making the CI policy explicit and enforceable. Once that was clear, we could package the reusable parts.&lt;/p&gt;

&lt;p&gt;After improving the workflow, the next question was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do we really need to set up the same workflow manually in every repository?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer was still no.&lt;/p&gt;

&lt;p&gt;Instead of copying the improved &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; logic into every repository, we created a shared GitLab CI component.&lt;/p&gt;

&lt;p&gt;The consumer repository only needs to include the shared template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-group/ci-components&lt;/span&gt;
    &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v0.1.0&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/templates/issue-ai-review.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared component provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;issue_status_in_review&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;issue_status_done&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ai_review&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ai_review_gate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository still owns its own build, lint, unit test, E2E, and deployment jobs.&lt;/p&gt;

&lt;p&gt;This separation is important.&lt;/p&gt;

&lt;p&gt;A Go repository should not be forced to use a Python repository structure. A frontend repository should not inherit a backend test command.&lt;/p&gt;

&lt;p&gt;But both repositories may still need the same merge request policy.&lt;/p&gt;

&lt;p&gt;The reusable part is the quality policy, not every command in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Review Needs a Deterministic Boundary
&lt;/h2&gt;

&lt;p&gt;AI review is useful, but an AI response should not directly decide whether a merge request can be merged.&lt;/p&gt;

&lt;p&gt;The AI reviewer produces a structured report. It evaluates ticket coverage, correctness, regression risk, security, performance, maintainability, and other categories.&lt;/p&gt;

&lt;p&gt;Critical and High findings are then independently verified.&lt;/p&gt;

&lt;p&gt;The final gate checks deterministic conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the report for the current commit?&lt;/li&gt;
&lt;li&gt;Was the finding independently confirmed?&lt;/li&gt;
&lt;li&gt;Is the severity still blocking?&lt;/li&gt;
&lt;li&gt;Was a human override added after the report was generated?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives us a better boundary.&lt;/p&gt;

&lt;p&gt;AI is useful for reasoning over a large merge request.&lt;/p&gt;

&lt;p&gt;The pipeline is responsible for enforcing the policy.&lt;/p&gt;

&lt;p&gt;That distinction matters because AI output is probabilistic, while a merge gate should be predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Workflow Safe to Reuse
&lt;/h2&gt;

&lt;p&gt;The quality of the workflow does not come from the AI step alone. We also need to make the automation predictable and safe to reuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing closing references become visible
&lt;/h3&gt;

&lt;p&gt;If a merge request does not contain a reference such as &lt;code&gt;Closes #123&lt;/code&gt;, the pipeline automatically leaves a comment explaining what is missing.&lt;/p&gt;

&lt;p&gt;The job also fails, so the problem is visible before the merge. After the description is updated, the pipeline can be retried.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pin the shared component
&lt;/h3&gt;

&lt;p&gt;Each repository uses a specific component version, such as &lt;code&gt;v0.1.0&lt;/code&gt;, instead of silently following the latest changes.&lt;/p&gt;

&lt;p&gt;This means an update to the shared CI does not unexpectedly change every repository. A repository can update the version when the change has been reviewed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a dedicated bot for automation
&lt;/h3&gt;

&lt;p&gt;The workflow needs credentials to update issue labels and comments. Those credentials are stored at the group level and belong to a dedicated CI bot instead of a developer's personal account.&lt;/p&gt;

&lt;p&gt;This keeps ownership with the team and makes the automation easier to maintain when people or responsibilities change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep repository-specific checks local
&lt;/h3&gt;

&lt;p&gt;The shared component handles the common merge request policy. Each repository still defines its own build, lint, unit test, E2E, and deployment jobs.&lt;/p&gt;

&lt;p&gt;Labels can also be configured per repository. A Go project and a TypeScript project do not need to use the same commands just because they share the same review policy.&lt;/p&gt;

&lt;p&gt;The result is a common quality bar without pretending that every repository has the same structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes in Engineering Leadership?
&lt;/h2&gt;

&lt;p&gt;AI changes the bottleneck.&lt;/p&gt;

&lt;p&gt;Before, a large part of the work was translating requirements into code and manually checking whether the CI workflow captured the intended policy.&lt;/p&gt;

&lt;p&gt;Now, AI can reduce much of that implementation friction.&lt;/p&gt;

&lt;p&gt;The engineering leadership role becomes more focused on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;defining reusable boundaries;&lt;/li&gt;
&lt;li&gt;deciding which checks must remain deterministic;&lt;/li&gt;
&lt;li&gt;turning repeated policy into automation;&lt;/li&gt;
&lt;li&gt;making AI output verifiable;&lt;/li&gt;
&lt;li&gt;preserving flexibility between repositories; and&lt;/li&gt;
&lt;li&gt;ensuring that speed does not remove evidence from the process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The work is still complex.&lt;/p&gt;

&lt;p&gt;But the complexity can be organized into smaller, reusable systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaways
&lt;/h2&gt;

&lt;p&gt;Complex requirements still need careful thinking.&lt;/p&gt;

&lt;p&gt;AI does not remove the need for architecture, testing, or review.&lt;/p&gt;

&lt;p&gt;But it changes what is practical.&lt;/p&gt;

&lt;p&gt;A review process that depended on manual inspection can become an automated, evidence-based gate.&lt;/p&gt;

&lt;p&gt;Once that quality policy is clear, the repeated CI logic can become a versioned shared component.&lt;/p&gt;

&lt;p&gt;The biggest improvement is not just writing code faster.&lt;/p&gt;

&lt;p&gt;It is creating a system where complex work becomes easier to coordinate, verify, and reuse.&lt;/p&gt;

&lt;p&gt;That is where AI creates the most leverage for engineering teams.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cicd</category>
      <category>softwareengineering</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Day 171 of Learning MERN Stack</title>
      <dc:creator>Ali Hamza</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:49:22 +0000</pubDate>
      <link>https://dev.to/ali_hamza_589ec7b3eb6688d/day-171-of-learning-mern-stack-3jkc</link>
      <guid>https://dev.to/ali_hamza_589ec7b3eb6688d/day-171-of-learning-mern-stack-3jkc</guid>
      <description>&lt;p&gt;Hello Dev Community! 👋&lt;/p&gt;

&lt;p&gt;It is officially &lt;strong&gt;Day 171&lt;/strong&gt; of my full-stack web development journey, and I am thrilled to announce that &lt;strong&gt;QuickChat (MERN Stack Real-Time Chat App)&lt;/strong&gt; is officially complete! 🎉🚀&lt;/p&gt;

&lt;p&gt;Today, I finalized the last key security layer: &lt;strong&gt;JWT Authentication &amp;amp; Route Protection Middleware&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Technical Breakdown: JWT Route Security
&lt;/h2&gt;

&lt;p&gt;As captured in my implementation snapshots (&lt;strong&gt;&lt;code&gt;auth.js&lt;/code&gt; &amp;amp; &lt;code&gt;utils.js&lt;/code&gt;&lt;/strong&gt;):&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Token Signature Utility (&lt;code&gt;utils.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Configured a centralized token generator helper signing payload &lt;code&gt;userId&lt;/code&gt; parameters using environment secrets:&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
  const generateToken = (userId) =&amp;gt; {
    return jwt.sign({ userId }, process.env.JWT_SECRET);
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>mongodb</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Everything Is Reversed</title>
      <dc:creator>Sinisa Kusic</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:48:09 +0000</pubDate>
      <link>https://dev.to/ku5ic/everything-is-reversed-2j2l</link>
      <guid>https://dev.to/ku5ic/everything-is-reversed-2j2l</guid>
      <description>&lt;p&gt;&lt;em&gt;What happens to a medicated ADHD brain when AI removes the last thing that used to slow it down.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have ADHD, and I am medicated for it. I take it first thing. It lands about thirty minutes later, roughly as I finish the first coffee. Then I sit down to write software with an agent, and the day stops keeping time.&lt;/p&gt;

&lt;p&gt;I look up thinking an hour and a half has gone, two at the outside. Six hours have gone. Later I step out for a cigarette, convinced I have been away too long, that it has been the better part of an hour. It has been fifteen minutes.&lt;/p&gt;

&lt;p&gt;Both directions, most days. The work compresses and the breaks dilate. Nothing in me reports the difference, so I only find out by looking at a clock, and by then whichever it was has already happened.&lt;/p&gt;

&lt;p&gt;The clock has run like that for years, medicated or not. That part is not about AI and I am not going to pretend it is. What AI changed is what fits inside those six hours.&lt;/p&gt;

&lt;p&gt;Before the medication it arrived in bursts. A few good hours, occasionally a few good days, and long stretches in between where I could see the task perfectly clearly and could not begin it. Task paralysis is a mild name for what that is like. The bursts were real and I could not schedule them.&lt;/p&gt;

&lt;p&gt;That part is gone. What replaced it does not switch off, and one task turns out not to be enough to hold it. There are two laptops on my desk. Some days both of them are working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The appetite was already there
&lt;/h2&gt;

&lt;p&gt;My brain likes information. Not needs, likes. Give it a thread and it will pull, and pulling feels like progress the entire time it is happening. The only thing that ever limited that was effort. Finding things was slow, so the appetite ran into a wall several times an hour, and the wall was doing something useful even though it felt like an obstacle.&lt;/p&gt;

&lt;p&gt;The wall is gone. I can point AI at a repository, a ticket history, a chat archive, a document nobody has opened in two years, and have it back in seconds, summarised, in whatever shape I asked for. Gathering costs nothing now.&lt;/p&gt;

&lt;p&gt;This is fuel on a fire that was already lit. The thing my brain most wants to do all day is the thing that just became free and unlimited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheap to gather, expensive to know
&lt;/h2&gt;

&lt;p&gt;Here is the reversal that matters more than the clock.&lt;/p&gt;

&lt;p&gt;Gathering information is cheap. Working out which of it is relevant is not. Working out which of it is still true is not. Those decisions are mine, every one of them, and there are now vastly more of them per day because there is vastly more in front of me.&lt;/p&gt;

&lt;p&gt;The AI will hand me a summary of a decision made three years ago with exactly the same confidence it hands me something from Tuesday. It has no view on which one the team still believes. I have to hold both, judge both, and pick. Then do it again, on the next thing, having had no gap in between.&lt;/p&gt;

&lt;p&gt;That is the cost, and it is the part that does not get cheaper. Everything upstream of it collapsed to nothing, which means the whole day is now made of the expensive part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nobody calls out the AI
&lt;/h2&gt;

&lt;p&gt;The thing that keeps this from being a productivity story is that ownership never moved.&lt;/p&gt;

&lt;p&gt;Whatever ships with my name on it is mine. If the call was wrong, I am the one it comes back to. Nobody in a review has ever asked the model to explain itself. So the judgment cannot be delegated, cannot be batched, and cannot be skipped, and it sits on top of a brain that has already spent six hours in a state it could not measure.&lt;/p&gt;

&lt;p&gt;I do not have a tidy answer to this. I know when it has gone badly, because it is seven in the evening and I would have told you it was three. I know the clock is the only instrument I have that tells the truth. And I know the part that costs me is not the work the tools took over. It is all the work they left behind, which turns out to have been the whole job.&lt;/p&gt;

</description>
      <category>adhd</category>
      <category>ai</category>
      <category>mentalhealth</category>
      <category>engineering</category>
    </item>
    <item>
      <title>The real cost of Instagram data in 2026 (measured, not estimated)</title>
      <dc:creator>Sieve Seo</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:45:16 +0000</pubDate>
      <link>https://dev.to/sieve_seo_c4e0747f2ecc0e2/the-real-cost-of-instagram-data-in-2026-measured-not-estimated-2o3f</link>
      <guid>https://dev.to/sieve_seo_c4e0747f2ecc0e2/the-real-cost-of-instagram-data-in-2026-measured-not-estimated-2o3f</guid>
      <description>&lt;p&gt;I spent the last week building a small "who unfollowed me" tool, which means I needed one boring thing: the follower list of an Instagram account. Here is everything I measured while figuring out how to get it in 2026 — what's dead, what's free, and what the paid options actually cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The free paths are mostly dead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Follower &lt;em&gt;lists&lt;/em&gt; require a logged-in session. Period.&lt;/strong&gt; I tried three separate unauthenticated paths (web API, GraphQL, mobile endpoints) and every one returns 401 for list data. No header combination fixes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follower &lt;em&gt;counts&lt;/em&gt; are still public&lt;/strong&gt; — &lt;code&gt;web_profile_info&lt;/code&gt; with the &lt;code&gt;x-ig-app-id&lt;/code&gt; header returns the count, privacy flag, and following count in one response. But it's aggressively rate-limited per IP: in my tests, roughly &lt;strong&gt;20 requests inside a 30–60 minute sliding window&lt;/strong&gt; trips a 401 block that recovers after ~29 minutes. Spacing requests out doesn't help; it's a sliding window, not a fixed quota.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't bother from cloud servers.&lt;/strong&gt; The same endpoint that worked from my home IP returned &lt;strong&gt;429 with a 0-byte body in 16ms on the very first request&lt;/strong&gt; from a cloud IP (I tested from a Vercel build container). That's not my usage history — that's the entire IP range being pre-banned. Public cloud IPs inherit the abuse history of every tenant before you, so you cannot have a "clean" cloud IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And no, the user's own browser can't do it for you.&lt;/strong&gt; Even if your user is logged into Instagram, your web app's origin can't read Instagram responses — CORS blocks it, and &lt;code&gt;no-cors&lt;/code&gt; mode gives you an opaque 0-byte response. This is a browser guarantee, not something you can engineer around.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one genuinely free path: the official data export
&lt;/h2&gt;

&lt;p&gt;Instagram's "Download your information" export gives every user their own follower/following lists as JSON or HTML — and it works for private accounts too. If your product can tolerate a "request export, come back later" flow, this costs exactly $0.&lt;/p&gt;

&lt;p&gt;Parsing it has landmines, though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;followers_1.json&lt;/code&gt; and &lt;code&gt;following.json&lt;/code&gt; have &lt;strong&gt;different schemas&lt;/strong&gt; — followers keep the handle in &lt;code&gt;string_list_data[0].value&lt;/code&gt;, but following keeps it in &lt;code&gt;title&lt;/code&gt;, and the &lt;code&gt;value&lt;/code&gt; field simply doesn't exist there.&lt;/li&gt;
&lt;li&gt;Non-ASCII display names arrive &lt;strong&gt;mojibake'd&lt;/strong&gt; (UTF-8 bytes decoded as latin-1: &lt;code&gt;이름&lt;/code&gt; becomes &lt;code&gt;ì´ë¦&lt;/code&gt;). Handles are ASCII-safe, but you must repair names before showing them.&lt;/li&gt;
&lt;li&gt;Exports expire in 4 days, and a "full" export can be 500MB+ because it includes DMs — read the ZIP's central directory and slice out just the two files you need instead of unpacking it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What hosted APIs actually cost
&lt;/h2&gt;

&lt;p&gt;For the "type a handle, get results now" flow, I ended up on &lt;a href="https://hikerapi.com" rel="noopener noreferrer"&gt;HikerAPI&lt;/a&gt; after comparing alternatives. Numbers from my real usage, not the pricing page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A full scan of a ~1,060-follower account took &lt;strong&gt;43 requests, about 3 minutes&lt;/strong&gt; (cursor-chained pagination, so you can only parallelize followers vs following, not pages).&lt;/li&gt;
&lt;li&gt;Page sizes differ per endpoint family and it matters for cost: the &lt;code&gt;g2&lt;/code&gt; follower endpoint returns &lt;strong&gt;25 users per billed request&lt;/strong&gt;, while &lt;code&gt;g1&lt;/code&gt; returns &lt;strong&gt;46 per billed request&lt;/strong&gt; — same data, nearly half the cost. The docs don't advertise this; I found it by reading their public OpenAPI spec and testing.&lt;/li&gt;
&lt;li&gt;Billing is verifiable: &lt;code&gt;/sys/balance&lt;/code&gt; shows a request counter, and I confirmed call-by-call that list pages bill exactly 1 unit — and that a deprecated endpoint returning 410 billed &lt;strong&gt;nothing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Accuracy: I cross-checked one page of API results against data collected directly from Instagram's web interface — 89/90 matched, and the one mismatch was a follower gained between snapshots.&lt;/li&gt;
&lt;li&gt;Cost at the $100 prepaid tier: &lt;strong&gt;$0.001/request&lt;/strong&gt;, so ~$0.04 per 1,000 followers scanned with the 46-per-page endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two honest gotchas: card top-ups carry a &lt;strong&gt;15% fee deducted from your balance&lt;/strong&gt; (only crypto avoids it), and until you've deposited $100 total you pay ~20x per request, which makes small-scale testing feel more expensive than the steady state.&lt;/p&gt;

&lt;p&gt;For comparison, the cheapest Instagram scraper actors I tested on Apify worked out to &lt;strong&gt;9–20x more per follower&lt;/strong&gt; than the numbers above — partly because one actor advertised "50 users per $0.009 batch" but returned 25 users while billing the full batch. Always measure billed units, not advertised prices. Enterprise proxies (Bright Data class) were 30x+ for this use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  A curl you can verify all of this with
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# profile gate: 1 request buys you count + privacy flag&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-access-key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.hikerapi.com/v1/user/by/username?username=instagram"&lt;/span&gt;

&lt;span class="c"&gt;# follower page: 1 request, ~46 users, cursor in response&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-access-key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.hikerapi.com/g1/user/followers?user_id=25025320"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(One quirk: their Cloudflare config rejects Python's &lt;code&gt;urllib&lt;/code&gt; user-agent with a 403 — &lt;code&gt;curl&lt;/code&gt; and normal HTTP clients are fine.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Decide early whether your product needs &lt;strong&gt;lists&lt;/strong&gt; or just &lt;strong&gt;counts&lt;/strong&gt; — counts are free-ish from a residential IP, lists are never free without the user's own export.&lt;/li&gt;
&lt;li&gt;If you go hosted, &lt;strong&gt;read the OpenAPI spec and measure billed units yourself&lt;/strong&gt; — endpoint families differ by ~2x in cost for identical data, and advertised prices routinely don't survive contact with real usage.&lt;/li&gt;
&lt;li&gt;Keep the official export flow as a fallback: it's the only path that covers private accounts, and it costs nothing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What else are people using for this in 2026? Especially curious if anyone has a cleaner answer for private-account analytics than the export flow.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Two of my projects were about to jump a grade. Nobody had fixed anything.</title>
      <dc:creator>FromZeroToShip</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:41:00 +0000</pubDate>
      <link>https://dev.to/fromzerotoship/two-of-my-projects-were-about-to-jump-a-grade-nobody-had-fixed-anything-2med</link>
      <guid>https://dev.to/fromzerotoship/two-of-my-projects-were-about-to-jump-a-grade-nobody-had-fixed-anything-2med</guid>
      <description>&lt;p&gt;I have a scanner that grades my own projects. It reads the code, applies a list of rules, subtracts points per finding, and hands each project a letter grade. It exists because I build with AI and needed something that wasn't me deciding whether my work was fine.&lt;/p&gt;

&lt;p&gt;Someone in a thread here pointed at a flaw in it that I couldn't argue with.&lt;/p&gt;

&lt;p&gt;His point: my rules overlap. If two rules fire on the same underlying defect, the project is charged twice for one mistake. And — this is the part that made me sit up — &lt;strong&gt;that error can only land on projects that have the defect.&lt;/strong&gt; A clean project is never double-charged, because there's nothing to charge. So the penalty is regressive: it pushes the worst projects further down and leaves the good ones untouched, which means my grade distribution is stretched at the bottom by a bug rather than by reality.&lt;/p&gt;

&lt;p&gt;That's a real argument. My instinct was to fix it that evening.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did instead
&lt;/h2&gt;

&lt;p&gt;I've been burned enough times this month by fixes that felt obviously right. So before changing the scoring, I wrote something to measure whether the thing he described is actually happening in my data.&lt;/p&gt;

&lt;p&gt;The measurement is simple: for every finding, record which rule fired and where. Then count how often two different rules fire at the same file and line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// count co-occurrence of different rule keys at the same file:line&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&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;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                       &lt;span class="c1"&gt;// "file:line" -&amp;gt; Set(ruleKey)&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="nx"&gt;findings&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;at&lt;/span&gt; &lt;span class="o"&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&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;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;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;seen&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="nx"&gt;at&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&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;pairs&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;Map&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;keys&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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;ks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&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;p&lt;/span&gt; &lt;span class="o"&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;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&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;pairs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pairs&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="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;Then: recompute every project's score with duplicates collapsed, and compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came back
&lt;/h2&gt;

&lt;p&gt;72 projects. Ten rule pairs that ever co-occur. Here are the top four:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule pair (co-firing at the same line)&lt;/th&gt;
&lt;th&gt;Times&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;missing social preview + missing standard footer&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;SELECT *&lt;/code&gt; + no performance diagnostics&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;suspected N+1 query + no performance diagnostics&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;suspected N+1 query + &lt;code&gt;SELECT *&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Look at the top one. "No social preview tag" and "no standard footer" are not one defect seen twice. They're two genuinely separate omissions that happen to live in the same file, because that file is the page template. Collapsing them wouldn't remove a double charge — it would stop charging for a real second problem.&lt;/p&gt;

&lt;p&gt;The same is true further down. &lt;code&gt;SELECT *&lt;/code&gt; and missing diagnostics co-occur because both are symptoms of a page written quickly, not because one implies the other.&lt;/p&gt;

&lt;p&gt;Then I checked his actual prediction — that the correction should lift the &lt;em&gt;worst&lt;/em&gt; projects most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Original grade band&lt;/th&gt;
&lt;th&gt;Projects&lt;/th&gt;
&lt;th&gt;Avg. points gained by deduplicating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;F (0–59)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D (60–69)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;+1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C (70–79)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+1.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B (80–89)&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;+0.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A (90–100)&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;+0.39&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The uplift peaks in the &lt;strong&gt;middle&lt;/strong&gt;, not at the bottom. If the regressive error he described were the dominant effect in my data, that F row should be the largest number in the table. It's the second smallest.&lt;/p&gt;

&lt;p&gt;Two projects would have moved from B to A. Not because they improved. Because I applied a correction for a problem I hadn't confirmed I had.&lt;/p&gt;

&lt;p&gt;So I didn't ship it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I have to admit
&lt;/h2&gt;

&lt;p&gt;Here's what stops this from being a story about being clever.&lt;/p&gt;

&lt;p&gt;Look at the sample sizes. F is &lt;strong&gt;two projects&lt;/strong&gt;. D is &lt;strong&gt;one&lt;/strong&gt;. The bottom of that table, where the entire argument lives, is three data points. "The uplift peaks at C" is a sentence I can write and cannot really defend.&lt;/p&gt;

&lt;p&gt;So the honest conclusion isn't "he was wrong." It's narrower and less satisfying: &lt;strong&gt;I don't have enough bad projects to know whether he's right, and shipping the fix would have changed real grades based on a theory I couldn't test.&lt;/strong&gt; The measurement didn't prove him wrong. It proved I was about to act on faith and call it engineering.&lt;/p&gt;

&lt;p&gt;If my next twenty projects include eight bad ones, that F row becomes meaningful and I'll rerun it. The script is committed, the baseline is stored, and it compares against that baseline on every run — so the day the answer changes, something tells me instead of waiting for me to wonder.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually took from it
&lt;/h2&gt;

&lt;p&gt;Three things, and only one of them is about scoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A good argument is not a measurement.&lt;/strong&gt; His reasoning was sound and might still be correct. Sound reasoning about your data is a hypothesis, and it costs about an hour to find out which one you have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixes have a direction, and you should predict it before you look.&lt;/strong&gt; The reason this was checkable at all is that his claim made a prediction: the correction should help the worst projects most. That gave me something to compare against. Advice that doesn't predict anything can only be accepted or ignored — never tested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report what the measurement can't say.&lt;/strong&gt; My first draft of this post ended at the table, with the uplift peaking at C, sounding conclusive. Adding "n=2" to that row changed my own conclusion while I was writing it.&lt;/p&gt;

&lt;p&gt;So, genuinely: thank you for the flaw. It's on the list, with the measurement attached and a condition that will tell me when it starts to matter. That's a better outcome than shipping it would have been — and it took longer to write this post than to run the test.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of &lt;a href="https://fromzerotoship.com/blog/#stolen" rel="noopener noreferrer"&gt;Stolen from the Feed&lt;/a&gt; — what a non-developer carries home from dev.to and actually builds. Sister series to From Zero to Ship.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>discuss</category>
      <category>productivity</category>
      <category>community</category>
    </item>
    <item>
      <title>What UK Bootstrapped SaaS Founders Actually Pay for Business Banking</title>
      <dc:creator>Ben Clifford</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:39:38 +0000</pubDate>
      <link>https://dev.to/benten_clifford/what-uk-bootstrapped-saas-founders-actually-pay-for-business-banking-mk6</link>
      <guid>https://dev.to/benten_clifford/what-uk-bootstrapped-saas-founders-actually-pay-for-business-banking-mk6</guid>
      <description>&lt;p&gt;"Free business banking" is one of the most misleading phrases in UK fintech marketing. Every major digital bank advertises a free tier — Starling, Tide, Mettle, Monzo, Revolut — but "free" depends entirely on how many transfers you make, whether you invoice, and whether you touch foreign currency. Here's what five common bootstrapped-founder scenarios actually cost, worked out line by line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 1: Solo SaaS founder, 10 transfers a month, no invoicing
&lt;/h2&gt;

&lt;p&gt;You're paying a couple of contractors and a hosting bill. Nothing fancy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Starling:&lt;/strong&gt; £0. Unlimited free UK transfers, no monthly fee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mettle:&lt;/strong&gt; £0. Same story, plus FreeAgent (~£150/year value) thrown in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monzo Lite:&lt;/strong&gt; £0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tide:&lt;/strong&gt; 5 transfers free, then 5 × £0.20 = £1/month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revolut:&lt;/strong&gt; £10/month minimum (Basic plan fee applies regardless of volume).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this volume, Starling, Mettle, and Monzo Lite are functionally tied at zero cost. Revolut is the expensive outlier here because you're paying for multi-currency capability you're not using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 2: Invoice-heavy consultancy, 50 transfers a month, client invoicing
&lt;/h2&gt;

&lt;p&gt;You bill clients directly and chase payments monthly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tide:&lt;/strong&gt; 5 free transfers, then 45 × £0.20 = £9/month, plus 3 free invoices monthly (extra invoices need the paid Smart plan at £12.49/month if you exceed that).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starling:&lt;/strong&gt; £0 for transfers, but invoicing is a £7/month add-on — so roughly £7/month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monzo Pro:&lt;/strong&gt; £9/month flat, invoicing included.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mettle:&lt;/strong&gt; £0, invoicing included via bundled FreeAgent the cheapest option if you don't need multi-user access or international payments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a solo consultancy that only invoices UK clients, Mettle wins on pure cost. The moment you need a second user or an international client, that calculus changes fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 3: Cash-heavy small retailer/market trader, £2,000/month in cash deposits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Starling:&lt;/strong&gt; 0.7% at the Post Office, minimum £3 — on £2,000 that's £14/month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tide:&lt;/strong&gt; 0.5% at Post Office (min £2.50) or 3% at PayPoint Post Office route costs £10/month, PayPoint costs £60.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mettle:&lt;/strong&gt; up to £500/day free at the Post Office, so £2,000 spread across the month can land at £0 if you deposit in smaller batches but confirm current terms directly with Mettle, as cash deposit policy details vary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monzo Lite:&lt;/strong&gt; £500/month free, so anything above that starts incurring charges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cash-handling businesses should model this scenario specifically it's the one place "free" digital banking quietly becomes the most expensive option on the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 4: Exporting SaaS, billing US and EU clients in USD/EUR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Revolut:&lt;/strong&gt; £10/month, but you get 25+ currencies at interbank rates for the first £1,000 monthly, then 0.6% FX on weekdays (1% weekends). For a founder converting say £5,000/month, that's roughly £10 + (0.6% × £4,000) = £34/month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starling:&lt;/strong&gt; EUR add-on only, limited multi-currency workable for EU-only, not for USD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Others:&lt;/strong&gt; no meaningful multi-currency support, meaning you'd be paying your card processor's FX markup on top of a domestic-only account, which is typically a worse rate than Revolut's interbank pricing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're actually invoicing in foreign currency every month, Revolut's £10 fee is usually cheaper than eating a worse FX spread elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenario 5: Growth-stage, want lending headroom
&lt;/h2&gt;

&lt;p&gt;Only one account on this list offers overdrafts and business lending as a fully licensed bank: Starling. Monzo Business also offers overdrafts and loans subject to credit checks. &lt;br&gt;
Tide, Mettle, and Revolut don't offer overdrafts at all. If you're planning to need short-term credit as you scale, that's not a fee comparison — it's a structural feature only some of these accounts have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual lesson
&lt;/h2&gt;

&lt;p&gt;None of these accounts is "cheap" or "expensive" in the abstract cost is a function of your transaction pattern, not the marketing page. Map your real monthly numbers (transfers, invoices, cash, foreign currency) against the scenario closest to yours before deciding, because the gap between the cheapest and most expensive option for your specific pattern can be £30-40 a month real money for a bootstrapped founder.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All fees and limits verified against provider websites as of mid-2026 banking fee structures change, so confirm current pricing before choosing an account.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>saas</category>
      <category>uk</category>
      <category>ai</category>
    </item>
    <item>
      <title>Day 170 of Learning MERN Stack</title>
      <dc:creator>Ali Hamza</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:34:21 +0000</pubDate>
      <link>https://dev.to/ali_hamza_589ec7b3eb6688d/day-170-of-learning-mern-stack-53ck</link>
      <guid>https://dev.to/ali_hamza_589ec7b3eb6688d/day-170-of-learning-mern-stack-53ck</guid>
      <description>&lt;p&gt;Hello Dev Community! 👋&lt;/p&gt;

&lt;p&gt;It is officially &lt;strong&gt;Day 170&lt;/strong&gt; of my full-stack engineering track! Today, I designed and implemented the backend message retrieval and chat history processing endpoints (&lt;strong&gt;&lt;code&gt;getUserforSidebar&lt;/code&gt;&lt;/strong&gt; &amp;amp; &lt;strong&gt;&lt;code&gt;getMessages&lt;/code&gt;&lt;/strong&gt;) for &lt;strong&gt;QuickChat&lt;/strong&gt; using &lt;strong&gt;Node.js, Express, and MongoDB&lt;/strong&gt;! ⚙️💬&lt;/p&gt;

&lt;p&gt;Here is how I structured the database queries for chat retrieval and read receipts.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Technical Breakdown: Message Fetching &amp;amp; Unseen Counters
&lt;/h2&gt;

&lt;p&gt;As captured in my implementation snapshots (&lt;strong&gt;&lt;code&gt;messageController.js&lt;/code&gt;&lt;/strong&gt;):&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Dynamic User Discovery &amp;amp; Parallel Unseen Counting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Filtered out the currently logged-in user using Mongoose &lt;code&gt;$ne&lt;/code&gt; operator while stripping out password projections.&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;Promise.all&lt;/code&gt; alongside &lt;code&gt;map&lt;/code&gt; iterations to asynchronously gather unread message counts per user card:&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
javascript
  const filteredUser = await user.find({ _id: { $ne: userId } }).select("-password");

  const promise = filteredUser.map(async (user) =&amp;gt; {
    const messages = await message.find({ senderId: user._id, receiverId: userId, seen: false });
    if (messages.length &amp;gt; 0) {
      unseenMessages[user._id] = messages.length;
    }
  });
  await Promise.all(promise);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>mongodb</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Stopped My AI From Making Up AWS Answers With One Command</title>
      <dc:creator>Steven Leung</dc:creator>
      <pubDate>Sat, 01 Aug 2026 07:34:07 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-stopped-my-ai-from-making-up-aws-answers-with-one-command-5aba</link>
      <guid>https://dev.to/aws-builders/i-stopped-my-ai-from-making-up-aws-answers-with-one-command-5aba</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;AWS announces changes continuously, any reader here would be familiar with that, and with the &lt;a href="https://aws.amazon.com/blogs/aws/" rel="noopener noreferrer"&gt;AWS News Blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Imagine you're a developer in July 2026 asked to work with &lt;strong&gt;Amazon S3 Files&lt;/strong&gt;, which went generally available during April 2026.&lt;/p&gt;

&lt;p&gt;Your AI assistant has a knowledge cutoff: a fixed date after which the model stops receiving new training data. Past that date, it's completely unaware of new product releases, feature launches, or documentation changes. Time is of the essence and you can't exactly pause until the next model ships.&lt;/p&gt;

&lt;p&gt;AI assistants have made it easy to skip the manual grind of reading docs and researching things yourself, right up until the assistant's knowledge runs out of date. So what do you do when it doesn't know about the thing you need it to know about?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Enter the AWS Knowledge Model Context Protocol (MCP) Server, an open-source tool from &lt;a href="https://github.com/awslabs" rel="noopener noreferrer"&gt;AWS Labs&lt;/a&gt; (AWS's official open source GitHub organisation) that connects AI models directly to authoritative, up-to-date AWS documentation.&lt;/p&gt;

&lt;p&gt;It lets AI clients like Claude and Copilot pull real-time AWS information without waiting on a model update: current documentation, blog posts, What's New announcements, and Well-Architected guidance, all fetched live rather than recalled from training data.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is MCP, for the uninitiated
&lt;/h3&gt;

&lt;p&gt;The Model Context Protocol (MCP) is the standard AI tools use to talk to external tools and knowledge sources. AWS Knowledge is a remote MCP server, hosted by AWS, that puts AWS-specific material in front of the model in a format it can use.&lt;/p&gt;

&lt;p&gt;You'd be forgiven for thinking this sounds like RAG (Retrieval-Augmented Generation). MCP enables RAG, but it isn't RAG itself, and RAG doesn't need MCP either. People were doing RAG long before MCP existed. MCP is the connector standard; RAG is the "look something up, then answer using what you found" pattern. MCP just happens to be a common way of wiring that pattern up.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it gives the model
&lt;/h3&gt;

&lt;p&gt;The server exposes a handful of tools the AI can call directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;search_documentation&lt;/code&gt;: keyword search across all AWS documentation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read_documentation&lt;/code&gt;: fetches a specific documentation page, converted to markdown&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recommend&lt;/code&gt;: suggests related documentation pages for a given URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_regions&lt;/code&gt;: lists all AWS regions and their identifiers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_regional_availability&lt;/code&gt;: checks which regions support a given service, feature, SDK API, or CloudFormation resource&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retrieve_skill&lt;/code&gt;: pulls down a domain-specific "agent skill", a packaged set of workflows and best practices for a more involved AWS task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is worth a quick note: skills aren't new actions bolted onto the model, they're structured guidance the model reads and follows, the same way it would follow a detailed how-to doc, just retrieved on demand instead of baked into the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  The important details
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client-server pattern&lt;/strong&gt;: your AI tool is the client, AWS Knowledge is the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No AWS account required&lt;/strong&gt;: it's public and unauthenticated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free to use&lt;/strong&gt;: no cost, which makes everything easier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limited&lt;/strong&gt;: it's a shared public service, so standard limits apply.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A quick note on privacy
&lt;/h3&gt;

&lt;p&gt;Worth knowing what actually leaves your machine here: just your query text (e.g. "does Lambda support S3 Files yet") plus your IP address, going to a public AWS endpoint, the same exposure as searching the AWS docs site yourself. Nothing from your codebase, credentials, or AWS account goes with it, this server only answers questions, it doesn't read your project or touch your infrastructure. AWS states that telemetry from this server isn't used for model training, and since there's no authentication step, there's nothing tying a query back to your identity beyond standard request logs.&lt;/p&gt;

&lt;p&gt;One rule of thumb regardless: fine for general AWS knowledge lookups, not the place to paste anything client-specific or commercially sensitive as part of a query.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it in action
&lt;/h2&gt;

&lt;p&gt;Once it's wired in, the difference shows up the moment you ask about anything recent. Try something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is Amazon S3 Files generally available yet, and which regions support it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without AWS Knowledge connected, most models will either hedge ("I'm not aware of this service" or "as of my knowledge cutoff, this doesn't exist") or, worse, guess. With it connected, the assistant calls &lt;code&gt;search_documentation&lt;/code&gt;, finds the GA announcement, and answers with the actual launch date and region list, sourced from the live docs rather than a guess.&lt;/p&gt;

&lt;p&gt;That's the whole pitch in one prompt: the model goes and checks, instead of you doing it in a second tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;claude mcp add&lt;/code&gt; command is specific to Claude Code's CLI, but AWS Knowledge itself is just a standard MCP server, so the same URL works in any MCP-compatible client. Only the registration step changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http aws-knowledge https://knowledge-mcp.global.api.aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want it available across a whole project, and checked into git so the rest of the team gets it too, add &lt;code&gt;--scope project&lt;/code&gt; instead of leaving it at the personal default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--scope&lt;/span&gt; project &lt;span class="nt"&gt;--transport&lt;/span&gt; http aws-knowledge https://knowledge-mcp.global.api.aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify it worked:&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;claude mcp list
claude mcp get aws-knowledge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;list&lt;/code&gt; shows connection status at a glance, &lt;code&gt;get&lt;/code&gt; shows the full config and whether it's reachable right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code, Visual Studio, JetBrains, and other Copilot IDEs
&lt;/h3&gt;

&lt;p&gt;These all share a similar &lt;code&gt;mcp.json&lt;/code&gt; config shape. The root key is &lt;code&gt;servers&lt;/code&gt;, not &lt;code&gt;mcpServers&lt;/code&gt;, that's the detail that trips people up if they're copying config from Claude or Cursor.&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;"servers"&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;"aws-knowledge"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://knowledge-mcp.global.api.aws"&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;Since AWS Knowledge is public and needs no authentication, that's the whole config, no tokens or headers required. Where you put it and how you open it differs by IDE:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VS Code&lt;/strong&gt;: open the Command Palette and run &lt;code&gt;MCP: Open User Configuration&lt;/code&gt; for something that applies everywhere, or create &lt;code&gt;.vscode/mcp.json&lt;/code&gt; in a repo to scope it to that project and commit it for the team.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Verify it worked&lt;/em&gt;: run &lt;code&gt;MCP: List Servers&lt;/code&gt; from the Command Palette, select &lt;code&gt;aws-knowledge&lt;/code&gt;, and check its status. If it's erroring, choose "Show Output" to see the actual server log. In Copilot Chat (Agent mode), click the tools icon to see the live list of available MCP tools and confirm &lt;code&gt;aws-knowledge&lt;/code&gt; is in it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;JetBrains&lt;/strong&gt; (IntelliJ, WebStorm, PyCharm, etc.): click the GitHub Copilot icon, then Edit Settings, then Model Context Protocol, then Configure, which opens the same &lt;code&gt;mcp.json&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Verify it worked&lt;/em&gt;: open Copilot Chat in Agent mode and click the tools icon at the bottom of the chat window. This opens the live tool list, confirm the AWS Knowledge tools show up there. If they don't appear right after saving, try reopening the chat window rather than assuming it's misconfigured, that's the most common false negative.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Visual Studio&lt;/strong&gt; (2022, version 17.14.9+): create an &lt;code&gt;.mcp.json&lt;/code&gt; file in your solution directory or &lt;code&gt;%USERPROFILE%&lt;/code&gt;, using the same config shown above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Verify it worked&lt;/em&gt;: after saving, Visual Studio shows a CodeLens prompt above the server entry, click it to connect. Then open Copilot Chat in Agent mode and use the tool picker to confirm &lt;code&gt;aws-knowledge&lt;/code&gt;'s tools are listed. Since this server needs no auth, it should connect straight away with no credential prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Claude Desktop
&lt;/h3&gt;

&lt;p&gt;This one works differently. Claude Desktop doesn't pick up remote HTTP servers from a config file at all, &lt;code&gt;claude_desktop_config.json&lt;/code&gt; is for local servers only. Remote servers go through the UI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Settings, then Connectors (Organization integrations, if you're on a Team or Enterprise plan).&lt;/li&gt;
&lt;li&gt;Add a custom connector.&lt;/li&gt;
&lt;li&gt;Paste in &lt;code&gt;https://knowledge-mcp.global.api.aws&lt;/code&gt; as the server URL.&lt;/li&gt;
&lt;li&gt;Confirm. The tools are available from your next chat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No OAuth step here either, since the server doesn't require authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify it worked:&lt;/strong&gt; open the Connectors panel again and confirm &lt;code&gt;aws-knowledge&lt;/code&gt; shows as connected with its tools listed, or just ask it something time-sensitive in a new chat and see whether it searches before answering.&lt;/p&gt;

&lt;h2&gt;
  
  
  If it won't connect
&lt;/h2&gt;

&lt;p&gt;This is a public, high-traffic endpoint, so occasionally the issue is on AWS's end rather than your config. There's a known case where the server has briefly returned connection errors during initialisation across every client at once. If &lt;code&gt;aws-knowledge&lt;/code&gt; looks correctly configured but won't connect, it's worth trying again a few minutes later before assuming your JSON is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prefer to keep it local?
&lt;/h2&gt;

&lt;p&gt;If you'd rather not depend on a remote, AWS-hosted server, for offline work or a stricter environment, AWS also publishes &lt;code&gt;awslabs.aws-documentation-mcp-server&lt;/code&gt;, a locally run equivalent with overlapping search and read tools, installed via &lt;code&gt;uvx&lt;/code&gt; instead of a URL. Same idea, runs on your machine instead of AWS's.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing worth watching
&lt;/h2&gt;

&lt;p&gt;Every MCP server you connect adds to the list of tools your assistant has to consider on each turn. Adding AWS Knowledge is low-cost since it's just a handful of tools, but it's worth treating "add every MCP server that looks useful" as the wrong instinct. Add what you'll actually reach for, not everything available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;This is low effort for high value, for only a bit of setup. One command, or one small JSON block, and you're done.&lt;/p&gt;

&lt;p&gt;After trying it out, I'd call this a must for every developer working with AWS, and it left me wondering how I'd gone so long without it.&lt;/p&gt;

&lt;p&gt;It cuts down the hallucinations by giving the model something current to check itself against, instead of relying purely on training data that's already going stale by the time you're reading this.&lt;/p&gt;

&lt;p&gt;The bigger win for me, honestly, is staying in one place. No more jumping between the IDE (or terminal) and a browser tab full of AWS docs. That context switch between "coding" and "reading documentation" is a small tax we pay dozens of times a day, and it adds up. With this wired in, your AI assistant can just go check for itself, and you stay exactly where you were working.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What's the most recent AWS feature your AI assistant got completely wrong? &lt;/p&gt;

&lt;p&gt;Drop it below, I want to see how bad the gap actually is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you'd like to connect or keep the conversation going, find me on &lt;a href="https://www.linkedin.com/in/stevenwleung/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/awslabs/mcp" rel="noopener noreferrer"&gt;The AWS Labs MCP GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/10/aws-knowledge-mcp-server-generally-available/" rel="noopener noreferrer"&gt;AWS Knowledge MCP Server GA announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://awslabs.github.io/mcp/servers/aws-knowledge-mcp-server/" rel="noopener noreferrer"&gt;AWS Knowledge MCP Server documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
