<?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: Ifeanyi Chima</title>
    <description>The latest articles on DEV Community by Ifeanyi Chima (@ifeanyichima).</description>
    <link>https://dev.to/ifeanyichima</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%2F763463%2F47ea4de3-e833-48d3-8a9b-507ac915483f.jpg</url>
      <title>DEV Community: Ifeanyi Chima</title>
      <link>https://dev.to/ifeanyichima</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ifeanyichima"/>
    <language>en</language>
    <item>
      <title>Writing unit test for Firefox private browsing window</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:35:35 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/writing-unit-test-for-firefox-private-browsing-window-25oj</link>
      <guid>https://dev.to/ifeanyichima/writing-unit-test-for-firefox-private-browsing-window-25oj</guid>
      <description>&lt;p&gt;Our indicator looks like this: an icon + a label. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3gd1anw6rzz19qhkawbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3gd1anw6rzz19qhkawbb.png" alt="private window indicator" width="235" height="63"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The browser has an indicator for when a user is in private mode. The indicator lives in two places under different instances: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The first instance is when the user is in "Horizontal Tabs" when the browser tabs are in the &lt;code&gt;TabsToolbar&lt;/code&gt; (tab-bar or toolbar). So, the indicator shows both the icon and it's label. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second instance is when the user has "Vertical Tabs" turned on. So, the indicator moves or shows up in the &lt;code&gt;Navbar&lt;/code&gt; (which contains extensions, address bar) and this time only the icon shows up, no label. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our task is to write a unit test to confirm that the icon shows up in both instance. &lt;/p&gt;

&lt;p&gt;We start off by running this search &lt;code&gt;grep -r "private-browsing-indicator-with-label" --include="*.xhtml" --include="*.js" --include="*.css" .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We found two things. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The "Shared" CSS file that controls the visibility of this icon &lt;code&gt;browser/themes/shared/browser-shared.css&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;The &lt;code&gt;browser/base/content/private-browsing-indicator.inc.xhtml&lt;/code&gt; — this is where the indicator's actual markup (HTML-like structure) lives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, we can check the CSS file for where it controls the visibility of the indicator by using this syntax &lt;code&gt;grep -n -A 10 "private-browsing-indicator-with-label" browser/themes/shared/browser-shared.css&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;What we find out in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.private-browsing-indicator-with-label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="py"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;privatebrowsingmode&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"temporary"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;navigator-toolbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;tabs-hidden&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;nav-bar&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;#navigator-toolbox&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;tabs-hidden&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;#nav-bar&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;margin-inline-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--space-medium&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;.private-browsing-indicator-label&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this means: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When the window isn't a private window (we are in normal mode) - it will hide the indicator completely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When we have horizontal tabs in private window, hide the indicator from the navbar (which contains extensions, address bar) and then the indicator will move to the &lt;code&gt;TabsToolbar&lt;/code&gt; (which actually browser tabs).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvf2v4een8lrkp5lwrry5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvf2v4een8lrkp5lwrry5.png" alt="Horizontal Tabs enabled" width="800" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The image above shows that we are still in horizontal tabs&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When we have vertical tabs while still in private window, the &lt;code&gt;private-browsing-indicator&lt;/code&gt; will move to the navbar and then hide the label part of the indicator and keep only the icon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcv3izr2pj29ar99jzunu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcv3izr2pj29ar99jzunu.png" alt="Vertical Tab is enabled" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The above image shows vertical tabs enabled&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the tests
&lt;/h2&gt;

&lt;p&gt;Before we begin writing the tests, we would need to find other existing tests in the codebase to take inspiration from them, by using the search query: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;grep -n "is_visible\|isVisible\|is_hidden\|isHidden" testing/mochitest/BrowserTestUtils/BrowserTestUtils.sys.mjs&lt;/code&gt;and then we read the file &lt;code&gt;sed -n '325,350p' testing/mochitest/BrowserTestUtils/BrowserTestUtils.sys.mjs&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;This gives us a lot of information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;What to check: both copies of &lt;code&gt;.private-browsing-indicator-with-label&lt;/code&gt; (one inside &lt;code&gt;TabsToolbar&lt;/code&gt;, one inside &lt;code&gt;nav-bar&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to toggle vertical tabs: by using the pref &lt;code&gt;sidebar.verticalTabs&lt;/code&gt; as seen here&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;SpecialPowers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushPrefEnv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;set&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="s2"&gt;sidebar.revamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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="s2"&gt;sidebar.verticalTabs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;ul&gt;
&lt;li&gt;&lt;p&gt;Task structure: &lt;code&gt;add_task(async function () {...})&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check visibility: &lt;code&gt;BrowserTestUtils.isVisible(element)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to open a private window (modern style): &lt;code&gt;BrowserTestUtils.openNewBrowserWindow({ private: true })&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="cm"&gt;/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */&lt;/span&gt;

&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The private browsing indicator lives in two possible spots:&lt;/span&gt;
&lt;span class="c1"&gt;// - Inside #TabsToolbar (shown with icon + label, for horizontal tabs)&lt;/span&gt;
&lt;span class="c1"&gt;// - Inside #nav-bar (shown with icon only, for vertical tabs)&lt;/span&gt;
&lt;span class="c1"&gt;// This test makes sure at least one copy is always visible in a&lt;/span&gt;
&lt;span class="c1"&gt;// private window, no matter which tabs layout is active.&lt;/span&gt;

&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test_indicator_horizontal_tabs&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;win&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;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openNewBrowserWindow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;private&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tabsToolbarIndicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#TabsToolbar .private-browsing-indicator-with-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;navBarIndicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#nav-bar .private-browsing-indicator-with-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabsToolbarIndicator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indicator should be visible in TabsToolbar with horizontal tabs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;navBarIndicator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indicator in nav-bar should be hidden with horizontal tabs&lt;/span&gt;&lt;span class="dl"&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;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test_indicator_vertical_tabs&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;SpecialPowers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushPrefEnv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;set&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidebar.revamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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="s2"&gt;sidebar.verticalTabs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;win&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;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openNewBrowserWindow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;private&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tabsToolbarIndicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#TabsToolbar .private-browsing-indicator-with-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;navBarIndicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#nav-bar .private-browsing-indicator-with-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabsToolbarIndicator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indicator in TabsToolbar should be hidden with vertical tabs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;navBarIndicator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indicator should be visible in nav-bar with vertical tabs&lt;/span&gt;&lt;span class="dl"&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;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;win&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;SpecialPowers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popPrefEnv&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test_indicator_not_shown_in_normal_window&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;win&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;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openNewBrowserWindow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;indicators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.private-browsing-indicator-with-label&lt;/span&gt;&lt;span class="dl"&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;let&lt;/span&gt; &lt;span class="nx"&gt;indicator&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;indicators&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isHidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Indicator should never be visible in a normal (non-private) window&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;win&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;&lt;em&gt;Explaining the test:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, here is what this test is doing. Remember at the beginning, I said our private browsing lives in two possible spots: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Inside #TabsToolbar (shown with icon + label, for horizontal tabs): This is when we have private browsing enabled, but we are still using horizontal browser tabs (the default)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inside #nav-bar (shown with icon only, for vertical tabs): This is when the user has enabled / turned on Vertical browser tabs. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Test 1: Horizontal Browser Tabs
&lt;/h3&gt;

&lt;p&gt;The first test is saying open a private browser window using &lt;code&gt;await BrowserTestUtils.openNewBrowserWindow({ private: true });&lt;/code&gt;  and then we are making sure that when we are in Horizontal Mode - the navbar indicator IS NOT showing. The only indicator that should be showing in Horizontal Mode is the &lt;code&gt;tabsToolbarIndicator&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: Vertical Browser Tabs
&lt;/h3&gt;

&lt;p&gt;We open a private browser window using &lt;code&gt;await BrowserTestUtils.openNewBrowserWindow({ private: true });&lt;/code&gt; and now, we assume that the user has turned on vertical tabs, how do we do that in our test ?.... by using this &lt;code&gt;pref&lt;/code&gt; to turn on vertical tabs during the test run&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SpecialPowers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushPrefEnv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;set&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sidebar.revamp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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="s2"&gt;sidebar.verticalTabs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;Now, in Vertical Mode the only indicator that should show is the &lt;code&gt;navBarIndicator&lt;/code&gt; and the other one should not show. Also do not forget to close the browser window and cleanup the &lt;code&gt;Pref&lt;/code&gt; using&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;BrowserTestUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;win&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;SpecialPowers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popPrefEnv&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test 3: Normal Browser Window
&lt;/h3&gt;

&lt;p&gt;In this test, we turn off private browsing and we make sure that none of those labels either &lt;code&gt;navBarIndicator&lt;/code&gt; or &lt;code&gt;tabsToolbarIndicator&lt;/code&gt; show up when the user is NOT using private browsing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TLDR:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What each test does, briefly&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;test_indicator_horizontal_tabs&lt;/code&gt;: opens a private window in normal (default) tabs mode, checks the TabsToolbar copy is visible and the nav-bar copy is hidden.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_indicator_vertical_tabs&lt;/code&gt;: turns on vertical tabs mode, opens a private window, checks the opposite: nav-bar copy visible, TabsToolbar copy hidden.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_indicator_not_shown_in_normal_window&lt;/code&gt;: opens a completely normal, non-private window, and makes sure neither copy shows up at all, since this isn't a private window in the first place.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;SUMMARY:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user switches Firefox to private browsing mode, the indicator shows up, and the bug report was that there was never a unit test to ensure that the indicator is present in various browsing modes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Horizontal tabs mode (while private browsing is turned on)&lt;/li&gt;
&lt;li&gt;Vertical tabs mode (while private browsing is turned on)&lt;/li&gt;
&lt;li&gt;Normal browsing mode. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find the Patch Here: &lt;a href="https://phabricator.services.mozilla.com/D310523" rel="noopener noreferrer"&gt;https://phabricator.services.mozilla.com/D310523&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Find the Bug Here: &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1927615" rel="noopener noreferrer"&gt;https://bugzilla.mozilla.org/show_bug.cgi?id=1927615&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;THANK YOU&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>html</category>
      <category>browser</category>
      <category>firefox</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Mon, 29 Jun 2026 08:29:49 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/-3pbj</link>
      <guid>https://dev.to/ifeanyichima/-3pbj</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/devengers/dev-opportunity-radar-5-a-fully-funded-trip-to-aws-reinvent-google-cloud-career-launchpad-and-3p6e" class="crayons-story__hidden-navigation-link"&gt;Dev Opportunity Radar #5: A Fully Funded Trip to AWS re:Invent, Google Cloud Career Launchpad, and a $1,000 Award&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/devengers/dev-opportunity-radar-5-a-fully-funded-trip-to-aws-reinvent-google-cloud-career-launchpad-and-3p6e" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Weekly community-sourced tech resources&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/devengers"&gt;
            &lt;img alt="The DEVengers logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12483%2Fb3a03d2d-fbb6-4355-9593-dff0f92638e9.webp" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/hemapriya_kanagala" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3307586%2F2dffaf97-946d-44a6-8a39-07d94a72e07d.png" alt="hemapriya_kanagala profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/hemapriya_kanagala" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hemapriya Kanagala
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hemapriya Kanagala
                
              
              &lt;div id="story-author-preview-content-3999153" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/hemapriya_kanagala" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3307586%2F2dffaf97-946d-44a6-8a39-07d94a72e07d.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hemapriya Kanagala&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/devengers" class="crayons-story__secondary fw-medium"&gt;The DEVengers&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/devengers/dev-opportunity-radar-5-a-fully-funded-trip-to-aws-reinvent-google-cloud-career-launchpad-and-3p6e" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/devengers/dev-opportunity-radar-5-a-fully-funded-trip-to-aws-reinvent-google-cloud-career-launchpad-and-3p6e" id="article-link-3999153"&gt;
          Dev Opportunity Radar #5: A Fully Funded Trip to AWS re:Invent, Google Cloud Career Launchpad, and a $1,000 Award
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/community"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;community&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opportunities"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opportunities&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/resources"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;resources&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/devengers/dev-opportunity-radar-5-a-fully-funded-trip-to-aws-reinvent-google-cloud-career-launchpad-and-3p6e" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;70&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/devengers/dev-opportunity-radar-5-a-fully-funded-trip-to-aws-reinvent-google-cloud-career-launchpad-and-3p6e#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              25&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Tue, 23 Jun 2026 13:37:49 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/-54id</link>
      <guid>https://dev.to/ifeanyichima/-54id</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/kristinz/from-react-developer-to-ai-engineer-what-actually-changes-581n" class="crayons-story__hidden-navigation-link"&gt;From React Developer to AI Engineer: What Actually Changes&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/kristinz" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3973719%2F2ff45f78-40d5-4877-bb58-746f577abeee.png" alt="kristinz profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/kristinz" class="crayons-story__secondary fw-medium m:hidden"&gt;
              KristinZ
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                KristinZ
                
              
              &lt;div id="story-author-preview-content-3846873" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/kristinz" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3973719%2F2ff45f78-40d5-4877-bb58-746f577abeee.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;KristinZ&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/kristinz/from-react-developer-to-ai-engineer-what-actually-changes-581n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 8&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/kristinz/from-react-developer-to-ai-engineer-what-actually-changes-581n" id="article-link-3846873"&gt;
          From React Developer to AI Engineer: What Actually Changes
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/career"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;career&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/kristinz/from-react-developer-to-ai-engineer-what-actually-changes-581n" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/kristinz/from-react-developer-to-ai-engineer-what-actually-changes-581n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              3&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Solstice cipher: Turing complete</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Mon, 08 Jun 2026 20:49:21 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/solstice-cipher-turing-complete-2k1b</link>
      <guid>https://dev.to/ifeanyichima/solstice-cipher-turing-complete-2k1b</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Solstice Cipher is a browser-based game built around the tension between light and darkness — the core theme of the June Solstice.&lt;/p&gt;

&lt;p&gt;The screen is split into two halves: pitch black on the left, golden yellow on the right. A Caesar cipher is presented to the player in a floating modal. Decode it correctly and the light grows. Guess wrong and the darkness creeps forward.&lt;/p&gt;

&lt;p&gt;The game ends when either side fully consumes the screen. Darkness wins by default if you keep getting answers wrong.&lt;/p&gt;

&lt;p&gt;The cipher words are selected randomly from a pool of over 6,500 common English words and encrypted using Caesar cipher with a shift of 3. Google's Gemma 4 model handles the encryption of each word on the server, with a verified fallback to ensure the player always gets a mathematically correct puzzle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/tFLnM4Jbg5I"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MasterIfeanyi" rel="noopener noreferrer"&gt;
        MasterIfeanyi
      &lt;/a&gt; / &lt;a href="https://github.com/MasterIfeanyi/solstice-game" rel="noopener noreferrer"&gt;
        solstice-game
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;This is a &lt;a href="https://nextjs.org" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt; project bootstrapped with &lt;a href="https://github.com/vercel/next.js/tree/canary/packages/create-next-app" rel="noopener noreferrer"&gt;&lt;code&gt;create-next-app&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;First, run the development server:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run dev
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or&lt;/span&gt;
yarn dev
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or&lt;/span&gt;
pnpm dev
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or&lt;/span&gt;
bun dev&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; with your browser to see the result.&lt;/p&gt;
&lt;p&gt;You can start editing the page by modifying &lt;code&gt;app/page.js&lt;/code&gt;. The page auto-updates as you edit the file.&lt;/p&gt;
&lt;p&gt;This project uses &lt;a href="https://nextjs.org/docs/app/building-your-application/optimizing/fonts" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;next/font&lt;/code&gt;&lt;/a&gt; to automatically optimize and load &lt;a href="https://vercel.com/font" rel="nofollow noopener noreferrer"&gt;Geist&lt;/a&gt;, a new font family for Vercel.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Learn More&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;To learn more about Next.js, take a look at the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs" rel="nofollow noopener noreferrer"&gt;Next.js Documentation&lt;/a&gt; - learn about Next.js features and API.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/learn" rel="nofollow noopener noreferrer"&gt;Learn Next.js&lt;/a&gt; - an interactive Next.js tutorial.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can check out &lt;a href="https://github.com/vercel/next.js" rel="noopener noreferrer"&gt;the Next.js GitHub repository&lt;/a&gt; - your feedback and contributions are welcome!&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Deploy on Vercel&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The easiest way to deploy your Next.js app is to use the &lt;a href="https://vercel.com/new?utm_medium=default-template&amp;amp;filter=next.js&amp;amp;utm_source=create-next-app&amp;amp;utm_campaign=create-next-app-readme" rel="nofollow noopener noreferrer"&gt;Vercel Platform&lt;/a&gt; from the creators of Next.js.&lt;/p&gt;
&lt;p&gt;Check out our &lt;a href="https://nextjs.org/docs/app/building-your-application/deploying" rel="nofollow noopener noreferrer"&gt;Next.js deployment documentation&lt;/a&gt; for more…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/MasterIfeanyi/solstice-game" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The stack is Next.js 16 with JavaScript, plain CSS modules, and Tailwind v4.&lt;/p&gt;

&lt;p&gt;The architecture is built around a single GameContext using React's Context API, which broadcasts game state to every component without prop drilling. localStorage persists the light and dark widths across page refreshes so your progress survives an accidental reload.&lt;/p&gt;

&lt;p&gt;The word selection pipeline works like this: a random word is picked on the server from a curated list of 6,500+ words sourced from Google's open source 10,000 most common English words list. That word is sent to Gemma 4 via OpenRouter to encrypt using Caesar cipher shift 3. Our server then verifies Gemma's encryption independently and corrects it silently if there is a mismatch, so the player always gets a valid puzzle.&lt;/p&gt;

&lt;p&gt;For true randomness, a unique seed and offset generated with Math.floor(Math.random() * 1000000) is passed with every request. A rate limit fallback handles OpenRouter 429 responses gracefully by falling back to local encryption, and a network failure fallback handles complete internet loss.&lt;/p&gt;

&lt;p&gt;Key architecture decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DRY principle applied throughout with reusable Button, Modal, Header, and Navbar components.&lt;/li&gt;
&lt;li&gt;A storage.js utility abstracts all localStorage operations with SSR safety guards.&lt;/li&gt;
&lt;li&gt;Caesar cipher logic lives in one encryptCaesar function used everywhere on the server.&lt;/li&gt;
&lt;li&gt;useRef tracks recently used words to prevent Gemma from repeating itself without triggering re-renders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Ode to Alan Turing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a computer science grad myself, I look up to Turing. His work in cryptography inspired this mini-game. I am in awe of his ability to turn a problem of decrypting German messages into a mathematical problem and then how he built a physical machine that took his instructions and was able to decrypt German signals.&lt;/p&gt;

&lt;p&gt;That same spirit lives in Solstice Cipher. The Caesar cipher is one of the oldest encryption algorithms in history, and this game puts the player in the position Turing's team faced at Bletchley Park: staring at encrypted text and racing to decode it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Google AI Usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google's Gemma 4 model powers the encryption engine of every round via OpenRouter. Each word selected for the player is sent to Gemma 4 with a structured prompt instructing it to apply Caesar cipher shift 3 and return a verified JSON response. The server independently verifies Gemma's output and corrects any mismatch, ensuring the AI is genuinely doing cryptographic work on every single round rather than acting as a simple random number generator.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Copilot with an Eagle eye</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Mon, 25 May 2026 19:31:49 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/copilot-with-an-eagle-eye-1j0d</link>
      <guid>https://dev.to/ifeanyichima/copilot-with-an-eagle-eye-1j0d</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;In Nigeria today, insecurity and corruption is a very big issue with no hope in sight of solving it as they both go in tandem. I have been in multiple situations where I wished I could contact emergency services as soon as possible but to no avail. Also, to be honest when I started this project in 2025, the goal was to provide a very simple way to combat corruption by turning even the layman into a watchful vigilante. So, the goal of this project was to prototype a simple system called "Eagle Eye" that will allow you to make reports to the appropriate authorities about corruption and any insecurity in an area. &lt;/p&gt;

&lt;p&gt;I really hope to make this platform or website actually grow into a nationwide tool complete with an admin dashboard and a response team. I took massive inspiration from the London underground railway system: "see something, say something".&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/VFb07sPWJzU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Live link: &lt;a href="https://ifeanyi-eagle-eye.netlify.app/home" rel="noopener noreferrer"&gt;https://ifeanyi-eagle-eye.netlify.app/home&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub Repo: &lt;a href="https://github.com/MasterIfeanyi/Eagle-Eye-App" rel="noopener noreferrer"&gt;https://github.com/MasterIfeanyi/Eagle-Eye-App&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;The project was previously using Firebase before and I racked up a bill of $180 which hurt massively, so I decided to re-write it using Nextjs and MongoDB, and my assistant of choice was GitHub Co-pilot. I chose Nextjs and MongoDB because Nextjs to me feels like a full-stack option with a built-in /api folder and MongoDB Atlas is a free database service that I could use to store reports and perform authentication of users. Along the way I decided to add a page where you could get phone number of emergency services. If not for this hack-a-thon, I may have never come back to this project because I have been putting it off for some time as I am was busy.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Building Eagle Eye was not a smooth process. There were moments where the project felt completely stuck, and honestly, GitHub Copilot was a massive part of that process, as I primarily write Nextjs, but Co-pilot guided me and took away my fears. GitHub Copilot helped me think through the architecture from the ground up, guiding decisions like how to structure the Next.js app directory, how to handle authentication with JWT and cookies, and how to connect the frontend to MongoDB. When I hit bugs, like a broken scroll on the hotlines page, a sticky header that refused to stick, or a location detector pulling the wrong city, GitHub Copilot did not just hand me answers. It explained why things were breaking so I actually understood the fix. What I appreciate most is that GitHub Copilot treated me like a developer who was learning, not one who needed things done for them. This project genuinely would not exist in its current state without that support. Although, I was limited to the Claude Haiku 4.5 model (free plan), I feel like it was sufficient enought to help me.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
    </item>
    <item>
      <title>How to use Google Gemma 4 model</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Thu, 14 May 2026 08:58:30 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/how-to-use-google-gemma-4-model-2o3l</link>
      <guid>https://dev.to/ifeanyichima/how-to-use-google-gemma-4-model-2o3l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Write About Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I Built a Celebrity AI Chat App with Google Gemma 4 26B-A4B, Here's Everything You Need to Know&lt;/p&gt;

&lt;p&gt;I've always been fascinated by the idea of talking to your Elon Musk, but for now we would have to settle down for a &lt;strong&gt;chatbot&lt;/strong&gt; that actually &lt;em&gt;feels&lt;/em&gt; like them. So when Google dropped Gemma 4 in April 2026, I saw my chance. I built a web app with a chat UI where you pick a celebrity, and Gemma 4 slips into their persona and holds a full conversation with you. The results were genuinely impressive and sometimes hilarious.&lt;/p&gt;

&lt;p&gt;But before I walk you through how I built it, let me give you a proper introduction to the models that made it possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Understanding the Google Gemma 4 Model Family
&lt;/h2&gt;

&lt;p&gt;Gemma 4 is Google DeepMind's latest generation of open models, released on April 2, 2026. It comes in &lt;strong&gt;four distinct sizes&lt;/strong&gt;, each targeting a different use case, from running on your phone to powering a server-grade workstation. All four models are multimodal (they can process text and images), licensed under Apache 2.0, and support over 140 languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Comparison Table
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Audio&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E2B&lt;/td&gt;
&lt;td&gt;Efficient Dense&lt;/td&gt;
&lt;td&gt;128K&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Mobile / Edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E4B&lt;/td&gt;
&lt;td&gt;Efficient Dense&lt;/td&gt;
&lt;td&gt;128K&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;On-device assistants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26B A4B&lt;/td&gt;
&lt;td&gt;Mixture of Experts&lt;/td&gt;
&lt;td&gt;256K&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;High-throughput APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31B&lt;/td&gt;
&lt;td&gt;Dense&lt;/td&gt;
&lt;td&gt;256K&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Maximum quality&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  2. How to Get an API Key
&lt;/h2&gt;

&lt;p&gt;Before you can start building anything with Gemma 4, you need API access. The good news? You have a couple of options and one of them is completely free to start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Google AI Studio (Direct from Google)
&lt;/h3&gt;

&lt;p&gt;Google provides direct API access to Gemma 4 through &lt;strong&gt;Google AI Studio&lt;/strong&gt;. Here's how to get your key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://aistudio.google.com" rel="noopener noreferrer"&gt;aistudio.google.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sign in with your Google account&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Get API key"&lt;/strong&gt; in the left sidebar&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Create API key"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Copy your key and store it somewhere safe (you won't be able to see it again)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmip5p7ed8ne4dzfj82v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmip5p7ed8ne4dzfj82v1.png" alt="Google AI Studio" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Google AI Studio gives you a generous free tier to experiment with before you need to think about billing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never hardcode your API key in your frontend code. Always store it in an environment variable (&lt;code&gt;.env&lt;/code&gt; file) and access it from your backend.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; before your first commit, not after.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Option B: OpenRouter
&lt;/h3&gt;

&lt;p&gt;OpenRouter is a unified API gateway that gives you access to hundreds of models including all Gemma 4 variants through a single, OpenAI-compatible endpoint. That means you can switch models with one line of code change, get automatic fallbacks if a provider goes down, and even access the &lt;strong&gt;free tier&lt;/strong&gt; of Gemma 4 31B without spending a cent to start.&lt;/p&gt;

&lt;p&gt;To get your OpenRouter API key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://openrouter.ai" rel="noopener noreferrer"&gt;openrouter.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Sign In"&lt;/strong&gt; and create an account&lt;/li&gt;
&lt;li&gt;Once logged in, click &lt;strong&gt;"Get API Key"&lt;/strong&gt; button from your dashboard&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"New Key"&lt;/strong&gt;, give it a name (e.g. &lt;code&gt;celeb-chat-dev&lt;/code&gt;), and hit create&lt;/li&gt;
&lt;li&gt;Copy and save your key it starts with &lt;code&gt;sk-or-...&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can optionally add credits to your account for paid usage, but to start experimenting, the free-tier models (including &lt;code&gt;google/gemma-4-26b-a4b-it:free&lt;/code&gt;) are more than enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How to Use Google Gemma 4 Through OpenRouter
&lt;/h2&gt;

&lt;p&gt;Alright, this is where the fun begins. Let me walk you through exactly how I wired up Gemma 4 via OpenRouter to power my celebrity chat app, from the first API call all the way to a working chat UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Base URL and Model IDs
&lt;/h3&gt;

&lt;p&gt;OpenRouter exposes Gemma 4 through a single endpoint that's fully compatible with the OpenAI Chat API format. If you've ever called GPT-4 or any other model via OpenAI's SDK, this will feel immediately familiar, you're literally changing one URL and one model string.&lt;/p&gt;

&lt;p&gt;The base URL is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;https://openrouter.ai/api/v1/chat/completions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Gemma 4 model IDs on OpenRouter are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Model ID&lt;/th&gt;
&lt;th&gt;Free Tier?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E2B&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google/gemma-4-e2b-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 E4B&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google/gemma-4-e4b-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 26B A4B&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google/gemma-4-26b-a4b-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ (&lt;code&gt;:free&lt;/code&gt; suffix)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemma 4 31B&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google/gemma-4-31b-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ (&lt;code&gt;:free&lt;/code&gt; suffix)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To use the free tier of the 26B model for example, use &lt;code&gt;google/gemma-4-26b-a4b-it:free&lt;/code&gt; as the model string.&lt;/p&gt;




&lt;h3&gt;
  
  
  Switching Models Without Rewriting Your Code
&lt;/h3&gt;

&lt;p&gt;This is the quiet superpower of going through OpenRouter. Because the API schema is identical across all models, switching from the free 31B to the 26B MoE (which is faster and cheaper in production) is literally one line:&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;// During development&lt;/span&gt;
&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google/gemma-4-31b-it:free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// In production (faster, cheaper MoE)&lt;/span&gt;
&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google/gemma-4-26b-a4b-it:free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No refactoring. No new SDK. Just change the string. This is why I recommend OpenRouter for any serious project, you can prototype on free-tier models and graduate to the right production model without touching your integration code.&lt;/p&gt;




&lt;h3&gt;
  
  
  Your First API Call
&lt;/h3&gt;

&lt;p&gt;Let's start simple with a raw &lt;code&gt;fetch&lt;/code&gt; call in JavaScript, no SDKs needed:&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://openrouter.ai/api/v1/chat/completions&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&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="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENROUTER_API_KEY&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&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="s2"&gt;application/json&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google/gemma-4-26b-a4b-it:free&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What's the capital of Nigeria?&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="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;data&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Three things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your API key goes in the &lt;code&gt;Authorization&lt;/code&gt; header as a Bearer token&lt;/li&gt;
&lt;li&gt;The response shape mirrors OpenAI, your answer lives at &lt;code&gt;data.choices[0].message.content&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You're billed (or rate-limited on free tier) per token, so &lt;code&gt;max_tokens&lt;/code&gt; is worth setting explicitly&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I was getting this error&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google/gemma-4-26b-a4b-it:free is temporarily rate-limited upstream.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The free tier of Gemma 4 on OpenRouter is being hammered by too many users right now and Google AI Studio is throttling it. When you use the free tier on OpenRouter &lt;code&gt;(google/gemma-4-26b-a4b-it:free)&lt;/code&gt;, you are sharing a pool of rate limits with every other developer in the world using that same free model. So when too many people hit it at once, Google throttles it and you get that &lt;code&gt;429&lt;/code&gt; error. But you already have a Google AI Studio API key sitting in your &lt;code&gt;.env.local&lt;/code&gt; doing nothing right now. OpenRouter lets you connect your own Google AI Studio key so that your requests go through your personal quota instead of the shared free pool.&lt;/p&gt;

&lt;p&gt;You have two options:&lt;/p&gt;

&lt;p&gt;Option 1 — Add your Google AI Studio key to OpenRouter&lt;/p&gt;

&lt;p&gt;Option 2 — Add retry logic to your API route&lt;/p&gt;

&lt;p&gt;I went with Option 1 which is adding my Google AI Studio key to OpenRouter integrations&lt;/p&gt;

&lt;p&gt;Go to: &lt;code&gt;openrouter.ai/settings/integrations&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find Google AI Studio&lt;/li&gt;
&lt;li&gt;Paste your Google AI Studio API key there&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you your own dedicated rate limit instead of sharing the free pool with everyone&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8ghwn94mnwxihpsmxkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8ghwn94mnwxihpsmxkw.png" alt="OpenRouter integration" width="799" height="326"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The System Prompt; The Secret Weapon for My Project
&lt;/h3&gt;

&lt;p&gt;Here's where Gemma 4 genuinely impressed me. Gemma 4 officially supports the &lt;code&gt;system&lt;/code&gt; role natively; a big deal for an open model, since earlier generations required hacky workarounds. However, in my actual project I used a slightly different pattern that I found worked even better for persona-heavy apps: the &lt;strong&gt;user/assistant injection pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of passing the persona as a &lt;code&gt;system&lt;/code&gt; message, I injected it as the first &lt;code&gt;user&lt;/code&gt; message and followed it with a primed &lt;code&gt;assistant&lt;/code&gt; response where the model explicitly "agrees" to become the character before the real conversation even starts:&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;formattedMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// persona instructions injected here&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// the model "confirms" the persona before the conversation begins&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Understood. I am &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;personName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. I will only speak about my own life, work, and experiences. Ask me anything about who I am.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// then the real conversation history follows&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;messages&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;The subtle advantage of this approach over a plain &lt;code&gt;system&lt;/code&gt; message: the model has explicitly &lt;em&gt;agreed&lt;/em&gt; to the role as part of the conversation context. That in-context confirmation produces noticeably stronger character lock-in across long conversations. The model is far less likely to slip out of persona when it has already "committed" to it in an early assistant turn.&lt;/p&gt;

&lt;p&gt;The results were honestly uncanny. The model held the persona across multiple turns, matched the celebrity's tone and vocabulary, and even pushed back on out-of-character questions in a way that felt natural. The 256K context window meant I could carry a long conversation without the model ever "forgetting" who it was supposed to be.&lt;/p&gt;




&lt;h3&gt;
  
  
  Enabling Reasoning Mode
&lt;/h3&gt;

&lt;p&gt;One of Gemma 4's standout features is its configurable thinking mode, you can ask the model to reason through a problem before giving its final answer. OpenRouter exposes this through a &lt;code&gt;reasoning&lt;/code&gt; parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google/gemma-4-26b-a4b-it&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// turn on chain-of-thought&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When enabled, the response includes a &lt;code&gt;reasoning_details&lt;/code&gt; array showing the model's internal thought process before the final answer. I didn't use this for the celebrity chat (you don't want Morgan Freeman visibly deliberating), but it's incredibly useful for any app where you want to show &lt;em&gt;why&lt;/em&gt; the model said something for example; tutoring apps, code explainers, or decision-support tools.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One tip from the OpenRouter docs: if you're building an agentic tool or integrating with something like Claude Code or Cline, &lt;strong&gt;turn reasoning off&lt;/strong&gt;. It adds latency and those tools are optimized for fast back-and-forth, not deep deliberation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Thanks for Reading !
&lt;/h3&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>My Buddy and I</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Wed, 13 May 2026 16:33:04 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/my-buddy-and-i-5i5</link>
      <guid>https://dev.to/ifeanyichima/my-buddy-and-i-5i5</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I chose this project because as a kid (since age: 17) I have always had this vivid dreams of speaking with Elon Musk, he is kind of like my Idol and I would cherish the opportunity to have a conversation with him, until that day comes, this is what we would manage. When I came across this hackathon to test the lates model built by Google (Gemma 4) it was a no-brainer for me. I want the opportunity to understand the memory and context management of this model. I decided not to limit it to Elon Musk and included as much celebrities as possible. I want to give my friends the opportunity to test and use it. &lt;/p&gt;

&lt;p&gt;Buddy is a web application that lets you have real, in-character conversations with iconic public figures such as athletes, billionaires, historical leaders, artists, and innovators. You pick a person, and the AI steps into their shoes completely. It speaks the way they speak, stays grounded in their real life and career, and refuses to break character.&lt;/p&gt;

&lt;p&gt;Although, with this project I am not solving a problem like climate change or malaria cure, The problem Buddy solves is simple. Most AI chatbots are general-purpose. You can ask them anything, and they will answer as a neutral assistant. That is useful, but it is not immersive. Buddy flips that entirely. The AI is not an assistant anymore, it is a person. Ask Lionel Messi about his childhood in Rosario. Ask Steve Jobs why he believed design was everything. You get answers that feel like a real conversation, not a Wikipedia summary read back to you.&lt;/p&gt;

&lt;p&gt;The experience Buddy creates is curiosity without friction. It is the feeling of sitting across from someone remarkable and just... asking them things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Link: &lt;a href="https://celeb-chat.netlify.app" rel="noopener noreferrer"&gt;https://celeb-chat.netlify.app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/MasterIfeanyi/google-gemma4-hackathon" rel="noopener noreferrer"&gt;https://github.com/MasterIfeanyi/google-gemma4-hackathon&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;Buddy runs on Gemma 4 26B, accessed via OpenRouter using the &lt;code&gt;google/gemma-4-26b-a4b-it:free model&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The choice of Gemma 4 over the smaller variants came down to one core requirement; persona consistency. Keeping an AI locked inside a specific person's personality across a full multi-turn conversation is genuinely hard. The model needs to remember who it is, match the person's tone and vocabulary, deflect off-topic questions naturally without sounding robotic, and stay grounded in real publicly known facts all at the same time. The smaller E2B and E4B variants are impressive for their size, but they tend to drift out of character more easily under pressure, especially when a user deliberately tries to break the persona with trick questions or unrelated topics.&lt;/p&gt;

&lt;p&gt;The 26B model holds the character much more firmly. When you ask Messi a question in Spanish, it does not just translate, it responds with the rhythm and warmth of how Messi actually speaks. When you push Steve Jobs on a topic he would find irrelevant, it redirects with the kind of sharp confidence Jobs was known for. That level of nuance needs a bigger model.&lt;/p&gt;

&lt;p&gt;One specific technical challenge worth mentioning is Gemma 4 does not support a native system role in its message format. To get around this, Buddy injects the personality prompt as a user / assistant turn pair at the very start of every request, effectively tricking the model into treating the persona as established context before the real conversation begins. The 26B model responds to this pattern reliably and stays locked in. Smaller models were noticeably less consistent with the same approach.&lt;/p&gt;

&lt;p&gt;What &lt;strong&gt;Gemma 4&lt;/strong&gt; ultimately unlocked for Buddy was believability. Because the model is large enough to hold complex persona context across many turns, we never had to simplify the experience to make it work. We did not have to limit conversation length, restrict the types of questions users could ask, or hardcode fallback responses. The model handled edge cases we never explicitly planned for. When a user asked Messi to speak Spanish, &lt;strong&gt;Gemma 4&lt;/strong&gt; switched languages naturally and stayed in character at the same time, responding with the warmth and rhythm of how Messi actually communicates, without any instruction from us to do so. When users tried to break the persona with off-topic questions, the model deflected in the voice of the person, not with a generic AI refusal. That level of emergent, in-character behaviour is what a smaller model cannot reliably give you and it is what makes Buddy feel like a conversation rather than a demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you.
&lt;/h2&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>Google Next '26 - My thoughts</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Tue, 28 Apr 2026 08:01:47 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/google-next-26-my-thoughts-5dj3</link>
      <guid>https://dev.to/ifeanyichima/google-next-26-my-thoughts-5dj3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-cloud-next-2026-04-22"&gt;Google Cloud NEXT Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I have been eyeing to participate in a DEV.TO hackathon for a while now and when I saw this come across my timeline, I jumped up excitedly. I want the badge of honor. The big focus for the Google Cloud NEXT event is on what Google is debuting in the A.I. race especially agents. The future of developing on the cloud is exciting !! &lt;/p&gt;

&lt;h2&gt;
  
  
  Keynote &amp;amp; Demos
&lt;/h2&gt;

&lt;p&gt;In this article, I’ll break down the Developer Keynote because that stood out most to me and why.&lt;/p&gt;

&lt;p&gt;I tuned in for the &lt;a href="https://www.youtube.com/watch?v=A01DQ8_xy7Q" rel="noopener noreferrer"&gt;Developer Keynote (April, 23)&lt;/a&gt; which discussed building a marathon planning agent. A Gemini Enterprise Agent planned an entire Las Vegas marathon experience, coordinating hotels, routes, schedules, and logistics using multiple connected agents. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo 1 (Mofi Rahman) - Building agents with Agent Platform&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can design an agent in agent designer. When building an agent you need three things: Instructions; to help the agent understand its role as the marathon planner. Skills; help the agent progressively understand what is available to the agent to complete the work. such as using Google maps, handling Geo-spatial data. Map skills can make your agent an expert at using Google maps. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recap&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give the agent a prompt&lt;/li&gt;
&lt;li&gt;Agent loads the skills&lt;/li&gt;
&lt;li&gt;Agent executes the tools to find viable routes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this means for developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For developers, this means they can build specialized AI agents by composing three core components: instructions, skills, and tools, rather than writing complex logic from scratch. The agent designer platform handles how the agent loads skills, processes prompts, and decides which tools to call, so developers can focus on defining what the agent should do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo 2 (Ivan Nardini &amp;amp; Casey West) - Creating multi-agent systems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have a planner agent that needs to talk to 2 other agents: the simulator agent and the evaluator agent. The planner agent creates potential routes for the race. Evaluator sub-agent will judge routes based on specific criteria that we pick. Simulator agent works with the planner to get approved routes, run them and show results. How can we facilitate communication between all agents, we need two components; A2A protocol and agent registry. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A2A Protocol:&lt;/strong&gt; Google introduced an open-source protocol called A2A protocol. This protocol eliminates API code to connect agents. Agents can just share a card that represents the capabilities of that agent. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agent Registry:&lt;/strong&gt; This is a directory where agents get registered and it resolves every agent identity and maps their specific skillset across the agent network.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What this means for developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For developers, this means they can build multi-agent systems where agents communicate and collaborate without writing custom API integration code. The A2A protocol lets agents share capability cards with each other, while the agent registry acts as a directory that tracks every agent's identity and skillset across the network, so the planner, simulator, and evaluator agents can find and talk to each other automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo 3 (Lucia Subatin &amp;amp; Jack Wotherspoon) - Enhancing agents with memory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When using gemini to do my job in the office, I usually copy all the files one-by-one and shove it into one prompt, in a bid to give gemini as much context as possible. For our planner agent we improve the context by giving it the ability to manage sessions, add memory and access other data sources. In this demo, we saw how we can use a skill to make the agent an expert at using alloyDB and vector functions. Basically what this Demo was showing is that by managing context efficiently and adding memory to our agent you can see the simulator agent adjust its route based on the memory and data it now had. The memory aspect comes from how the agent uses alloyDB together with vector functions to store and recall context from previous sessions, so it (alloyDB) behaves like a memory bank. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means for developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For developers, this means they can replace the manual process of dumping files into a prompt by giving agents structured memory and database access instead. By connecting the agent to tools like AlloyDB and vector functions, the agent automatically manages its own context across sessions, so it can make smarter, more informed decisions without the developer having to feed it everything upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo 4 (Megan O'keefe) - Debugging agents at scale&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We all use &lt;code&gt;console.log("here")&lt;/code&gt; to debug a point in our codebase, well in artificial intelligence, we can not do that but, Google provides tools for us to debug; Agent observability and Cloud assist investigation agent. Using these tools we will find and investigate the root cause of the error and then we will deploy a proactive fix. In this demo, we had an error and we were suggested a fix by the agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; The simulator agent is failing to call the gemini model API due to a request error. Something is wrong that the payloads were sending to the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud assist prompt:&lt;/strong&gt; Resume the Gemini cloud assist investigation about the simulator agent. What is wrong with agent.py&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; The agent is suggesting a fix that we add a token threshold parameter to our event compaction config so that we are periodically compressing context more often with each invocation. This is because we have a 1 million context token limit.&lt;/p&gt;

&lt;p&gt;From this demo, I learned that Google cloud provides a full observability suite for agents - cloud assist, coding agent. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means for developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For developers, this means they have a proper debugging suite for AI agents rather than guessing what went wrong. Google Cloud's observability tools let developers investigate agent failures, understand why an agent misbehaved, and get suggested fixes, so in this case the tool spotted that the simulator agent was hitting Gemini's 1 million token context limit and recommended compressing context more frequently to stay within that boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google Cloud NEXT made one thing clear: building AI agents is becoming a first class developer experience. You no longer have to stitch together APIs, manually manage context, or guess why your agent broke. Google is giving developers a full stack for agent development, from designing and connecting agents, to giving them memory, to debugging them when things go wrong. The A2A protocol, agent registry, AlloyDB memory, and Cloud observability tools all point in the same direction: agents are becoming software components that developers can build, scale, and maintain just like any other part of their stack. The marathon planner was just a demo but the underlying tools are real and they are ready to be used.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>cloudnextchallenge</category>
      <category>googlecloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>Outreachy May, 2026</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Thu, 16 Apr 2026 00:20:49 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/outreachy-may-2026-2b7l</link>
      <guid>https://dev.to/ifeanyichima/outreachy-may-2026-2b7l</guid>
      <description>&lt;h2&gt;
  
  
  Open source
&lt;/h2&gt;

&lt;p&gt;I have been singing about open-source to my friends for about some time now. I write articles about it. I have done hacktoberfest (twice in 2022 &amp;amp; 2023). It has always been on my mind to take on the big 3 - &lt;strong&gt;MLH, Outreachy and G-Soc&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Outreachy - initial applications
&lt;/h2&gt;

&lt;p&gt;Alright, let me start by saying: "Thank you universe for the reminder at the eleventh hour". This is because I literally forgot that the initial applications for contribution phase for May-August, 2026 Outreachy internship had already started. It opened Feb 6 - Feb 13, 2026. I was only reminded by a random tweet from a stranger on the 11th hour (Feb 13, 2 pm). Applications closed by (Feb 13, 5 pm).&lt;/p&gt;

&lt;h3&gt;
  
  
  Interlude
&lt;/h3&gt;

&lt;p&gt;Once you get selected after the initial applications, you would see different organisations, select the one that suits you best. I chose Mozilla firefox because it required knowledge of HTML, CSS, and JavaScript. &lt;/p&gt;

&lt;h2&gt;
  
  
  Outreachy - Contribution phase
&lt;/h2&gt;

&lt;p&gt;Before you get selected as an intern with Outreachy, you need to take part in the contribution phase which lasts for about three (3) weeks. It actually ends today (March 20, 2026 - April 15, 2026). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqmkkgkzixt6pj8hgsdh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqmkkgkzixt6pj8hgsdh.gif" alt="Wizard meme" width="250" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I learned quickly how to use bugzilla to find issues on my own (I made sure to look out for bugs with the label &lt;code&gt;good-first-issue&lt;/code&gt;) and I also learned how to create patches using phabricator. &lt;strong&gt;Bugzilla&lt;/strong&gt; and &lt;strong&gt;Phabricator&lt;/strong&gt; those words sound big and fancy but phrabicator is just a single line of code - &lt;code&gt;moz-phab submit --single&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;here is a sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; bug-2022414-array-profiles

git add &lt;span class="s2"&gt;"file name"&lt;/span&gt;

git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix: migrations for profiles with backup enabled"&lt;/span&gt;

moz-phab submit &lt;span class="nt"&gt;--single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You submit a patch for a bug, you get feedback or review from a mentor and then you push again.&lt;/p&gt;

&lt;p&gt;One thing I learned, always make sure that you &lt;code&gt;checkout -b&lt;/code&gt; into a new branch from &lt;code&gt;main&lt;/code&gt; so that the branch does not have unrealated histroy from other bugs or commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outreachy - Final Application
&lt;/h2&gt;

&lt;p&gt;In your final application, you would be asked a few questions, you are to write essays answering them, they ask about your experience with the organisation. Also, You need submit a timeline of the work you propose to do during the thirteen week internship. You look at the goal of the organisation and you outline how you intend to approach the task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpe5o7x3k2lwc34hf2n6n.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpe5o7x3k2lwc34hf2n6n.jpg" alt="Job and Experience meme" width="800" height="809"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My life is literally this meme and the best way to get a job is to have some experience under your belt, which the Outreachy internship will provide. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you, please follow me.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This blog is only a single part, in a multi-series blog that I will write about my experience !!!&lt;/p&gt;

&lt;p&gt;Stay tuned !!!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Useless project.</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Mon, 06 Apr 2026 13:42:06 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/useless-project-371a</link>
      <guid>https://dev.to/ifeanyichima/useless-project-371a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A button. Just a button.&lt;/p&gt;

&lt;p&gt;You click it, and lines start crawling across your screen. 50 vertical lines and 50 horizontal lines slowly forming a grid of boxes like graph paper possessed by a ghost. That's it. That's the whole app. No data. No purpose. No redemption arc.&lt;/p&gt;

&lt;p&gt;You can control how many lines appear, how fast they draw, and what color they are. You can also clear the grid and do it all over again, because apparently that's how you want to spend your time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvb0ol88puo6snf14gp3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvb0ol88puo6snf14gp3.gif" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/MasterIfeanyi" rel="noopener noreferrer"&gt;
        MasterIfeanyi
      &lt;/a&gt; / &lt;a href="https://github.com/MasterIfeanyi/grid-lines" rel="noopener noreferrer"&gt;
        grid-lines
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Built with pure HTML5 Canvas, vanilla JavaScript, and zero regrets.&lt;/p&gt;

&lt;p&gt;The core idea is simple, the canvas is just a blank piece of paper. I grab a "pencil" using &lt;code&gt;getContext('2d')&lt;/code&gt;, pre-calculate all the x and y positions for each line by dividing the canvas width and height by the number of lines, store them in two arrays (&lt;code&gt;verticals[]&lt;/code&gt; and &lt;code&gt;horizontals[]&lt;/code&gt;), then draw them one by one using &lt;code&gt;setTimeout&lt;/code&gt; so you can actually watch the chaos unfold in real time.&lt;/p&gt;

&lt;p&gt;No frameworks. No libraries. No npm install. Just a canvas, a loop, and a questionable life decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;Community Favorite.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Two worlds of mine colliding !</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Wed, 18 Feb 2026 17:30:37 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/two-worlds-of-mine-colliding--24h3</link>
      <guid>https://dev.to/ifeanyichima/two-worlds-of-mine-colliding--24h3</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/devteam/a-new-chapter-dev-is-joining-forces-with-major-league-hacking-mlh-3kfd" class="crayons-story__hidden-navigation-link"&gt;A New Chapter: DEV is Joining Forces with Major League Hacking (MLH)&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/gGUkTiiCqxE" title="A New Chapter: DEV is Joining Forces with Major League Hacking (MLH)"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/devteam"&gt;
            &lt;img alt="The DEV Team logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/ben" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1%2Fbabb96d0-9cd2-49bc-a412-2dc4caf94c2a.png" alt="ben profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ben" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ben Halpern
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ben Halpern
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3265532" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/ben" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1%2Fbabb96d0-9cd2-49bc-a412-2dc4caf94c2a.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ben Halpern&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/devteam" class="crayons-story__secondary fw-medium"&gt;The DEV Team&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/devteam/a-new-chapter-dev-is-joining-forces-with-major-league-hacking-mlh-3kfd" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Feb 18&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/devteam/a-new-chapter-dev-is-joining-forces-with-major-league-hacking-mlh-3kfd" id="article-link-3265532"&gt;
          A New Chapter: DEV is Joining Forces with Major League Hacking (MLH)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/news"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;news&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devto"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devto&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hackathon"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hackathon&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/devteam/a-new-chapter-dev-is-joining-forces-with-major-league-hacking-mlh-3kfd" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;398&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/devteam/a-new-chapter-dev-is-joining-forces-with-major-league-hacking-mlh-3kfd#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              110&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>news</category>
      <category>devto</category>
      <category>hackathon</category>
      <category>discuss</category>
    </item>
    <item>
      <title>A must read on how to implement GitHub actions.</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Sun, 18 Jan 2026 10:55:01 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/a-must-read-on-how-to-implement-github-actions-2gkk</link>
      <guid>https://dev.to/ifeanyichima/a-must-read-on-how-to-implement-github-actions-2gkk</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/ifeanyichima/github-actions-example-29pj" class="crayons-story__hidden-navigation-link"&gt;GitHub actions example&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/ifeanyichima" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F763463%2F47ea4de3-e833-48d3-8a9b-507ac915483f.jpg" alt="ifeanyichima profile" class="crayons-avatar__image" width="605" height="403"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ifeanyichima" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ifeanyi Chima
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ifeanyi Chima
                
              
              &lt;div id="story-author-preview-content-1793929" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/ifeanyichima" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F763463%2F47ea4de3-e833-48d3-8a9b-507ac915483f.jpg" class="crayons-avatar__image" alt="" width="605" height="403"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ifeanyi Chima&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/ifeanyichima/github-actions-example-29pj" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 18 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/ifeanyichima/github-actions-example-29pj" id="article-link-1793929"&gt;
          GitHub actions example
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/githubactions"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;githubactions&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/ifeanyichima/github-actions-example-29pj" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/ifeanyichima/github-actions-example-29pj#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>devops</category>
      <category>githubactions</category>
    </item>
  </channel>
</rss>
