<?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>Getting the "Verify Claim" Exercise to Work on My Own Computer</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Sat, 17 Oct 2026 14:35:46 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/getting-the-verify-claim-exercise-to-work-on-my-own-computer-4o95</link>
      <guid>https://dev.to/ifeanyichima/getting-the-verify-claim-exercise-to-work-on-my-own-computer-4o95</guid>
      <description>&lt;p&gt;&lt;strong&gt;WikiEduDashboard&lt;/strong&gt; is the project I picked. It is a website that helps teachers and students run Wikipedia assignments. A teacher makes a course, students join it, and the site tracks what the students edit.&lt;/p&gt;

&lt;p&gt;The microtask is called claim verification. The project lead (Rage Soss) asked applicants to try out the "verify claim" exercise as a student would, and share ideas to make it better.&lt;/p&gt;

&lt;p&gt;To have ideas about a student's experience, I first have to be a student, on my own computer, with real-looking data. That is the problem this article is about. So, this is the job, and that is the reason for me writing this article: I can't suggest improvements to an exercise/projects I can't use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;The "Fact Verification Exercise" shows a student a sentence from a Wikipedia article, along with the source that sentence cites. The student then goes to the source and checks whether the sentence is really backed up. This matters because every fact on Wikipedia should be backed by a reliable source.&lt;/p&gt;

&lt;p&gt;I tried to experience this as a student and hit two walls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;On the live demo site (production)&lt;/strong&gt;: every time I clicked "Select this claim to verify", it told me to enroll in the course first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On my own computer (localhost)&lt;/strong&gt;: the page &lt;code&gt;/courses/Uffizi/WDG_-_AF_2018_Florence/verify_claim&lt;/code&gt; said &lt;strong&gt;"No articles available"&lt;/strong&gt;, so there was nothing to click.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because of this, I could not go through the exercise like a real student.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was actually going wrong (the diagnosis)
&lt;/h2&gt;

&lt;p&gt;A fresh local copy of the site/codebase starts with an almost empty database.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the page needs&lt;/th&gt;
&lt;th&gt;Plain meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;An &lt;strong&gt;article&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;A Wikipedia page the exercise can use (we used "Surrogacy in India")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A &lt;strong&gt;claim&lt;/strong&gt; attached to that article&lt;/td&gt;
&lt;td&gt;One sentence from the article, plus the source it cites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A link between the article and my course&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Without it, the article does not show up in the course's list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Me, enrolled as a student&lt;/strong&gt; in that course&lt;/td&gt;
&lt;td&gt;Without it, the site refuses with "You must be enrolled in the course"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My local codebase had none of the first three, so the page was empty with no article showing up for me. The "enroll first" message on production was the same rule showing up in a different place (locally).&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;Gabina, someone in the project's Slack, suggested that I fill my local database with fake-but-realistic data. This is optional and just for exploring. Her idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make sure my local setup already works.&lt;/li&gt;
&lt;li&gt;Make sure I have some course data locally.&lt;/li&gt;
&lt;li&gt;Run two database statements that add one article and one claim.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then sign in as a student in a local course and open the verify-claim page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quote marks must match&lt;/strong&gt;. The claim sentence has the words "surrogacy packages" in quotes. Gabina's version used plain straight quotes, but the real Wikipedia page uses curly ones. The page finds the sentence by looking for an exact match, so a straight quote and a curly quote look like two different letters to the computer. No match means no purple highlight and no popup.&lt;br&gt;
&lt;strong&gt;Link the article to the course&lt;/strong&gt;. Otherwise it will not show in the Step 1 list.&lt;/p&gt;
&lt;h2&gt;
  
  
  The plan, step by step
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Get into the local database.&lt;/li&gt;
&lt;li&gt;Check the number that identifies English Wikipedia in my database.&lt;/li&gt;
&lt;li&gt;Add the article and the claim.&lt;/li&gt;
&lt;li&gt;Add more claims, with curly quotes, and link the article to my course.&lt;/li&gt;
&lt;li&gt;Add myself to the course as a student.&lt;/li&gt;
&lt;li&gt;Open the page and try it like a student.&lt;/li&gt;
&lt;li&gt;Clean up, so none of this ends up in GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what the terminal looks like&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%2Ffhsn620wzwdy3uzi3hs6.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%2Ffhsn620wzwdy3uzi3hs6.png" alt=" " width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Getting into the database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I needed the database password. I did not know it, so I looked in the project's settings file, in your terminal run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config/database.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returned this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;default: &amp;amp;default
  adapter: mysql2
  encoding: utf8mb4
  username: wiki
  password: wikiedu
  host: 127.0.0.1
  port: 3306
  pool: 40

development:
  &amp;lt;&amp;lt;: &lt;span class="k"&gt;*&lt;/span&gt;default
  database: dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this you can see that my password is &lt;strong&gt;wikiedu&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is config/database.yml&lt;/strong&gt; is the file that tells the website how to log in to its database. Anything I need to log in myself is in there too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Checking the Wikipedia id&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, we know our database password. We have to check the Wikipedia id. Gabina's statements assume English Wikipedia has the id number &lt;code&gt;1&lt;/code&gt; in my database. I checked instead of trusting:&lt;/p&gt;

&lt;p&gt;Firstly, &lt;strong&gt;Open the database&lt;/strong&gt; console using this, open a second terminal and run this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails dbconsole
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can type this SQL syntax in the console to check the Wikipedia id&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wikis&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'wikipedia'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what I got&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;MariaDB [dashboard]&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT &lt;span class="nb"&gt;id &lt;/span&gt;FROM wikis WHERE project &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'wikipedia'&lt;/span&gt; AND language &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="go"&gt;+----+
| id |
+----+
|  3 |
+----+
1 row in set (0.007 sec)

&lt;/span&gt;&lt;span class="gp"&gt;MariaDB [dashboard]&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: &lt;strong&gt;3&lt;/strong&gt;, not &lt;strong&gt;1&lt;/strong&gt;. If I had pasted her statements without checking, the article and claim would have been tagged with the wrong wiki. So I swapped 1 for 3 in both statements.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; the id is just a label. The article has to carry the label of the wiki it belongs to, and my local label is different from hers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since my English Wikipedia id is 3, not 1, so I have to swap it in.&lt;/p&gt;

&lt;p&gt;The next steps are to add the article and the claim.&lt;/p&gt;

&lt;p&gt;Here are both statements with the change already made. Paste them one at a time, pressing Enter after each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the article:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Statement 1 (the article): This adds one row to the &lt;code&gt;articles&lt;/code&gt; table ("Surrogacy in India")&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rating_updated_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;average_views&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wiki_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mw_page_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;376975008&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Surrogacy_in_India'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2022-11-06 23:16:53'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-10-16 11:33:53'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'start'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-10-16 11:33:53'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'25122640'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add the first claim:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Statement 2 (the claim): This adds one row to the &lt;code&gt;verification_claims&lt;/code&gt; table (one sentence plus its source)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;verification_claims&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alert_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;archive_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cite_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;courses_users_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mw_rev_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mw_rev_timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offline_source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ref_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sentence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_course_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wiki_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1060&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'459064'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'376975008'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Surrogacy_in_India'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hibino, Yuri (2023-07-07). "The advantages and disadvantages of altruistic and commercial surrogacy in India". Philosophy, ethics, and humanities in medicine: PEHM. 18 (1): 8. doi:10.1186/s13010-023-00130-y. ISSN 1747-5341. PMC 10327345. PMID 37420245.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'The legal history of commercial gestational surrogacy trace the emergence of commercial gestational surrogacy in India to the early 2000s, when assisted reproductive technologies expanded rapidly and a 2002 court decision was interpreted as legalising commercial surrogacy. Clinics and agencies began offering full "surrogacy packages" to both Indian and foreign clients, and surrogacy was promoted as part of a wider medical tourism strategy.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'264083'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-01 16:36:49.275569'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1321874993'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2025-11-13 03:47:18.000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'cite_note-:0-1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Clinics and agencies began offering full "surrogacy packages" to both Indian and foreign clients, and surrogacy was promoted as part of a wider medical tourism strategy.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'19096'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://pmc.ncbi.nlm.nih.gov/articles/PMC10327345/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'LGBT+ Studies'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-01 16:36:49.275569'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each should reply with something like &lt;code&gt;Query OK, 1 row affected&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then type &lt;code&gt;exit&lt;/code&gt; to leave the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem(s)&lt;/strong&gt;: &lt;strong&gt;The article showed up on the page, but there was no claim to verify.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two things were wrong, matching the warnings from the group chat:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 1: the claim doesn’t light up (quote marks)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The page finds the claim by searching for the exact sentence, letter for letter. Gabina’s sentence uses plain straight quote marks around “surrogacy packages”. The real Wikipedia page uses the fancy curly ones. To the computer, those are different characters, so it can’t find the sentence and nothing gets highlighted. Swap in curly quotes and the match works, which brings back the purple highlight and the popup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2: the article isn’t connected to your course&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adding the article to the database isn’t enough. It also has to be linked to your course, otherwise it won’t show in the Step 1 list. That’s why you see the article but no claim to verify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: A script that does all of it &lt;/p&gt;

&lt;p&gt;Instead of typing many database statements, I saved a small Ruby script called &lt;code&gt;seed_claims.rb&lt;/code&gt; and ran it. &lt;/p&gt;

&lt;p&gt;Save the script as a file (still in the &lt;code&gt;WikiEduDashboard&lt;/code&gt; folder).&lt;/p&gt;

&lt;p&gt;It does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds English Wikipedia, my course, and the article.&lt;/li&gt;
&lt;li&gt;Links the article to the course.&lt;/li&gt;
&lt;li&gt;Adds nine claims from the History section of the article (so I can also test the "Next claim" and "Previous claim" buttons).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the shape of it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;seed_claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;'RUBY'&lt;/span&gt;&lt;span class="sh"&gt;
wiki    = Wiki.find_by(project: 'wikipedia', language: 'en') || Wiki.first
course  = Course.find_by(slug: 'Uffizi/WDG_-_AF_2018_Florence')
article = Article.find_by(id: 376975008)

# Link the article to the course so it shows up in Step 1
ArticlesCourses.find_or_create_by(course: course, article: article)

claims = [
  {
    id: 1060,
    sentence: 'Clinics and agencies began offering full “surrogacy packages” to both Indian and foreign clients, and surrogacy was promoted as part of a wider medical tourism strategy.',
    context: 'The legal history of commercial gestational surrogacy trace the emergence of commercial gestational surrogacy in India to the early 2000s, when assisted reproductive technologies expanded rapidly and a 2002 court decision was interpreted as legalising commercial surrogacy. Clinics and agencies began offering full “surrogacy packages” to both Indian and foreign clients, and surrogacy was promoted as part of a wider medical tourism strategy.'
  },
{
    id: 1061,
    sentence: 'Estimates by industry groups suggested that surrogacy generated more than US$2 billion annually, with thousands of IVF clinics across the country.',
    context: 'Clinics and agencies began offering full “surrogacy packages” to both Indian and foreign clients, and surrogacy was promoted as part of a wider medical tourism strategy. Estimates by industry groups suggested that surrogacy generated more than US$2 billion annually, with thousands of IVF clinics across the country.'
  },
{
    id: 1062,
    sentence: 'Throughout the 2000s and early 2010s, India became a major global destination for transnational commercial surrogacy, attracting heterosexual couples, single intended parents and same-sex couples from abroad.',
    context: 'Throughout the 2000s and early 2010s, India became a major global destination for transnational commercial surrogacy, attracting heterosexual couples, single intended parents and same-sex couples from abroad. At the same time, a series of highly publicised cases including deaths of egg donors and surrogates, visa and citizenship disputes, and abandonment of children fuelled concerns about exploitation, inadequate oversight and legal uncertainty.'
  },
  {
    id: 1063,
    sentence: 'At the same time, a series of highly publicised cases including deaths of egg donors and surrogates, visa and citizenship disputes, and abandonment of children fuelled concerns about exploitation, inadequate oversight and legal uncertainty.',
    context: 'Throughout the 2000s and early 2010s, India became a major global destination for transnational commercial surrogacy, attracting heterosexual couples, single intended parents and same-sex couples from abroad. At the same time, a series of highly publicised cases including deaths of egg donors and surrogates, visa and citizenship disputes, and abandonment of children fuelled concerns about exploitation, inadequate oversight and legal uncertainty.'
  },
  {
    id: 1064,
    sentence: 'Draft Assisted Reproductive Technology (ART) Bills were prepared in 2008, 2010 and 2014, and Indian Council of Medical Research guidelines were issued in 2005, but no comprehensive national statute on surrogacy was enacted.',
    context: 'At the same time, a series of highly publicised cases including deaths of egg donors and surrogates, visa and citizenship disputes, and abandonment of children fuelled concerns about exploitation, inadequate oversight and legal uncertainty. Draft Assisted Reproductive Technology (ART) Bills were prepared in 2008, 2010 and 2014, and Indian Council of Medical Research guidelines were issued in 2005, but no comprehensive national statute on surrogacy was enacted.'
  },
  {
    id: 1065,
    sentence: 'Visa rules for international intended parents were tightened, and in November 2015 medical visas related to surrogacy were suspended, effectively excluding foreign clients from accessing commercial surrogacy in India.',
    context: 'From 2012 onwards, the policy direction shifted toward restriction. Visa rules for international intended parents were tightened, and in November 2015 medical visas related to surrogacy were suspended, effectively excluding foreign clients from accessing commercial surrogacy in India.'
  },
  {
    id: 1066,
    sentence: 'In 2016 the government introduced the Surrogacy (Regulation) Bill 2016, which proposed banning commercial surrogacy altogether and permitting only altruistic surrogacy for Indian heterosexual married couples who met infertility criteria.',
    context: 'Visa rules for international intended parents were tightened, and in November 2015 medical visas related to surrogacy were suspended, effectively excluding foreign clients from accessing commercial surrogacy in India. In 2016 the government introduced the Surrogacy (Regulation) Bill 2016, which proposed banning commercial surrogacy altogether and permitting only altruistic surrogacy for Indian heterosexual married couples who met infertility criteria.'
  },
  {
    id: 1067,
    sentence: 'Under this model, surrogates had to be Indian women aged 25–35, already mothers to at least one child, and “close relatives” of the commissioning couple; they could receive reimbursement of medical expenses and insurance but no other payment.',
    context: 'In 2016 the government introduced the Surrogacy (Regulation) Bill 2016, which proposed banning commercial surrogacy altogether and permitting only altruistic surrogacy for Indian heterosexual married couples who met infertility criteria. Under this model, surrogates had to be Indian women aged 25–35, already mothers to at least one child, and “close relatives” of the commissioning couple; they could receive reimbursement of medical expenses and insurance but no other payment.'
  },
  {
    id: 1068,
    sentence: 'The 2016 bill was examined by the 102nd Parliamentary Standing Committee on Health and Family Welfare, which recommended changes, and subsequently lapsed.',
    context: 'The 2016 bill was examined by the 102nd Parliamentary Standing Committee on Health and Family Welfare, which recommended changes, and subsequently lapsed. A revised Surrogacy (Regulation) Bill 2019 was passed by the Lok Sabha and referred to a Rajya Sabha Select Committee.'
  }

]

claims.each do |c|
  claim = VerificationClaim.find_or_initialize_by(id: c[:id])
  claim.assign_attributes(
    article_id: article.id,
    article_title: 'Surrogacy_in_India',
    sentence: c[:sentence],
    context: c[:context],
    source_url: 'https://pmc.ncbi.nlm.nih.gov/articles/PMC10327345/',
    source_course_id: course.id,
    wiki_id: wiki.id
    # plus a few other fields the table asks for
  )
  claim.save!(validate: false)
end

puts "Done! Total claims for Surrogacy in India: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;VerificationClaim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;article_id: &lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
puts "Linked to course: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; (&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;)"
&lt;/span&gt;&lt;span class="no"&gt;RUBY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your terminal, copy and paste this syntax, then press enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails runner seed_claims.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is rails runner&lt;/strong&gt; runs a script inside the website's world, so the script can use the same tools the website uses (Course, Article, and so on) instead of writing raw database statements.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the end it should print “Done! Total claims for Surrogacy in India” with a number, then the name and address of the course it linked to.&lt;/p&gt;

&lt;p&gt;Then go to &lt;code&gt;localhost:3000/courses/&amp;lt;that course address&amp;gt;/verify_claim&lt;/code&gt;, to see if we have articles and a claim&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; &lt;strong&gt;I got this "You must be enrolled in the course to do that"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The article and claims were now in place, but the page still refused me. This is the same message I saw on production, and the cause is the same: &lt;strong&gt;my user account was not a student in that course on my local copy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix is to add myself to the course as a student:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rails runner &lt;span class="s2"&gt;"user = User.find_by(username: 'Chimaifeanyi29'); course = Course.find_by(slug: 'Uffizi/WDG_-_AF_2018_Florence'); CoursesUsers.find_or_create_by(user: user, course: course, role: CoursesUsers::Roles::STUDENT_ROLE); puts CoursesUsers.where(user: user, course: course).count"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it works, it prints 1. That means you are now a student in the course.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is CoursesUsers:&lt;/strong&gt; is the table that says "this person has this role in this course". Having an account is not enough. The site checks this table before it lets you do student things.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Open the student workflow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In your browser:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open WikiEduDashboard locally&lt;/li&gt;
&lt;li&gt;visit the URL &lt;code&gt;localhost:3000/courses/Uffizi/WDG_-_AF_2018_Florence/verify_claim&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The article should show up under Step 1. Click it and the first sentence should light up purple.&lt;/p&gt;

&lt;h1&gt;
  
  
  THANK YOU, PLEASE FOLLOW ME
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>outreachy33</category>
      <category>ubuntu</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to translate MDN web docs to spanish.</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Thu, 03 Sep 2026 23:21:22 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/how-to-translate-mdn-web-docs-to-spanish-2p0f</link>
      <guid>https://dev.to/ifeanyichima/how-to-translate-mdn-web-docs-to-spanish-2p0f</guid>
      <description>&lt;p&gt;Good day, for the past 3 weeks I have been busy translating the popular documentation platform known as &lt;a href="https://developer.mozilla.org/en-US/" rel="noopener noreferrer"&gt;MDN web docs&lt;/a&gt; into the Spanish version, to help Spanish-speaking readers as it had been outdated for years with respect to the English version. &lt;/p&gt;

&lt;p&gt;This has been part of my open-source journey. In this article, I want to talk about the process, to enable future contributors to seamlessly contribute one day. &lt;/p&gt;

&lt;p&gt;Here are the &lt;a href="https://github.com/mdn/translated-content/blob/main/docs/es/README.md" rel="noopener noreferrer"&gt;rules to follow&lt;/a&gt; when trying to do the Spanish-translation work. Everything that I am going to write here is already mentioned inside of the &lt;a href="https://github.com/mdn/translated-content/blob/main/docs/es/README.md" rel="noopener noreferrer"&gt;rules&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, what is our job here ? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's a page in English for example: Django's "Admin site" feature.&lt;/li&gt;
&lt;li&gt;There's already a Spanish version of that page, but it's old and out of date.&lt;/li&gt;
&lt;li&gt;Your job is to go through the whole Spanish file, compare it to the whole English file, and rewrite/update it so it matches: same sections, same order, nothing left in English&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is one rule to just bear in mind &lt;/p&gt;

&lt;p&gt;&lt;em&gt;is this text something a human reads (translate it) or something the code/interpreter looks up by exact name (leave it untouched)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  File Paths
&lt;/h2&gt;

&lt;p&gt;This is the most important one; File paths (bolded or inline) are never translated. It stays the same. Below, we will discuss the only thing that changes, which is &lt;code&gt;en-US&lt;/code&gt; to &lt;code&gt;es&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;English: &lt;code&gt;docs/Web/JavaScript/Reference/Global_Objects/Object/assign&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Spanish: &lt;code&gt;docs/Web/JavaScript/Reference/Global_Objects/Object/assign&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Front-matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Final front-matter should only contain: &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;slug&lt;/code&gt;, &lt;code&gt;l10n.sourceCommit&lt;/code&gt; (plus &lt;code&gt;short-title&lt;/code&gt; when the English file has one).&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;page-type&lt;/code&gt;, &lt;code&gt;sidebar&lt;/code&gt;, &lt;code&gt;original_slug&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt; (and &lt;code&gt;short-title&lt;/code&gt;): translate to Spanish.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;slug&lt;/code&gt;: These are file paths, not visible text, so they stay exactly as in English do not translate.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;l10n.sourceCommit&lt;/code&gt;: set to the exact SHA given for that English source commit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;External links:&lt;/strong&gt; These are links point to external web pages and they should be left as is. For example:  &lt;a href="https://validator.w3.org/" rel="noopener noreferrer"&gt;https://validator.w3.org/&lt;/a&gt; or &lt;a href="https://developer.flutterwave.com/docs/testing" rel="noopener noreferrer"&gt;https://developer.flutterwave.com/docs/testing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal links:&lt;/strong&gt; All the internal links that point to pages with in the MDN web docs website have to be changed from &lt;code&gt;/en-US/&lt;/code&gt; to &lt;code&gt;/es/&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;For example: &lt;/p&gt;

&lt;p&gt;Original (English source):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has to be changed to point to a spanish file now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/es/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Link Text:&lt;/strong&gt; (ONLY the visible label) gets translated when it's an internal MDN link. &lt;/p&gt;

&lt;p&gt;For example&lt;/p&gt;

&lt;p&gt;Original (English source):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Object basics&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;/en-US/docs/Learn_web_development/Core/Scripting/Object_basics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have to translate The link text "Object basics" to "Fundamentos de objetos"&lt;/p&gt;

&lt;p&gt;Translated (Spanish):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Fundamentos de objetos&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;/es/docs/Learn_web_development/Core/Scripting/Object_basics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Anchors:&lt;/strong&gt; when you have an anchor like this, &lt;code&gt;#heading_about_a_paragraph&lt;/code&gt;. Two things are involved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need to check if that section that the anchor is referring to exists. &lt;/li&gt;
&lt;li&gt;If it exists, then you should translate it to Spanish &lt;code&gt;#encabezado_sobre_un_párrafo&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bold / italics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep the bold/italic markers in the same place, only translate the text content.&lt;/li&gt;
&lt;li&gt;Text content that were bold or italics in English stay bold or italics in Spanish.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Original (English source):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="ge"&gt;_this is supposed to be italic_&lt;/span&gt; and &lt;span class="gs"&gt;**this is supposed to be bold**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="ge"&gt;_esto se supone que debe estar en cursiva_&lt;/span&gt; and &lt;span class="gs"&gt;**esto se supone que debe estar en negrita**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Image(s)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Never copy images into the files/es/ folder because the build pulls them from the English folder automatically. Just leave the image file path as is&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only the alt text (the [...] part) gets translated; the filename or file path stays exactly as-is.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example: &lt;/p&gt;

&lt;p&gt;Original (English source)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Prototype chain for myObject&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;myobject-prototype-chain.svg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Cadena de prototipos para myObject&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;myobject-prototype-chain.svg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Callouts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In GitHub markdown, callouts such as &lt;code&gt;[!NOTE]&lt;/code&gt; and &lt;code&gt;[!WARNING]&lt;/code&gt; stay untranslated because translating them breaks the styled box. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ONLY the user-facing text content contained within the box gets translated. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example,&lt;/p&gt;

&lt;p&gt;Original (English source)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gt"&gt;&amp;gt;[!NOTE]&lt;/span&gt;
&lt;span class="gt"&gt;&amp;gt; See also the [glossary entry on API](/en-US/docs/Glossary/API) for more information.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gt"&gt;&amp;gt;[!NOTE]&lt;/span&gt;
&lt;span class="gt"&gt;&amp;gt; Consulta también la [entrada del glosario sobre API](/es/docs/Glossary/API) para más información.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice: &lt;code&gt;[!NOTE]&lt;/code&gt; remains untouched&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Plain Code blocks:&lt;/strong&gt; These are code blocks that contain only literal data: no comments, no prose mixed in so there's nothing to translate at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/articles/
https://example.com/articles/23
https://example.com/articles/?page=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stays exactly the same in the Spanish file — same URLs, same formatting, no changes whatsoever. There's no comment explaining anything and no prose sentence inside it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name
age
city
greet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are actual JavaScript property names being listed for the reader to look at, not descriptive text, so translating "city" to "ciudad" or "age" to "edad" would misrepresent what the code actually contains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML table structure:&lt;/strong&gt;&lt;br&gt;
Table tags &lt;code&gt;(&amp;lt;table&amp;gt;, &amp;lt;tr&amp;gt;, &amp;lt;th&amp;gt;, etc.)&lt;/code&gt; stay exactly the same, only the visible text inside is translated.&lt;/p&gt;

&lt;p&gt;For example: &lt;/p&gt;

&lt;p&gt;Original (English source):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Prerequisites&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        A basic understanding of &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/en-US/docs/Learn_web_development/Core/Scripting"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;JavaScript basics&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.
      &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Objective&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;To understand JavaScript's prototype-based inheritance model.&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Prerrequisitos&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        Un entendimiento básico de &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/es/docs/Learn_web_development/Core/Scripting"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;los fundamentos de JavaScript&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.
      &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;scope=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Objetivo&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Comprender el modelo de herencia basado en prototipos de JavaScript.&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Kumascript macros {{...}}&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Macro names and their arguments (page paths) are never translated. The macro names such as (&lt;code&gt;NextMenu&lt;/code&gt;, &lt;code&gt;PreviousMenuNext&lt;/code&gt;) stay in English because they're internal MDN template functions, not prose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;{{glossary("term")}}&lt;/code&gt;: if the English term doesn't naturally read well in Spanish, add a second argument with the translated display text — &lt;code&gt;{{glossary("term", "término")}}&lt;/code&gt;. If the term is used as-is in Spanish (e.g. &lt;code&gt;HTTP&lt;/code&gt;, &lt;code&gt;JavaScript&lt;/code&gt;, &lt;code&gt;Python&lt;/code&gt;), leave it with no second argument.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigation macros (&lt;code&gt;{{NextMenu(...)}}&lt;/code&gt;, &lt;code&gt;{{PreviousMenuNext(...)}}&lt;/code&gt;) always left fully untouched whether in English or Spanish.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{NextMenu("Learn_web_development/Core/Scripting/Object_prototypes", "Learn_web_development/Core/Scripting")}}

{{PreviousMenuNext("Learn_web_development/Core/Scripting/Object_prototypes", "Learn_web_development/Core/Scripting/Object_building_practice", "Learn_web_development/Core/Scripting")}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Headings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Translate headings to match the reference heading list given in the issue, using consistent verb style (gerund/imperative) across the doc. 
When you finish translating, review your work and make sure you have same headings, same order, same structure as the English source and Nothing is left untranslated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;English (Original Source)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Summary
## Basics: anatomy of a URL
## Scheme
## Authority
## Path to resource
## Parameters
## Anchor
## How to use URLs
## Absolute URLs vs. relative URLs
## URL usernames and passwords
## Semantic URLs
## See also
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Resumen
## Conceptos básicos: anatomía de una URL
## Esquema
## Autoridad
## Ruta al recurso
## Parámetros
## Ancla
## Cómo utilizar las URL
## URL absolutas frente a URL relativas
## Nombres de usuario y contraseñas en las URL
## URL semánticas
## Véase también
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Watch for accented characters affecting auto-generated anchor targets (e.g. "Validación" becomes anchor &lt;code&gt;validación_html&lt;/code&gt;, not validacion_html) because a missing accent in a link breaks the anchor. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Link: &lt;code&gt;/es/docs/Learn_web_development/Core/Forms/Form_validation#validación_html&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;For example;&lt;/p&gt;

&lt;p&gt;English (Original source)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Validation HTML&lt;/span&gt;

Here's a small but easy-to-miss detail about headings and links. When MDN builds a page, it auto-generates an anchor ID for every heading by lowercasing the text and replacing spaces with underscores. The catch is that it keeps accented characters exactly as they are.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated Spanish&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Validación HTML&lt;/span&gt;

Aquí hay un detalle pequeño pero fácil de pasar por alto sobre los encabezados y los enlaces. Cuando MDN construye una página, genera automáticamente un ancla para cada encabezado convirtiendo el texto a minúsculas y reemplazando los espacios con guiones bajos. La particularidad es que mantiene los caracteres acentuados exactamente como están.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comments
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Comments inside of Code blocks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This has two (2) cases: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A comment that is explaining something to the user (reader) should be translated.
&lt;/li&gt;
&lt;li&gt;A comment that does not add any additional useful information should be discarded.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Useful comment(s)
&lt;/h4&gt;

&lt;p&gt;A comment explaining something (translate it): &lt;br&gt;
This comment is real prose explaining why the line exists or what it does. A Spanish-speaking reader benefits from having this translated&lt;/p&gt;

&lt;p&gt;English (Original Source)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Number of visits to this view, as counted in the session
&lt;/span&gt;&lt;span class="n"&gt;num_visits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;num_visits&lt;/span&gt;&lt;span class="sh"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Número de visitas a esta vista, contado en la sesión
&lt;/span&gt;&lt;span class="n"&gt;num_visits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;num_visits&lt;/span&gt;&lt;span class="sh"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Throwaway comment(s)
&lt;/h4&gt;

&lt;p&gt;Example 1&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.contrib.sessions&lt;/span&gt;&lt;span class="sh"&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;Here, &lt;code&gt;# ...&lt;/code&gt; isn't explaining anything — it's just a visual placeholder telling the reader "there's more code here we're not showing." There's no real content to translate; translating &lt;code&gt;...&lt;/code&gt; doesn't mean anything different in Spanish. Best left as-is.&lt;/p&gt;

&lt;p&gt;Example 2&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;wheels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alloy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or comment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of comment is minor, vague, and doesn't add much context either way, so it's genuinely a coin-flip: leave it in English since it's a throwaway aside, or translate it for full consistency with the rest of the document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keywords, Variables, Functions:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;DO NOT TRANSLATE ANY OF THESE&lt;/p&gt;

&lt;p&gt;Variable names — e.g. &lt;code&gt;numOfVisits&lt;/code&gt;, &lt;code&gt;myCar&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Function/method names — e.g. &lt;code&gt;getPrototypeOf()&lt;/code&gt;, &lt;code&gt;index()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Class names — e.g. &lt;code&gt;Person&lt;/code&gt;, &lt;code&gt;Author&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Property/dictionary keys — e.g. &lt;code&gt;city&lt;/code&gt;, &lt;code&gt;wheels&lt;/code&gt;, &lt;code&gt;my_car['wheels']&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Exception names — e.g. &lt;code&gt;KeyError&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boolean values (true, false):&lt;/strong&gt; remain untranslated in both English and Spanish&lt;/p&gt;

&lt;p&gt;English (Original Source)&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;// Uses 'toString()' by default, returns true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Usa 'toString()' por defecto, devuelve true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Methods&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Method names mentioned inside comments stay untouched.&lt;/p&gt;

&lt;p&gt;English (Original Source)&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;// Calls 'getPrototypeOf()' to find the parent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated (Spanish)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Llama a 'getPrototypeOf()' para encontrar al padre
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Console.log()&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;There are two (2) incidents here: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For literal strings printed to the user, you should translate it. An example is &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;English (Original Source):&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Greetings from Madrid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Translated Spanish:&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;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Saludos desde Madrid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;short/generic literal, here it's a judgment call, and the practical approach is to flag it for the reviewer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example:&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;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pluralization
&lt;/h2&gt;

&lt;p&gt;If translating a word breaks a language feature (e.g. pluralization), adapt the word so the mechanism still works (e.g. "time" becomes "vece").&lt;/p&gt;

&lt;p&gt;"time" plural form is "times" in English, but the literal spanish translation would be wrong (broken) spanish.&lt;/p&gt;

&lt;p&gt;if we were to follow a literal translation of "time" into spanish "vez" literal plural translation "vezs" (which is wrong spanish)&lt;/p&gt;

&lt;p&gt;"vez" correct plural form in spanish is "vece". &lt;/p&gt;

&lt;p&gt;&lt;code&gt;num_visits&lt;/code&gt; = 3, Here, pluralize looks at &lt;code&gt;num_visits&lt;/code&gt; and, if it's more than 1, appends an "s" to whatever comes right before the template tag — turning "time" into "times".&lt;/p&gt;

&lt;p&gt;English (Original Source)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;You have visited this page &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;num_visits&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt; time&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;num_visits&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;pluralize&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Adapted translation (mechanism still works):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;Has visitado esta página &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;num_visits&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt; vece&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;num_visits&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;pluralize&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  More Information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run Prettier on the finished file, when you are done translating.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prettier &lt;span class="nt"&gt;--write&lt;/span&gt; files/es/learn_web_development/core/scripting/object_prototypes/index.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Check for invisible/zero-width characters (e.g. using a script scanning for &lt;code&gt;U+200B&lt;/code&gt;, &lt;code&gt;U+200C&lt;/code&gt;, &lt;code&gt;U+200D&lt;/code&gt;, &lt;code&gt;U+FEFF&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unicodedata&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;INVISIBLE_CHARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x200B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x200C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x200D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFEFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;unicodedata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;char&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;INVISIBLE_CHARS&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;repr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;check_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Thank you and please follow me.
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to setup WikiEduDashboard for OSS contribution</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Sun, 30 Aug 2026 15:22:12 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/how-to-setup-wikiedudashboard-for-oss-contribution-53e5</link>
      <guid>https://dev.to/ifeanyichima/how-to-setup-wikiedudashboard-for-oss-contribution-53e5</guid>
      <description>&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;I wanted to contribute to an open source project called &lt;strong&gt;WikiEduDashboard&lt;/strong&gt;, a web application built by Wiki Education. It helps instructors and program leaders run Wikipedia-editing classes and campaigns: students join a course, make edits to Wikipedia, and the dashboard tracks their work.&lt;/p&gt;

&lt;p&gt;To contribute code to this project, I first need a working copy of it running on my own PC. This is called a "local development environment." Without it, I can't test any changes I make before sending them back to the project.&lt;/p&gt;

&lt;p&gt;The problem: this project was built with tools that work best on Mac or Linux, not on plain Windows. So the first challenge wasn't even the project itself, it was figuring out how to run a Linux-friendly project on a Windows PC.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution (High Level)
&lt;/h2&gt;

&lt;p&gt;Instead of fighting Windows directly, we used a feature built into Windows called &lt;strong&gt;WSL (Windows Subsystem for Linux)&lt;/strong&gt;. WSL lets a real Linux system (Ubuntu, in our case) run inside Windows, side by side with your normal Windows apps. It's not a separate computer or a virtual machine you have to babysit, it just works like an extra terminal environment on the same PC.&lt;/p&gt;

&lt;p&gt;Once inside Ubuntu, we could follow the project's official setup instructions exactly as written, since those instructions assume a Mac or Linux machine.&lt;/p&gt;

&lt;p&gt;The overall plan looked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get a Linux environment running on Windows (WSL + Ubuntu)&lt;/li&gt;
&lt;li&gt;Get a personal copy of the project's code (fork it on GitHub, then clone it)&lt;/li&gt;
&lt;li&gt;Install the programming language the project is built with (Ruby)&lt;/li&gt;
&lt;li&gt;Run the project's automated setup script, which installs the rest of the required tools (database, background job system, etc.)&lt;/li&gt;
&lt;li&gt;Start the actual application and view it in a browser&lt;/li&gt;
&lt;li&gt;Build the frontend (the visual, interactive part of the site)&lt;/li&gt;
&lt;li&gt;Set up an editor (VS Code) that can actually see and edit the code living inside Ubuntu&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step by Step: What We Did and Why
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Step 1: Install WSL and Ubuntu
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; WSL is a Windows feature that runs a real Linux distribution (we used Ubuntu) inside Windows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why we needed it:&lt;/strong&gt; WikiEduDashboard's setup script only officially supports Debian-based Linux (like Ubuntu), Fedora, and macOS. Plain Windows isn't supported well: things like Ruby, Redis, and MySQL are much harder to install and configure correctly on native Windows.&lt;/p&gt;

&lt;p&gt;Here is what Ubuntu looks like from the Microsoft store&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%2F5p3pwbzf3hrhk7tc4822.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5p3pwbzf3hrhk7tc4822.jpg" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Command used (from PowerShell, as Administrator):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;wsl&lt;/span&gt; &lt;span class="na"&gt;--install
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing, opening "Ubuntu" from the Start menu gives a terminal prompt that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;adminuser@DESKTOP-OF73031:~$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;~&lt;/code&gt; symbol means "home folder", it's the starting point for everything else we did.&lt;/p&gt;

&lt;p&gt;Here is what the console looks like &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%2Fzl46xe3dc0nczp3lqw8b.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%2Fzl46xe3dc0nczp3lqw8b.png" alt=" " width="799" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Confirm Git Was Available
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; Git is a tool used to download ("clone") and manage code from GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; We need it to grab a copy of the project's code onto our PC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returned &lt;code&gt;git version 2.43.0&lt;/code&gt;, confirming git was already installed inside Ubuntu.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3: Fork and Clone the Repository
&lt;/h4&gt;

&lt;p&gt;We forked the project on GitHub first (via the browser, clicking the "Fork" button on the project's page), then cloned our fork:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/YOUR_USERNAME/WikiEduDashboard.git
&lt;span class="nb"&gt;cd &lt;/span&gt;WikiEduDashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Install Ruby
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What is Ruby?&lt;/strong&gt; Ruby is the programming language WikiEduDashboard's backend (the "engine" of the app) is written in, specifically using a framework called &lt;strong&gt;Ruby on Rails&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why we needed a specific version:&lt;/strong&gt; The project requires Ruby 3.4.8 exactly. Running the setup script without Ruby installed gave 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;ruby: command not found
Ruby-3.4.8 not found. Please install ruby-3.4.8 and run this script again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How we installed it:&lt;/strong&gt; using a tool called &lt;strong&gt;RVM&lt;/strong&gt; (Ruby Version Manager), which makes it easy to install and switch between specific Ruby versions.&lt;/p&gt;

&lt;p&gt;Here is how we install the RVM tool, copy and paste this block of command into your &lt;code&gt;ubuntu&lt;/code&gt; terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; gnupg2
gpg &lt;span class="nt"&gt;--keyserver&lt;/span&gt; keyserver.ubuntu.com &lt;span class="nt"&gt;--recv-keys&lt;/span&gt; 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; https://get.rvm.io | bash &lt;span class="nt"&gt;-s&lt;/span&gt; stable
&lt;span class="nb"&gt;source&lt;/span&gt; /home/adminuser/.rvm/scripts/rvm
rvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"ruby-3.4.8"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirming it worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;code&gt;ruby 3.4.8 (2025-12-17 revision 995b59f666) +PRISM [x86_64-linux]&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 5: Run the Automated Setup Script
&lt;/h4&gt;

&lt;p&gt;The project ships with a Python script (&lt;code&gt;setup.py&lt;/code&gt;) that automates installing everything else the project needs: gems (Ruby's package system), a database (MySQL/MariaDB), configuration files, and JavaScript packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; Doing all of this by hand (the "manual setup" path in the project's docs) is long and error-prone. The script does it consistently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installed Ruby "gems" (Ruby's version of code libraries/packages)&lt;/li&gt;
&lt;li&gt;Created configuration files (&lt;code&gt;config/application.yml&lt;/code&gt; and &lt;code&gt;config/database.yml&lt;/code&gt;) from example templates&lt;/li&gt;
&lt;li&gt;Created the development and test databases&lt;/li&gt;
&lt;li&gt;Created a MySQL user for the app to use&lt;/li&gt;
&lt;li&gt;Ran database migrations (setting up the actual tables the app needs)&lt;/li&gt;
&lt;li&gt;Installed JavaScript packages using &lt;strong&gt;Yarn&lt;/strong&gt; (a package manager for frontend/JavaScript code, similar in purpose to &lt;code&gt;gem&lt;/code&gt; for Ruby)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end, it printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your development environment setup is complete.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 6: Start Redis
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What is Redis?&lt;/strong&gt; Redis is a fast, lightweight in-memory data store. In this project, it's used by a background job system called &lt;strong&gt;Sidekiq&lt;/strong&gt;, which handles tasks like scheduled updates and syncing data, things that shouldn't slow down the main website while they run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; without Redis running, features that rely on background jobs (some Wikipedia-editing features especially) won't work properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We got an error saying the port was already in use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Could not create server TCP listening socket *:6379: bind: Address already in use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This actually meant Redis was &lt;strong&gt;already running&lt;/strong&gt; in the background (likely started automatically during setup). We confirmed this instead of restarting it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-cli ping
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;code&gt;PONG&lt;/code&gt;, meaning Redis was alive and responding correctly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 7: Start the Rails Server
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;What:&lt;/strong&gt; This starts the actual backend server, the part that listens for requests and serves the website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once running, it showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;* Listening on http://127.0.0.1:3000
Use Ctrl-C to stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the app was now reachable at &lt;code&gt;http://localhost:3000&lt;/code&gt; in a browser. This terminal window needs to stay open and running the whole time the app is in use.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 8: Open the Frontend Build
&lt;/h4&gt;

&lt;p&gt;open a &lt;strong&gt;second&lt;/strong&gt; Ubuntu terminal window (leaving the first one running &lt;code&gt;rails s&lt;/code&gt; untouched), move into the project folder, and run &lt;code&gt;yarn start&lt;/code&gt;, see example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;WikiEduDashboard
yarn start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles the frontend and also watches for future changes, automatically rebuilding when files are edited.&lt;/p&gt;

&lt;p&gt;After this finished, refreshing &lt;code&gt;http://localhost:3000&lt;/code&gt; showed the actual dashboard homepage, fully styled and working, logged in as &lt;code&gt;Chimaifeanyi29&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 9: Set Up VS Code to Edit the Code
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; the project's code physically lives inside the Ubuntu (WSL) filesystem, not in a normal Windows folder. Opening VS Code the usual way on Windows wouldn't show the right files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; VS Code has a "Remote - WSL" extension that lets it connect directly into the Ubuntu environment.&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install VS Code normally on Windows (if not already installed)&lt;/li&gt;
&lt;li&gt;Inside VS Code, go to the Extensions tab and install the extension called &lt;strong&gt;"WSL"&lt;/strong&gt; (by Microsoft)&lt;/li&gt;
&lt;li&gt;From inside the Ubuntu terminal, inside the project folder, run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens VS Code on the Windows desktop, but connected directly to the Ubuntu filesystem, shown by a "WSL: Ubuntu" indicator in the bottom-left corner of the editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary: What's Currently Running
&lt;/h2&gt;

&lt;p&gt;At the end of this session, the local development setup consists of three things running at once:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rails s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The backend server (handles requests, talks to the database)&lt;/td&gt;
&lt;td&gt;Terminal 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yarn start&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Builds and watches the frontend (JavaScript/React code)&lt;/td&gt;
&lt;td&gt;Terminal 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis (background)&lt;/td&gt;
&lt;td&gt;Powers background jobs via Sidekiq&lt;/td&gt;
&lt;td&gt;Runs automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The app is reachable in a browser at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Terms Explained (Quick Reference)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WSL (Windows Subsystem for Linux):&lt;/strong&gt; A way to run real Linux (Ubuntu, here) inside Windows without a separate computer or full virtual machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork:&lt;/strong&gt; Your own personal copy of someone else's GitHub project, needed before you can propose changes back to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clone:&lt;/strong&gt; Downloading a copy of a GitHub project's code onto your own machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruby / Ruby on Rails:&lt;/strong&gt; The programming language and web framework the backend of this project is built with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RVM:&lt;/strong&gt; A tool for installing and managing specific versions of Ruby.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gems:&lt;/strong&gt; Ruby's term for external code packages/libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yarn:&lt;/strong&gt; A package manager for JavaScript, used here to install and build the frontend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis:&lt;/strong&gt; A fast in-memory data store, used to support background job processing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidekiq:&lt;/strong&gt; A background job system (built on Ruby) that uses Redis, used for scheduled tasks like data syncing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rails server (&lt;code&gt;rails s&lt;/code&gt;):&lt;/strong&gt; The command that starts the actual running web application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;manifest.json:&lt;/strong&gt; A file generated by the frontend build process that tells the backend where compiled frontend assets live.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I solved a 5 year old bug</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Thu, 30 Jul 2026 22:48:44 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/i-solved-a-5-year-old-bug-356h</link>
      <guid>https://dev.to/ifeanyichima/i-solved-a-5-year-old-bug-356h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Let me start by saying I am an Open-source contributor at Mozilla, I am trying to get better as a software engineer. I will be using the words &lt;strong&gt;Site&lt;/strong&gt; or &lt;strong&gt;Tile&lt;/strong&gt; interchangeably, they mean the same thing. &lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Report
&lt;/h2&gt;

&lt;p&gt;The title of the &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1628669" rel="noopener noreferrer"&gt;bug&lt;/a&gt; report was "&lt;strong&gt;Cannot remove amazon.com from top sites list&lt;/strong&gt;" on Bugzilla and I believe it really captures the pain-point that users experienced because of the bug.&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%2Fvmt25jbxqyaolmqn8lg7.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%2Fvmt25jbxqyaolmqn8lg7.png" alt="Bug report" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this bug report the user is complaining that he cannot remove &lt;u&gt;amazon.com&lt;/u&gt; from &lt;code&gt;Top Sites&lt;/code&gt; in the &lt;strong&gt;New tab page&lt;/strong&gt; of his Mozilla Firefox browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;A little back-story of what &lt;code&gt;Top Sites&lt;/code&gt;, &lt;code&gt;New tab page&lt;/code&gt; and &lt;code&gt;Places&lt;/code&gt; is: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Top Sites&lt;/strong&gt; is that grid of website shortcuts you see on Firefox's New Tab Page. It shows the sites you visit most often, plus some sponsored tiles (called "spocs," short for sponsored content) that Mozilla partners provide. The image below shows a row of Top Sites on the New Tab page.&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%2Fb2e764iiwkxsp94k97rr.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%2Fb2e764iiwkxsp94k97rr.png" alt="Top Sites in Firefox" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New tab page&lt;/strong&gt; is the page that opens when you open a new tab or click the home button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Places&lt;/strong&gt; is a SQLite database that Firefox uses to store browsing history.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine you open a new tab in Firefox and see a grid of your most-visited websites (called "Top Sites"). &lt;u&gt;amazon.com&lt;/u&gt; keeps showing up there, even though you never visit it and you keep trying to delete it.&lt;/p&gt;

&lt;p&gt;You right-click on the Amazon icon, click "Delete from History," but... nothing happens. It just sits there, refusing to leave. You try again. Still there. You try "Dismiss." Still there. It's like a stubborn guest who won't leave your house.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Happens
&lt;/h2&gt;

&lt;p&gt;Firefox's Top Sites can come from three places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your actual browsing history (sites you really visited)&lt;/li&gt;
&lt;li&gt;Pinned sites (sites you manually pinned to stay there)&lt;/li&gt;
&lt;li&gt;Default/curated sites (sites Firefox puts there automatically, like Amazon, because they think you might want them)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay with me and we would find out which one of these is the culprit. &lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Bug (Part A)
&lt;/h2&gt;

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

&lt;p&gt;By reading through the thread on the bug report, I discovered a trick other users on the thread had tried&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one user on the thread had the idea of changing the URL and then the problem went away, but after a new firefox release Firefox 82.0 (64-bit) the trick could no longer work.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;To reproduce the bug locally, I had to visit &lt;code&gt;https://amazon.com&lt;/code&gt; multiple times (up to 20 times) on my local PC so that I can get &lt;code&gt;amazon.com&lt;/code&gt; to show up as a &lt;strong&gt;Top Site&lt;/strong&gt; on my New Tab page. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reviewer Feedback&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first attempt was to block the &lt;code&gt;url&lt;/code&gt; whenever a user clicked "Delete History" so that it will never show up again in &lt;strong&gt;Top Sites&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;mak&lt;/strong&gt; (a mozilla engineer) pushed back on this, pointing out that if a user just wants to delete history, silently also blocking the site (removing it entirely from Top Sites without saying so) gives them unexpected behavior.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The problem with the fix is that the dialog asks to the user if they want to remove from history, but then the code proceed also blocking the site, that is not told to the user and may be unexpected.&lt;br&gt;
If the user wanted to effectively just remove history, not block the site, they will get the wrong behavior.&lt;/p&gt;

&lt;p&gt;I'm not expert of this code, but it looks like the proposed solution is broad and may cause unexpected behavior, actually removing control from the user rather than increasing it.&lt;/p&gt;

&lt;p&gt;It's touching the right problem though, someone should eval a UX solution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I got a second feedback from another reviewer &lt;strong&gt;thecount&lt;/strong&gt; (a mozilla engineer) and he was the one who led me to the right fix for the bug.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you log &lt;code&gt;link.url&lt;/code&gt; and &lt;code&gt;searchProvider.url&lt;/code&gt;, like this &lt;code&gt;console.log(link.url, searchProvider.url);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Right before this line: &lt;a href="https://searchfox.org/firefox-main/source/toolkit/modules/NewTabUtils.sys.mjs#1093" rel="noopener noreferrer"&gt;https://searchfox.org/firefox-main/source/toolkit/modules/NewTabUtils.sys.mjs#1093&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then on startup, I see this in the logs: "&lt;a href="https://www.amazon.com/" rel="noopener noreferrer"&gt;https://www.amazon.com/&lt;/a&gt; &lt;a href="https://amazon.com" rel="noopener noreferrer"&gt;https://amazon.com&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;So this is the line that causes the url to be changed, and now the &lt;code&gt;url&lt;/code&gt; is no longer removable because it no longer matches the original places &lt;code&gt;url&lt;/code&gt;.&lt;br&gt;
one solution might be to store "originalUrl" &lt;code&gt;link.originalUrl = link.url&lt;/code&gt; in the block I linked above, and then when we delete the url, check if we have "originalUrl" and use that instead"? So something like &lt;code&gt;link.originalUrl || link.url&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;With this feedback, I was able to finally understand the bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue&lt;/strong&gt;&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;// toolkit/modules/NewTabUtils.sys.mjs&lt;/span&gt;

&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;searchProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSearchProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NewTabUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shortURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;= swap is done here&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 issue was from here: you see Firefox does a check to see if &lt;u&gt;amazon.com&lt;/u&gt; is already stored as a search-shortcut provider and then it quietly drops the &lt;code&gt;url&lt;/code&gt; and replaces it with the internal &lt;code&gt;searchProvider.url&lt;/code&gt;. This is why when we click "Delete History" nothing happens because we have lost the &lt;code&gt;url&lt;/code&gt; that was supposed to match what we have in &lt;code&gt;Places&lt;/code&gt; (a database in Firefox).&lt;/p&gt;

&lt;p&gt;What is a search-shortcut provider, It means "a site Firefox knows how to search directly", and Amazon qualifies because Amazon has its own product search.&lt;/p&gt;

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

&lt;p&gt;Before a tile ever reaches the New Tab page, Firefox checks each link against its built-in list of search-shortcut sites (Google, Amazon, Bing, DuckDuckGo, eBay, Twitter, Wikipedia). If the link matches one of these, Firefox swaps that tile's url to a version meant to trigger a search on that site, not to visit the homepage you actually browsed to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Bug (Part B)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mental model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The mental model to have in Firefox is&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; the New Tab Page (React), shows the tiles, builds the request&lt;br&gt;
&lt;strong&gt;The request itself:&lt;/strong&gt; the payload sent from frontend to backend&lt;br&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; &lt;code&gt;PlacesFeed.sys.mjs&lt;/code&gt;, receives the request, touches the actual database (your browsing history)&lt;/p&gt;

&lt;p&gt;Like when building a To-do App, when you click on an item to delete from the database, you send the &lt;code&gt;id&lt;/code&gt; of the item as a payload to the backend (either in the URL or Request Body). But here, the real &lt;code&gt;id&lt;/code&gt; (&lt;code&gt;original_url&lt;/code&gt;) was quietly being generated and never forwarded, the frontend only ever knew about &lt;code&gt;url&lt;/code&gt;, which by that point pointed to the wrong thing. &lt;/p&gt;

&lt;p&gt;So, for the fix we are going to be borrowing heavily from the full-stack MVC architecture &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Breaking down the fix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We make sure that we do not lose reference to the &lt;code&gt;url&lt;/code&gt; before it gets silently swapped out by Firefox, so we do something like this &lt;code&gt;link.original_url = link.url;&lt;/code&gt; here, we are storing away the &lt;code&gt;url&lt;/code&gt; in a variable called &lt;code&gt;original_url&lt;/code&gt; and it will be referenced later when we want to delete a &lt;strong&gt;site&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Allow me to break down the fix using REST API from Nodejs.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;A&lt;/strong&gt; This is like a backend endpoint that serves your frontend a list which we use to create the &lt;strong&gt;Tiles&lt;/strong&gt;. Here is where we create the &lt;strong&gt;Top Sites&lt;/strong&gt; and send to the frontend, but we quietly rename a field along the way.&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/topsites&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLinksFromDatabase&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;searchProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSearchProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;original_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// &amp;lt;=backup the real id/url&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// &amp;lt;=overwrite what the frontend will see&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&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;response&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;strong&gt;B&lt;/strong&gt; A user clicks a button which fires off a delete request and then we send both the &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;original_url&lt;/code&gt; to the backend server.&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="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;/api/delete-history&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;DELETE&lt;/span&gt;&lt;span class="dl"&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;original_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;original_url&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;&lt;strong&gt;C&lt;/strong&gt; The backend receives the request and decides what to actually delete&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/delete-history&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;original_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;original_url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;It checks: "do I have an original_url? If yes, use that to delete. If not, fall back to url." &lt;/p&gt;

&lt;p&gt;This is important, because if it only used url, it would try to delete the search provider's link instead of the real saved site, and the deletion would silently fail or delete the wrong thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Finally !!!!, now that we have a solid understanding of the issue and the fix, let us go ahead and implement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A.&lt;/strong&gt; Equivalent to Step 1 from the Rest API&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;// toolkit/modules/NewTabUtils.sys.mjs&lt;/span&gt;
&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;searchProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSearchProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NewTabUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shortURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;original_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;&lt;strong&gt;B.&lt;/strong&gt; Equivalent to Step 2 from the Rest API&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;// browser/extensions/newtab/content-src/lib/link-menu-options.mjs&lt;/span&gt;
&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onConfirm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AlsoToMain&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DELETE_HISTORY_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;pocket_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pocket_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;forceBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookmarkGuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;original_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;original_url&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="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;strong&gt;C.&lt;/strong&gt; Equivalent to Step 3 from the Rest API&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;// browser/extensions/newtab/lib/PlacesFeed.sys.mjs&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DELETE_HISTORY_URL&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pocket_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;original_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;action&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;lazy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NewTabUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activityStreamLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteHistoryEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;original_url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;break&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I waited for my patch to be approved and landed. &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%2Fu2p2jja42oync33gyao3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu2p2jja42oync33gyao3.gif" alt="Bug Fix" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The issue was that Amazon was in the user's actual history, but the displayed &lt;code&gt;url&lt;/code&gt; got silently swapped to the &lt;code&gt;search-shortcut&lt;/code&gt; version, so the delete call looked for a &lt;code&gt;url&lt;/code&gt; that no longer existed in &lt;code&gt;Places&lt;/code&gt;. &lt;/p&gt;

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

&lt;p&gt;🕷️ BUG: &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1628669" rel="noopener noreferrer"&gt;https://bugzilla.mozilla.org/show_bug.cgi?id=1628669&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💗 PATCH: &lt;a href="https://phabricator.services.mozilla.com/D292910" rel="noopener noreferrer"&gt;https://phabricator.services.mozilla.com/D292910&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👤 MY PROFILE ON BUGZILLA: &lt;a href="https://bugzilla.mozilla.org/user_profile?user_id=786766" rel="noopener noreferrer"&gt;https://bugzilla.mozilla.org/user_profile?user_id=786766&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you, please follow me
&lt;/h2&gt;

</description>
      <category>mozilla</category>
      <category>browser</category>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Ask me anything (RAG)</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Sat, 25 Jul 2026 16:15:19 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/ask-me-anything-rag-32jl</link>
      <guid>https://dev.to/ifeanyichima/ask-me-anything-rag-32jl</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;At LASU, students constantly ask each other things like "is this lecturer strict," "is this hostel safe," "what's SIWES actually like at this company," but that knowledge lives scattered across WhatsApp groups, Twitter threads, and word of mouth. It gets lost the moment a graduating class leaves. AMA was built to solve that: a shared memory for the school, where real student experiences get collected once and stay searchable by anyone, forever, instead of vanishing into a thousand disconnected chats.&lt;/p&gt;

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

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

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

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

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

&lt;p&gt;We used Gemma 4 (via OpenRouter) as the answer-generation model, and built the project around Retrieval-Augmented Generation (RAG), not fine-tuning, since we needed the answers to update instantly as new stories get submitted, without retraining anything.&lt;/p&gt;

&lt;p&gt;The pipeline works like this: when a student submits a story or review, we generate an embedding for it using Google's Gemini embedding model and store both the text and the embedding in MongoDB Atlas, using MongoDB Vector Search as our vector database. When someone asks a question, we embed the question the same way, run a vector similarity search to pull the most relevant stories, and pass only those into Gemma 4 as context, so every answer is grounded in real submissions instead of the model guessing.&lt;/p&gt;

&lt;p&gt;On the frontend, we used Next.js (App Router), Tailwind CSS v4 for styling, Zustand for lightweight UI state, and TanStack React Query for handling data fetching and caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hackathon
&lt;/h2&gt;

&lt;p&gt;This was done as part of a university hackathon hosted by Google. &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%2Ffbq82re8jb7n9hrstwqx.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffbq82re8jb7n9hrstwqx.jpg" alt="LASU Google Gemma Hackathon" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank you, please follow me
&lt;/h2&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
      <category>ai</category>
    </item>
    <item>
      <title>Fullscreen icon RTL language support in Firefox browser</title>
      <dc:creator>Ifeanyi Chima</dc:creator>
      <pubDate>Tue, 14 Jul 2026 11:34:31 +0000</pubDate>
      <link>https://dev.to/ifeanyichima/fullscreen-icon-rtl-language-support-in-firefox-browser-247</link>
      <guid>https://dev.to/ifeanyichima/fullscreen-icon-rtl-language-support-in-firefox-browser-247</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&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%2Fy593f7jw14pebfx828e0.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%2Fy593f7jw14pebfx828e0.png" alt="Zoom Icon in LTR mode" width="389" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our target is to make sure that when the firefox browser is set to RTL mode the fullscreen icon flips as well to follow suite.&lt;/p&gt;

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

&lt;p&gt;When Firefox is set to an RTL (right-to-left) language like Arabic, the fullscreen icon inside the zoom sidebar still looks like it's designed for LTR (left-to-right) languages. It should be mirrored/flipped to match RTL direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's RTL?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Languages like Arabic and Hebrew read from right to left. Browsers have a concept called &lt;code&gt;dir="rtl"&lt;/code&gt; that flips the layout. Icons that have a "direction" to them (like arrows, expand icons) should also flip to feel natural.&lt;/p&gt;

&lt;h3&gt;
  
  
  The plan:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;First, let's find where the zoom sidebar and its fullscreen icon live in the Firefox codebase&lt;/li&gt;
&lt;li&gt;See how the icon is currently being used&lt;/li&gt;
&lt;li&gt;Figure out how to make it flip for RTL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can set your firefox browser to read from right-to-left using this in the console &lt;code&gt;CTRL+SHIFT+ALT+I&lt;/code&gt;&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dir&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;rtl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Find the files:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Zoom sidebar HTML file&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can start by searching Searchfox (Mozilla's online code search) for terms like "zoom sidebar fullscreen" and "appmenu zoom controls." That search led me to &lt;code&gt;appmenu-viewcache.inc.xhtml&lt;/code&gt;, and inside it I saw this markup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;toolbaritem&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"appMenu-zoom-controls"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"subviewbutton toolbaritem-combined-buttons"&lt;/span&gt; &lt;span class="na"&gt;closemenu=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"toolbarbutton-text"&lt;/span&gt; &lt;span class="na"&gt;data-l10n-id=&lt;/span&gt;&lt;span class="s"&gt;"appmenuitem-zoom"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;toolbarbutton&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"appMenu-fullscreen-button2"&lt;/span&gt;
                       &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"subviewbutton subviewbutton-iconic"&lt;/span&gt;
                       &lt;span class="na"&gt;data-l10n-id=&lt;/span&gt;&lt;span class="s"&gt;"appmenuitem-fullscreen"&lt;/span&gt;
                       &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
                       &lt;span class="na"&gt;tooltip=&lt;/span&gt;&lt;span class="s"&gt;"dynamic-shortcut-tooltip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;observes&lt;/span&gt; &lt;span class="na"&gt;element=&lt;/span&gt;&lt;span class="s"&gt;"View:FullScreen"&lt;/span&gt; &lt;span class="na"&gt;attribute=&lt;/span&gt;&lt;span class="s"&gt;"checked"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/toolbarbutton&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/toolbaritem&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FLUENT:&lt;/strong&gt; Firefox uses the &lt;code&gt;fluent&lt;/code&gt; localization system (more in later articles) &lt;/p&gt;

&lt;p&gt;&lt;code&gt;appmenuitem-zoom&lt;/code&gt; is the localization label for the word "Zoom" that sits above the row of buttons (&lt;code&gt;-&lt;/code&gt;, &lt;code&gt;100%&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, fullscreen). It's a distinctive, low-noise string and searching for it locally in VS code is a good way to land on the exact zoom row markup without pulling in unrelated matches.&lt;/p&gt;

&lt;p&gt;Let's start by searching for the zoom sidebar files using this &lt;code&gt;grep&lt;/code&gt; syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"appmenuitem-zoom"&lt;/span&gt; browser/base/content/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we found: &lt;/p&gt;

&lt;p&gt;The fullscreen icon lives inside the zoom row of the hamburger menu (&lt;code&gt;☰&lt;/code&gt;). In the HTML file at &lt;code&gt;browser/base/content/appmenu-viewcache.inc.xhtml&lt;/code&gt;, the button is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;toolbarbutton&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"appMenu-fullscreen-button2"&lt;/span&gt;
                       &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"subviewbutton subviewbutton-iconic"&lt;/span&gt;
                       &lt;span class="na"&gt;data-l10n-id=&lt;/span&gt;&lt;span class="s"&gt;"appmenuitem-fullscreen"&lt;/span&gt;
                       &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
                       &lt;span class="na"&gt;tooltip=&lt;/span&gt;&lt;span class="s"&gt;"dynamic-shortcut-tooltip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;observes&lt;/span&gt; &lt;span class="na"&gt;element=&lt;/span&gt;&lt;span class="s"&gt;"View:FullScreen"&lt;/span&gt; &lt;span class="na"&gt;attribute=&lt;/span&gt;&lt;span class="s"&gt;"checked"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/toolbarbutton&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ID of the fullscreen button in the zoom row of the hamburger menu is &lt;code&gt;appMenu-fullscreen-button2&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This is very important to keep in mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zoom sidebar CSS file&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let us find the CSS file where its icon is defined using this &lt;code&gt;grep&lt;/code&gt; syntax&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"appMenu-fullscreen-button2"&lt;/span&gt; browser/themes/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we found: &lt;/p&gt;

&lt;p&gt;This finds &lt;code&gt;browser/themes/shared/menupanel.css&lt;/code&gt;, the file where the fullscreen icon's image is actually set, which is where we will make our fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fullscreen icon&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt; &lt;span class="s2"&gt;"fullscreen.svg"&lt;/span&gt; browser/themes/shared/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that there is an image &lt;code&gt;fullscreen.svg&lt;/code&gt; that serves as the icon, located at &lt;code&gt;browser/themes/shared/icons/fullscreen.svg&lt;/code&gt; and gets applied via &lt;code&gt;browser/themes/shared/menupanel.css&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, we see the fullscreen icon, which looks like pointing outward from a center. &lt;/p&gt;

&lt;p&gt;Left-to-right &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%2F15pgzm4czgdduwx28nd3.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%2F15pgzm4czgdduwx28nd3.png" alt="LTR" width="43" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Right-to-left&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%2Fvazo4hobdx0izwdtwyuu.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%2Fvazo4hobdx0izwdtwyuu.png" alt="RTL" width="51" height="67"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Figuring out how to make it flip
&lt;/h3&gt;

&lt;p&gt;We need to take note of two things that will guide us to the actual fix.&lt;/p&gt;

&lt;p&gt;From what we saw here, we know that the zoom icon could live inside of &lt;code&gt;toolbarbutton&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;toolbarbutton&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"appMenu-fullscreen-button2"&lt;/span&gt;
                       &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"subviewbutton subviewbutton-iconic"&lt;/span&gt;
                       &lt;span class="na"&gt;data-l10n-id=&lt;/span&gt;&lt;span class="s"&gt;"appmenuitem-fullscreen"&lt;/span&gt;
                       &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
                       &lt;span class="na"&gt;tooltip=&lt;/span&gt;&lt;span class="s"&gt;"dynamic-shortcut-tooltip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="nt"&gt;&amp;lt;observes&lt;/span&gt; &lt;span class="na"&gt;element=&lt;/span&gt;&lt;span class="s"&gt;"View:FullScreen"&lt;/span&gt; &lt;span class="na"&gt;attribute=&lt;/span&gt;&lt;span class="s"&gt;"checked"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/toolbarbutton&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to Mozilla &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/toolbarbutton" rel="noopener noreferrer"&gt;guidelines&lt;/a&gt;, &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;toolbarbutton&amp;gt;&lt;/code&gt; is a Firefox custom element (defined in toolkit code, not in this XHTML file), and it generates its own internal "anonymous content" at runtime; meaning the &lt;code&gt;&amp;lt;image class="toolbarbutton-icon"&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;label class="toolbarbutton-text"&amp;gt;&lt;/code&gt; children are injected by the custom element's own implementation at render time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can confirm the actual DOM structure 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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appMenu-fullscreen-button2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;outerHTML&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;toolbarbutton&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"appMenu-fullscreen-button2"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"subviewbutton subviewbutton-iconic"&lt;/span&gt; &lt;span class="na"&gt;data-l10n-id=&lt;/span&gt;&lt;span class="s"&gt;"appmenuitem-fullscreen"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;tooltip=&lt;/span&gt;&lt;span class="s"&gt;"dynamic-shortcut-tooltip"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Full screen"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;observes&lt;/span&gt; &lt;span class="na"&gt;element=&lt;/span&gt;&lt;span class="s"&gt;"View:FullScreen"&lt;/span&gt; &lt;span class="na"&gt;attribute=&lt;/span&gt;&lt;span class="s"&gt;"checked"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"toolbarbutton-icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Full screen"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"toolbarbutton-text"&lt;/span&gt; &lt;span class="na"&gt;crop=&lt;/span&gt;&lt;span class="s"&gt;"end"&lt;/span&gt; &lt;span class="na"&gt;flex=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Full screen"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/toolbarbutton&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we know &lt;code&gt;&amp;lt;toolbarbutton&amp;gt;&lt;/code&gt; elements in Firefox's UI don't render the icon directly on themselves; they render it inside a child element with the class &lt;code&gt;.toolbarbutton-icon&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;We can confirm this once again by inspecting the element in the Browser Toolbox, when we 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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appMenu-fullscreen-button2&lt;/span&gt;&lt;span class="dl"&gt;"&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;.toolbarbutton-icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;image&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"toolbarbutton-icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;label=&lt;/span&gt;&lt;span class="s"&gt;"Full screen"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms that's where the icon or image actually lives and we can target it using this:&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="nf"&gt;#appMenu-fullscreen-button2&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;.toolbarbutton-icon&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This leads us to the next step in finding the fix.&lt;/p&gt;

&lt;p&gt;According to Mozilla &lt;a href="https://firefox-source-docs.mozilla.org/code-quality/coding-style/rtl_guidelines.html" rel="noopener noreferrer"&gt;guidelines&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;icons implying direction should mirror in RTL, using this exact pattern as the recommended technique&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.element-with-icon&lt;/span&gt;&lt;span class="nd"&gt;:dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;rtl&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scaleX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/** scale: -1 1; does the same horizontal flip **/&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we know what our fix should be&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The fix, then, is to add a CSS rule that uses &lt;code&gt;:dir(rtl)&lt;/code&gt; to horizontally flip the fullscreen icon whenever Firefox is in RTL mode. Since the icon itself lives inside the &lt;code&gt;.toolbarbutton-icon&lt;/code&gt; child element, that's the element we need to target, using &lt;code&gt;scale: -1 1&lt;/code&gt; to perform the flip.&lt;/p&gt;

&lt;p&gt;The file to edit is: &lt;code&gt;browser/themes/shared/menupanel.css&lt;/code&gt;&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="c"&gt;/* To flip an icon for RTL using scale: */&lt;/span&gt;
&lt;span class="nf"&gt;#appMenu-fullscreen-button2&lt;/span&gt;&lt;span class="nd"&gt;:dir&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;rtl&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;.toolbarbutton-icon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-1&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means "when this specific button is inside an RTL context, flip its icon child horizontally."&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;When you open the hamburger menu (&lt;code&gt;☰&lt;/code&gt;) in Firefox, there's a zoom row that looks like: &lt;code&gt;Zoom — [-] 100% [+] [⤢]&lt;/code&gt;. That last icon &lt;code&gt;[⤢]&lt;/code&gt; is the fullscreen button (appMenu-fullscreen-button2).&lt;/p&gt;

&lt;p&gt;In Arabic and other RTL languages, this icon doesn't flip to match the reading direction, it stays the same as in English (LTR). It should be mirrored.&lt;/p&gt;

&lt;p&gt;💗 PATCH: &lt;a href="https://phabricator.services.mozilla.com/D309258" rel="noopener noreferrer"&gt;https://phabricator.services.mozilla.com/D309258&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🕷️ BUG: &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=2029514" rel="noopener noreferrer"&gt;https://bugzilla.mozilla.org/show_bug.cgi?id=2029514&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👤 MY PROFILE ON BUGZILLA: &lt;a href="https://bugzilla.mozilla.org/user_profile?user_id=786766" rel="noopener noreferrer"&gt;https://bugzilla.mozilla.org/user_profile?user_id=786766&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>firefox</category>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <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 contains 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;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, we can write the tests as follows:&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="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;h2&gt;
  
  
  Explaining the test:
&lt;/h2&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;h5&gt;
  
  
  Test 1: Horizontal Browser Tabs
&lt;/h5&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;h5&gt;
  
  
  Test 2: Vertical Browser Tabs
&lt;/h5&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;h5&gt;
  
  
  Test 3: Normal Browser Window
&lt;/h5&gt;

&lt;p&gt;In this test, we open a normal browsing window 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 indicator is visible and the nav-bar indicator is hidden.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_indicator_vertical_tabs&lt;/code&gt;: opens a private window, turns on vertical tabs mode, check that the nav-bar indicator is visible, TabsToolbar indicator 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 indicator shows up at all since this isn't a private window in the first place.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Registering the test
&lt;/h2&gt;

&lt;p&gt;In Firefox, whenever you write a test, you must register the test in &lt;code&gt;browser.toml&lt;/code&gt; which is meant for that particular component - We are writing a test for the private browsing component; hence the file is located at &lt;code&gt;browser/components/privatebrowsing/test/browser/browser.toml&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Components are registered in an alphabetical order in this file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;["browser_privatebrowsing_favicon.js"]&lt;/span&gt;

&lt;span class="nn"&gt;["browser_privatebrowsing_history_shift_click.js"]&lt;/span&gt;

&lt;span class="nn"&gt;["browser_privatebrowsing_indicator.js"]&lt;/span&gt; &lt;span class="c"&gt;# our unit test &lt;/span&gt;

&lt;span class="nn"&gt;["browser_privatebrowsing_last_private_browsing_context_exited.js"]&lt;/span&gt;

&lt;span class="nn"&gt;["browser_privatebrowsing_lastpbcontextexited.js"]&lt;/span&gt;

&lt;span class="nn"&gt;["browser_privatebrowsing_localStorage.js"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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 and so we wrote unit tests for those instances of the browser mode&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;71&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;
              

              26&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;/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;/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>
  </channel>
</rss>
