<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://anvox.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://anvox.github.io/" rel="alternate" type="text/html" /><updated>2026-04-07T08:02:33+00:00</updated><id>https://anvox.github.io/feed.xml</id><title type="html">anvox</title><subtitle>A collection of super short text to summarize what I learn everyday.</subtitle><entry><title type="html">📖 “Tao of React” take away</title><link href="https://anvox.github.io/tech-reading/react/2025/03/17/tao-of-react-take-away.html" rel="alternate" type="text/html" title="📖 “Tao of React” take away" /><published>2025-03-17T03:55:00+00:00</published><updated>2025-03-17T03:55:00+00:00</updated><id>https://anvox.github.io/tech-reading/react/2025/03/17/tao-of-react-take-away</id><content type="html" xml:base="https://anvox.github.io/tech-reading/react/2025/03/17/tao-of-react-take-away.html"><![CDATA[<h2 id="about-the-book-tao-of-react">About the book: Tao of React</h2>
<p>You could find the <a href="https://alexkondov.com/tao-of-react/">☁️ 📖 Online version here</a>. Or buy it <a href="https://www.taoofreact.com/">here</a> which I recommend you should. I remind author’s introduction.</p>

<blockquote>
  <p>Tao of React is for beginners who want to advance further in their knowledge. The best time to read it is when you’ve grasped the fundamentals and are starting to build real applications.</p>
</blockquote>

<p>You should read this after couple months to a year of using react. It won’t bring more value to the fresher, but when familar with react, the book will help you advance your react code. I don’t mean all the things in this book are the best practices. The author just gave some recommendations for each topic, and including the pros and cons, or reasoning on which is good in which case. Besides, the book also focus on organizing code, balancing testing - things I belive are the key differences between senior and junior mindset. The book are easy to read and adapt, even with a backend dev like me.</p>

<h2 id="take-away">Take away</h2>

<p>I just note some take away to make the longer 😜 for a full experience, you should try it yourself.</p>

<ul>
  <li>I love the idea react component should be simple and focus on rendering, split helpers. Let’s the custom hook control the logic behind. Therefor, the code of component will be simply straightforward, like the <a href="https://pragprog.com/titles/agcr/confident-ruby/">Confident ruby</a>, the code should be like story telling: State, logic, condition, render.</li>
  <li>Testing 🤓 the book pointed out the differences between logic-focus testing (on backend) and UI-focus testing (on frontend), and suggest the balanced approaching for good amount of both, to achieve the main goal: quality.</li>
  <li>Use loops and configuration objects - yes, simple, but I have to review this smell over and over.</li>
</ul>]]></content><author><name></name></author><category term="tech-reading" /><category term="react" /><summary type="html"><![CDATA[About the book: Tao of React You could find the ☁️ 📖 Online version here. Or buy it here which I recommend you should. I remind author’s introduction.]]></summary></entry><entry><title type="html">My first custom elixir dataloader</title><link href="https://anvox.github.io/elixir/dataloader/experiment/til/2025/03/16/my-first-custom-elixir-dataloader.html" rel="alternate" type="text/html" title="My first custom elixir dataloader" /><published>2025-03-16T15:19:00+00:00</published><updated>2025-03-16T15:19:00+00:00</updated><id>https://anvox.github.io/elixir/dataloader/experiment/til/2025/03/16/my-first-custom-elixir-dataloader</id><content type="html" xml:base="https://anvox.github.io/elixir/dataloader/experiment/til/2025/03/16/my-first-custom-elixir-dataloader.html"><![CDATA[<p>I could safely assume that, if you use absinthe with ecto, you are almost familiar with dataloader. The idea is simple, defering query data queries, mostly, for relations, then query in batch at once. It integrates smoothly with ecto relations that I sometime thought it’s natually magic.</p>

<p>Until some anti-patterns come. I’m not sure if these anti-patterns are traditional approaches, or are they fit to the context. But trying to make run leads me to some funny experiments and allowed me understand absinthe, dataloader better. Let’s move on.</p>

<h2 id="one-way-rellation">One way rellation</h2>

<p>Consider schemas. Normally, we have product has_one profile, and vs profile belongs_to product. 
But in my graph, <code class="language-plaintext highlighter-rouge">External</code> depends on <code class="language-plaintext highlighter-rouge">Products</code> and <code class="language-plaintext highlighter-rouge">Products</code> should know nothing about <code class="language-plaintext highlighter-rouge">External</code>, even the module name. Every business functions will be kept on <code class="language-plaintext highlighter-rouge">External</code>.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Products</span><span class="o">.</span><span class="no">Product</span> <span class="k">do</span>
  <span class="n">schema</span> <span class="s2">"products"</span> <span class="k">do</span>
    <span class="c1"># Lots of fields</span>
    <span class="c1"># has_one(:external_profile, External.Profile)</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">defmodule</span> <span class="no">External</span><span class="o">.</span><span class="no">Profile</span> <span class="k">do</span>
  <span class="n">alias</span> <span class="no">Products</span><span class="o">.</span><span class="no">Product</span>

  <span class="n">schema</span> <span class="s2">"external_profiles"</span> <span class="k">do</span>
    <span class="n">belongs_to</span><span class="p">(</span><span class="ss">:product</span><span class="p">,</span> <span class="no">Product</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Thing went well, until exposing to graphql, I had 2 options, creating a root query for profile, or making it a product’s field - which is obviously, clearer and lesser work. But without <code class="language-plaintext highlighter-rouge">has_one</code> - there’s no magic powder to let absinthe know how to load it. So I had to make my own magic powder. It was the point I unveiled the not-so-magic of dataloader.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">def</span> <span class="n">dataloader</span><span class="p">()</span> <span class="k">do</span>
    <span class="k">fn</span> <span class="n">parent</span><span class="p">,</span> <span class="n">_args</span><span class="p">,</span> <span class="p">%{</span><span class="ss">context:</span> <span class="p">%{</span><span class="ss">loader:</span> <span class="n">loader</span><span class="p">}}</span> <span class="o">-&gt;</span>
      <span class="n">product_id</span> <span class="o">=</span>
        <span class="k">case</span> <span class="n">parent</span> <span class="k">do</span>
          <span class="p">%</span><span class="no">Product</span><span class="p">{</span><span class="ss">id:</span> <span class="n">product_id</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">product_id</span>
          <span class="p">%{</span><span class="ss">product_id:</span> <span class="n">product_id</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">product_id</span>
          <span class="n">_</span> <span class="o">-&gt;</span> <span class="no">nil</span>
        <span class="k">end</span>

      <span class="n">loader</span>
      <span class="o">|&gt;</span> <span class="no">Dataloader</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:one</span><span class="p">,</span> <span class="bp">__MODULE__</span><span class="p">},</span> <span class="ss">product_id:</span> <span class="n">product_id</span><span class="p">)</span>
      <span class="o">|&gt;</span> <span class="n">on_load</span><span class="p">(</span><span class="k">fn</span> <span class="n">loader</span> <span class="o">-&gt;</span>
        <span class="p">{</span>
          <span class="ss">:ok</span><span class="p">,</span>
          <span class="no">Dataloader</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">loader</span><span class="p">,</span> <span class="bp">__MODULE__</span><span class="p">,</span> <span class="p">{</span><span class="ss">:one</span><span class="p">,</span> <span class="bp">__MODULE__</span><span class="p">},</span> <span class="ss">product_id:</span> <span class="n">product_id</span><span class="p">)</span>
        <span class="p">}</span>
      <span class="k">end</span><span class="p">)</span>
    <span class="k">end</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>In the end, it’s just 2 parts: loading and getting. The most important here is the way we extract the key to load and get. In this case, the product_id from profile. Without <code class="language-plaintext highlighter-rouge">has_one</code>, we must define it ourself.</p>

<h2 id="array-uuid-field">Array UUID field</h2>

<p>The same approach when I once tried to load list of participants from list of participant id.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">Order</span> <span class="k">do</span>
  <span class="n">schema</span> <span class="s2">"orders"</span> <span class="k">do</span>
    <span class="c1"># ...</span>
    <span class="n">field</span> <span class="ss">:participants</span><span class="p">,</span> <span class="p">{</span><span class="ss">:array</span><span class="p">,</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">UUID</span><span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Having a link table is overkill this case. We only need to show and filter the order by participants sometimes. 
Before, I returned list of id, then client make another query to get list of participants. Filtering is even easier with array of uuid, no joining, just a nested query. 
To eliminate the 2nd query from client, I applied the custom dataloader. Similar to case above, replace load/get by load_many/get_many in this case, and the way we get the keys - participant_ids.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="n">dataloader</span><span class="p">()</span> <span class="k">do</span>
  <span class="k">fn</span> <span class="n">parent</span><span class="p">,</span> <span class="n">_args</span><span class="p">,</span> <span class="p">%{</span><span class="ss">context:</span> <span class="p">%{</span><span class="ss">loader:</span> <span class="n">loader</span><span class="p">}}</span> <span class="o">-&gt;</span>
    <span class="n">participant_ids</span> <span class="o">=</span>
      <span class="k">case</span> <span class="n">parent</span> <span class="k">do</span>
        <span class="p">%{</span><span class="ss">participants:</span> <span class="n">ids</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">ids</span>
        <span class="n">_</span> <span class="o">-&gt;</span> <span class="p">[]</span>
      <span class="k">end</span>

    <span class="n">loader</span>
    <span class="o">|&gt;</span> <span class="no">Dataloader</span><span class="o">.</span><span class="n">load_many</span><span class="p">(</span><span class="ss">:default</span><span class="p">,</span> <span class="no">Participant</span><span class="p">,</span> <span class="n">participant_ids</span><span class="p">)</span>
    <span class="o">|&gt;</span> <span class="n">on_load</span><span class="p">(</span><span class="k">fn</span> <span class="n">loader</span> <span class="o">-&gt;</span>
      <span class="n">participants</span> <span class="o">=</span> <span class="no">Dataloader</span><span class="o">.</span><span class="n">get_many</span><span class="p">(</span><span class="n">loader</span><span class="p">,</span> <span class="ss">:default</span><span class="p">,</span> <span class="no">Participant</span><span class="p">,</span> <span class="n">participant_ids</span><span class="p">)</span>

      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">participants</span><span class="p">}</span>
    <span class="k">end</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>By this, order.participants is list of participants. The code on frontend is simple now, and I could remove the participants query in the root.</p>

<h2 id="conclusion">Conclusion</h2>

<p>To make a conclusion, I believe it’s fun to write custom dataloader. But as an anti-pattern, too many custom dataloader will cause you headache. When looking back the first case, I still regret that a single <code class="language-plaintext highlighter-rouge">has_one</code> relation could save lots of custom code 😂.</p>]]></content><author><name></name></author><category term="elixir" /><category term="dataloader" /><category term="experiment" /><category term="til" /><summary type="html"><![CDATA[I could safely assume that, if you use absinthe with ecto, you are almost familiar with dataloader. The idea is simple, defering query data queries, mostly, for relations, then query in batch at once. It integrates smoothly with ecto relations that I sometime thought it’s natually magic.]]></summary></entry><entry><title type="html">2024 Goals - Set and setting revise</title><link href="https://anvox.github.io/personal-goal/2024/01/08/goal-set-and-settings-revise.html" rel="alternate" type="text/html" title="2024 Goals - Set and setting revise" /><published>2024-01-08T03:55:00+00:00</published><updated>2024-01-08T03:55:00+00:00</updated><id>https://anvox.github.io/personal-goal/2024/01/08/goal-set-and-settings-revise</id><content type="html" xml:base="https://anvox.github.io/personal-goal/2024/01/08/goal-set-and-settings-revise.html"><![CDATA[<h2 id="2024-goals">2024 Goals</h2>

<ol>
  <li>🎄 Finish Code avent of 2023. I need to figure out what prepare in advance.</li>
  <li>📚 Finish reading and summarize 6 tech books.</li>
  <li>♾️ Relearn Probability and statistics</li>
  <li>🧮 Relearn calculus</li>
  <li>🎰 Built a project in new language. In Rust or Zig or Crystal, I prefer something supports webasm.</li>
  <li>🏋️ Loose weight, to 75kg at the end of 2024.</li>
  <li>📖 Read and summary 4 novels, in english.</li>
  <li>🏆 Get a certificate of ML or Cloud Devops.</li>
  <li>👨🏻‍💻 Hosting an elixir workshop</li>
</ol>

<h2 id="set-and-setting">Set and Setting</h2>

<p>Revise some goal setting related</p>

<h3 id="why">Why?</h3>

<p>Why do we set goals? Why do we need to follow up?<br />
Having goals is a good way to focus attention on the things that are important. It allows us to create a vision of how we would like our life to be. When we have a goal, we tend to increase the amount of time and effort we spend on an activity, and develop effective strategies to achieve that goal.</p>

<p><strong>[WIP]</strong> Answer why on each goal.<br /></p>
<ol>
  <li>🎄</li>
  <li>📚</li>
  <li>♾️</li>
  <li>🧮</li>
  <li>🎰</li>
  <li>🏋️</li>
  <li>📖</li>
  <li>🏆</li>
  <li>👨🏻‍💻</li>
</ol>

<h3 id="what-is-a-good-goal">What is a good goal?</h3>

<p>Some questions to concern:</p>

<ul>
  <li>Does goal SMART enough? I usually convern of measureable, to me, it’s the most important to achieve.</li>
  <li>Does it an OKR goal?</li>
  <li>The objective 100% in our control or not? “Being a senior developer” is different from “Exhibit behavior of senior developer”</li>
  <li>Goal could be break into checklist?</li>
  <li>How do we track goal progress?</li>
  <li>Action is recurring or one off?</li>
  <li>Is it feeling, like: “Confident to present in a meeting”?</li>
  <li>Does it align with company goals?</li>
  <li>Does it align with personal growth or hobbies: Mental health, physic health, networkings, social…?</li>
  <li>Which changes will help you significant improve?</li>
  <li>What blocked you from moving faster, better in daily tasks?</li>
</ul>

<p>Remember: There’s no right or wrong, chose which is helpful.</p>

<h3 id="how">How?</h3>

<p>Get started with your <strong>focus area</strong>, look back last incompleted goals. Some questions need to answer:</p>

<ul>
  <li>What will I be in next 1 year? 5 years?</li>
  <li>What which role/position I want to be in next 1 year? 3 year?</li>
  <li>What do I want to be celebrating in next 1 year?</li>
  <li>On the road to achieve X, how close am I to the destination?</li>
  <li>Follow up at https://github.com/anvox/anvox.github.io/issues/1</li>
</ul>]]></content><author><name></name></author><category term="personal-goal" /><summary type="html"><![CDATA[2024 Goals]]></summary></entry><entry><title type="html">Sort Absinthe Generated GraphQL SDL</title><link href="https://anvox.github.io/absinthe/graphql-sdl/2023/09/24/sort-absinthe-generated-graphql-sdl.html" rel="alternate" type="text/html" title="Sort Absinthe Generated GraphQL SDL" /><published>2023-09-24T17:55:00+00:00</published><updated>2023-09-24T17:55:00+00:00</updated><id>https://anvox.github.io/absinthe/graphql-sdl/2023/09/24/sort-absinthe-generated-graphql-sdl</id><content type="html" xml:base="https://anvox.github.io/absinthe/graphql-sdl/2023/09/24/sort-absinthe-generated-graphql-sdl.html"><![CDATA[<h2 id="the-problem-of-maintaining-large-graphql-files">The Problem of Maintaining Large GraphQL Files</h2>

<p>When I took over the Deliany project, one of the immediate challenges was how it exposed almost all data to GraphQL. Nearly every Ecto schema had a corresponding GraphQL type. While this isn’t a dealbreaker for small projects, it becomes a maintenance nightmare as the application grows across multiple layers.</p>

<p>Consider a <code class="language-plaintext highlighter-rouge">books</code> table in PostgreSQL: we have a <code class="language-plaintext highlighter-rouge">Book</code> Ecto schema, which leads to a <code class="language-plaintext highlighter-rouge">BookType</code> in Absinthe. Absinthe then generates a <code class="language-plaintext highlighter-rouge">type Book</code> in <code class="language-plaintext highlighter-rouge">schema.graphql</code>, which is passed to the frontend to generate a TypeScript <code class="language-plaintext highlighter-rouge">type Book</code>. To make matters worse, tools like the Apollo GraphQL generator often produce many <code class="language-plaintext highlighter-rouge">Maybe&lt;Type&gt;</code> fields—which is reasonable but often too loose for strict application logic. This often forces us to generate yet another validated type, like <code class="language-plaintext highlighter-rouge">SafeBook</code>, with stricter typing for forms and displays. I felt there was significant duplication in storing and syncing schemas across Absinthe, the backend SDL, frontend types, and TypeScript, often requiring custom CI scripts to keep them in sync.</p>

<p>Another issue is the volume of data. Exposing everything might speed up development initially, especially with a frontend-heavy team, but pushing too much business logic to the frontend risks data leaks, wastes network traffic, and leads to stale data. Reorganizing these types, auditing their usage, and moving logic back to the backend is high-impact work that optimizes both the application and the workflow.</p>

<p>During this reorganization, I noticed that even harmless changes—like moving or renaming files—caused massive, noisy diffs in the <code class="language-plaintext highlighter-rouge">schema.graphql</code> file. This happens because Absinthe renders the SDL based on the order in which types are encountered. I wanted the generated schema to be deterministic and resistant to these structural changes. While Absinthe doesn’t support sorted output out of the box, the good news is that it allows for custom rendering. The rendering logic is quite straightforward, as seen in the <a href="https://github.com/absinthe-graphql/absinthe/blob/v1.6.8/lib/absinthe/schema/notation/sdl_render.ex">Absinthe source code</a>. To make the schema easier to diff, we can sort types and fields by their names before rendering.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defp</span> <span class="n">render</span><span class="p">(%</span><span class="no">Blueprint</span><span class="p">{}</span> <span class="o">=</span> <span class="n">bp</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="k">do</span>
    <span class="c1"># ...</span>
    <span class="n">all_type_definitions</span> <span class="o">=</span>
      <span class="n">type_definitions</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="nv">&amp;1</span><span class="o">.</span><span class="n">__struct__</span> <span class="o">==</span> <span class="no">Blueprint</span><span class="o">.</span><span class="no">Schema</span><span class="o">.</span><span class="no">SchemaDeclaration</span><span class="p">))</span>
      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">sort_by</span><span class="p">(</span><span class="k">fn</span>
        <span class="p">%{</span><span class="ss">name:</span> <span class="s2">"RootQueryType"</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="s2">"0"</span>

        <span class="p">%{</span><span class="ss">name:</span> <span class="s2">"RootMutationType"</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="s2">"1"</span>

        <span class="p">%{</span><span class="ss">name:</span> <span class="s2">"RootSubscriptionType"</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="s2">"2"</span>

        <span class="p">%</span><span class="no">Absinthe</span><span class="o">.</span><span class="no">Blueprint</span><span class="o">.</span><span class="no">Schema</span><span class="o">.</span><span class="no">ScalarTypeDefinition</span><span class="p">{</span><span class="ss">name:</span> <span class="n">name</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="s2">"3</span><span class="si">#{</span><span class="no">Absinthe</span><span class="o">.</span><span class="no">Blueprint</span><span class="o">.</span><span class="no">Schema</span><span class="o">.</span><span class="no">ScalarTypeDefinition</span><span class="si">}</span><span class="s2">-</span><span class="si">#{</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>

        <span class="p">%</span><span class="no">Absinthe</span><span class="o">.</span><span class="no">Blueprint</span><span class="o">.</span><span class="no">Schema</span><span class="o">.</span><span class="no">EnumTypeDefinition</span><span class="p">{</span><span class="ss">name:</span> <span class="n">name</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="s2">"4</span><span class="si">#{</span><span class="no">Absinthe</span><span class="o">.</span><span class="no">Blueprint</span><span class="o">.</span><span class="no">Schema</span><span class="o">.</span><span class="no">EnumTypeDefinition</span><span class="si">}</span><span class="s2">-</span><span class="si">#{</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>

        <span class="p">%</span><span class="n">m</span><span class="p">{</span><span class="ss">name:</span> <span class="n">name</span><span class="p">}</span> <span class="o">-&gt;</span>
          <span class="s2">"</span><span class="si">#{</span><span class="n">m</span><span class="si">}</span><span class="s2">-</span><span class="si">#{</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>
      <span class="k">end</span><span class="p">)</span>

    <span class="c1"># ...</span>
  <span class="k">end</span>
</code></pre></div></div>

<p>I’ve shared my full version here: <a href="https://gist.github.com/anvox/d5b4777a3092b8b9a243130f014b7b74">Utils.Absinthe.Schema.Notation.SDL.MyRender</a>.</p>

<p>How do we tell Absinthe to use this custom renderer? We need to create a custom Mix task to generate the <code class="language-plaintext highlighter-rouge">schema.graphql</code> file. You can follow the pattern of the <a href="https://github.com/absinthe-graphql/absinthe/blob/v1.5.5/lib/mix/tasks/absinthe.schema.sdl.ex">standard Absinthe SDL task</a> but swap in the custom renderer:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="n">generate_schema</span><span class="p">(%</span><span class="no">Options</span><span class="p">{</span><span class="ss">schema:</span> <span class="n">schema</span><span class="p">})</span> <span class="k">do</span>
  <span class="c1"># ...</span>
  <span class="k">case</span> <span class="no">Absinthe</span><span class="o">.</span><span class="no">Pipeline</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">schema</span><span class="o">.</span><span class="n">__absinthe_blueprint__</span><span class="p">(),</span> <span class="n">pipeline</span><span class="p">)</span> <span class="k">do</span>
    <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">blueprint</span><span class="p">,</span> <span class="n">_phases</span><span class="p">}</span> <span class="o">-&gt;</span>
      <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span>
       <span class="no">Utils</span><span class="o">.</span><span class="no">Absinthe</span><span class="o">.</span><span class="no">Schema</span><span class="o">.</span><span class="no">Notation</span><span class="o">.</span><span class="no">SDL</span><span class="o">.</span><span class="no">MyRender</span><span class="o">.</span><span class="n">inspect</span><span class="p">(</span><span class="n">blueprint</span><span class="p">,</span> <span class="p">%{</span><span class="ss">pretty:</span> <span class="no">true</span><span class="p">})}</span>

    <span class="n">_</span> <span class="o">-&gt;</span>
      <span class="p">{</span><span class="ss">:error</span><span class="p">,</span> <span class="s2">"Failed to render schema"</span><span class="p">}</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<h3 id="tldr">TL;DR</h3>

<p>While some features like deterministic SDL sorting seem obvious but are missing from the Absinthe core, the library is flexible enough to be patched for your specific needs. I’m generally not a fan of maintaining custom patches, but compared to the pain of managing noisy diffs across multiple 4,000-line GraphQL schemas, this small customization is a price well worth paying.</p>]]></content><author><name></name></author><category term="absinthe" /><category term="graphql-sdl" /><summary type="html"><![CDATA[The Problem of Maintaining Large GraphQL Files]]></summary></entry><entry><title type="html">Config Sentry with NextJS and ApolloJS</title><link href="https://anvox.github.io/sentry/nextjs/apollojs/2023/08/24/config-sentry-with-nextjs-apollo.html" rel="alternate" type="text/html" title="Config Sentry with NextJS and ApolloJS" /><published>2023-08-24T17:55:00+00:00</published><updated>2023-08-24T17:55:00+00:00</updated><id>https://anvox.github.io/sentry/nextjs/apollojs/2023/08/24/config-sentry-with-nextjs-apollo</id><content type="html" xml:base="https://anvox.github.io/sentry/nextjs/apollojs/2023/08/24/config-sentry-with-nextjs-apollo.html"><![CDATA[<p>While integrating Sentry into a NextJS project quite straightforward, just follow their <a href="https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/">guide</a>, it’s attempted to strip all the complex config to quickly send error to Sentry. I mean, just 3 block of codes, then errors are sent to Sentry.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// both sentry.client.config.js and sentry.server.config.ts</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">Sentry</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@sentry/nextjs</span><span class="dl">'</span><span class="p">;</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_SENTRY_DSN</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">Sentry</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
    <span class="na">release</span><span class="p">:</span> <span class="s2">`project-name@</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_VERSION</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
    <span class="na">enabled</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">dsn</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_SENTRY_DSN</span><span class="p">,</span>
    <span class="na">environment</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_SENTRY_ENV</span><span class="p">,</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="c1">// next.config.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">withSentryConfig</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@sentry/nextjs</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// ...</span>
  <span class="nl">sentry</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">disableClientWebpackPlugin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">disableServerWebpackPlugin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="p">},</span>
<span class="c1">// ...</span>
<span class="kr">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">withSentryConfig</span><span class="p">(</span><span class="nx">nextConfig</span><span class="p">);</span>
</code></pre></div></div>

<p>It’s cool, but useless. Yes, for error on node server, it may acceptable. But errors on client browsers are nearly useless. Without source map, minified stacktrace in error report like a forest. I have to admit that I never find out to fix any bug by guessing root cause from those minified source code.
Besides, we’re using apollojs, which mean all the <code class="language-plaintext highlighter-rouge">fetch</code>ing from server will be shown as <code class="language-plaintext highlighter-rouge">fetch POST https://backend.com/api/graphql [200] 21:07:17</code> 🥲</p>

<p>We lived with it for years, until I was getting mad of tons of Sentry report which broken our package, but had no clues of what are they, how to fix them. After hour of googling and dive in Sentry documentation with tries, surprisingly, we was only one step away from the finish line. I dont get into detail on how do I tested them out, just all the config here to save future-me one day.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// both sentry.client.config.js and sentry.server.config.ts</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">Sentry</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@sentry/nextjs</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">excludeGraphQLFetch</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">apollo-link-sentry</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">SENTRY_DSN</span><span class="p">:</span> <span class="kr">string</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_SENTRY_DSN</span><span class="p">;</span>

<span class="k">if</span> <span class="p">(</span><span class="nx">SENTRY_DSN</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">Sentry</span><span class="p">.</span><span class="nx">init</span><span class="p">({</span>
    <span class="na">release</span><span class="p">:</span> <span class="s2">`project@</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_VERSION</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
    <span class="na">enabled</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">dsn</span><span class="p">:</span> <span class="nx">SENTRY_DSN</span><span class="p">,</span>
    <span class="na">environment</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_SENTRY_ENV</span><span class="p">,</span>
    <span class="na">ignoreErrors</span><span class="p">:</span> <span class="p">[</span>
      <span class="dl">'</span><span class="s1">Validation failed</span><span class="dl">'</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">Can't find variable: zaloJSV2</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">],</span>
    <span class="na">tracesSampleRate</span><span class="p">:</span>
      <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_SENTRY_ENV</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">production</span><span class="dl">'</span> <span class="p">?</span> <span class="mf">0.15</span> <span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="na">allowUrls</span><span class="p">:</span> <span class="p">[</span><span class="sr">/https</span><span class="se">?</span><span class="sr">:</span><span class="se">\/\/</span><span class="sr">backend.com/</span><span class="p">],</span>
    <span class="na">beforeBreadcrumb</span><span class="p">:</span> <span class="nx">excludeGraphQLFetch</span><span class="p">,</span>
  <span class="p">});</span>
<span class="p">}</span>


<span class="c1">// next.config.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">withSentryConfig</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">@sentry/nextjs</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// ... nextConfig</span>
  <span class="nl">sentry</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">hideSourceMaps</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">widenClientFileUpload</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="na">disableServerWebpackPlugin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="p">},</span>
<span class="c1">// ...</span>
<span class="kd">const</span> <span class="nx">sentryWebpackPluginOptions</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">org</span><span class="p">:</span> <span class="dl">'</span><span class="s1">org</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">project</span><span class="p">:</span> <span class="dl">'</span><span class="s1">project</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">authToken</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">SENTRY_AUTH_TOKEN</span><span class="p">,</span>
  <span class="na">silent</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// Enable to debug when build and upload artifact</span>
  <span class="na">release</span><span class="p">:</span> <span class="s2">`project@</span><span class="p">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NEXT_PUBLIC_VERSION</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="p">};</span>

<span class="kr">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">withSentryConfig</span><span class="p">(</span><span class="nx">nextConfig</span><span class="p">,</span> <span class="nx">sentryWebpackPluginOptions</span><span class="p">);</span>

<span class="c1">// apollo client</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span>
    <span class="na">link</span><span class="p">:</span> <span class="nx">ApolloLink</span><span class="p">.</span><span class="k">from</span><span class="p">([</span>
      <span class="k">new</span> <span class="nx">SentryLink</span><span class="p">({</span>
        <span class="na">setTransaction</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
        <span class="na">setFingerprint</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
        <span class="na">attachBreadcrumbs</span><span class="p">:</span> <span class="p">{</span> <span class="na">includeError</span><span class="p">:</span> <span class="kc">true</span> <span class="p">},</span>
      <span class="p">]),</span>
<span class="c1">// ...</span>
</code></pre></div></div>

<p>Some take away:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">release</code> in <code class="language-plaintext highlighter-rouge">Sentry.init</code> must be the sames with in <code class="language-plaintext highlighter-rouge">sentryWebpackPluginOptions</code>, or you’ll find 2 tracked versions on Sentry.</li>
  <li><code class="language-plaintext highlighter-rouge">sentryWebpackPluginOptions</code> <code class="language-plaintext highlighter-rouge">silent</code> is useful when checking if source map is generated and pushed?</li>
  <li>Performance trace is run automatedly, adjust sample rate to match Sentry package capacity.</li>
  <li>Session Replay is an interesting feature, but only provided to business and higher packages.</li>
</ul>]]></content><author><name></name></author><category term="sentry" /><category term="nextjs" /><category term="apollojs" /><summary type="html"><![CDATA[While integrating Sentry into a NextJS project quite straightforward, just follow their guide, it’s attempted to strip all the complex config to quickly send error to Sentry. I mean, just 3 block of codes, then errors are sent to Sentry.]]></summary></entry><entry><title type="html">Elixir match? function</title><link href="https://anvox.github.io/elixir/til/match/pattern-matching/2023/04/07/elixir-match-function.html" rel="alternate" type="text/html" title="Elixir match? function" /><published>2023-04-07T02:55:00+00:00</published><updated>2023-04-07T02:55:00+00:00</updated><id>https://anvox.github.io/elixir/til/match/pattern-matching/2023/04/07/elixir-match-function</id><content type="html" xml:base="https://anvox.github.io/elixir/til/match/pattern-matching/2023/04/07/elixir-match-function.html"><![CDATA[<h2 id="context">Context</h2>

<p>When processing in batch, I get a lot cases just remove the invalid item in list. Like this.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">estimations</span>
<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="k">fn</span>
  <span class="p">%{</span><span class="s2">"total_price"</span> <span class="o">=&gt;</span> <span class="n">_</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="no">true</span>
  <span class="n">_</span> <span class="o">-&gt;</span> <span class="no">false</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div></div>

<p>In the beginning, it was just fine. But after I wrote the 10th block like this, it annoyed me. Most of case, the filter function feed to <code class="language-plaintext highlighter-rouge">Enum.filter/1</code> always the same, the only different part is a pattern, why don’t we just define a function <code class="language-plaintext highlighter-rouge">filter_by_pattern(pattern)</code>? Just return <code class="language-plaintext highlighter-rouge">true</code> or <code class="language-plaintext highlighter-rouge">false</code> :3 if matched.
It’s turn out I was late, I’m not the one think about it. Other languages support pattern matching use a <code class="language-plaintext highlighter-rouge">match</code> function, why don’t <code class="language-plaintext highlighter-rouge">elixir</code> - a modern-pure-pattern-mattching-based language support <code class="language-plaintext highlighter-rouge">match</code> function?</p>

<p><a href="https://hexdocs.pm/elixir/1.14/Kernel.html#match?/2">https://hexdocs.pm/elixir/1.14/Kernel.html#match?/2</a></p>

<p>Supported from early versions, so we could write shorter.</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">estimations</span>
<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">match?</span><span class="p">(%{</span><span class="s2">"total_price"</span> <span class="o">=&gt;</span> <span class="n">_</span><span class="p">},</span> <span class="nv">&amp;1</span><span class="p">))</span>
</code></pre></div></div>

<h2 id="tldr">tl;dr</h2>

<ul>
  <li>Elixir suppports <code class="language-plaintext highlighter-rouge">match?/2</code> to check match or not, which fit exactly to <code class="language-plaintext highlighter-rouge">Enum.filer/1</code></li>
  <li>Even though, it’s not 100% the same. <a href="https://hexdocs.pm/elixir/1.14/Kernel.html#match?/2">https://hexdocs.pm/elixir/1.14/Kernel.html#match?/2</a> points out the differences already.</li>
  <li>Less code, less bugs. When you get bored of repeatedly code, find away to avoid it, most of case, someone else did it.</li>
</ul>]]></content><author><name></name></author><category term="elixir" /><category term="til" /><category term="match" /><category term="pattern-matching" /><summary type="html"><![CDATA[Context]]></summary></entry><entry><title type="html">Postgresql counting over</title><link href="https://anvox.github.io/postgresql/count/count-over/date_trunc/til/2022/10/31/postgresql-counting-over.html" rel="alternate" type="text/html" title="Postgresql counting over" /><published>2022-10-31T05:19:00+00:00</published><updated>2022-10-31T05:19:00+00:00</updated><id>https://anvox.github.io/postgresql/count/count-over/date_trunc/til/2022/10/31/postgresql-counting-over</id><content type="html" xml:base="https://anvox.github.io/postgresql/count/count-over/date_trunc/til/2022/10/31/postgresql-counting-over.html"><![CDATA[<h2 id="truncate-datetime">Truncate datetime</h2>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">DISTINCT</span>
       <span class="n">date_trunc</span><span class="p">(</span><span class="s1">'minute'</span><span class="p">,</span> <span class="nv">"when"</span><span class="p">)</span> <span class="k">AS</span> <span class="k">minute</span>
     <span class="p">,</span> <span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="n">OVER</span> <span class="p">(</span><span class="k">ORDER</span> <span class="k">BY</span> <span class="n">date_trunc</span><span class="p">(</span><span class="s1">'minute'</span><span class="p">,</span> <span class="nv">"when"</span><span class="p">))</span> <span class="k">AS</span> <span class="n">running_ct</span>
<span class="k">FROM</span>   <span class="n">mytable</span>
<span class="k">ORDER</span>  <span class="k">BY</span> <span class="mi">1</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="extract-parts-of-datetime">Extract parts of datetime</h2>

<p>Ref <a href="https://www.geeksforgeeks.org/postgresql-date_part-function/">date_part</a></p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">date_part</span><span class="p">(</span><span class="s1">'year'</span><span class="p">,</span> <span class="nv">"inserted_at"</span><span class="p">)</span> <span class="k">FROM</span> <span class="n">users</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="postgresql" /><category term="count" /><category term="count-over" /><category term="date_trunc" /><category term="til" /><summary type="html"><![CDATA[Truncate datetime]]></summary></entry><entry><title type="html">Database range constraint</title><link href="https://anvox.github.io/elixir/postgresql/constraint/tsrange/array/2022/10/24/database-range-constraint.html" rel="alternate" type="text/html" title="Database range constraint" /><published>2022-10-24T03:17:00+00:00</published><updated>2022-10-24T03:17:00+00:00</updated><id>https://anvox.github.io/elixir/postgresql/constraint/tsrange/array/2022/10/24/database-range-constraint</id><content type="html" xml:base="https://anvox.github.io/elixir/postgresql/constraint/tsrange/array/2022/10/24/database-range-constraint.html"><![CDATA[<p>##</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="n">EXTENSION</span> <span class="n">IF</span> <span class="k">NOT</span> <span class="k">EXISTS</span> <span class="n">btree_gist</span><span class="p">;</span>

<span class="k">ALTER</span> <span class="k">TABLE</span> <span class="n">promotions</span>
<span class="k">ADD</span> <span class="k">CONSTRAINT</span> <span class="n">overlapping_running_time</span>
<span class="n">EXCLUDE</span> <span class="k">USING</span> <span class="n">gist</span> <span class="p">(</span>
  <span class="n">product_id</span> <span class="k">WITH</span> <span class="o">=</span><span class="p">,</span>
  <span class="n">tsrange</span><span class="p">(</span><span class="nv">"start_at"</span><span class="p">,</span> <span class="nv">"end_at"</span><span class="p">,</span> <span class="s1">'[)'</span><span class="p">)</span> <span class="k">WITH</span> <span class="o">&amp;&amp;</span>
<span class="p">)</span> <span class="k">WHERE</span> <span class="p">(</span><span class="nv">"status"</span> <span class="o">=</span> <span class="s1">'active'</span><span class="p">);</span>
</code></pre></div></div>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="o">|&gt;</span> <span class="n">exclusion_constraint</span><span class="p">(</span><span class="ss">:start_at</span><span class="p">,</span>
    <span class="ss">name:</span> <span class="ss">:overlapping_running_time</span><span class="p">,</span>
    <span class="ss">message:</span> <span class="s2">"Discount of product </span><span class="si">#{</span><span class="n">product_id</span><span class="si">}</span><span class="s2"> is overlapped"</span>
  <span class="p">)</span>
</code></pre></div></div>

<p>🚨 Only <code class="language-plaintext highlighter-rouge">superuser</code> role has permission to <code class="language-plaintext highlighter-rouge">CREATE EXTENSION</code>.</p>]]></content><author><name></name></author><category term="elixir" /><category term="postgresql" /><category term="constraint" /><category term="tsrange" /><category term="array" /><summary type="html"><![CDATA[##]]></summary></entry><entry><title type="html">Production checklist</title><link href="https://anvox.github.io/devops/release/2022/09/12/production-checklist.html" rel="alternate" type="text/html" title="Production checklist" /><published>2022-09-12T10:17:00+00:00</published><updated>2022-09-12T10:17:00+00:00</updated><id>https://anvox.github.io/devops/release/2022/09/12/production-checklist</id><content type="html" xml:base="https://anvox.github.io/devops/release/2022/09/12/production-checklist.html"><![CDATA[<h2 id="checklist-before-release-to-production">Checklist before release to production</h2>

<p>Or Production grade release</p>

<ol>
  <li>CI</li>
  <li>CD</li>
  <li>Test coverage report</li>
  <li>Code scan report</li>
  <li>Logging: level and storage</li>
  <li>APM</li>
</ol>]]></content><author><name></name></author><category term="devops" /><category term="release" /><summary type="html"><![CDATA[Checklist before release to production]]></summary></entry><entry><title type="html">Computer color is broken</title><link href="https://anvox.github.io/til/color/2022/06/07/computer-color-is-broken.html" rel="alternate" type="text/html" title="Computer color is broken" /><published>2022-06-07T02:17:00+00:00</published><updated>2022-06-07T02:17:00+00:00</updated><id>https://anvox.github.io/til/color/2022/06/07/computer-color-is-broken</id><content type="html" xml:base="https://anvox.github.io/til/color/2022/06/07/computer-color-is-broken.html"><![CDATA[<p>Source: <a href="https://www.youtube.com/watch?v=LKnqECcg6Gw">Computer Color is Broken</a></p>

<h1 id="problem">Problem</h1>

<p>Sometimes, when render chart, to have new color for new item, I usually use average from items around. i.e. between <span style="background-color:#ff0000">red</span> bar and <span style="background-color:#00ff00">green</span> bar, I used the average transition color <span style="background-color:#7f7f00">#7f7f00 (127,127,0)</span> bar. You could see, <span style="background-color:#ff0000">#ff0000</span> <span style="background-color:#7f7f00">#7f7f00</span> <span style="background-color:#00ff00">#00ff00</span> it’s weird, seems a bit darker than expected.</p>

<h1 id="solution">Solution</h1>

<h2 id="tldr">tl;dr</h2>

<p>Use another lighter color: <span style="background-color:#b4b400">#b4b400 (180,180,0)</span> in this case.
<br />
<span style="background-color:#ff0000">#ff0000</span><span style="background-color:#b4b400">#b4b400</span><span style="background-color:#00ff00">#00ff00</span></p>

<p><span style="background-color:#ff0000">#ff0000</span><span style="background-color:#7f7f00">#7f7f00</span><span style="background-color:#00ff00">#00ff00</span></p>

<p>How do we have the number <code class="language-plaintext highlighter-rouge">180</code>/<code class="language-plaintext highlighter-rouge">0xb4</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>r' = SQRT(r0^2 + r1^2) = SQRT((255^2) + (0^2)) = 180 = 0xb4
</code></pre></div></div>

<h2 id="explaination">Explaination</h2>

<p>There are 2 factors of this problem.</p>

<p>First is the way our human eyes preceive the brightness. It’s not straight, but in a roughly logarithmic scale. Technically, <code class="language-plaintext highlighter-rouge">rgb(0,0,0)</code> has 0 brightness, or 0 photons; <code class="language-plaintext highlighter-rouge">rgb(1,1,1)</code> - full brightness - has 100% photons. But <code class="language-plaintext highlighter-rouge">rgb(0.5, 0.5, 0.5)</code> has only 22% photons - 22% brightness. Even worse, <code class="language-plaintext highlighter-rouge">rgb(0.25, 0.25, 0.25)</code> has only 5% photons, not 25% brightness as our expected. The average doesn’t apply here.</p>

<p>Second, the way CCD sensor records a pixel in computer. It’s pure number. Moreover, it stores square root of the brightness, to save storage. By this, dark color datapoints - near 0 - are recorded more than bright color datapoints - near 1. This roughly imitate human vision. Then square back when display.</p>

<p>But, when we want to modify, remember, the rgb numbers we work on are square root, we must square back before take any action.</p>]]></content><author><name></name></author><category term="til" /><category term="color" /><summary type="html"><![CDATA[Source: Computer Color is Broken]]></summary></entry></feed>