<?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: Suraj Mishra</title>
    <description>The latest articles on DEV Community by Suraj Mishra (@isurajmisra).</description>
    <link>https://dev.to/isurajmisra</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%2F395397%2Fef1c28cf-6df7-4a1a-81cf-1fbae8b677cc.jpeg</url>
      <title>DEV Community: Suraj Mishra</title>
      <link>https://dev.to/isurajmisra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isurajmisra"/>
    <language>en</language>
    <item>
      <title>Two mechanisms that look redundant, and why only one of them was</title>
      <dc:creator>Suraj Mishra</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:19:26 +0000</pubDate>
      <link>https://dev.to/isurajmisra/two-mechanisms-that-look-redundant-and-why-only-one-of-them-was-53ap</link>
      <guid>https://dev.to/isurajmisra/two-mechanisms-that-look-redundant-and-why-only-one-of-them-was-53ap</guid>
      <description>&lt;h2&gt;
  
  
  The setup: one product, two rankings
&lt;/h2&gt;

&lt;p&gt;Kingpin shows the same listings on two surfaces. &lt;code&gt;/feed&lt;/code&gt; is a swipeable media feed; &lt;code&gt;/board&lt;/code&gt; is&lt;br&gt;
the ladder people actually land on and share. They had &lt;strong&gt;two different ranking formulas&lt;/strong&gt;:&lt;/p&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;ranked by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/feed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rankCards()&lt;/code&gt; — bid baseline, shifted by crowd votes, eviction at −5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/board&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;candidateListings()&lt;/code&gt; — &lt;code&gt;effectiveCents&lt;/code&gt; only: money + 5%/day decay&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the crowd could dethrone you inside the swipe feed, and &lt;em&gt;only&lt;/em&gt; there. On the main page, money&lt;br&gt;
still bought a permanent position. The product's central claim — &lt;em&gt;the crowd decides who stays on&lt;br&gt;
top&lt;/em&gt; — was silently false on the most-visited surface.&lt;/p&gt;

&lt;p&gt;Nobody designed this. It's residue: the board is the pre-pivot leaderboard (Phases 0–8), the feed&lt;br&gt;
came with the pivot (Phases 9–15), and the old surface was never retired. &lt;strong&gt;Architectural drift&lt;br&gt;
usually looks like this — not a bad decision, but an old decision nobody re-opened.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix was to rank both through the same function. What made it interesting was what surfaced&lt;br&gt;
while doing it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Decision 1: decay looked redundant. It wasn't.
&lt;/h2&gt;

&lt;p&gt;Once the board ranked on votes, a fair question came up: &lt;em&gt;why decay a bid automatically at all?&lt;br&gt;
Downvotes already push bad listings down — isn't decay doing the same job twice?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It sounds right. It's wrong, and the reason is worth internalising.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They answer different questions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A downvote answers &lt;strong&gt;"is this bad?"&lt;/strong&gt; It requires a human to actively dislike the thing.&lt;/li&gt;
&lt;li&gt;Decay answers &lt;strong&gt;"you paid in March — why are you still #1 in September?"&lt;/strong&gt; It requires nobody.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap between those is &lt;em&gt;indifference&lt;/em&gt;. A listing that's merely dull collects roughly &lt;strong&gt;zero&lt;/strong&gt;&lt;br&gt;
net votes forever. Nobody downvotes a product for being old. So removing decay means: pay once,&lt;br&gt;
avoid annoying anyone, hold the top spot permanently — precisely the thing the product promises&lt;br&gt;
cannot happen. &lt;strong&gt;Indifference is not a downvote, and a system relying on downvotes alone is&lt;br&gt;
relying on indifference to do a job it can't do.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There was a second, less obvious cost. Decay is the only reason the escalating &lt;strong&gt;refuel&lt;/strong&gt; price&lt;br&gt;
exists — positions must erode for a re-bid to mean anything. Deleting decay would have silently&lt;br&gt;
retired a revenue model and left working, tested code permanently unreachable. That consequence&lt;br&gt;
was nowhere near the code being discussed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But the instinct behind the question was still correct.&lt;/strong&gt; With ~2 listings and effectively no&lt;br&gt;
voters, decay was the &lt;em&gt;only&lt;/em&gt; force acting on the board, it moved one direction, and the only way&lt;br&gt;
to recover position was to pay again. That doesn't read as "the crowd decides." It reads as the&lt;br&gt;
platform billing people for standing still — and it punishes founders for the &lt;em&gt;platform's&lt;/em&gt; lack&lt;br&gt;
of traffic, which isn't their fault.&lt;/p&gt;

&lt;p&gt;So the fix wasn't to the mechanism, it was to its &lt;strong&gt;conditions&lt;/strong&gt;: decay stays fully implemented&lt;br&gt;
behind &lt;code&gt;FLAG_DECAY&lt;/code&gt;, defaulting &lt;strong&gt;off&lt;/strong&gt;, flipped on deliberately once vote volume makes the crowd&lt;br&gt;
the dominant force.&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;// lib/scoring.mjs — one gate, honoured by BOTH effectiveCents and bidBaseCents,&lt;/span&gt;
&lt;span class="c1"&gt;// so no surface can disagree with another about what a listing is worth.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decayEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt; &lt;span class="o"&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="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;decay&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why a manual flag and not an automatic traffic threshold:&lt;/strong&gt; a threshold that trips on its own&lt;br&gt;
would erode every paid position the instant it crossed, with no warning to any founder. A flag&lt;br&gt;
someone flips means the moment is chosen, announceable, and reversible. Same end state, no ambush.&lt;/p&gt;
&lt;h3&gt;
  
  
  The generalisable lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Before deleting a mechanism that looks redundant, write down the exact question each mechanism&lt;br&gt;
answers. If the questions differ, the redundancy is an illusion — and check what &lt;em&gt;else&lt;/em&gt; depends&lt;br&gt;
on it, because load-bearing things are rarely labelled.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A useful tell: the mechanisms overlapped in &lt;em&gt;effect&lt;/em&gt; (both push a listing down) but not in&lt;br&gt;
&lt;em&gt;trigger&lt;/em&gt; (dislike vs. time). Overlapping effects are what make two things look like one.&lt;/p&gt;


&lt;h2&gt;
  
  
  Decision 2: founders don't vote
&lt;/h2&gt;

&lt;p&gt;A founder has a direct financial interest in every rival's rank. Once votes moved paid position&lt;br&gt;
on the main board, leaving founders able to vote would have turned the crowd signal into a proxy&lt;br&gt;
war between paying competitors — and made the cheapest listing on the platform a ticket to&lt;br&gt;
downvote the leader.&lt;/p&gt;

&lt;p&gt;Two design calls worth recording:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope: all voting, not just others' listings.&lt;/strong&gt; "Founders can't vote on other people's&lt;br&gt;
listings" immediately invites &lt;em&gt;"…so they can vote for themselves?"&lt;/em&gt; — and self-voting is the more&lt;br&gt;
blatant manipulation of the two. One rule with no edge cases beats a narrower rule that's harder&lt;br&gt;
to defend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This rejection is loud, when every other rejection in that handler is silent.&lt;/strong&gt; The vote&lt;br&gt;
pipeline deliberately fails quietly, because a clear error tells a bot exactly which control it&lt;br&gt;
tripped. This case is the opposite: a &lt;em&gt;published policy&lt;/em&gt;, not a detection. Returning a fake&lt;br&gt;
success to a paying founder would be lying to a customer.&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;// vote.mjs — 403 with a reason, not the usual silent { applied: false }&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;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FounderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Founders cannot vote. …&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Silence is a security tool, not a default.&lt;/strong&gt; Use it where disclosure helps an attacker tune.&lt;br&gt;
Where the rule is public anyway, silence buys nothing and costs trust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The limit, stated honestly
&lt;/h3&gt;

&lt;p&gt;The link is per-device, not per-person, so it is a &lt;strong&gt;deterrent and a statement of the rule, not&lt;br&gt;
an unconditional wall&lt;/strong&gt; — the same per-IP/per-ASN caps, confidence weighting, and velocity&lt;br&gt;
shadow-ban that resist any other bad actor are what actually backs it up.&lt;/p&gt;

&lt;p&gt;That distinction matters more than it sounds. A control described as airtight when it isn't&lt;br&gt;
produces worse decisions later, because someone downstream stops building the defence that&lt;br&gt;
actually does the work.&lt;/p&gt;




&lt;h2&gt;
  
  
  A small thing that keeps recurring
&lt;/h2&gt;

&lt;p&gt;Wiring &lt;code&gt;FLAG_DECAY&lt;/code&gt; meant adding it to the root stack &lt;strong&gt;and&lt;/strong&gt; forwarding it to both nested&lt;br&gt;
stacks. The existing &lt;code&gt;FLAG_STREAK&lt;/code&gt; was never forwarded — it only ever used each nested stack's&lt;br&gt;
default. Harmless while the default is what you want; invisible breakage the day someone tries to&lt;br&gt;
flip it.&lt;/p&gt;

&lt;p&gt;This is the third time today a parameter has been declared somewhere it couldn't actually be set&lt;br&gt;
from (see entry 001's &lt;code&gt;HostingerFrom&lt;/code&gt;). &lt;strong&gt;A config value you cannot demonstrate changing the&lt;br&gt;
behaviour of is not configuration — it's a constant with extra steps.&lt;/strong&gt; Worth testing the flip,&lt;br&gt;
not just the default, whenever a flag is introduced.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.kingpin.ltd/blog/two-mechanisms-that-look-redundant/" rel="noopener noreferrer"&gt;Kingpin build log&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>rankingdesign</category>
    </item>
    <item>
      <title>A variable that looked in scope, and wasn't</title>
      <dc:creator>Suraj Mishra</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:19:24 +0000</pubDate>
      <link>https://dev.to/isurajmisra/a-variable-that-looked-in-scope-and-wasnt-gkj</link>
      <guid>https://dev.to/isurajmisra/a-variable-that-looked-in-scope-and-wasnt-gkj</guid>
      <description>&lt;h2&gt;
  
  
  Part 1: the sign-out button that only sometimes existed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The report
&lt;/h3&gt;

&lt;p&gt;A founder came back to &lt;code&gt;/dashboard/&lt;/code&gt; after their session had naturally expired — the&lt;br&gt;
dashboard deliberately issues a 24-hour session (&lt;code&gt;FOUNDER_SESSION_TTL_S&lt;/code&gt;, shorter than the&lt;br&gt;
7-day Certified-voter session, because this one gates money-moving actions) — and saw an&lt;br&gt;
empty "Your cards" list. Not an error. Not a sign-in prompt. Just... nothing, as if they'd&lt;br&gt;
never had a listing.&lt;/p&gt;

&lt;p&gt;That's a worse failure than an error message. An error says "something's wrong, here's what&lt;br&gt;
to do." An empty list says "you have nothing," which for a founder who just paid to be on&lt;br&gt;
Kingpin is a small, specific kind of alarming.&lt;/p&gt;

&lt;p&gt;The fix mandate was direct: &lt;strong&gt;if the session is dead, say so and bounce back to sign-in.&lt;/strong&gt;&lt;br&gt;
Not silently render nothing.&lt;/p&gt;
&lt;h3&gt;
  
  
  The fix, and the bug it introduced
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;getFounderListings()&lt;/code&gt; in &lt;code&gt;web/lib/api.js&lt;/code&gt; already distinguishes a session-dead 401 from any&lt;br&gt;
other failure (&lt;code&gt;/founder/listings&lt;/code&gt; takes no listing id, so every 401 on it is unambiguous —&lt;br&gt;
unlike &lt;code&gt;/refuel&lt;/code&gt; or &lt;code&gt;/media&lt;/code&gt;, there's no "valid session, wrong resource" 401 to confuse it&lt;br&gt;
with). Wiring that into the UI meant: catch that 401, call the dashboard's existing&lt;br&gt;
&lt;code&gt;signOut()&lt;/code&gt;, done.&lt;/p&gt;

&lt;p&gt;The actual edit was one line inside &lt;code&gt;web/app/dashboard/page.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyListings&lt;/span&gt; &lt;span class="na"&gt;onSessionExpired&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your session expired — sign in again.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiled. It passed lint. It looked correct sitting next to the rest of the dashboard's&lt;br&gt;
JSX. And it threw &lt;code&gt;ReferenceError: signOut is not defined&lt;/code&gt; the instant a real browser tried&lt;br&gt;
to run it — but only on the one path that actually exercises it (an expired session), so a&lt;br&gt;
normal signed-in load never touched the broken line at all.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why "it's right there" was wrong
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;web/app/dashboard/page.js&lt;/code&gt; has several &lt;strong&gt;top-level&lt;/strong&gt; function components in one file:&lt;br&gt;
&lt;code&gt;Dashboard()&lt;/code&gt; (where &lt;code&gt;signOut&lt;/code&gt; is actually defined, around line 129), &lt;code&gt;CardLookup()&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;MyListings()&lt;/code&gt;. In the &lt;em&gt;rendered&lt;/em&gt; page, &lt;code&gt;&amp;lt;MyListings&amp;gt;&lt;/code&gt; visually sits "inside" the dashboard.&lt;br&gt;
In the &lt;em&gt;source&lt;/em&gt;, &lt;code&gt;Dashboard()&lt;/code&gt; doesn't render &lt;code&gt;&amp;lt;MyListings&amp;gt;&lt;/code&gt; directly — it renders&lt;br&gt;
&lt;code&gt;&amp;lt;CardLookup session={session} /&amp;gt;&lt;/code&gt;, and &lt;code&gt;CardLookup&lt;/code&gt; is the one that renders &lt;code&gt;&amp;lt;MyListings&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboard()             &amp;lt;- signOut() is defined HERE
  └─ &amp;lt;CardLookup&amp;gt;        &amp;lt;- a SEPARATE top-level function. No access to Dashboard's locals.
       └─ &amp;lt;MyListings&amp;gt;   &amp;lt;- and this is a THIRD one, nested inside CardLookup's own return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;signOut&lt;/code&gt; is a plain local variable of &lt;code&gt;Dashboard&lt;/code&gt;. React component nesting in JSX has&lt;br&gt;
&lt;strong&gt;nothing to do with JavaScript closure scope&lt;/strong&gt; — a function component only sees what's in&lt;br&gt;
its own closure (module scope, its own body) plus whatever arrives as props. &lt;code&gt;CardLookup&lt;/code&gt;&lt;br&gt;
never captured &lt;code&gt;signOut&lt;/code&gt;, because &lt;code&gt;CardLookup&lt;/code&gt; is not defined inside &lt;code&gt;Dashboard&lt;/code&gt;'s function&lt;br&gt;
body; it's a sibling declaration in the same file. The visual nesting in the returned JSX is&lt;br&gt;
a red herring — it tells you about the DOM tree, not the JavaScript scope chain.&lt;/p&gt;

&lt;p&gt;This is an easy mistake precisely because it &lt;em&gt;reads&lt;/em&gt; right. &lt;code&gt;signOut&lt;/code&gt; is right there, a few&lt;br&gt;
dozen lines up, doing exactly the job you want. Nothing about the code around the call site&lt;br&gt;
signals "this identifier is not actually reachable from here."&lt;/p&gt;
&lt;h3&gt;
  
  
  How it was actually found
&lt;/h3&gt;

&lt;p&gt;Not by reading the code again — a second read of code that looks obviously correct rarely&lt;br&gt;
catches its own mistake. By running it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrote the failing-session e2e test first (&lt;code&gt;web/e2e/specs/dashboard.spec.js&lt;/code&gt;, "an expired
session auto-signs-out instead of silently showing 'no listings'"), matching the
&lt;code&gt;mock.js&lt;/code&gt; router's &lt;code&gt;api.fail('founderListings', 401, ...)&lt;/code&gt; pattern already used elsewhere
in the suite.&lt;/li&gt;
&lt;li&gt;Ran it. It failed — not with an assertion mismatch, but a timeout waiting for the sign-in
form to reappear. A timeout tells you "nothing happened," not "why."&lt;/li&gt;
&lt;li&gt;Added Playwright's &lt;code&gt;page.on('pageerror', ...)&lt;/code&gt; fixture temporarily (a &lt;code&gt;consoleErrors&lt;/code&gt;
array pushed to on every uncaught page error) and printed it. That's what surfaced the
exact line: &lt;code&gt;"pageerror: signOut is not defined"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirmed the scope story by mapping every top-level function declaration in the file
(&lt;code&gt;grep -n "^function "&lt;/code&gt;) rather than trusting indentation — indentation in a return
statement tells you nothing about which &lt;code&gt;function&lt;/code&gt; keyword actually owns a variable.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;

&lt;p&gt;Thread &lt;code&gt;onSessionExpired&lt;/code&gt; explicitly as a prop through every layer that needs it, instead of&lt;br&gt;
reaching for a variable that "should" be in scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboard() defines signOut, passes onSessionExpired={() =&amp;gt; signOut(...)} to CardLookup
CardLookup receives onSessionExpired as a prop, forwards it unchanged to MyListings
MyListings calls onSessionExpired?.() — never touches signOut directly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The generalisable lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In a component tree, "visually nested" and "in scope" are different graphs.&lt;/strong&gt; JSX&lt;br&gt;
indentation shows you the render tree. Scope is decided by where a &lt;code&gt;function&lt;/code&gt; keyword's&lt;br&gt;
body literally begins and ends in the source file. When one top-level component's JSX&lt;br&gt;
contains another top-level component's tag, nothing written by the first is reachable&lt;br&gt;
inside the second except through explicit props.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A cheap habit that would have caught this before running anything: before referencing a&lt;br&gt;
name inside a component, ask "whose &lt;code&gt;function { ... }&lt;/code&gt; am I physically inside of right now,"&lt;br&gt;
not "what does this render next to."&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: the card with a hole in the middle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The report
&lt;/h3&gt;

&lt;p&gt;A screenshot: the Arena ladder cards on &lt;code&gt;/board&lt;/code&gt; at phone width had a large, dead patch of&lt;br&gt;
empty vertical space between the climbing avatar and the row below it. "Product listing card&lt;br&gt;
is not very nicely designed for mobile devices."&lt;/p&gt;

&lt;h3&gt;
  
  
  Root cause
&lt;/h3&gt;

&lt;p&gt;Adding vote buttons to each row (this session's board-voting feature) meant adding a new row&lt;br&gt;
to the card's compact layout — call it row 4, sitting under the existing title/stat rows.&lt;br&gt;
That made the card taller. Nothing else about the card's code changed.&lt;/p&gt;

&lt;p&gt;But the &lt;code&gt;Climber&lt;/code&gt; avatar — the little circular monogram that visually races up the ladder —&lt;br&gt;
is &lt;code&gt;position: absolute&lt;/code&gt; with &lt;strong&gt;no explicit &lt;code&gt;top&lt;/code&gt;&lt;/strong&gt;. An absolutely-positioned element with no&lt;br&gt;
inset value doesn't just float free; as a flex child, it still participates in the flex&lt;br&gt;
container's cross-axis alignment (&lt;code&gt;items-center&lt;/code&gt; on the row). Its resting position is&lt;br&gt;
wherever the &lt;em&gt;center&lt;/em&gt; of the row currently is.&lt;/p&gt;

&lt;p&gt;Before the vote row existed, the row was short, so "centered in the row" and "centered on&lt;br&gt;
the title" were close enough to look intentional. Add a row underneath and the row's center&lt;br&gt;
moves down — but the visual anchor the eye expects (the title text) didn't move. The avatar&lt;br&gt;
drifted away from the thing it's supposed to be racing next to, leaving a gap that reads as&lt;br&gt;
"broken," even though every individual style rule was doing exactly what it was told to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;

&lt;p&gt;Not a fix to the avatar's positioning — the actual fix was to not grow the card in the first&lt;br&gt;
place. The vote count was previously a separate &lt;code&gt;dl&lt;/code&gt; "votes" stat &lt;em&gt;and&lt;/em&gt; the vote buttons were&lt;br&gt;
a separate row; those got merged into one &lt;code&gt;ArenaVoteControl&lt;/code&gt; (fire icon, live count, trash&lt;br&gt;
icon, all inline, &lt;code&gt;h-7 w-7&lt;/code&gt; buttons) placed inside the row that already existed, with&lt;br&gt;
&lt;code&gt;flex-wrap&lt;/code&gt; so it only spills to a second line on the very narrowest phones instead of&lt;br&gt;
unconditionally reserving the space.&lt;/p&gt;

&lt;h3&gt;
  
  
  The generalisable lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A layout bug that "makes no sense" from the changed code alone is often downstream of an&lt;br&gt;
element with an implicit anchor&lt;/strong&gt; — &lt;code&gt;position: absolute&lt;/code&gt; with no inset, &lt;code&gt;flex&lt;/code&gt; alignment&lt;br&gt;
with no explicit &lt;code&gt;align-self&lt;/code&gt;, a CSS variable scoped to the wrong ancestor (see entry 001's&lt;br&gt;
footer). The element didn't change. The thing it was silently anchored to did.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before adding a new row/column to any layout that has an absolutely-positioned or&lt;br&gt;
implicitly-aligned child, check what that child's position is actually relative to — and&lt;br&gt;
whether growing the container moves that reference point.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.kingpin.ltd/blog/a-variable-that-looked-in-scope/" rel="noopener noreferrer"&gt;Kingpin build log&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>react</category>
      <category>debugging</category>
    </item>
    <item>
      <title>A cache with no way back to the source</title>
      <dc:creator>Suraj Mishra</dc:creator>
      <pubDate>Tue, 15 Sep 2026 09:16:59 +0000</pubDate>
      <link>https://dev.to/isurajmisra/a-cache-with-no-way-back-to-the-source-5dja</link>
      <guid>https://dev.to/isurajmisra/a-cache-with-no-way-back-to-the-source-5dja</guid>
      <description>&lt;h2&gt;
  
  
  The report
&lt;/h2&gt;

&lt;p&gt;A founder listed an X (Twitter) profile — Kingpin lets you point a listing at&lt;br&gt;
&lt;code&gt;https://x.com/&amp;lt;handle&amp;gt;&lt;/code&gt; instead of a normal website. On their dashboard, the picker button&lt;br&gt;
for that listing showed no usable name. Not their handle, not their profile, nothing worth&lt;br&gt;
recognizing as "mine." The ask was specific: it should show either the founder's own profile&lt;br&gt;
name, or the X handle (&lt;code&gt;@username&lt;/code&gt;) — and it should never be a dead end for &lt;em&gt;any&lt;/em&gt; listing,&lt;br&gt;
generic or social.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two sources of the same fact, one of them frozen at birth
&lt;/h2&gt;

&lt;p&gt;Checkout deliberately asks for nothing but a URL — no title field on the add-listing form.&lt;br&gt;
So a listing's name doesn't exist yet the moment money lands; it's filled in later, two&lt;br&gt;
different ways depending on the listing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for a normal site, &lt;code&gt;lib/sitemeta.mjs&lt;/code&gt;'s &lt;code&gt;captureSiteMeta()&lt;/code&gt; fetches the destination page,
during async moderation, and adopts its real &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;/&lt;code&gt;og:title&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;for an X or Instagram profile, fetching is close to useless — both platforms serve a
logged-out crawler a login wall, so the page's own &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; says "X. It's what's
happening," not the founder's name. &lt;code&gt;lib/social.mjs&lt;/code&gt;'s &lt;code&gt;socialTitle()&lt;/code&gt; exists specifically
to produce &lt;code&gt;@handle&lt;/code&gt; instead, and &lt;code&gt;captureSiteMeta&lt;/code&gt; prefers it when the URL is recognized
as a social profile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That part works. The canonical listing row (&lt;code&gt;CATEGORY#&amp;lt;cat&amp;gt;/ITEM#&amp;lt;id&amp;gt;&lt;/code&gt;) does end up with&lt;br&gt;
&lt;code&gt;Title: '@theirhandle'&lt;/code&gt; once moderation runs.&lt;/p&gt;

&lt;p&gt;The dashboard's picker, though, doesn't read the canonical row. It reads a &lt;strong&gt;separate&lt;/strong&gt;&lt;br&gt;
ownership-index row, &lt;code&gt;FOUNDER#&amp;lt;id&amp;gt;/LISTING#&amp;lt;cat&amp;gt;#&amp;lt;id&amp;gt;&lt;/code&gt; (&lt;code&gt;founder-listings.mjs&lt;/code&gt;, a bounded&lt;br&gt;
&lt;code&gt;Query&lt;/code&gt; on the founder's own session — the right call, since the alternative is Querying&lt;br&gt;
every category's listings and filtering, which doesn't scale and isn't even ownership-scoped).&lt;br&gt;
That row exists so the dashboard doesn't have to fan out across every category to find "my&lt;br&gt;
listings," and it carries its own copy of &lt;code&gt;Title&lt;/code&gt; for exactly the reason indexes usually&lt;br&gt;
duplicate a field: so the read that needs it doesn't have to join.&lt;/p&gt;

&lt;p&gt;The bug is in &lt;em&gt;when&lt;/em&gt; that copy gets written. &lt;code&gt;linkFounderToListing()&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;src/lambda/lib/founder.mjs&lt;/code&gt;) runs synchronously inside the checkout handler&lt;br&gt;
(&lt;code&gt;payments-consumer.mjs&lt;/code&gt;), &lt;strong&gt;before&lt;/strong&gt; the listing has gone through moderation, which is where&lt;br&gt;
&lt;code&gt;captureSiteMeta&lt;/code&gt; actually runs. At that moment there is no real title yet — checkout never&lt;br&gt;
asked for one. So the ownership-index row gets written with &lt;code&gt;Title: ''&lt;/code&gt;, and then nothing&lt;br&gt;
ever touches it again. &lt;code&gt;captureSiteMeta&lt;/code&gt; faithfully updates the canonical row later; it has&lt;br&gt;
no idea a second, independent copy of the same fact exists elsewhere and needs the same&lt;br&gt;
update.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checkout (payments-consumer.mjs)
  → writes CATEGORY#/ITEM# row,        Title = hostname placeholder
  → writes FOUNDER#/LISTING# row,      Title = ''                    ← frozen here, forever
  → enqueues moderation

moderation (moderate.mjs, async, seconds later)
  → captureSiteMeta() updates CATEGORY#/ITEM#,   Title = '@handle'   ← the real value lands
  → (nothing updates FOUNDER#/LISTING#)                              ← stays ''
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same shape of bug as ADR-027/entry 003's decay-vs-downvotes question, just&lt;br&gt;
one level more mundane: &lt;strong&gt;two representations of one fact, updated on different schedules,&lt;br&gt;
with nothing keeping them in sync.&lt;/strong&gt; An index is a cache. A cache that's written once and&lt;br&gt;
never invalidated isn't a performance optimization, it's a slow-motion staleness bug with a&lt;br&gt;
delay before it's noticed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix: write-time sync, plus a read-time self-heal
&lt;/h2&gt;

&lt;p&gt;Two changes, addressing two different timeframes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Going forward — keep the copies from drifting apart.&lt;/strong&gt; &lt;code&gt;captureSiteMeta()&lt;/code&gt; now asks&lt;br&gt;
DynamoDB to hand back the updated canonical row (&lt;code&gt;ReturnValues: 'ALL_NEW'&lt;/code&gt; on the &lt;code&gt;UpdateItem&lt;/code&gt;&lt;br&gt;
it already had to do) and, if a &lt;code&gt;FounderId&lt;/code&gt; is present on it, writes the same title into the&lt;br&gt;
ownership-index row right there:&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;// lib/sitemeta.mjs, right after the canonical Title/TitleSafe write&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;founderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;FounderId&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;title&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;founderId&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UpdateCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;PK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`FOUNDER#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;founderId&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;SK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`LISTING#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;category&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;listingId&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;UpdateExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SET Title = :t&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ConditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;attribute_exists(PK)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ExpressionAttributeValues&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;:t&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;title&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="nx"&gt;err&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;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&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;// best-effort — never blocks a paid listing&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 deliberately best-effort and swallowed on failure, for the same reason every write in&lt;br&gt;
this function already is: this is decoration on top of money that has already been captured.&lt;br&gt;
A missed sync here should never be able to fail a real listing.&lt;/p&gt;

&lt;p&gt;It's also deliberately &lt;em&gt;not&lt;/em&gt; guaranteed to run before &lt;code&gt;FounderId&lt;/code&gt; exists —&lt;br&gt;
&lt;code&gt;linkFounderToListing&lt;/code&gt; and &lt;code&gt;captureSiteMeta&lt;/code&gt; fire from two different Lambda invocations&lt;br&gt;
(checkout vs. moderation) with no ordering contract between them. If moderation happens to&lt;br&gt;
run first, &lt;code&gt;FounderId&lt;/code&gt; won't be on the row yet, and this sync silently does nothing. That's&lt;br&gt;
why there's a second half to the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Already-broken rows — self-heal on read, instead of waiting for a rewrite that will never&lt;br&gt;
come.&lt;/strong&gt; The founder who reported this has a listing whose ownership-index &lt;code&gt;Title&lt;/code&gt; was already&lt;br&gt;
frozen at &lt;code&gt;''&lt;/code&gt; &lt;em&gt;before this fix existed at all&lt;/em&gt; — the write-time fix only prevents new drift,&lt;br&gt;
it can't retroactively repair a value that was already written wrong. &lt;code&gt;founder-listings.mjs&lt;/code&gt;&lt;br&gt;
now checks for that case directly:&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;// GET /founder/listings, per row&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Title&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;title&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;live&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canonicalKey&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt; &lt;span class="c1"&gt;// one point read&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;live&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;TitleSafe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;live&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TitleSafe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// repair the cached row so this founder's next load doesn't pay the read again&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UpdateCommand&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;indexKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="na"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;title&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="o"&gt;=&amp;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;This isn't a &lt;code&gt;Scan&lt;/code&gt;, and it isn't the public feed/board read path — invariant 5 ("reads never&lt;br&gt;
hit DynamoDB") governs the materialized, unauthenticated, high-traffic surfaces; this is a&lt;br&gt;
single authenticated founder's own small, already-bounded result set, and the extra read only&lt;br&gt;
happens for the specific rows that are actually blank. It fixes the founder's dashboard on&lt;br&gt;
their very next load, with no backfill script and no manual data fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Untitled" stays as the last-resort fallback
&lt;/h2&gt;

&lt;p&gt;A brand-new listing that hasn't been through moderation yet genuinely has no title anywhere —&lt;br&gt;
neither copy exists. Showing "Untitled" for &lt;em&gt;that&lt;/em&gt; case is honest: it says "this is real,&lt;br&gt;
it's just not named yet," which is different from silently mislabeling a properly-titled&lt;br&gt;
listing. The bug wasn't that a fallback string exists; it was that the fallback was&lt;br&gt;
triggering for listings that unambiguously &lt;em&gt;did&lt;/em&gt; have a real name sitting one table read&lt;br&gt;
away, because the code was looking in the one place that had never been told about it.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before adding a second copy of a fact for a legitimate reason (an index, a cache, a&lt;br&gt;
denormalized field for a query pattern the primary shape can't serve), write down every&lt;br&gt;
place the &lt;em&gt;first&lt;/em&gt; copy can change after the second one is created — and make each of those&lt;br&gt;
writers responsible for the copy too, not just the field's original writer.&lt;/strong&gt; A denormalized&lt;br&gt;
field's staleness bug never announces itself at write time; it only shows up later, to&lt;br&gt;
whoever reads the stale copy, looking exactly like a missing feature rather than a sync gap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And, paired with it: a &lt;strong&gt;self-healing read&lt;/strong&gt; is worth adding whenever a cache can already be&lt;br&gt;
stale in production before the write-time fix ships — it turns "we shipped a fix, but&lt;br&gt;
existing users are still broken until some backfill job runs" into "existing users are fixed&lt;br&gt;
on their very next request," for the cost of one conditional point-read on the empty-cache&lt;br&gt;
path only.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.kingpin.ltd/blog/a-cache-with-no-way-back/" rel="noopener noreferrer"&gt;Kingpin build log&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dynamodb</category>
      <category>datadesign</category>
    </item>
  </channel>
</rss>
