<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Divyansh Rawat — Blogs and Writing</title>
        <link>https://divyansh.is-a.dev/blog</link>
        <atom:link href="https://divyansh.is-a.dev/feed.xml" rel="self" type="application/rss+xml"/>
        <description>Notes on backend systems, agentic AI and the things I ship.</description>
        <language>en</language>
        <item>
            <title>The contribution heatmap on this site draws fewer weeks on a phone</title>
            <link>https://divyansh.is-a.dev/blog/heatmaps-that-fit-the-phone</link>
            <guid isPermaLink="true">https://divyansh.is-a.dev/blog/heatmaps-that-fit-the-phone</guid>
            <pubDate>Tue, 18 Aug 2026 09:00:00 GMT</pubDate>
            <description>A 53-week grid is 742px wide. No phone can show that, so the grid asks how much room it has and draws only the columns that fit.</description>
            <content:encoded><![CDATA[                <p>Every GitHub-style contribution heatmap has the same problem on mobile. The grid is 53 columns of 7 cells. At a 12px cell with a 2px gap that is 742 pixels, and the widest phone in common use gives you about 390. Most sites solve it by putting the grid in a horizontal scroller.</p>
                <p>That solution is worse than it looks. The most interesting part of a contribution graph is the right-hand edge, because that is this week. Put it in a scroller and the default view shows a year ago, with the part anyone cares about parked off-screen behind a gesture nobody performs.</p>
                <h2 id="ask-the-container-first">Ask the container first<a class="anchor" href="#ask-the-container-first" aria-label="Link to this section">#</a></h2>
                <p>The grid on this page measures its own host before it draws anything:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">weeksThatFit</span>(<span class="hljs-params">prefix</span>) {
                    <span class="hljs-keyword">var</span> grid = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(prefix + <span class="hljs-string">&#x27;-grid&#x27;</span>);
                    <span class="hljs-keyword">var</span> host = grid &amp;&amp; grid.<span class="hljs-property">parentElement</span>;
                    <span class="hljs-keyword">var</span> w = host ? host.<span class="hljs-property">clientWidth</span> : <span class="hljs-number">0</span>;
                    <span class="hljs-keyword">if</span> (!w) <span class="hljs-keyword">return</span> <span class="hljs-variable constant_">WEEKS_MAX</span>;
                    <span class="hljs-keyword">return</span> <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">max</span>(<span class="hljs-number">10</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">min</span>(<span class="hljs-variable constant_">WEEKS_MAX</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(w / <span class="hljs-title function_">cellStep</span>())));
                }</code></pre></div>
                <p><code>cellStep()</code> returns 14 on desktop and 12 below 560px, matching the cell and gap sizes in the stylesheet. Whatever comes back is how many week-columns get rendered, ending with the current week. On a laptop that is the full 53. On a phone it is closer to 30.</p>
                <p>The count under the grid then has to stop lying about the range:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">var</span> span = <span class="hljs-variable constant_">WEEKS</span> &gt;= <span class="hljs-variable constant_">WEEKS_MAX</span>
                    ? <span class="hljs-string">&#x27;in the last year&#x27;</span>
                    : <span class="hljs-string">&#x27;in the last &#x27;</span> + <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">max</span>(<span class="hljs-number">1</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">round</span>(<span class="hljs-variable constant_">WEEKS</span> / <span class="hljs-number">4.345</span>)) + <span class="hljs-string">&#x27; months&#x27;</span>;</code></pre></div>
                <p>So a phone reads &quot;412 contributions in the last 7 months&quot; instead of claiming a year it never drew. Fewer weeks, no scrollbar, and the label is still true.</p>
                <p>There is one catch. Column count depends on width, so rotating the phone invalidates the layout. A resize listener re-runs <code>weeksThatFit</code> and only re-renders when the number actually changed, which is why the rendered counts are kept in memory rather than refetched.</p>
                <h2 id="shading-against-yourself-not-against-a-constant">Shading against yourself, not against a constant<a class="anchor" href="#shading-against-yourself-not-against-a-constant" aria-label="Link to this section">#</a></h2>
                <p>The other thing worth doing differently is the colour ramp. Fixed thresholds — 1 commit is level one, 10 is level four — make a normal month look empty next to whoever you borrowed the thresholds from.</p>
                <p>These cells are bucketed against the quartiles of the person&#39;s own non-zero days:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">var</span> q = <span class="hljs-keyword">function</span> (<span class="hljs-params">p</span>) { <span class="hljs-keyword">return</span> vals[<span class="hljs-title class_">Math</span>.<span class="hljs-title function_">min</span>(vals.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>, <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(vals.<span class="hljs-property">length</span> * p))]; };
                <span class="hljs-keyword">var</span> t1 = <span class="hljs-title function_">q</span>(<span class="hljs-number">0.25</span>), t2 = <span class="hljs-title function_">q</span>(<span class="hljs-number">0.5</span>), t3 = <span class="hljs-title function_">q</span>(<span class="hljs-number">0.75</span>);</code></pre></div>
                <p>A quiet year still shows contrast, because the scale is relative to that year. It is a graph of your rhythm, not a leaderboard.</p>
                <h2 id="codeforces-makes-you-work-for-it">Codeforces makes you work for it<a class="anchor" href="#codeforces-makes-you-work-for-it" aria-label="Link to this section">#</a></h2>
                <p>The last piece is unrelated to layout but was the thing that actually kept the card blank. Codeforces rate-limits to roughly one call every two seconds, and it signals a violation by returning <strong>HTTP 200</strong> with <code>{&quot;status&quot;:&quot;FAILED&quot;}</code> in the body.</p>
                <blockquote>
                <p>A 200 that means failure defeats every <code>fetch().then()</code> you have written. <code>r.ok</code> is true, <code>JSON.parse</code> succeeds, and you render a card from an error.</p>
                </blockquote>
                <p>Two things fix it. Check <code>status</code> rather than the HTTP code, and space the calls out instead of firing them together:</p>
                <div class="code-wrap"><span class="code-lang">js</span><button class="code-copy" type="button" aria-label="Copy code">Copy</button><pre><code class="hljs language-js"><span class="hljs-keyword">function</span> <span class="hljs-title function_">cfUnwrap</span>(<span class="hljs-params">d</span>) {
                    <span class="hljs-keyword">if</span> (!d || d.<span class="hljs-property">status</span> !== <span class="hljs-string">&#x27;OK&#x27;</span> || !d.<span class="hljs-property">result</span>) {
                        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(d &amp;&amp; d.<span class="hljs-property">comment</span> ? d.<span class="hljs-property">comment</span> : <span class="hljs-string">&#x27;CF request failed&#x27;</span>);
                    }
                    <span class="hljs-keyword">return</span> d.<span class="hljs-property">result</span>;
                }</code></pre></div>
                <p>The card also has a serverless proxy behind it as a second route, which helps mostly because it comes from a different source IP and caches at the edge.</p>
                <p>And when both routes fail, the card does not disappear. It renders an empty grid with a note saying the API could not be reached. A block that silently vanishes reads as a broken layout. A block that says what went wrong reads as a site someone maintains.</p>]]></content:encoded>
            <category>frontend</category>
            <category>ux</category>
        </item>
        <item>
            <title>You cannot be dumb and lazy at the same time</title>
            <link>https://divyansh.is-a.dev/blog/you-cannot-be-dumb-and-lazy-at-the-same-time</link>
            <guid isPermaLink="true">https://divyansh.is-a.dev/blog/you-cannot-be-dumb-and-lazy-at-the-same-time</guid>
            <pubDate>Tue, 18 Aug 2026 09:00:00 GMT</pubDate>
            <description>If you are lazy, you have to be sharp enough to solve things in ten lines. If you are still building intuition, you have to outwork the problem. Trying to do both is how you end up staring at broken builds all weekend.</description>
            <content:encoded><![CDATA[                <p>There is a blunt rule of thumb that holds up everywhere in engineering: you can be unexceptional, or you can be lazy, but you cannot afford to be both.</p>
                <p>If you are terrifyingly sharp, you can get away with working four hours a day. You glance at a stack trace, spot the unindexed database scan or the off-by-one race condition in three minutes, write a fifteen-line fix, and move on. Your leverage is high enough that your output looks effortless.</p>
                <p>If you aren&#39;t that person—and almost nobody is when they start—you still have a completely viable path: you just outwork the gap. You pull down the library&#39;s source code instead of guessing what the docs meant. You fire up a debugger and step through twenty stack frames. You write a script that generates ten thousand edge-case payloads until the bug reproduces locally. It takes longer, but you arrive at the exact same truth.</p>
                <p>The disaster is when people try to borrow the work habits of the first person while possessing the context of the second.</p>
                <h2 id="the-guessing-game">The guessing game<a class="anchor" href="#the-guessing-game" aria-label="Link to this section">#</a></h2>
                <p>You see this most clearly in how people debug.</p>
                <p>When someone lacks both deep understanding and the patience to grind, debugging turns into superstition. They change a variable name, wrap a synchronous call in an unnecessary retry loop, sprinkle random null-checks, and rerun the test suite. If it passes once, they push to main and pray nobody touches it again.</p>
                <p>Nothing was diagnosed. No invariants were verified. The underlying race condition or memory leak is still there, quietly waiting for traffic to spike.</p>
                <p>It happens because genuine diagnosis is tedious:</p>
                <ul>
                <li>Reading a 40-page protocol spec when you only care about three fields.</li>
                <li>Profiling memory allocations under load instead of blindly bumping the container&#39;s RAM limit.</li>
                <li>Tracing raw TCP packets when a connection drops silently behind a reverse proxy.</li>
                </ul>
                <p>None of that is glamorous work. It is mostly reading, testing hypotheses, and being wrong ten times in a row before finding the one line that actually matters.</p>
                <h2 id="the-leverage-tax">The leverage tax<a class="anchor" href="#the-leverage-tax" aria-label="Link to this section">#</a></h2>
                <p>Productive laziness is real, but it is an earned state.</p>
                <p>The engineers who build clean, minimal systems aren&#39;t &quot;lazy&quot; in the sense that they skip the hard parts. They are lazy in the sense that they refuse to solve the same problem twice. They spend three days designing a clean abstraction or writing an idempotent pipeline specifically so they never have to wake up for an on-call alert on Saturday.</p>
                <p>That kind of simplicity requires enormous upfront cognitive work. Making something simple is much harder than making something complicated.</p>
                <p>If you don&#39;t have the mental models yet to build that leverage, you can&#39;t skip straight to the relaxed afternoon. Your only real option is to put in the hours: read the manuals, write the test harnesses, inspect the logs, and build the intuition from scratch.</p>
                <p>One handicap is workable. Both at once is just negligence.</p>]]></content:encoded>
            <category>engineering</category>
            <category>craft</category>
        </item>
    </channel>
</rss>
