<?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: Keshav Varshney</title>
    <description>The latest articles on DEV Community by Keshav Varshney (@ikeshavvarshney).</description>
    <link>https://dev.to/ikeshavvarshney</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%2F3840062%2F87c1b3dd-df21-473e-9f20-8b66c32ccc70.jpg</url>
      <title>DEV Community: Keshav Varshney</title>
      <link>https://dev.to/ikeshavvarshney</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ikeshavvarshney"/>
    <language>en</language>
    <item>
      <title>My Go tests never executed a line of the JavaScript they were testing</title>
      <dc:creator>Keshav Varshney</dc:creator>
      <pubDate>Thu, 03 Sep 2026 14:40:42 +0000</pubDate>
      <link>https://dev.to/ikeshavvarshney/my-go-tests-never-executed-a-line-of-the-javascript-they-were-testing-4m0m</link>
      <guid>https://dev.to/ikeshavvarshney/my-go-tests-never-executed-a-line-of-the-javascript-they-were-testing-4m0m</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built a Real User Monitoring tool as one Go binary with an empty&lt;br&gt;
dependency manifest, zero entries in &lt;code&gt;go.mod&lt;/code&gt;. 245 test functions, &lt;code&gt;go vet&lt;/code&gt;&lt;br&gt;
clean, CI green on every push, a reproducible build verified on every commit.&lt;br&gt;
Six bugs survived all of it. This is the story of a build that stamped a git&lt;br&gt;
SHA into a binary I had called deterministic, an &lt;code&gt;int64&lt;/code&gt; that saturated into&lt;br&gt;
six-year chart buckets, a demo page that was genuinely slow and reported&lt;br&gt;
green, and a fix that was correct, deployed, and invisible for an hour.&lt;/p&gt;

&lt;p&gt;None of them were findable from Go. That is the actual subject of this post.&lt;/p&gt;


&lt;h2&gt;
  
  
  The premise is a joke that turned out to be the product
&lt;/h2&gt;

&lt;p&gt;RUM tells you how slow your site feels to real visitors. Every mainstream way&lt;br&gt;
of collecting it ships 30 to 60KB of third-party JavaScript from someone&lt;br&gt;
else's CDN, executing before your own code, on every page view. The tool that&lt;br&gt;
measures page weight is itself page weight.&lt;/p&gt;

&lt;p&gt;So the target was a beacon small enough that the size is the argument. It&lt;br&gt;
landed at &lt;strong&gt;942 bytes&lt;/strong&gt;, against 7,226 for Google's &lt;code&gt;web-vitals&lt;/code&gt;. Everything&lt;br&gt;
else followed from refusing to make that number worse: no framework, because&lt;br&gt;
a framework is a bundle; no chart library, because a chart library is 60KB to&lt;br&gt;
draw six lines; no database driver, because the thing being stored is five&lt;br&gt;
floats per page view.&lt;/p&gt;

&lt;p&gt;Writing that was the easy half. The question that decides whether any of it&lt;br&gt;
is real is: how do you know it works?&lt;/p&gt;


&lt;h2&gt;
  
  
  The gap: no Go test can execute JavaScript
&lt;/h2&gt;

&lt;p&gt;Testing a browser beacon from a Go suite means testing everything except the&lt;br&gt;
beacon. I could prove &lt;code&gt;/b.js&lt;/code&gt; was served with the right MIME type and ETag,&lt;br&gt;
that it stayed under its 1024-byte budget, that the ingest endpoint parsed a&lt;br&gt;
payload I hand-wrote and the API read it back. All of that passed, and none&lt;br&gt;
of it proves &lt;code&gt;PerformanceObserver&lt;/code&gt; fires, that &lt;code&gt;visibilitychange&lt;/code&gt; triggers&lt;br&gt;
the flush, or that CLS session-window arithmetic produces a number a browser&lt;br&gt;
would agree with.&lt;/p&gt;

&lt;p&gt;Go tests embed the file, serve it, assert on its bytes, and never execute a&lt;br&gt;
line of it. To a Go test, JavaScript is a byte slice.&lt;/p&gt;

&lt;p&gt;So I drove real Chrome over the DevTools Protocol. Node 22 ships a global&lt;br&gt;
&lt;code&gt;WebSocket&lt;/code&gt;, so the harness needed nothing installed:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;webSocketDebuggerUrl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;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;CDP&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/json/version`&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;ws&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;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webSocketDebuggerUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tests/*.mjs (external harness)     drives real Chrome over CDP
        │  Page.navigate, Runtime.evaluate, Input.dispatchMouseEvent
        ▼
vitals binary                      serves dashboard, demo pages, beacon.min.js
        │  beacon executes for real, PerformanceObserver fires for real
        ▼
/api/collect  →  /api/series, /api/events (EventSource)
        │
        ▼
45 scripted assertions against what the browser actually measured
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The harness lives outside the repository, because a browser automation&lt;br&gt;
dependency inside a zero-dependency project would be absurd.&lt;/p&gt;

&lt;p&gt;Two details from building it are worth passing on. Chrome 152 removed&lt;br&gt;
&lt;code&gt;Emulation.setPageVisibilityState&lt;/code&gt;, the usual way to fake a tab switch, so I&lt;br&gt;
replaced it with what a visitor actually does: open a second tab and activate&lt;br&gt;
it. And I was clicking with &lt;code&gt;element.click()&lt;/code&gt;, which made INP appear only&lt;br&gt;
intermittently, because synthetic clicks do not reliably produce Event Timing&lt;br&gt;
entries. Switching to &lt;code&gt;Input.dispatchMouseEvent&lt;/code&gt; made it deterministic and&lt;br&gt;
produced the single best piece of evidence in the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;metric inp collected by the browser  p75=600.00 ms (poor, n=1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;600.00ms against a handler deliberately blocking for exactly 600ms.&lt;/p&gt;




&lt;h2&gt;
  
  
  Six bugs, in increasing order of "that would have shipped"
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The reproducible build that wasn't
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;make repro&lt;/code&gt; builds twice and compares hashes. CI does the same on a clean&lt;br&gt;
runner and runs &lt;code&gt;cmp&lt;/code&gt;. Both passed from the day I wrote them. The README&lt;br&gt;
said the binary contains no build timestamp, git SHA, or injected version,&lt;br&gt;
which is what makes the output byte-identical. False. I caught it only&lt;br&gt;
because a hash changed between two commits that touched nothing but&lt;br&gt;
Markdown.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go version &lt;span class="nt"&gt;-m&lt;/span&gt; vitals
&lt;span class="go"&gt;build   vcs.revision=6acd604c28b79a3db52850287e061b98c0018d7e
build   vcs.time=2026-08-30T10:56:20Z
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since Go 1.18 the toolchain stamps the git revision, a timestamp, and a&lt;br&gt;
module pseudo-version into any binary built inside a repository. On by&lt;br&gt;
default. The fix is one flag, &lt;code&gt;-buildvcs=false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix is not the interesting part. My test checked a weaker property than&lt;br&gt;
my claim. "Two builds at the same commit produce identical bytes" and "the&lt;br&gt;
binary contains no commit metadata" are different sentences. Only the first&lt;br&gt;
was under test. The second was what the README asserted, and a passing test&lt;br&gt;
sat next to it the whole time looking like evidence.&lt;/p&gt;

&lt;p&gt;I proved the fix the way I should have proved the claim: build, make an&lt;br&gt;
empty commit so the SHA changes, build again, compare. Most&lt;br&gt;
reproducible-build advice stops at &lt;code&gt;-trimpath&lt;/code&gt; and &lt;code&gt;-ldflags=-buildid=&lt;/code&gt;. The&lt;br&gt;
VCS stamp is newer, quieter, and it undermines you without saying anything.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. The int64 that saturated
&lt;/h3&gt;

&lt;p&gt;I loaded the dashboard and read the caption under the chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 of 48 buckets have samples, 3202560 min per bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six years per bucket. On a chart labelled "last 24 hours".&lt;/p&gt;

&lt;p&gt;The dashboard calls &lt;code&gt;/api/series?from=24h&lt;/code&gt; and sends no &lt;code&gt;to&lt;/code&gt;, because "now"&lt;br&gt;
is the obvious default. An open range end was normalised to the year 9999.&lt;br&gt;
Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buckets&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;time.Duration&lt;/code&gt; is an &lt;code&gt;int64&lt;/code&gt; of nanoseconds, topping out near 292 years.&lt;br&gt;
Subtracting 1970 from 9999 does not error, does not panic, does not warn. It&lt;br&gt;
saturates. Divide by 48 and you get six-year buckets, rendered without&lt;br&gt;
complaint.&lt;/p&gt;

&lt;p&gt;No test caught it because I wrote the tests alongside the API, thinking&lt;br&gt;
about the API, so every one passed both &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; like a well-behaved&lt;br&gt;
caller. The dashboard, the only real client this API has, passes one. The&lt;br&gt;
bug lived exactly in the gap between how I documented the endpoint and how&lt;br&gt;
the only thing calling it actually calls it.&lt;/p&gt;

&lt;p&gt;The regression test does not assert a bucket width. It asserts that no&lt;br&gt;
combination of parameters can produce a span longer than a century, because&lt;br&gt;
a span that long means an open end leaked through and the arithmetic&lt;br&gt;
saturated.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. The demo that demonstrated nothing
&lt;/h3&gt;

&lt;p&gt;The project ships four deliberately broken demo pages so the dashboard shows&lt;br&gt;
something other than a wall of green. One is "heavy image": a hero of 2,600&lt;br&gt;
inline SVG shapes, meant to tank Largest Contentful Paint. It is genuinely&lt;br&gt;
expensive to render.&lt;/p&gt;

&lt;p&gt;In Chrome it measured &lt;strong&gt;220ms&lt;/strong&gt;. Good band. Green.&lt;/p&gt;

&lt;p&gt;An inline &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element is not an LCP candidate. The spec is specific&lt;br&gt;
about what counts: &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; inside an SVG, video poster frames,&lt;br&gt;
background images, and block-level text. A root inline &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; is not on&lt;br&gt;
that list. The page burned real paint time while LCP quietly reported on a&lt;br&gt;
paragraph above it.&lt;/p&gt;

&lt;p&gt;This one stings because it is precisely the failure a performance dashboard&lt;br&gt;
exists to prevent: the page was slow, the metric said fast, both were true,&lt;br&gt;
and the gap was a spec detail I had not read carefully enough.&lt;/p&gt;

&lt;p&gt;Fixed with an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; and a data URI so it is actually a candidate, plus a&lt;br&gt;
deliberate main-thread block so it is slow on any hardware rather than only&lt;br&gt;
on a bad laptop. It reports 2852ms now. My first attempt used a 4200ms block&lt;br&gt;
to force the poor band, which broke differently: the page was still blocked&lt;br&gt;
when the tab hid, so the beacon flushed before any paint entry existed and&lt;br&gt;
sent a record containing only TTFB. Worse demo, worse data. The page now&lt;br&gt;
explains the trap in its own copy, because the trap is more interesting than&lt;br&gt;
the demo.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. I measured my own tooling and called it a result
&lt;/h3&gt;

&lt;p&gt;The headline claim is a size comparison. That claim is the entire pitch, so&lt;br&gt;
it had better be clean. My first numbers were not: I measured our beacon&lt;br&gt;
with Go's gzip and theirs with Python's. Same algorithm, different&lt;br&gt;
implementations, a few percent apart. Nobody would ever have caught it. It&lt;br&gt;
would have been a property of my toolchain, presented as a property of my&lt;br&gt;
code, in the one number the project rests on.&lt;/p&gt;

&lt;p&gt;So I wrote a tool that runs every file through one compressor:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Raw&lt;/th&gt;
&lt;th&gt;Gzipped&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;vitals beacon&lt;/td&gt;
&lt;td&gt;942 B&lt;/td&gt;
&lt;td&gt;571 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vitals full beacon&lt;/td&gt;
&lt;td&gt;2,656 B&lt;/td&gt;
&lt;td&gt;1,415 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;web-vitals.iife.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7,226 B&lt;/td&gt;
&lt;td&gt;2,601 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;web-vitals.attribution.iife.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12,505 B&lt;/td&gt;
&lt;td&gt;4,172 B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;7.7x smaller raw, 4.6x gzipped. And still not a fair fight, in both&lt;br&gt;
directions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unfair to us:&lt;/strong&gt; &lt;code&gt;web-vitals&lt;/code&gt; only measures. It hands each metric to a&lt;br&gt;
callback and leaves transport entirely to you, so a real deployment adds&lt;br&gt;
reporting code on top of those bytes. Our 942 already include JSON&lt;br&gt;
serialisation, &lt;code&gt;sendBeacon&lt;/code&gt;, a &lt;code&gt;fetch&lt;/code&gt; fallback, and flush-on-hide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unfair to them:&lt;/strong&gt; &lt;code&gt;web-vitals&lt;/code&gt; does more per metric: back-forward cache&lt;br&gt;
restoration, prerendering and &lt;code&gt;activationStart&lt;/code&gt;, soft navigations,&lt;br&gt;
attribution to the element that caused the bad LCP, real INP grouped by&lt;br&gt;
&lt;code&gt;interactionId&lt;/code&gt; rather than the longest single event, and years of Safari&lt;br&gt;
workarounds.&lt;/p&gt;

&lt;p&gt;At 942 bytes I did none of that. So I wrote a second beacon that does five&lt;br&gt;
of those six, at 2,656 bytes, and kept it as a separate file rather than&lt;br&gt;
folding it into the first. Two reasons, one technical and one not. The&lt;br&gt;
technical one: most sites want the small one, and paying 1.7KB for prerender&lt;br&gt;
correction on a site that never prerenders is exactly the trade this project&lt;br&gt;
exists to argue against. The other: the sub-1KB number is the headline&lt;br&gt;
claim, and quietly redefining "the beacon" to mean the bigger file is the&lt;br&gt;
move I would criticise in someone else's README.&lt;/p&gt;

&lt;p&gt;Neither beacon does the Safari and Firefox work. That is years of&lt;br&gt;
accumulated browser bug knowledge, not something you reproduce by reading a&lt;br&gt;
spec. A comparison is more convincing with its caveats than without, because&lt;br&gt;
a reader who spots an unstated one stops trusting the rest of your numbers&lt;br&gt;
too.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. The cache that outlived the bug
&lt;/h3&gt;

&lt;p&gt;I broke &lt;code&gt;dash.js&lt;/code&gt; with a one-character mistake, a literal newline inside a&lt;br&gt;
string literal, which is a syntax error. The whole script failed to parse,&lt;br&gt;
so the dashboard rendered nothing. Every Go test passed. Of course they did.&lt;br&gt;
&lt;code&gt;node --check&lt;/code&gt; is now in the pre-finish checklist, alongside &lt;code&gt;make check&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The instructive part came next. I fixed it, rebuilt, restarted, and the page&lt;br&gt;
was still blank:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: public, max-age=3600
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The asset names carry no content hash. A browser that had already fetched&lt;br&gt;
the broken script would not ask again for an hour. Server fixed, disk&lt;br&gt;
fixed, user still broken, and no amount of restarting changes that, because&lt;br&gt;
nothing on the server participates in the decision.&lt;/p&gt;

&lt;p&gt;The fix is a policy split rather than a blanket. The dashboard's own scripts&lt;br&gt;
are now &lt;code&gt;no-cache&lt;/code&gt;, which still caches them but forces a revalidation that&lt;br&gt;
answers &lt;code&gt;304&lt;/code&gt; with an empty body. The beacon keeps the long &lt;code&gt;max-age&lt;/code&gt;,&lt;br&gt;
because it is fetched by every page view of an instrumented site, where a&lt;br&gt;
conditional request per view is a real cost. Same file server, two&lt;br&gt;
policies, chosen by who is asking and how often.&lt;/p&gt;

&lt;p&gt;A long cache lifetime is a promise you cannot revoke. It is only safe when&lt;br&gt;
the URL changes with the content. If you are not hashing names, you are&lt;br&gt;
betting you will never ship a bug.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. The half-open window that dropped the present
&lt;/h3&gt;

&lt;p&gt;An end-to-end test started failing about half the time. Same code, same&lt;br&gt;
machine, same command.&lt;/p&gt;

&lt;p&gt;The dashboard's default window ends at "now". The store's range is&lt;br&gt;
half-open, &lt;code&gt;[from, to)&lt;/code&gt;. A measurement recorded in the same clock tick as&lt;br&gt;
the request that reads it lands exactly on the exclusive bound and falls&lt;br&gt;
outside its own window. On Windows, where the wall clock is coarse enough&lt;br&gt;
that two calls microseconds apart return the same value, that is a coin&lt;br&gt;
flip.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsZero&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;            &lt;span class="c"&gt;// excludes anything stamped at this instant&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every test passing an explicit &lt;code&gt;to&lt;/code&gt; was fine. Only the default was wrong,&lt;br&gt;
and the default is what the only real client sends: the same gap as bug 2,&lt;br&gt;
in a different corner of the same function. The failure mode was "I just&lt;br&gt;
recorded a measurement and it is not there", intermittently, which is the&lt;br&gt;
worst kind of bug report to receive from yourself.&lt;/p&gt;

&lt;p&gt;The fix is one millisecond, and the reasoning matters more than the line.&lt;br&gt;
Records are stored at millisecond resolution, so ending the default window&lt;br&gt;
one millisecond after now includes the present instant and nothing that has&lt;br&gt;
not happened yet. An explicit &lt;code&gt;to&lt;/code&gt; is left exactly where the caller put it.&lt;/p&gt;

&lt;p&gt;A flaky test is a race you have already reproduced. I nearly re-ran it and&lt;br&gt;
moved on.&lt;/p&gt;


&lt;h2&gt;
  
  
  The bug the whole suite was structurally blind to
&lt;/h2&gt;

&lt;p&gt;This one arrived last and it is the one I would most want a reviewer to&lt;br&gt;
see. The dashboard has a button that copies a report as a prompt for an AI&lt;br&gt;
agent. Clicking it threw:&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="nx"&gt;Cannot&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="nx"&gt;properties&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nf"&gt;undefined &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reading&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;good&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 Go struct tagged one field &lt;code&gt;json:"Distribution"&lt;/code&gt; with a capital D. The&lt;br&gt;
dashboard, the API documentation, and the contract test all said&lt;br&gt;
&lt;code&gt;distribution&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is why nothing caught it: &lt;code&gt;encoding/json&lt;/code&gt; matches field names&lt;br&gt;
case-insensitively when decoding. The contract test unmarshals the response&lt;br&gt;
into a struct tagged &lt;code&gt;json:"distribution"&lt;/code&gt;, and it bound happily to&lt;br&gt;
&lt;code&gt;"Distribution"&lt;/code&gt;. It passed against either spelling. Every Go test in the&lt;br&gt;
repository was structurally blind to this, and only a browser, which is not&lt;br&gt;
case-insensitive about anything, could ever see it.&lt;/p&gt;

&lt;p&gt;The regression test therefore does not use a struct. It decodes into&lt;br&gt;
&lt;code&gt;map[string]any&lt;/code&gt; and walks every key in the document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="sc"&gt;'A'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="sc"&gt;'Z'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key %s%s starts upper-case; a JavaScript client "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"reading the documented lower-case name gets undefined"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&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;Decoding into a map is the entire point. It is the only way to assert the&lt;br&gt;
key a non-Go client actually receives, rather than the key Go is willing to&lt;br&gt;
accept.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I did not build
&lt;/h2&gt;

&lt;p&gt;Stated here rather than left for someone to notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The binary segment storage format was planned and cut.&lt;/strong&gt; The design called&lt;br&gt;
for compacting sealed day logs into a hand-written binary format with varint&lt;br&gt;
delta timestamps and a string dictionary. It stayed JSONL. That decision now&lt;br&gt;
has a number attached rather than a shrug: replaying 100,000 records takes&lt;br&gt;
&lt;strong&gt;593ms&lt;/strong&gt;, of which roughly 500ms is &lt;code&gt;encoding/json&lt;/code&gt; on the read path. A&lt;br&gt;
million records is about six seconds and 1.6GB to open. That 500ms is&lt;br&gt;
exactly what the segment format would have removed, and it is the honest&lt;br&gt;
size of what cutting it gave up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no authentication.&lt;/strong&gt; The dashboard and API are open to anyone who&lt;br&gt;
can reach the port. Collection is rate limited per client address, but&lt;br&gt;
forwarded headers are ignored because they are trivially spoofable, which&lt;br&gt;
means behind a reverse proxy every visitor shares one bucket and one&lt;br&gt;
derived session id.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;reportAllChanges&lt;/code&gt; is not implemented.&lt;/strong&gt; &lt;code&gt;web-vitals&lt;/code&gt; can emit every LCP&lt;br&gt;
candidate as it changes. This sends one record per page view on hide, which&lt;br&gt;
is the whole reason it fits in 942 bytes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The decision I would take back
&lt;/h2&gt;

&lt;p&gt;The beacon exists as two files: &lt;code&gt;beacon.src.js&lt;/code&gt;, readable and commented, and&lt;br&gt;
&lt;code&gt;beacon.min.js&lt;/code&gt;, minified by hand. There is no minifier in the project,&lt;br&gt;
because a minifier is a build dependency and the project has no build step.&lt;/p&gt;

&lt;p&gt;The cost is that a human keeps two files in sync, and humans drift. I&lt;br&gt;
mitigated it rather than solved it. Tests assert that every metric key,&lt;br&gt;
every observed entry type, and the collection endpoint appear in both&lt;br&gt;
files, and that the minified file is actually minified rather than a copy&lt;br&gt;
of the readable one under the wrong name. That catches the catastrophic&lt;br&gt;
mistake. It does not catch a subtle logic change applied to one file and&lt;br&gt;
not the other.&lt;/p&gt;

&lt;p&gt;A real minifier would make the drift structurally impossible, and I would&lt;br&gt;
take the build step. The zero-dependency rule was about &lt;em&gt;runtime&lt;/em&gt;&lt;br&gt;
dependencies, and I let it bleed into build tooling where it bought nothing&lt;br&gt;
but risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  By the numbers
&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;Beacon&lt;/td&gt;
&lt;td&gt;942 B raw, 571 B gzipped, 1024 B budget enforced by the build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full beacon&lt;/td&gt;
&lt;td&gt;2,656 B raw, 1,415 B gzipped, separate 2816 B budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vs &lt;code&gt;web-vitals&lt;/code&gt; 4.2.4&lt;/td&gt;
&lt;td&gt;7.7x smaller raw, 4.6x gzipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;0. &lt;code&gt;go list -m all&lt;/code&gt; prints one line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;~13,750 lines, of which ~8,100 are tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;245 functions, 10 benchmarks, 1 fuzz target, 20+ table-driven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;~3,400 lines, no framework, no bundler, no build step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packages replaced&lt;/td&gt;
&lt;td&gt;41, each with a note on where the original is better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser checks&lt;/td&gt;
&lt;td&gt;45 scripted, in real Chrome 152&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commits&lt;/td&gt;
&lt;td&gt;50, with the tree building and &lt;code&gt;make check&lt;/code&gt; passing at every one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of the later features came from measuring rather than reasoning.&lt;br&gt;
Benchmarking the store turned up a genuine quadratic path: an out-of-order&lt;br&gt;
insert rebuilt both secondary indexes, &lt;strong&gt;0.7 to 1.0 ms against 66 µs&lt;/strong&gt; for&lt;br&gt;
shifting them instead, and out-of-order arrival was not the rare event I had&lt;br&gt;
assumed. The collector stamps the wall clock and takes the store lock as a&lt;br&gt;
separate step, so concurrent page views land reversed constantly. And "you&lt;br&gt;
lose at most two seconds on a crash" was still only a sentence, so I wrote a&lt;br&gt;
test that builds the binary, runs it as a child process, feeds it 500&lt;br&gt;
measurements, and kills it. A clean &lt;code&gt;Close&lt;/code&gt; had never tested the thing the&lt;br&gt;
sentence claimed.&lt;/p&gt;

&lt;p&gt;Said out loud rather than buried: percentiles are read from histogram&lt;br&gt;
buckets and carry up to 4.9% relative error on millisecond metrics and&lt;br&gt;
0.0025 absolute on CLS, while band counts are exact. The small beacon&lt;br&gt;
approximates INP as the longest event over 16ms, so it is pessimistic in&lt;br&gt;
the tail, which is why the full beacon exists. Firefox and Safari are&lt;br&gt;
untested, and so are the full beacon's own bfcache, soft-navigation, and&lt;br&gt;
prerender paths: reviewed against the specs, never watched running in a&lt;br&gt;
browser. That is the largest honest gap in the project, and it is why the&lt;br&gt;
small beacon remains the default.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the time actually went on
&lt;/h2&gt;

&lt;p&gt;Anyone can write a &lt;code&gt;PerformanceObserver&lt;/code&gt; that compiles and a Go server that&lt;br&gt;
serves. The beacon is 942 bytes; it took an afternoon. What the rest of it&lt;br&gt;
went on was building enough ways to catch myself being wrong: a dependency&lt;br&gt;
checker that fails the build on a &lt;code&gt;require&lt;/code&gt; block, a CDN reference, or a web&lt;br&gt;
font; a size tool that measures every file with one compressor; a CI job&lt;br&gt;
that builds twice and runs &lt;code&gt;cmp&lt;/code&gt;; a fuzz target on the only place the&lt;br&gt;
program reads untrusted input; a test that kills a real process; forty-five&lt;br&gt;
scripted checks in a real browser, because my own suite could not execute&lt;br&gt;
the file it was shipping.&lt;/p&gt;

&lt;p&gt;Every one of those exists because a claim I had written down in English&lt;br&gt;
turned out not to be a claim anything was checking. That translation step,&lt;br&gt;
from the sentence in the README to the assertion in the test, is where all&lt;br&gt;
six bugs lived.&lt;/p&gt;

&lt;p&gt;Write the claim in plain English, then ask what would have to be true. It&lt;br&gt;
is the cheapest tool in the list and it found more than any of the others.&lt;/p&gt;

&lt;p&gt;The tool measures page weight and contributes 942 bytes of it. That was the&lt;br&gt;
whole idea, and it survived contact with a real browser. Eventually.&lt;/p&gt;

&lt;p&gt;Built for the &lt;a href="https://zerodepshack.com/" rel="noopener noreferrer"&gt;Zero Dependency Hackathon 2026&lt;/a&gt;, the&lt;br&gt;
72 hour dependency-free build hackathon run by &lt;a href="https://raptors.dev" rel="noopener noreferrer"&gt;Hackathon Raptors&lt;/a&gt;, Track D (Data &amp;amp; Storage). The full project,&lt;br&gt;
&lt;code&gt;STDLIB.md&lt;/code&gt;, the browser test harness, and the size comparison tool are all in&lt;br&gt;
the repo.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/ikeshavvarshney/vitals" rel="noopener noreferrer"&gt;https://github.com/ikeshavvarshney/vitals&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>webperf</category>
      <category>testing</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
