<?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: Sahil Rao</title>
    <description>The latest articles on DEV Community by Sahil Rao (@sahilyadavy251tech).</description>
    <link>https://dev.to/sahilyadavy251tech</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%2F4196186%2Fa5398672-373a-4c4b-a914-2cba431c4502.png</url>
      <title>DEV Community: Sahil Rao</title>
      <link>https://dev.to/sahilyadavy251tech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sahilyadavy251tech"/>
    <language>en</language>
    <item>
      <title>How I shipped a Nadi (BNN) astrology calculator as one 200 KB HTML file</title>
      <dc:creator>Sahil Rao</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:32:35 +0000</pubDate>
      <link>https://dev.to/sahilyadavy251tech/how-i-shipped-a-nadi-bnn-astrology-calculator-as-one-200-kb-html-file-flm</link>
      <guid>https://dev.to/sahilyadavy251tech/how-i-shipped-a-nadi-bnn-astrology-calculator-as-one-200-kb-html-file-flm</guid>
      <description>&lt;p&gt;A working Vedic astrology tool that computes planetary positions, finds Nadi bonds and projects slow-planet progression across a lifetime — with no build step, no npm, no server and no network calls.&lt;/p&gt;

&lt;p&gt;Here is what that constraint forced me to do, and the one bug that cost me the most time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint
&lt;/h2&gt;

&lt;p&gt;The tool had to run from a single file, opened straight from disk, on a phone, with no internet. That rules out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a CDN for the ephemeris&lt;/li&gt;
&lt;li&gt;a bundler&lt;/li&gt;
&lt;li&gt;a server to do the astronomy&lt;/li&gt;
&lt;li&gt;a timezone API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything has to be inside one &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — inlining an ephemeris
&lt;/h2&gt;

&lt;p&gt;Accurate planetary longitudes are the hard part. I did not want to write my own VSOP87 implementation, so I inlined the excellent &lt;a href="https://github.com/cosinekitty/astronomy" rel="noopener noreferrer"&gt;Astronomy Engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Astronomy Engine ships as a UMD bundle. Dropping it into a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag works — it detects the absence of &lt;code&gt;module&lt;/code&gt; and &lt;code&gt;define&lt;/code&gt; and assigns itself to &lt;code&gt;window.Astronomy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then my own engine reads 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Astro&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;Astronomy&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="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Astronomy&lt;/span&gt;
  &lt;span class="p"&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;require&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astronomy-engine&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 — the bug that cost me an hour
&lt;/h2&gt;

&lt;p&gt;The tool worked in my browser test. It did not work in Node. That is normal and expected — the UMD wrapper takes the CommonJS branch under Node.&lt;/p&gt;

&lt;p&gt;But then the browser version broke too, with a single unhelpful line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReferenceError: $jscomp is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astronomy Engine is compiled by Google Closure, and the compiled output depends on a &lt;code&gt;$jscomp&lt;/code&gt; runtime shim — iterator helpers, &lt;code&gt;makeIterator&lt;/code&gt;, &lt;code&gt;arrayIteratorImpl&lt;/code&gt;, and a dozen others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That shim is emitted at the very top of the bundle, before the UMD wrapper.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I extracted the library to inline it, I started my slice at the UMD wrapper. I had left the shim behind.&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;var&lt;/span&gt; &lt;span class="nx"&gt;$jscomp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;$jscomp&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;&lt;span class="nx"&gt;$jscomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;$jscomp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makeIterator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kd"&gt;function&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="c1"&gt;// ... about 110 KB of Closure helpers and the library itself ...&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&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="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;Astronomy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;r&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is one character: start the slice at byte 0, not at the wrapper.&lt;/p&gt;

&lt;p&gt;The lesson is general. If you inline a Closure-compiled bundle, you have to take the runtime shim with it. It does not announce itself; it fails only when the first compiled function that needs a helper is actually called. That is why the page rendered fine and only broke when I pressed the button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — what Bhrigu Nandi Nadi actually computes
&lt;/h2&gt;

&lt;p&gt;BNN is a Jaimini technique that ignores houses entirely and reads relationships between planets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonds (yuti).&lt;/strong&gt; Two planets in the same sign are in a bond. The planet with the lower degree &lt;em&gt;leads&lt;/em&gt; it. That leading planet is the one that expresses first.&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;groups&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;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;PLANETS&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="nf"&gt;signOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;groups&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;=&lt;/span&gt; &lt;span class="nx"&gt;groups&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;||&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;p&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;Each pair inside a group then gets a relation from a classical friend/enemy table:&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;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;NADI_FRIEND&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;NADI_FRIEND&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;f&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Friendly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inimical&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;Neutral&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;&lt;strong&gt;Trine circuits.&lt;/strong&gt; Every planet is placed in one of four circuits — Dharma (1, 5, 9), Artha (2, 6, 10), Kama (3, 7, 11), Moksha (4, 8, 12).&lt;/p&gt;

&lt;p&gt;That is the whole technique. No houses, no aspects. Just sign co-tenancy and degree order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — slow-planet progression
&lt;/h2&gt;

&lt;p&gt;Progression here means a planet advancing one sign at a fixed rate across a life:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Planet&lt;/th&gt;
&lt;th&gt;Years per sign&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jupiter&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saturn&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rahu / Ketu&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A retrograde planet progresses &lt;em&gt;backwards&lt;/em&gt;. The first transition is partial — the planet finishes the degrees left in its birth sign first:&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;frac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;degInSign&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&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;majorLeft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;frac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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;major&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there each subsequent sign takes the full period. The output is a table of age ranges, which is the useful form: "Jupiter sits in Libra from age 0 to 4.5, then Scorpio to 16.5."&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — keeping it offline
&lt;/h2&gt;

&lt;p&gt;Two things had to go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The timezone database.&lt;/strong&gt; A real tz database is large, and historical offsets for India alone are messy. I replaced it with a fixed UTC offset the user selects. Less clever, much smaller, and honest about its limits — the UI says so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The network.&lt;/strong&gt; Nothing is fetched. No fonts, no icons, no analytics. The whole thing is 206 KB and works on a plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test the inlined bundle in isolation first.&lt;/strong&gt; A five-line page that calls one function from the library would have caught the missing shim in thirty seconds instead of an hour of staring at my own UI code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the engine and the UI in separate files during development.&lt;/strong&gt; I concatenate them into one file at the end. Editing a 200 KB file directly is miserable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write the engine as a factory that exports a namespace.&lt;/strong&gt; Mine does — &lt;code&gt;root.KV = factory()&lt;/code&gt; — which is why the same file can be driven from a page, an iframe, or a test harness.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;The tool is at &lt;a href="https://sahilyadavy251-tech.github.io/nadi-bnn-progression/" rel="noopener noreferrer"&gt;sahilyadavy251-tech.github.io/nadi-bnn-progression&lt;/a&gt; and the source is on &lt;a href="https://github.com/sahilyadavy251-tech/nadi-bnn-progression" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. MIT licensed — fork it, embed it, ship it inside your own app.&lt;/p&gt;

&lt;p&gt;It is extracted from &lt;a href="https://advanced-occult-astrology.pages.dev" rel="noopener noreferrer"&gt;Advanced Occult Astrology&lt;/a&gt;, a free Vedic astrology app I maintain with 20 divisional charts, Vimshottari dasha to four levels, Shadbala, Ashtakavarga, Jaimini Chara Karakas and the KP system.&lt;/p&gt;

&lt;p&gt;If you have inlined a compiled bundle before and hit the same shim problem, I would like to hear how you caught it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>astrology</category>
    </item>
  </channel>
</rss>
