<?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: Isaiah Kim</title>
    <description>The latest articles on DEV Community by Isaiah Kim (@kyisaiah47).</description>
    <link>https://dev.to/kyisaiah47</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3981339%2F343de7a0-5c04-42b9-9944-8191ddbe2639.jpeg</url>
      <title>DEV Community: Isaiah Kim</title>
      <link>https://dev.to/kyisaiah47</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kyisaiah47"/>
    <language>en</language>
    <item>
      <title>Soft Money Journal, Still Mornings, Using It Up and Why Your Brain Does That</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Mon, 07 Sep 2026 23:06:03 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/soft-money-journal-still-mornings-using-it-up-and-why-your-brain-does-that-56db</link>
      <guid>https://dev.to/kyisaiah47/soft-money-journal-still-mornings-using-it-up-and-why-your-brain-does-that-56db</guid>
      <description>&lt;p&gt;People can open four archives here, each keeping a different kind of everyday record.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four archives
&lt;/h2&gt;

&lt;p&gt;These pages turn short films and videos into written archives. Readers get journal entries, morning readings, inventories, and explanations for familiar mental experiences.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://softmoney.kynth.studio/dv/7ndw21" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff7nwn88h1mbr5320nbea.png" alt="Soft Money Journal" width="128" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://softmoney.kynth.studio/dv/7ndw21" rel="noopener noreferrer"&gt;Soft Money Journal&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Soft Money Journal
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fxowekqdsttxwbhfxvusa.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fcrosspost%2Fdevto-digest%2Ffounder-devto%2Fsoft-money-journal-still-mornings-using-it-up-and-why-your-brain-does%2C%2520soft-money-journal-hero-1788800495502.png" alt="Soft Money Journal, screenshot of the landing page" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;Soft Money Journal is a written archive about the ten minutes, the notebook, and the order things get paid in. It is for readers who want a quiet record of money habits, with journal entries and a letter signup instead of advice or amounts.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://softmoney.kynth.studio/dv/7ndw21" rel="noopener noreferrer"&gt;softmoney.kynth.studio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stillmornings.kynth.studio/dv/g22soy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb8ar4fstzkhd1tzkywie.png" alt="Still Mornings" width="128" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stillmornings.kynth.studio/dv/g22soy" rel="noopener noreferrer"&gt;Still Mornings&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Still Mornings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fxowekqdsttxwbhfxvusa.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fcrosspost%2Fdevto-digest%2Ffounder-devto%2Fsoft-money-journal-still-mornings-using-it-up-and-why-your-brain-does%2C%2520still-mornings-hero-1788800496809.png" alt="Still Mornings, screenshot of the landing page" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;Still Mornings keeps the words from slow morning videos. It is for readers who want a quiet morning page, with the written reading, its hook, and a frame from the morning.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://stillmornings.kynth.studio/dv/g22soy" rel="noopener noreferrer"&gt;stillmornings.kynth.studio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://usingitup.kynth.studio/dv/lka383" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7uiltvlbku0kb7znikn5.png" alt="Using It Up" width="128" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://usingitup.kynth.studio/dv/lka383" rel="noopener noreferrer"&gt;Using It Up&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using It Up
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fxowekqdsttxwbhfxvusa.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fcrosspost%2Fdevto-digest%2Ffounder-devto%2Fsoft-money-journal-still-mornings-using-it-up-and-why-your-brain-does%2C%2520usingitup-hero-1788800497711.png" alt="Using It Up, screenshot of the landing page" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;Using It Up is a publication about finishing what you already own. It is for readers who want to keep, mend, and use things to the last of them, with entries about repairs, abandoned carts, and buying less.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://usingitup.kynth.studio/dv/lka383" rel="noopener noreferrer"&gt;usingitup.kynth.studio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://whyyourbrain.kynth.studio/dv/6srzwy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06fsyzg4tweecm93n7r4.png" alt="Why Your Brain Does That" width="128" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://whyyourbrain.kynth.studio/dv/6srzwy" rel="noopener noreferrer"&gt;Why Your Brain Does That&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Your Brain Does That
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fxowekqdsttxwbhfxvusa.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fcrosspost%2Fdevto-digest%2Ffounder-devto%2Fsoft-money-journal-still-mornings-using-it-up-and-why-your-brain-does%2C%2520whyyourbraindoesthat-hero-1788800498591.png" alt="Why Your Brain Does That, screenshot of the landing page" width="800" height="400"&gt;&lt;/td&gt;
&lt;td&gt;Why Your Brain Does That is an index of things your brain does and why doing them makes sense. It is for readers who want a name and explanation for familiar experiences, with short entries that explain what is happening and what changes on Monday.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://whyyourbrain.kynth.studio/dv/6srzwy" rel="noopener noreferrer"&gt;whyyourbrain.kynth.studio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send us someone who needs something built.&lt;/strong&gt; $150, fixed, for every client you send whose work goes ahead. One link, no tiers, no portal. &lt;a href="https://kynth.studio/referral" rel="noopener noreferrer"&gt;Get your link&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;One shipped product, taken apart, once a month.&lt;/strong&gt; What it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did, read off the repository and the live site, not written from memory. &lt;a href="https://kynth.studio/list" rel="noopener noreferrer"&gt;Join the list&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>discuss</category>
      <category>programming</category>
    </item>
    <item>
      <title>Claude Code subagents fail 2.8x more often than skills</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Sun, 06 Sep 2026 00:14:08 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/claude-code-subagents-fail-28x-more-often-than-skills-1d36</link>
      <guid>https://dev.to/kyisaiah47/claude-code-subagents-fail-28x-more-often-than-skills-1d36</guid>
      <description>&lt;p&gt;Publish a subagent with no description line and nobody's Claude Code will ever hand it a task. Your file sits in the repository looking correct, it parses, it installs, and the one sentence the agent reads to decide when to delegate is absent, so there is nothing to route on.&lt;/p&gt;

&lt;p&gt;That failure is silent by construction. A run that never selects a subagent does not error. It does the work inline, finishes, and reports that it finished. The self-report is accurate about the outcome and says nothing about the file you spent an afternoon writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the census found
&lt;/h2&gt;

&lt;p&gt;On 2026-08-14 I read every Claude Code skill, subagent, plugin and marketplace repository that could be found on GitHub and skills, from source, and checked one thing: whether an agent runtime could register the file at all. Not whether it is good, not whether it is popular. 445,348 artefacts, of which 43,199 fail, 9.7 percent.&lt;/p&gt;

&lt;p&gt;The aggregate hides the finding. Subagents fail at 21.9 percent, skills at 7.9 percent, and the gap is 2.8 times.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Read from source&lt;/th&gt;
&lt;th&gt;Failing&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;name&lt;/code&gt; required&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;description&lt;/code&gt; required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subagent&lt;/td&gt;
&lt;td&gt;71,430&lt;/td&gt;
&lt;td&gt;15,669&lt;/td&gt;
&lt;td&gt;21.9%&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skill&lt;/td&gt;
&lt;td&gt;347,382&lt;/td&gt;
&lt;td&gt;27,398&lt;/td&gt;
&lt;td&gt;7.9%&lt;/td&gt;
&lt;td&gt;No, falls back to the directory name&lt;/td&gt;
&lt;td&gt;Recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;20,212&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;0.16%&lt;/td&gt;
&lt;td&gt;Not a frontmatter question, &lt;code&gt;plugin.json&lt;/code&gt; must parse&lt;/td&gt;
&lt;td&gt;Not specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marketplace&lt;/td&gt;
&lt;td&gt;6,324&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;1.6%&lt;/td&gt;
&lt;td&gt;Not a frontmatter question, the manifest must list at least one plugin&lt;/td&gt;
&lt;td&gt;Not specified&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The failure causes, counted across all four kinds: 38,183 files whose settings block does not parse, 4,119 subagent files with no name, 1,799 subagent files with no description. A file can carry more than one, so those do not sum to the total.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="Claude Code subagents fail 2.8x more often than skills, code" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The same careless file is a working skill and a dead subagent
&lt;/h2&gt;

&lt;p&gt;The reason subagents fail three times harder is not that the people writing them are worse. It is a difference between two pages of documentation.&lt;/p&gt;

&lt;p&gt;Anthropic's subagent reference says "Only &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; are required" and marks both Required: Yes. The skills reference says "All fields are optional. Only description is recommended", marks &lt;code&gt;name&lt;/code&gt; Required: No, and defaults it to the directory name. So one hurried block of settings at the top of a markdown file loads cleanly as a skill and dies as a subagent, and the author has no way to tell which of the two they wrote unless they read both pages.&lt;/p&gt;

&lt;p&gt;That asymmetry is the whole 2.8x. It is a specification difference, not a culture difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  A verdict that rests on someone else's sentence
&lt;/h2&gt;

&lt;p&gt;Here is the part that made me change how the index stores its own rules. The pass or fail decision is exactly this, from &lt;code&gt;src/data/census.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"always"&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="s2"&gt;"broken-frontmatter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"broken-manifest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"empty-marketplace"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subagent_only"&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="s2"&gt;"no-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"no-description"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"works=false is exactly these flags and nothing else. Every other flag is a score deduction, not a load verdict."&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;&lt;code&gt;no-name&lt;/code&gt; and &lt;code&gt;no-description&lt;/code&gt; sit under &lt;code&gt;subagent_only&lt;/code&gt; for one reason: two sentences on claude say so. If Anthropic revises either page, 15,669 subagent failures stop being failures and the headline number moves, without a line of my code changing.&lt;/p&gt;

&lt;p&gt;So both sentences are stored in the product's claim register with a probe, which is the literal substring that has to still be present on the page. &lt;code&gt;ops/qa/facts-gate.mjs --verify&lt;/code&gt; refetches both references and fails when a probe stops matching. Two dates are kept apart on purpose: &lt;code&gt;asOf&lt;/code&gt; is the day a person read the source, &lt;code&gt;lastVerified&lt;/code&gt; is the day a machine last confirmed the probe is still there, and &lt;code&gt;--stamp&lt;/code&gt; may only move the second one. An HTTP 200 is not somebody having read the rule. Both probes last matched on 2026-09-04.&lt;/p&gt;

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

&lt;p&gt;I stopped treating a measured number as the thing that decays and started treating the rule underneath it as the thing that decays. The figures on the census page are my own measurements, so the only thing that can make them wrong is a bug in the export. The definition of "works" belongs to somebody else's documentation, and that is now the part with a probe on it and a gate that fails when it moves.&lt;/p&gt;

&lt;p&gt;Toolproof publishes nine indexes that read AI agent tooling from source and record what they find, with the method for each one stated on the page. The census is free, CC-BY-4.0, and every figure above comes out of it.&lt;/p&gt;

&lt;p&gt;A subagent with no description line never gets picked, so the rule that decides that is now stored with a probe and rechecked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://toolproof.kynth.studio/dv" rel="noopener noreferrer"&gt;https://toolproof.kynth.studio/dv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>agents</category>
      <category>opensource</category>
    </item>
    <item>
      <title>DoseTrace: a $99 DSCSA binder for small pharmacies</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Mon, 24 Aug 2026 14:26:13 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/dosetrace-a-99-dscsa-binder-for-small-pharmacies-3nk5</link>
      <guid>https://dev.to/kyisaiah47/dosetrace-a-99-dscsa-binder-for-small-pharmacies-3nk5</guid>
      <description>&lt;p&gt;DoseTrace builds flat-fee DSCSA readiness binders for independent pharmacies: the written record around the serialization data, not another platform. Its own tagline is "the DSCSA record an inspector actually reads."&lt;/p&gt;

&lt;p&gt;It is for the owner or pharmacist-in-charge of a small pharmacy, one store or a handful, who buys from two or three wholesalers and has no compliance officer. The person whose whole DSCSA plan right now is that the wholesaler probably handles it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually receive
&lt;/h2&gt;

&lt;p&gt;Nine documents, prepared for your pharmacy, each one its own PDF, delivered as a zip:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A binder index, carrying your name, address, state licence and the date it was prepared.&lt;/li&gt;
&lt;li&gt;The exemption determination: what your status is, the basis for it, and what changes at the sunset.&lt;/li&gt;
&lt;li&gt;The trading-partner verification log: every supplier you buy from, one row each, dated and evidenced.&lt;/li&gt;
&lt;li&gt;Three standard operating procedures: incoming product inspection, suspect and illegitimate product including the 24-hour FDA notification clock, and saleable returns.&lt;/li&gt;
&lt;li&gt;A tracing response runbook: the path from a request landing to an answer going out, inside 24 hours.&lt;/li&gt;
&lt;li&gt;A training attestation, one line per licensed staff member, signed against the SOP revision date on the binder.&lt;/li&gt;
&lt;li&gt;A records retention plan: six years, per wholesaler, naming where each record actually lives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The price is $99, charged once. No subscription, nothing renews, nothing to cancel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2pzxcw1wfhfooc15q6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2pzxcw1wfhfooc15q6g.png" alt="dosetrace — live" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The flat fee is a bet about the deadline
&lt;/h2&gt;

&lt;p&gt;The reason the fee is flat is written into the site's own FAQ: "Because the deadline could move. A subscription priced against a date that shifts is how you end up owing refunds."&lt;/p&gt;

&lt;p&gt;The deadline then moved. On 6 August 2026 FDA issued a new exemption letter granting small business dispensers a further year, running from 27 November 2026 to 27 November 2027. Its stated reason is that the assessment of small dispensers required by the statute is not finished. In the same letter FDA says the extension is "not intended to provide, and should not be viewed as providing, a justification for delaying efforts by small business dispensers to implement the enhanced drug distribution security requirements".&lt;/p&gt;

&lt;p&gt;Here is the part that matters for anyone building against a government source. FDA replaced that letter at the same media URL, serving a different document, with no redirect and no new id. A check that confirms the URL still returns 200 learns nothing at all. So the product keeps a register of every dated claim it makes, and each row records the byte length and now a sha256 of the PDF it was read from, so the next check is a comparison rather than a re-reading. The current letter is 331,269 bytes; the one it replaced was 321,074.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the free checker decides before you pay
&lt;/h2&gt;

&lt;p&gt;The front page has an exemption checker. It takes six answers, stores nothing, and needs no account. Two traps it exists to catch, both of which flip the verdict:&lt;/p&gt;

&lt;p&gt;The 25-employee threshold counts full-time pharmacists and technicians across the corporate entity that owns the pharmacy, not per store. A three-store owner who counts one location routinely believes they qualify. And "full-time" is the IRS test FDA cites: 30 or more hours a week on average, or 130 a month, so part-time staff below that line do not count at all.&lt;/p&gt;

&lt;p&gt;The second one is newer and it changed the shape of the question. FDA takes the headcount on a fixed day, and for the extra year that day is 27 November 2026, which has not happened yet. The tool used to answer "were you under the line on a day that has passed." It now has to answer "will you be under the line on a day that has not," so every qualifying verdict says out loud that it is a reading of where you stand today rather than a status already fixed. Hiring past 25 licensed staff before that date puts you outside the second exemption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three things the generator refuses to build
&lt;/h2&gt;

&lt;p&gt;The binder generator will not produce a document it should not produce. From &lt;code&gt;ops/src/pack.mjs&lt;/code&gt;:&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;// ---- GATE 2: no trading partners -------------------------------------------------&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wholesalers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wholesalers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;intake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wholesalers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&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;wholesalers&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no wholesalers in intake - a trading-partner verification log with zero partners evidences nothing. Get the supplier list before generating.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---- GATE 3: over the threshold is a judgment document ---------------------------&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fte&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;FTE_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;reviewPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`NotConfigured: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fte&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; licensed FTE is over the &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;FTE_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; threshold, so this pharmacy was never a small dispenser. That determination is a judgment about their past posture - it needs a hand-written review before anything ships.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third gate blocks after the sunset date: the entire binder is written in the voice of a determination you are carrying into a deadline, and after that deadline the framing is wrong, so it stops rather than hand a pharmacy a document that misdescribes its own status.&lt;/p&gt;

&lt;p&gt;The over-threshold gate is the one I would defend hardest. A pharmacy above 25 licensed staff was never a small dispenser, which means the enhanced requirements have applied to it the whole time. Writing that down is a judgment about somebody's past compliance posture, and no template gets to make it. It needs a person, and nothing auto-drafts and nothing auto-sends.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is not
&lt;/h2&gt;

&lt;p&gt;DoseTrace is not a track-and-trace platform and does not move serialization files. Your wholesalers already do that.&lt;/p&gt;

&lt;p&gt;That distinction is harder to see than it should be, because the category is sold under three letters that cover two different halves. Nine vendor domains were being cited for this buyer's questions; all nine were fetched on 23 August 2026 before a word was written about them. Five are not selling to an independent pharmacy at all. ConsortiEX says so in its own meta description, naming health systems and hospitals. RxERP's audience menu lists wholesalers, distributors and government, and a dispenser is not on it. Tecsys' home page contains zero occurrences of "DSCSA" and zero of "pharmac". Pulse by NABP is not a vendor at all. RedSail, parent of PioneerRx and PrimeRx, answers DSCSA by reselling: its own menu reads "DSCSA Compliance Solutions, LSPedia's Pharmacy Pro and InfiniTrak."&lt;/p&gt;

&lt;p&gt;Neither of those two publishes a price.&lt;/p&gt;

&lt;p&gt;And across both FDA exemption letters, the word "software" appears once, as a technology and software assessment FDA itself has to commission. The statutory language quoted at the pharmacy is "systems and processes." The processes half is the binder, and it is $99, once.&lt;/p&gt;

&lt;p&gt;Run the free exemption check first. It will tell you whether any of this reaches your pharmacy before you spend anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dosetrace.kynth.studio/dv" rel="noopener noreferrer"&gt;https://dosetrace.kynth.studio/dv&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>healthcare</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I turned on error tracking. Most of it was Outlook.</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Mon, 17 Aug 2026 14:27:01 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/i-turned-on-error-tracking-most-of-it-was-outlook-3eke</link>
      <guid>https://dev.to/kyisaiah47/i-turned-on-error-tracking-most-of-it-was-outlook-3eke</guid>
      <description>&lt;p&gt;A weekly report landed on August 17th telling me my sites had thrown 363 exceptions, up 156 percent from the week before, with crash-free sessions at 94.29 percent. That is the kind of number that reorganises a day. Something is broken badly enough that roughly one visit in eighteen hits it, and I have no idea which page.&lt;/p&gt;

&lt;p&gt;333 of those 363 were not my code. Not a subtle bug in my code, not a dependency of my code. Two browser artifacts and a dev server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually in there
&lt;/h2&gt;

&lt;p&gt;The products involved have nothing to do with each other. AgentWire is a curation lane for agentic developer tooling, MCP servers and coding-agent harnesses, and its site is a 1:1 reflection of what the accounts actually posted. KitGrade is a directory of SaaS starter kits scored from measured facts, where every kit is labelled hands-on or documented and the label records whether the kit was cloned onto a machine and built. Different data, different stacks underneath, different everything. The only thing they share is the analytics snippet.&lt;/p&gt;

&lt;p&gt;327 of the 363 were this string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Non-Error promise rejection captured with value: Object Not Found Matching Id:N,
MethodName:update, ParamCount:4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not from any code I wrote. It comes from the wrapper Microsoft puts around links opened out of Outlook, running inside the page it just opened. Which is why it fired on AgentWire and KitGrade and everything else at the same moment: an error that appears simultaneously across products sharing no code is not coming from any of them. That simultaneity is the only tell you get, and you only get it if you have more than one site reporting into the same place.&lt;/p&gt;

&lt;p&gt;6 more were &lt;code&gt;ResizeObserver loop completed with undelivered notifications&lt;/code&gt;, which is the notice a browser emits when a layout observer runs long. Nothing is on fire. Nobody sees anything.&lt;/p&gt;

&lt;p&gt;The remaining 30 were mine, in the least useful sense. They were a dev server on &lt;code&gt;http://localhost:3737&lt;/code&gt; reporting compile errors into the production project while I was mid-edit: &lt;code&gt;PAD_X is not defined&lt;/code&gt;, &lt;code&gt;SelectedWork is not defined&lt;/code&gt;. Errors I had already fixed before the report was written, filed alongside real ones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="I turned on error tracking. Most of it was Outlook. — code" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that generalises
&lt;/h2&gt;

&lt;p&gt;Turning on browser exception capture is one boolean now. In posthog-js, which I am on at 1.407.5, it is &lt;code&gt;capture_exceptions: true&lt;/code&gt; next to the pageview settings. You add a line, you deploy, and a week later you have a health metric.&lt;/p&gt;

&lt;p&gt;The metric is wrong out of the box, and it is wrong in the direction that costs you the most. It reads worse than reality, so the first thing it makes you do is go looking for a crash that is not there. Then, once you have learned that the report is noise, it reads as noise forever, and the first real one arrives in a mail that looks exactly like the previous four.&lt;/p&gt;

&lt;p&gt;The dev server half is worth its own sentence. The project key lives in the client bundle, so it is the same key on localhost, on a preview URL and in production. Every environment reports into one place unless you tell it not to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" alt="I turned on error tracking. Most of it was Outlook. — architecture" width="800" height="980"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate
&lt;/h2&gt;

&lt;p&gt;posthog-js has a &lt;code&gt;before_send&lt;/code&gt; hook that runs in the browser and drops the event when you return null. I put the filter there rather than in the dashboard, because a dropped event costs nothing and a filtered one still has to be sent, stored and then excluded from every view forever after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;before_send&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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;event&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$exception&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[\d&lt;/span&gt;&lt;span class="sr"&gt;.&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.vercel.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;$exception_values&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&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="sr"&gt;/Object Not Found Matching Id:&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&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="sr"&gt;/ResizeObserver loop/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// a noise gate must never be the thing that breaks a page&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;event&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;Three details in there I would keep if I wrote it again.&lt;/p&gt;

&lt;p&gt;The first line checks &lt;code&gt;event.event !== '$exception'&lt;/code&gt; and returns early. &lt;code&gt;before_send&lt;/code&gt; sees everything, including pageviews and clicks, so a filter written loosely here quietly eats your entire analytics feed instead of your error feed.&lt;/p&gt;

&lt;p&gt;The host check drops localhost, any &lt;code&gt;.localhost&lt;/code&gt; subdomain, bare IP addresses and preview deployments. That is the environment split I did not get for free.&lt;/p&gt;

&lt;p&gt;And the &lt;code&gt;catch&lt;/code&gt; is the one I care about most. This function runs inside the page, on every event, before anything is sent. If it throws, it throws in front of the visitor. A filter that exists to protect a dashboard is not allowed to be the reason a page stops working, so it fails open and lets the event through.&lt;/p&gt;

&lt;p&gt;The two regexes are the least durable part of this. They match strings that a browser vendor can change without telling anyone, and when that happens the noise comes back and I will have to read a digest carefully to notice. I would rather that than a filter loose enough to swallow something real.&lt;/p&gt;

&lt;p&gt;The number I get now is 30 exceptions smaller and it is entirely mine. That is the point of the exercise: not a better crash rate, a crash rate that is about my software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://agentwire.kynth.studio/?utm_source=founder-devto&amp;amp;utm_medium=social" rel="noopener noreferrer"&gt;https://agentwire.kynth.studio/?utm_source=founder-devto&amp;amp;utm_medium=social&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>debugging</category>
      <category>analytics</category>
    </item>
    <item>
      <title>I shipped a download that iOS turned into a navigation</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Fri, 14 Aug 2026 14:51:58 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/i-shipped-a-download-that-ios-turned-into-a-navigation-4g0i</link>
      <guid>https://dev.to/kyisaiah47/i-shipped-a-download-that-ios-turned-into-a-navigation-4g0i</guid>
      <description>&lt;p&gt;I read a session from an iPhone where the visitor pressed an export button four times, and &lt;code&gt;$pageleave&lt;/code&gt; fired about 150 milliseconds after each press, with a reset page when they came back. Those four departures were the export itself.&lt;/p&gt;

&lt;p&gt;WebKit does not honour the &lt;code&gt;download&lt;/code&gt; attribute on a &lt;code&gt;blob:&lt;/code&gt; URL. On iOS and iPadOS, where every browser is WebKit underneath regardless of what it says on the icon, &lt;code&gt;a.download&lt;/code&gt; pointed at a blob is ignored and the click becomes a navigation to that blob. The page unloads. Whatever state produced the file goes with it.&lt;/p&gt;

&lt;p&gt;That pattern is everywhere in my code, so I went and fixed the place where it costs the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the page was holding
&lt;/h2&gt;

&lt;p&gt;PartsProof is a Cyber Resilience Act tool I'm building under Kynth, and its free surface is an SBOM generator. You give it a public GitHub repo and an email, and it returns a CycloneDX 1.5 document you can download.&lt;/p&gt;

&lt;p&gt;The work behind that is not cheap. &lt;code&gt;src/app/api/sbom/route.ts&lt;/code&gt; resolves the repo and its default branch, pulls the full recursive tree, filters that down to dependency manifests, then fetches and parses up to thirty of them. &lt;code&gt;MAX_MANIFESTS&lt;/code&gt; is 30, so the whole run is as many as thirty-two GitHub round-trips before a single component is counted.&lt;/p&gt;

&lt;p&gt;Because it takes real time, the route streams NDJSON and fires a stage as each piece finishes, with the measured elapsed per stage. The five ids live in one file, &lt;code&gt;src/lib/stages.ts&lt;/code&gt;, with a note on top I put there for myself:&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="cm"&gt;/* ⛔ Adding a label here does not create a stage. A stage exists when the route fires it. */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StageId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;repo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tree&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifests&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lead&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So on a phone, the sequence was: wait through thirty-two round-trips, watch five stages report, get told how many distinct components came back, press download, and land on a blob viewer with the whole result gone. Pressing back gives you an empty form. The only way to see the number again is to run all of it again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="I shipped a download that iOS turned into a navigation — code" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The branch, and the iPad that says it is a Mac
&lt;/h2&gt;

&lt;p&gt;The fix in &lt;code&gt;src/components/Lookup.tsx&lt;/code&gt; is small. On WebKit mobile, stop pretending the anchor is a download and let it be what WebKit is going to make it anyway: a navigation, but into its own tab, where the document opens with a share sheet instead of destroying the page that produced it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&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;webkitMobile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="sr"&gt;/iP&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;hone|od|ad&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MacIntel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTouchPoints&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webkitMobile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noopener&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second clause of that test is the one I would have missed. An iPad running iPadOS reports &lt;code&gt;navigator.platform&lt;/code&gt; as &lt;code&gt;MacIntel&lt;/code&gt; and sends a desktop user agent string with no &lt;code&gt;iPad&lt;/code&gt; in it, so the regex alone reads it as a laptop and leaves it broken. &lt;code&gt;maxTouchPoints &amp;gt; 1&lt;/code&gt; is what separates the iPad from an actual MacIntel machine, and a Mac with a trackpad reports 0. It is a sniff, and I do not love that it is a sniff, but there is no feature query for "this browser ignores an attribute".&lt;/p&gt;

&lt;p&gt;I also considered the File System Access API and skipping the anchor entirely. Safari does not implement &lt;code&gt;showSaveFilePicker&lt;/code&gt;, so the fallback path is this same anchor, and adding a branch that only ever runs on the browsers already working buys nothing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" alt="I shipped a download that iOS turned into a navigation — architecture" width="800" height="980"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Revoking on the next line
&lt;/h2&gt;

&lt;p&gt;The other half of that commit is a bug that had been sitting there the whole time and would have been much harder to see.&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;aEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ← this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;click()&lt;/code&gt; starts a save. It does not finish one. Revoking the object URL on the very next statement pulls the blob out from under a transfer that has only just been handed to the browser. On desktop with a small JSON body it wins the race essentially always, which is exactly why it survives review: it looks like tidy cleanup and it behaves like tidy cleanup right up until the document is large or the device is slow or the save goes through a share sheet.&lt;/p&gt;

&lt;p&gt;It now reads:&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;// Revoking on the next line races the save the click just started; give it a window.&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sixty seconds of one blob held in memory is not a leak worth optimising against a file that sometimes does not arrive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2pzxcw1wfhfooc15q6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2pzxcw1wfhfooc15q6g.png" alt="partsproof — live" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Saying "downloaded" when nothing downloaded
&lt;/h2&gt;

&lt;p&gt;There was one more thing wrong, and it took a second pass to notice. The success message was a single template string:&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="nf"&gt;setSbomMsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`downloaded &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repoName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-sbom.cdx.json.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the WebKit path that sentence is false. Nothing landed in a downloads folder. The document opened in its own tab and is waiting for the reader to press Share. Telling someone a file was saved when it was not is the specific failure this panel exists to avoid, since the whole product is about producing a document you can hand to somebody.&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="nf"&gt;setSbomMsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;webkitMobile&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;repoName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-sbom.cdx.json opened in a new tab — use Share to save it.`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`downloaded &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repoName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-sbom.cdx.json.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch that changes behaviour and the branch that describes it have to be the same branch. Fixing the navigation without fixing the sentence would have left the tool confidently reporting a save on the one platform where it never performs one.&lt;/p&gt;

&lt;p&gt;None of this showed up in a build, a type check, or any gate I run against the deployed page. Blob downloads are one of the last things in a web app that no automated check I have will exercise, because the interesting part happens after the click, in the browser's own plumbing, on a platform I was not testing on. The telemetry found it, and it found it as four unexplained departures rather than as an error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://partsproof.kynth.studio/?utm_source=founder-devto&amp;amp;utm_medium=social" rel="noopener noreferrer"&gt;https://partsproof.kynth.studio/?utm_source=founder-devto&amp;amp;utm_medium=social&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ios</category>
      <category>react</category>
    </item>
    <item>
      <title>I built a Playwright gate that cried wolf 889 times</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:28:35 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/i-built-a-playwright-gate-that-cried-wolf-889-times-3nd5</link>
      <guid>https://dev.to/kyisaiah47/i-built-a-playwright-gate-that-cried-wolf-889-times-3nd5</guid>
      <description>&lt;p&gt;On August 2nd I shipped a landing page whose headline was in the DOM at the right size, the right colour and the right position, and invisible. StoreReady's &lt;code&gt;data-animate="17g0wbg"&lt;/code&gt; resolved against motion data from a different capture, so the h1 held its start frame, opacity 0.001, permanently. Every gate I had passed the build.&lt;/p&gt;

&lt;p&gt;They passed because they all read source. &lt;code&gt;icons-gate&lt;/code&gt; greps the icon set, &lt;code&gt;mark-gate&lt;/code&gt; greps the lockup, &lt;code&gt;workbench check&lt;/code&gt; hashes the vendored files. All of them are decidable without a renderer, and there is no string to grep for a pixel that never got painted.&lt;/p&gt;

&lt;p&gt;So I wrote one that never reads a file. &lt;code&gt;ops/qa/render-gate.mjs&lt;/code&gt; in the workbench repo takes a URL, drives real Chrome, and asks six questions a screenshot answers: effective opacity down the whole ancestor chain, WCAG against the computed background, horizontal scroll from 320 to 1920, control labels breaking to a second line, headline and CTA above the fold at 1280x800, and content trapped past the start edge of its own scroll container. Each product's &lt;code&gt;scripts/deploy.sh&lt;/code&gt; runs it after the deploy, against the live host, and a failure is hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first sweep reported 889 invisible elements
&lt;/h2&gt;

&lt;p&gt;Essentially all of them were scroll-reveal sections below the fold, sitting at opacity 0 because nothing had asked them to appear yet. A gate that cries wolf 889 times is not a gate.&lt;/p&gt;

&lt;p&gt;The part I want to keep from that run is why I did not see it coming. My own shell's entrance runtime reveals on mount rather than on intersection, so the sites already on it scored honestly by accident, which is the worst way to be right. Pointing the same measurement at pages with intersection-driven reveals is what exposed the model.&lt;/p&gt;

&lt;p&gt;The fix is that the gate judges only what is on screen. It walks the page, probes at each stop, and merges by element plus text keeping the best reading of each one. An element off-screen at opacity 0 is not a defect. An element in front of the reader at opacity 0 is the StoreReady bug. One scroll position cannot tell those apart.&lt;/p&gt;

&lt;p&gt;Per-stop settle is 750ms, long enough for a reveal to finish rather than merely to start. At 320ms it kept catching FetchDue's sections at opacity 0.061, mid-fade, which reads as a defect and is not one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="I built a Playwright gate that cried wolf 889 times — code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ways the walk still lied
&lt;/h2&gt;

&lt;p&gt;The first was arithmetic. The stops stepped a fixed 0.75 viewport down from the top and then took &lt;code&gt;.slice(0, 14)&lt;/code&gt; for the time budget, so on anything taller than about 9,450px at a 900px viewport the sweep just stopped and everything below was never judged at all.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&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;MAX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 14 stops at 750ms is the time budget&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;MAX&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;out&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;y&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;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spreading the same budget over the real height means density degrades on a very long page instead of coverage vanishing. A missed defect in the last third is worse than a coarser walk.&lt;/p&gt;

&lt;p&gt;The second is the interesting one. On a long page an element can be in front of the reader at exactly one stop. If that stop catches it entering at the bottom edge, or partway through a stagger, then the best-reading merge has only the bad reading to keep. The finding looks real, reproduces across runs, and describes nothing anyone would ever see. On QuorumFile that condemned three cells of a seventeen-row table, all three at opacity 1 the moment you scroll to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look at it again, on purpose
&lt;/h2&gt;

&lt;p&gt;Anything the walk condemns now gets one more measurement, taken deliberately.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;condemned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;OPACITY_FLOOR&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decorative&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipped&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;t&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;condemned&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;scrollTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;innerHeight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1400&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;fresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;probeText&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;better&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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="o"&gt;=&amp;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;sel&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;text&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;inFold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;better&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;better&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;better&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pxRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OPACITY_FLOOR&lt;/code&gt; is 0.08. Two details in there cost me something. Only condemned runs are re-measured, so a clean page pays nothing for this, which is what makes a 1400ms settle affordable at all. And the fresh reading replaces the stale one whole, rather than just its opacity. A reading taken while an element was at opacity 0 also carries the contrast it had against whatever was behind it, which is 1:1 because no ink was painted. Adopting the opacity alone clears the visibility finding and hands that dead 1:1 straight to the contrast check. One false finding becomes a different false finding.&lt;/p&gt;

&lt;p&gt;A finding that survives being scrolled to and stared at for a second is the real bug. One that does not was never a bug.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" alt="I built a Playwright gate that cried wolf 889 times — architecture"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;On August 8th, on a QuorumFile deploy, it failed on the pricing block's document preview: painted at opacity 0, and not marked decorative.&lt;/p&gt;

&lt;p&gt;The cause was not in the pricing block. &lt;code&gt;ScrollReveals&lt;/code&gt; collects auto-fade candidates from &lt;code&gt;main.page &amp;gt; *&lt;/code&gt; and walks down, so any element added to any section inherits a 0-opacity start state whether it asked for one or not. Mine carried no &lt;code&gt;data-reveal&lt;/code&gt; and got one anyway. The fix was to put it in the runtime's skip list next to &lt;code&gt;.header&lt;/code&gt;, for the same reason the header is there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SKIP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.header, .trail, .nav-list, .nav-dropdown-list, .sheet-overlay, .kd-artefact-link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gate cannot decide this on its own, and it does not try. My footers carry a brand wordmark at opacity 0.07, which is a watermark and correct. StoreReady's hero sat at 0.001, which was a bug and shipped. Measured, those are the same observation, so the rule is that text below the floor has to declare itself with &lt;code&gt;aria-hidden="true"&lt;/code&gt; or it counts as a failure. A heading is never decoration.&lt;/p&gt;

&lt;p&gt;I had opened that pricing block in a browser more than once before the deploy. The gate found it, I did not.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>playwright</category>
      <category>testing</category>
      <category>css</category>
    </item>
    <item>
      <title>The early return that shipped a false all-clear</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Sun, 09 Aug 2026 14:25:28 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/the-early-return-that-shipped-a-false-all-clear-1mfg</link>
      <guid>https://dev.to/kyisaiah47/the-early-return-that-shipped-a-false-all-clear-1mfg</guid>
      <description>&lt;p&gt;I typed the word "construction" into my own lookup tool and it answered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All 200 qualifying contracts under null have an approved Affidavit of Wages Paid on file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two things wrong in one sentence, and the more interesting one is that nothing had been checked against the affidavit file at all. The route had returned before it got there.&lt;/p&gt;

&lt;p&gt;The product is HeldBack, a free retainage blocker check I'm building under Kynth. You give it a company name or a 9-digit UBI, and it reads two of Washington's published Socrata files live: the Statements of Intent a contractor files before a public works job, and the Affidavits of Wages Paid filed after. A job with an intent and no matching approved affidavit is a job whose retainage, capped at 5% under RCW 60.28.011, is likely still sitting with the awarding agency.&lt;/p&gt;

&lt;h2&gt;
  
  
  The floors are most of the logic
&lt;/h2&gt;

&lt;p&gt;The naive version of this is a set difference: intents minus affidavits. That version is wrong in a way that destroys the product's credibility on first contact, because a job that started six months ago has no affidavit for the boring reason that the work isn't finished. Report that as frozen money and you've told a contractor something they know to be false about a job they remember clearly.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;src/app/api/lookup/route.ts&lt;/code&gt; filters before it compares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_CONTRACT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&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;MIN_AGE_MONTHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&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;priced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cntrct_amt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MIN_CONTRACT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;tooRecentOrUndated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expected_start_dt&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;At or under $5,000 the affidavit is folded into the combined intent form and never appears separately, so those rows are false positives by construction. Undated rows can't be aged at all, so &lt;code&gt;tooRecentOrUndated&lt;/code&gt; returns true for them and they drop out too.&lt;/p&gt;

&lt;p&gt;Both floors are correct. The bug was what happens when they eat everything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="The early return that shipped a false all-clear — code" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The return that skipped the comparison
&lt;/h2&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;priced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;searched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;intents&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;searched&lt;/code&gt; is the number the UI prints back. Everywhere else in the file it means "contracts we actually compared against the affidavit file". Here it meant "rows the first query returned", because at the moment I wrote the line those felt like the same quantity.&lt;/p&gt;

&lt;p&gt;They're the same quantity right up until a search term is generic. The intents query is capped at &lt;code&gt;$limit: '200'&lt;/code&gt;, and a common word matches far more than 200 filings across the whole state, none of them one company. Nearly all of them fail the $5,000 floor or the 24-month floor. &lt;code&gt;priced&lt;/code&gt; comes back empty, the route returns here, and the client is handed &lt;code&gt;searched: 200&lt;/code&gt; with an empty &lt;code&gt;rows&lt;/code&gt; array. The panel reads that as 200 contracts checked and zero blocked.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;null&lt;/code&gt; in the sentence has the same root. Company name is resolved as &lt;code&gt;priced[0]?.companyname&lt;/code&gt;, and on this path &lt;code&gt;priced&lt;/code&gt; is empty.&lt;/p&gt;

&lt;p&gt;The fix splits the count into two:&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;priced&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* `searched` IS THE QUALIFYING COUNT, NOT THE FILING COUNT, and this line used to return
   * `intents.length` for it. [...] `found` is the honest count for this case: filings
   * located, none of them judgeable. */&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;companyname&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="na"&gt;searched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;intents&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="na"&gt;cleared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;totalHeld&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;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;&lt;code&gt;searched: 0&lt;/code&gt; is now literally true. Nothing was searched against the affidavit file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" alt="The early return that shipped a false all-clear — architecture" width="800" height="980"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two branches for three outcomes
&lt;/h2&gt;

&lt;p&gt;The other half was in &lt;code&gt;src/components/HeroLookup.tsx&lt;/code&gt;, which had an empty state and a clear state and nothing else. Any result with &lt;code&gt;rows.length === 0&lt;/code&gt; fell into "nothing appears to be blocked". There was no way to express "we found filings and none of them can be judged yet", so that case borrowed the reassuring one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;found&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;0&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No public works contracts found under that name.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searched&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nothing here can be judged yet.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nothing appears to be blocked.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middle branch explains itself in the body copy: every match is under $5,000 or started inside the last 24 months, a job that recent has no affidavit because the work is not finished, and search the exact legal entity name or the UBI to narrow it. That last part matters more than the correction does. The person who typed a generic term wanted an answer about a company, and now the panel tells them how to get one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2pzxcw1wfhfooc15q6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2pzxcw1wfhfooc15q6g.png" alt="heldback — live" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The same short-circuit, one component away
&lt;/h2&gt;

&lt;p&gt;The wait panel streams NDJSON and fires a stage as each of the route's four real pieces of work finishes, with the measured elapsed printed per stage. On a real company that reads 587ms, 587ms, 756ms, 757ms, and the third line says "53 of 74 have an approved Affidavit on file". Two of the four stages are live network calls, which is where the seconds go.&lt;/p&gt;

&lt;p&gt;The orb at the head of that panel spun forever on the short-circuit path, because it asked whether all four stages were done. On an early return two of them never fire, so the answer is permanently no, and the orb kept spinning next to a finished answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;running&lt;/span&gt;&lt;span class="dl"&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;phase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;idle&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;failed&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;running&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;running&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What the orb reports is whether anything is still happening, not whether every stage reported.&lt;/p&gt;

&lt;p&gt;Neither of these turned up in reading. I found both by typing a word into the live thing and looking at the sentence it gave back, which is also the exact sentence a first-time visitor would be most likely to see, since a generic term is what you type before you know what the tool wants.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>nextjs</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I paged a table with no ORDER BY and lost 2,797 rows</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Fri, 07 Aug 2026 14:24:14 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/i-paged-a-table-with-no-order-by-and-lost-2797-rows-39n2</link>
      <guid>https://dev.to/kyisaiah47/i-paged-a-table-with-no-order-by-and-lost-2797-rows-39n2</guid>
      <description>&lt;p&gt;I ran a verification pass over 22,313 rows, it reported 22,313 rows, and I read that number as proof it had checked all of them. It had checked about 19,500 and looked at some of those twice.&lt;/p&gt;

&lt;p&gt;The product is BlockDex, an item-level index of public shadcn registries I'm building under Kynth. Every item page can render a Live preview — an iframe of the component's own docs page — under a caption that says this is the component running, not a screenshot of it. Nothing checked that claim. &lt;code&gt;preview_url&lt;/code&gt; was a URL &lt;em&gt;shape&lt;/em&gt; learned once per registry from a single sample item name, accepted on &lt;code&gt;status === 200 &amp;amp;&amp;amp; text.length &amp;gt; 500&lt;/code&gt;, then stamped onto every item in that registry.&lt;/p&gt;

&lt;p&gt;Two things went wrong with that. The last docs pattern tried is &lt;code&gt;{origin}/r/{name}&lt;/code&gt;, which on a large class of registries is the registry-item JSON route — the same route the install command reads. A 4KB JSON body clears a length test, the request sent &lt;code&gt;accept: 'text/html,*/*'&lt;/code&gt; so the &lt;code&gt;*/*&lt;/code&gt; forced nothing, and an API route sends no &lt;code&gt;X-Frame-Options&lt;/code&gt;, so the framability check returned true and marked that registry the most embeddable kind there is. One registry framed its own JSON and Chrome rendered its JSON viewer inside the panel. Separately, a shape learned from one item name does not hold for 7,794 items, so most of the rest resolved to the registry's 404.&lt;/p&gt;

&lt;p&gt;I fetched all 22,313 stored preview URLs. 7,382 were not component pages: 4,704 hard 404s, 1,588 JSON documents, 691 soft 404s, 399 server errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  The count that matched
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ops/verify-previews.mjs&lt;/code&gt; pulls its work list through a paging helper that walks a table with limit/offset — 23 pages at a thousand rows each. The query had no &lt;code&gt;ORDER BY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An offset over an unordered query is not a stable window. Postgres is free to return the rows in a different order for each page, so between page 4 and page 5 a row can shift across the offset boundary and be handed to you twice while another row slides the other way and never arrives. The totals still add up, because you asked for 23 slices of a thousand and got them.&lt;/p&gt;

&lt;p&gt;I found it the slow way. After the pass finished and wrote its updates, &lt;code&gt;shadcn-io/area-interactive&lt;/code&gt; still had a Live preview panel mounted over a 404. Its row was in the set the paging skipped.&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;// ⛔ `order=id.asc` IS LOAD BEARING. `selectAll` pages with limit/offset, and an offset over a&lt;/span&gt;
&lt;span class="c1"&gt;// query with no ORDER BY is not a stable window — Postgres is free to return the rows in a&lt;/span&gt;
&lt;span class="c1"&gt;// different order for each of the 23 pages, so a row can arrive twice and another never arrive&lt;/span&gt;
&lt;span class="c1"&gt;// at all. The first full run pulled exactly 22,313 rows, which looked like complete coverage&lt;/span&gt;
&lt;span class="c1"&gt;// and was not.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blockdex_items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;`select=id,registry_slug,name,preview_url,preview_embeddable,docs_url,status&amp;amp;preview_url=not.is.null&amp;amp;order=id.asc&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second pass with the sort key found 17,728 rows still carrying a preview URL where 14,931 were expected, and unpublished 1,603 more. A third pass agreed with the second: 16,121 of 16,125 verified, the remaining four a transient 502.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="I paged a table with no ORDER BY and lost 2,797 rows — code" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fan-out that wasn't doing anything
&lt;/h2&gt;

&lt;p&gt;The same file had a second bug I'd have shrugged at if I hadn't been staring at it. Global concurrency is 40. The real limiter is a per-host cap of 3, which is what keeps a registry running on one small deployment from getting hammered. And rows come back from PostgREST grouped by registry.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;shadcn-io&lt;/code&gt; is 7,794 contiguous rows on one host. So all 40 workers picked up shadcn.io items, 37 of them parked on that host's semaphore, and the other 127 hosts in the corpus sat idle. The run did 1,500 items in eleven minutes and was on course for about three hours.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byHost&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;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;rows&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;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preview_url&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registry_slug&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;byHost&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;host&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;byHost&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;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
  &lt;span class="nx"&gt;byHost&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;host&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&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;queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;byHost&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;work&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;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;work&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;rows&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="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;q&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;queues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;q&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;work&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Round-robin by host, then hand that array to the pool. Wall clock becomes the slowest single host instead of the sum of all of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" alt="I paged a table with no ORDER BY and lost 2,797 rows — architecture" width="800" height="980"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as a missing page
&lt;/h2&gt;

&lt;p&gt;The check itself, in &lt;code&gt;ops/lib/preview.mjs&lt;/code&gt;, is deliberately narrow. My first version read the first 4KB of the body looking for not-found markers, and on a 400-URL sample it condemned two real, rendering component pages that happened to carry the string &lt;code&gt;404&lt;/code&gt; in a nav and an inlined script. Deleting a working item from the index is the worse outcome, because a 404 that slips through is visible on the page and a missing item is not. So the test only reads &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, which is the one place a framework's not-found page reliably announces itself — fumadocs writes "Component not found", Next writes "404".&lt;/p&gt;

&lt;p&gt;A failure clears &lt;code&gt;docs_url&lt;/code&gt; alongside &lt;code&gt;preview_url&lt;/code&gt;, too. Downgrading a dead frame to a "See it running" link is the same claim in smaller type.&lt;/p&gt;

&lt;p&gt;Final state: 11,502 items carry a verified Live preview, 2,369 a verified link, 45,343 neither, 8,985 unpublished. The &lt;code&gt;order=id.asc&lt;/code&gt; has a comment sitting on top of it, because the version without it doesn't fail — it returns a number that matches the table.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I rewrote 1.2M Postgres rows a run to change nothing</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:25:17 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/i-rewrote-12m-postgres-rows-a-run-to-change-nothing-1lno</link>
      <guid>https://dev.to/kyisaiah47/i-rewrote-12m-postgres-rows-a-run-to-change-nothing-1lno</guid>
      <description>&lt;p&gt;I went looking for why one Supabase project kept draining its Disk IO Budget, and the answer was a Python script rewriting 1,206,313 rows to change nothing.&lt;/p&gt;

&lt;p&gt;The product is GoodStanding, a free EIN diagnostic I'm building under Kynth. You type in a nonprofit's EIN and it tells you whether that org is on the IRS Auto-Revocation List or on California's "May Not Operate or Solicit" list. The table behind it, &lt;code&gt;gs_orgs&lt;/code&gt;, is fed by &lt;code&gt;ops/src/ingest-registries.py&lt;/code&gt;, which pulls two files: the IRS bulk revocation zip, and the CA AG CSV that refreshes on the 1st and 3rd Wednesday.&lt;/p&gt;

&lt;p&gt;Both feeds republish their entire corpus every cycle. A few thousand rows actually move. The ingest didn't know that, so every run it upserted all 1.2 million.&lt;/p&gt;

&lt;p&gt;Measured against the stats reset on May 22, that one script was the largest consumer of disk IO on the whole project: 24,721 upsert statements, 10.2 GB read from disk, 3h17m of database time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fingerprint the row, not the feed
&lt;/h2&gt;

&lt;p&gt;The obvious fix is to diff against what's already in the table, but reading 1.2M rows back to decide whether to write 1.2M rows is the same IO with extra steps. What made a cheaper option available is a property of this particular table: &lt;code&gt;ingest-registries.py&lt;/code&gt; is the only writer to &lt;code&gt;gs_orgs&lt;/code&gt;. Nothing else touches it. So a local record of what the script last wrote is authoritative, and no read is needed at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Stable 16-hex digest of the columns we actually write.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\x1f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&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="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;COLUMNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blake2b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;digest_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hashing &lt;code&gt;COLUMNS&lt;/code&gt; and not the parsed source record matters. The feeds carry fields the table doesn't store, and a whitespace change in one of those would otherwise flag a row as changed and buy back the write I'm trying to avoid.&lt;/p&gt;

&lt;p&gt;The selection is then four lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ein&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ein&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merged&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="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_fingerprints&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ein&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&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="n"&gt;ein&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ein&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things about the cache file are deliberate. Missing it isn't an error — &lt;code&gt;load_fingerprints()&lt;/code&gt; swallows &lt;code&gt;FileNotFoundError&lt;/code&gt; and returns &lt;code&gt;{}&lt;/code&gt;, every row mismatches, and you get a full rewrite. The degraded state is the old behaviour, which is slow and correct. And it's written once, after the last batch lands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Written only after every batch landed: a crash mid-run leaves the cache
# untouched so the next run retries the same rows.
&lt;/span&gt;&lt;span class="nf"&gt;save_fingerprints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fresh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saving per batch would have been the natural thing to write, and it's the version where a timeout halfway through convinces the next run that rows it never wrote are already durable.&lt;/p&gt;

&lt;p&gt;Run against that day's corpus: 0 of 1,206,313 rows flagged as changed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="I rewrote 1.2M Postgres rows a run to change nothing — code" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The indexes nobody had ever used
&lt;/h2&gt;

&lt;p&gt;While I was in the stats I checked which indexes on &lt;code&gt;gs_orgs&lt;/code&gt; had been scanned. Three had a scan count of zero: &lt;code&gt;gs_orgs_name_trgm&lt;/code&gt;, &lt;code&gt;gs_orgs_posting_idx&lt;/code&gt;, &lt;code&gt;gs_orgs_ca_status_idx&lt;/code&gt;. Never once, and maintained on every one of those upserts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gs_orgs_name_trgm&lt;/code&gt; is the one worth sitting with. Despite the name it wasn't a trigram index at all — it was a btree on &lt;code&gt;lower(name) text_pattern_ops&lt;/code&gt;. The name search the app actually issues is in &lt;code&gt;src/app/api/lookup/route.ts:65&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`gs_orgs?name=ilike.*&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;safe&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;*&amp;amp;...&amp;amp;limit=8`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leading wildcard. A &lt;code&gt;text_pattern_ops&lt;/code&gt; btree can serve a prefix match and nothing else, so that query had been sequential-scanning the whole table since the day it shipped, next to an index named as though it were handling it. Dropping all three took the table's index footprint from 198 MB to 72 MB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" alt="I rewrote 1.2M Postgres rows a run to change nothing — architecture" width="800" height="980"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The write was the smaller problem
&lt;/h2&gt;

&lt;p&gt;The next day I made the bigger call, and it went the other way from where I started. The project had been moved onto its own Supabase instance for blast-radius isolation, at $10/month. For one product that isn't a trade worth making — so instead of paying to keep 1.2M rows somewhere safe, I asked what the product can actually do with them.&lt;/p&gt;

&lt;p&gt;An org revoked in 2013 is looking at a full 1023 re-application. GoodStanding sells a reinstatement Cure Pack; that org isn't a buyer, and its row is heap, index and WAL on a database the rest of the estate shares. California is different — AB 488 blocks a listed org's donation pages regardless of how old the listing is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;worth_keeping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ca_reg_no&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ca_registry_status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;                      &lt;span class="c1"&gt;# CA-listed: AB 488 buyer at any age
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reinstatement_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;                     &lt;span class="c1"&gt;# back in good standing; "clear" is correct
&lt;/span&gt;    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revocation_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;keep_since&lt;/span&gt; \
        &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;revocation_posting_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;keep_since&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;331,377 rows of 1,206,313. 261 MB down to 81 MB, back on the shared database, and the outreach delta came through unchanged: 716 IRS postings in the last 45 days, 8,219 CA delinquent or suspended.&lt;/p&gt;

&lt;p&gt;There's a trap in that, and it's the reason the comment block above &lt;code&gt;worth_keeping&lt;/code&gt; is longer than the function. &lt;code&gt;/api/lookup&lt;/code&gt; reads "no row" as clear — the table only ever holds orgs that are on a list, so absence is the good answer. Drop a still-revoked 2013 org and the tool tells that nonprofit it's fine.&lt;/p&gt;

&lt;p&gt;So the cutoff is load-bearing on a claim in the UI, and the two are now written down together. &lt;code&gt;GS_KEEP_REVOKED_SINCE&lt;/code&gt; defaults to &lt;code&gt;2020-01-01&lt;/code&gt;, the route file carries a retention warning at the top, and the clear-state copy in &lt;code&gt;EinLookup.tsx&lt;/code&gt; says "No IRS auto-revocation posted since 2020" rather than the unqualified all-clear it used to say. Widening the cutoff means widening that sentence.&lt;/p&gt;

&lt;p&gt;What I'd been treating as a performance problem had a correctness statement buried in it. The rows I was paying to rewrite twice a month were also the rows that decided what the product was allowed to promise, and I only found that by going looking for the disk IO.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>supabase</category>
      <category>python</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>unstable_cache stored my outage for a full hour</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Sun, 02 Aug 2026 14:22:41 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/unstablecache-stored-my-outage-for-a-full-hour-3njm</link>
      <guid>https://dev.to/kyisaiah47/unstablecache-stored-my-outage-for-a-full-hour-3njm</guid>
      <description>&lt;p&gt;I shipped a directory page that told visitors there were zero listings while rendering a grid of 48 of them. Fixed it. Then, about nine hours later, shipped a different bug with the exact same symptom, in the exact same function, for a completely unrelated reason.&lt;/p&gt;

&lt;p&gt;The product is SkillWorks, a scored directory of Claude Code skills, subagents, plugins and marketplace repos I'm building under Kynth. It reads from Supabase over PostgREST, and it needs a lot of row counts: total listings, counts per kind, how many are broken, how many have tracked installs. Counting rows without pulling them is the one thing PostgREST makes genuinely cheap — ask for &lt;code&gt;Prefer: count=exact&lt;/code&gt; with &lt;code&gt;Range: 0-0&lt;/code&gt; and the total comes back in the &lt;code&gt;Content-Range&lt;/code&gt; response header. No rows in the body at all.&lt;/p&gt;

&lt;p&gt;That header is where both bugs live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first zero: the header didn't survive the cache
&lt;/h2&gt;

&lt;p&gt;Original version, in &lt;code&gt;src/lib/db.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;countRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/rest/v1/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;table&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;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;select=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;limit=1`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;apikey&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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;KEY&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="na"&gt;Prefer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count=exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0-0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVALIDATE&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;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-range&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next's fetch cache stores the response &lt;em&gt;body&lt;/em&gt;. On a cache hit it hands you back a &lt;code&gt;Response&lt;/code&gt; assembled with a synthetic header set, and &lt;code&gt;content-range&lt;/code&gt; is not in it. So every cache hit read &lt;code&gt;null&lt;/code&gt; and fell through to &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What kept this hidden for a while is that statically prerendered routes were fine. Their one fetch happens at build time and is always a miss, so the real header is right there. Only routes rendering at runtime hit the cache, and those were the ones shipping "0 indexed" next to a full grid. &lt;code&gt;/marketplaces&lt;/code&gt; read "None of the 0 marketplaces".&lt;/p&gt;

&lt;p&gt;The fix is to stop caching a thing whose value lives outside the cached payload. Make the fetch &lt;code&gt;cache: 'no-store'&lt;/code&gt; and wrap the whole function in &lt;code&gt;unstable_cache&lt;/code&gt;, which memoises the &lt;em&gt;return value&lt;/em&gt; instead. Still one request an hour, but now what's stored is a parsed number, which survives being stored.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fornexm063d7u4a2y0342.png" alt="unstable_cache stored my outage for a full hour — architecture" width="800" height="980"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The second zero: a failed read stored as an answer
&lt;/h2&gt;

&lt;p&gt;That was August 1st, early afternoon. That evening Supabase had an outage, and I watched &lt;code&gt;/skills&lt;/code&gt; render "Search all 0 listings" above cards that looked completely normal.&lt;/p&gt;

&lt;p&gt;The cards were fine because they come from Next's fetch cache and it still had them. The counts came through the new uncached path, hit a dead index, and returned &lt;code&gt;0&lt;/code&gt; — which is the last line of the function above, doing exactly what it was written to do. And &lt;code&gt;unstable_cache&lt;/code&gt; dutifully stored that &lt;code&gt;0&lt;/code&gt; for the full &lt;code&gt;REVALIDATE&lt;/code&gt; window, which is 3600 seconds.&lt;/p&gt;

&lt;p&gt;That's the part that actually bothered me. The outage was maybe twenty minutes. The wrong number would have outlived it by forty. The database could be answering perfectly and the site would still be advertising zero listings, with no error, no degraded state, nothing to look at. A directory that confidently reports emptiness is worse off than one that's visibly broken, because nobody goes looking.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;|| 0&lt;/code&gt; was defensive code from the first bug that quietly became the second one. Returning a fallback and memoising a fallback are different operations, and one function was doing both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Throw inside the cache, degrade outside it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;countRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;unstable_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* … */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-store&lt;/span&gt;&lt;span class="dl"&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;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-range&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// THROW rather than return 0 when the index cannot be reached.&lt;/span&gt;
        &lt;span class="c1"&gt;// `unstable_cache` stores whatever the function RETURNS, for a full hour.&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`count &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;table&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;res&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`count &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: unparseable content-range`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REVALIDATE&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 0 is still what the caller gets — a directory page that 500s over a stat line is worse&lt;/span&gt;
    &lt;span class="c1"&gt;// than one that under-reports for a few seconds. The difference is this 0 is NOT cached.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caller still gets &lt;code&gt;0&lt;/code&gt;. A browse page shouldn't throw a 500 because a stat line in a search placeholder couldn't be computed. But the fallback now lives outside the memo, so nothing gets written, the next request re-reads, and the wrong number lasts exactly as long as the outage does and not one second longer.&lt;/p&gt;

&lt;p&gt;The same weekend, in a different product, I hit the same shape from the other direction. ListRun has pages where the row &lt;em&gt;is&lt;/em&gt; the page — a directory detail, a run, a paid receipt — and they all called a helper that returned &lt;code&gt;null&lt;/code&gt; on any failed read, then called &lt;code&gt;notFound()&lt;/code&gt;. Which means a slow database told a buyer their run does not exist, and on a cached render it baked that 404 in. The fix there was &lt;code&gt;apiRequired&lt;/code&gt; in &lt;code&gt;src/lib/site.ts&lt;/code&gt;: return &lt;code&gt;null&lt;/code&gt; only on an actual 404 from the route, throw on anything else, so &lt;code&gt;notFound()&lt;/code&gt; is always a statement about the row.&lt;/p&gt;

&lt;p&gt;Both are the same mistake. A read that fails and a read that legitimately returns nothing produce the same value, and then something downstream — a cache, a router — treats that value as established fact and keeps it around. The failure gets laundered into data on the way out.&lt;/p&gt;

&lt;p&gt;I've stopped trusting any fallback that sits inside a memo boundary. If a function can be wrong and can be stored, those two facts need to be separated by a &lt;code&gt;throw&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>caching</category>
      <category>webdev</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Custom element FOUC: the rule that hid nothing</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Fri, 31 Jul 2026 14:21:44 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/custom-element-fouc-the-rule-that-hid-nothing-5601</link>
      <guid>https://dev.to/kyisaiah47/custom-element-fouc-the-rule-that-hid-nothing-5601</guid>
      <description>&lt;p&gt;I shipped a CSS rule that could not possibly do its job, looked at the page, saw the flash still happening, and spent longer than I want to admit tuning the wrong thing.&lt;/p&gt;

&lt;p&gt;The product is Tearline, a small zero-dependency web component I'm building under Kynth. You wrap any HTML in one tag and it renders as a thermal receipt you can export as a PNG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tear-line&lt;/span&gt; &lt;span class="na"&gt;barcode=&lt;/span&gt;&lt;span class="s"&gt;"047320260726"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;MERIDIAN&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Coffee &lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt; Provisions&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tear-line&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom elements have a well-known ugly window. Until &lt;code&gt;customElements.define&lt;/code&gt; runs, those children are just ordinary HTML sitting in the page: a bare &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; and some &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;s in whatever the host's body font is, at a completely different height to the receipt that eventually replaces them. On a cold load that's visible long enough to read, and then the layout jumps.&lt;/p&gt;

&lt;p&gt;The standard fix is &lt;code&gt;:not(:defined)&lt;/code&gt;. &lt;code&gt;:defined&lt;/code&gt; only starts matching once the element upgrades, so hiding on &lt;code&gt;:not(:defined)&lt;/code&gt; is exactly the window you want to cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I put it, and why that was wrong
&lt;/h2&gt;

&lt;p&gt;I didn't want every consumer of the component to have to remember a stylesheet rule. So I had &lt;code&gt;tearline.js&lt;/code&gt; inject it:&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;// src/tearline.js — what I shipped first&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tear-line-fouc&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tear-line-fouc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tear-line:not(:defined){visibility:hidden}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tear-line&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TearLine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was a second reason I liked this, and it's a real one: if the script never loads at all, the rule never lands either, so the content stays visible instead of being hidden forever by a stylesheet with nothing left to reveal it. Failure mode handled. Nice.&lt;/p&gt;

&lt;p&gt;Except the whole thing is inert. That script running is the exact moment &lt;code&gt;:not(:defined)&lt;/code&gt; stops matching. The rule and the definition arrive in the same tick, so the rule only ever exists after the window it was written to cover. It hid nothing during the only period that mattered.&lt;/p&gt;

&lt;p&gt;I found this by throttling &lt;code&gt;tearline.js&lt;/code&gt; to a 1.2s delay in devtools, which made the unstyled window long enough to actually stare at. Before that I'd been reading the rule, agreeing with the rule, and assuming the flash was coming from somewhere else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6rigbd35dkes6a5jmle.png" alt="Custom element FOUC: the rule that hid nothing — code" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule has to be present on the first frame
&lt;/h2&gt;

&lt;p&gt;Which means it has to be in the page's stylesheet, not in the component. It's now in &lt;code&gt;src/app/template.css&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.js&lt;/span&gt; &lt;span class="nt"&gt;tear-line&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:defined&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.js&lt;/code&gt; prefix is how I keep the property I liked about self-injection. An inline script in &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; (&lt;code&gt;src/app/layout.tsx:70&lt;/code&gt;) does one thing:&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's inline and synchronous, so the class is on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; before anything can match against it. If scripting is off or the bundle dies, the class is never stamped, the rule never applies, and you get the raw markup rather than a permanently blank box. Same guarantee, moved to a place that's actually there on the first paint.&lt;/p&gt;

&lt;p&gt;This was the second time on this project I'd hit the same shape. The landing page's scroll reveals had it too: &lt;code&gt;ScrollReveals&lt;/code&gt; was setting each element's hidden opacity from a &lt;code&gt;useEffect&lt;/code&gt;, which runs after hydration, which is after the browser has already painted the server HTML fully visible. Every load was doing paint the whole page, blank the parts that reveal, fade them back in. No amount of tuning the IntersectionObserver fixes that, because the wrong frame has already been shown by the time the JS exists. Hidden state belongs in CSS; the runtime should only ever move things toward visible, which is a direction it can't be late for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiding is only half of the flash
&lt;/h2&gt;

&lt;p&gt;Once nothing was visible during the upgrade, the page still moved on arrival, because reserving zero space and then dropping in a full receipt is its own layout shift. Two more pieces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.panel-result&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.tl-stage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;260px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;tear-line&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-ready&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tl-arrive&lt;/span&gt; &lt;span class="m"&gt;0.4s&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;both&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;260px is a guess. Being roughly right and stable beats being exactly right one frame late.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;data-ready&lt;/code&gt; attribute took one more correction. My first version set it at the top of the render path, and the receipt faded in half-drawn: paper present, tear and barcode not yet. The clip path and the barcode are both built from the same seeded stream and get applied later in the same method, so "ready" had to mean after all three:&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;// The host page fades in on [data-ready]. Set on the next frame, after the&lt;/span&gt;
&lt;span class="c1"&gt;// paper, the tear and the barcode are all in place.&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-ready&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="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attribute is only set once, so re-renders (changing &lt;code&gt;seed&lt;/code&gt;, changing &lt;code&gt;width&lt;/code&gt;) don't re-trigger the entrance animation. And the whole thing is dropped under &lt;code&gt;prefers-reduced-motion: reduce&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Verified the same way I found it: throttle the script to 1.2s, watch the whole pre-upgrade window. Hidden throughout, no unstyled text at any point, no shift when it arrives.&lt;/p&gt;

&lt;p&gt;The part I'd keep from this: a rule whose job is to cover the window before a script runs cannot be delivered by that script. That sounds obvious written down. It did not look obvious in the diff, because the code was correct, well-commented, and answering a question about a completely different failure than the one I had.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webcomponents</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Ranking a dashboard by funnel depth, not counts</title>
      <dc:creator>Isaiah Kim</dc:creator>
      <pubDate>Mon, 27 Jul 2026 14:21:10 +0000</pubDate>
      <link>https://dev.to/kyisaiah47/ranking-a-dashboard-by-funnel-depth-not-counts-1npe</link>
      <guid>https://dev.to/kyisaiah47/ranking-a-dashboard-by-funnel-depth-not-counts-1npe</guid>
      <description>&lt;p&gt;I had a metrics page that told me nothing. Page views per host, signups, a couple of Stripe lines, all rendered as cards in a grid. Every morning I'd look at it, feel vaguely informed, and then go build whatever I'd already decided to build the night before. That's the tell: if the dashboard never changes what you do next, it isn't a dashboard, it's decoration.&lt;/p&gt;

&lt;p&gt;So I rewrote it around one question — how far do people actually get — and let that ordering decide what I see first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ordering is the product
&lt;/h2&gt;

&lt;p&gt;Kynth Studios ships a lot of separate products on separate hosts: FetchDue chasing overdue invoices for agencies, BenchFile doing NYC Local Law 84 filings, CertScope determining CPSC scope per SKU, ParseRail for developers integrating against the API. They don't share a funnel shape in any interesting way, but they do share a funnel &lt;em&gt;depth&lt;/em&gt;: someone lands, someone starts the demo, someone finishes the guided run, someone starts signup, someone finishes it, someone activates.&lt;/p&gt;

&lt;p&gt;Depth is the only axis where those products are comparable. A traffic number tells me a host got attention. A depth number tells me where attention stopped.&lt;/p&gt;

&lt;p&gt;So the model is a stage list plus a rollup per product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// stages are ordered; index IS the depth&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;STAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demo_start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demo_complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup_start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup_complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activated&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;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Stage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;STAGES&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ProductFunnel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// deepest stage a product has ANY reach into&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;depth&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;ProductFunnel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="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="nx"&gt;STAGES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;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;--&lt;/span&gt;&lt;span class="p"&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STAGES&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&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="c1"&gt;// the first stage that eats everything&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stall&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;ProductFunnel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Stage&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;1&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;STAGES&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&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;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STAGES&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;STAGES&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="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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;STAGES&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="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;depth&lt;/code&gt; sorts the list. &lt;code&gt;stall&lt;/code&gt; is the label on each row. Everything else on the page is subordinate to those two functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqcc1wctm9rmebpzwulea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqcc1wctm9rmebpzwulea.png" alt="Ranking a dashboard by funnel depth, not counts — code" width="800" height="1003"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vanity metrics are the ones with no stage below them
&lt;/h2&gt;

&lt;p&gt;The rule I ended up with for what stays on the page: a metric earns its spot if there's a next stage it's supposed to feed. Page views feed demo starts. Demo starts feed completions. Completions feed signup starts.&lt;/p&gt;

&lt;p&gt;Page views on their own feed nothing I control, so they came off. Same with a top-level "total events" number, which mostly moved when I deployed something. Anything that only ever goes up and never implies an action is a metric you can watch forever without learning anything.&lt;/p&gt;

&lt;p&gt;The one that hurt to cut was a combined cross-product total. It looked healthy. It was healthy — it just averaged a product where people finish the demo together with one where nobody gets past the landing page, and the average was never the thing I needed to know. Now every row is per-product and the sort does the summarizing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the data engine away from the rendering
&lt;/h2&gt;

&lt;p&gt;The part I'd do again on any dashboard: the thing that computes stages, predicates, sorts and rollups is framework-agnostic and knows nothing about React. It takes events, returns &lt;code&gt;ProductFunnel[]&lt;/code&gt; sorted by depth. The components take that array and draw it.&lt;/p&gt;

&lt;p&gt;That split has paid off more than once. When a number looks wrong, the first question is which half is wrong — and because the contract between them is a plain typed object, I can answer it by calling the rollup in a test and reading the output. A rendering bug costs nothing on the engine side. A rollup bug is visible without a browser.&lt;/p&gt;

&lt;p&gt;It also means the same rollup feeds a morning summary that isn't a web page at all. Same functions, different renderer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the page actually looks like now
&lt;/h2&gt;

&lt;p&gt;One list. Products sorted deepest-first, each row showing its stall stage and the counts on either side of it. FetchDue near the top because there's a one-click live demo with no signup form in front of it, so people reach the guided run. The compliance products — BenchFile, GoodStanding, DoseTrace — sit lower, because a done-for-you filing service has a different entry shape and the interesting stage is further along.&lt;/p&gt;

&lt;p&gt;That ordering is doing real work. Last week it put a product at the bottom of the list whose landing page I thought was fine, with a stall at &lt;code&gt;demo_start&lt;/code&gt; — plenty of people arriving, nobody starting. That's a specific bug in a specific place, which is more than the old grid ever gave me.&lt;/p&gt;

&lt;p&gt;FetchDue has no customers yet and the dashboard says so; the honest zero is on the page next to a link to the live product and a recorded run. I'd rather look at a zero I can locate than a total I can't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bit I'd tell someone rebuilding theirs
&lt;/h2&gt;

&lt;p&gt;Pick the axis before you pick the charts. I spent longer than I should have arranging cards, and none of that time mattered, because the layout question and the ranking question are different questions and only one of them changes what you build tomorrow.&lt;/p&gt;

&lt;p&gt;Sorting by depth also has a property I didn't expect: the top of the list is the least urgent thing. The products getting people deepest need the least attention. Reading the page bottom-up is the actual workflow, which means the sort could arguably be reversed — I've left it deepest-first because I want the ceiling visible, and I go to the bottom on purpose.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>product</category>
      <category>webdev</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
