<?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>Forem: Raz Chiriac</title>
    <description>The latest articles on Forem by Raz Chiriac (@raz_chiriac_b5dbbe8de7932).</description>
    <link>https://forem.com/raz_chiriac_b5dbbe8de7932</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F908203%2F8f7e6fb1-e96d-4daa-9b3f-d3be67575289.jpeg</url>
      <title>Forem: Raz Chiriac</title>
      <link>https://forem.com/raz_chiriac_b5dbbe8de7932</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/raz_chiriac_b5dbbe8de7932"/>
    <language>en</language>
    <item>
      <title>How I built the sudoku site I always wanted (Next.js 15, Supabase, keyboard-first)</title>
      <dc:creator>Raz Chiriac</dc:creator>
      <pubDate>Sun, 10 May 2026 15:17:14 +0000</pubDate>
      <link>https://forem.com/raz_chiriac_b5dbbe8de7932/how-i-built-the-sudoku-site-i-always-wanted-nextjs-15-supabase-keyboard-first-4pbp</link>
      <guid>https://forem.com/raz_chiriac_b5dbbe8de7932/how-i-built-the-sudoku-site-i-always-wanted-nextjs-15-supabase-keyboard-first-4pbp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Every sudoku site I've ever used has the same two problems: ads everywhere, and a mouse-only UI that makes fast solving feel clunky. I play sudoku to think, not to fight with my browser.&lt;/p&gt;

&lt;p&gt;So I built my own. It's called &lt;strong&gt;Kodiak Sudoku&lt;/strong&gt; — free, no ads, keyboard-first, with vim bindings and a leaderboard that doesn't lie. Here's what went into it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 15&lt;/strong&gt; with the App Router&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React 19&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; throughout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; for the database (auth, leaderboard storage, daily puzzle state)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drizzle ORM&lt;/strong&gt; on top of Supabase&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel&lt;/strong&gt; for hosting + Edge Config for feature flags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing exotic — just a stack I could move fast with and trust in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three things I actually cared about
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Free with no ads
&lt;/h3&gt;

&lt;p&gt;This sounds simple but it shapes every decision. No ad SDK to load, no layout shift, no third-party trackers. The page is fast because there's nothing slowing it down. I'm covering hosting costs out of pocket for now — Vercel and Supabase's free tiers go surprisingly far for a side project at this scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keyboard-first, with vim bindings
&lt;/h3&gt;

&lt;p&gt;Most sudoku sites are built for mouse/touch. Clicking individual cells is fine on mobile, but on desktop it's slow. I wanted to navigate the board the way I navigate everything else — with hjkl, or arrow keys, or number keys to fill in digits instantly.&lt;/p&gt;

&lt;p&gt;The input handling ended up being one of the more interesting parts of the build. React's synthetic event system and the browser's native focus behavior don't always agree, especially when you're trying to make a grid feel like a text editor. Getting vim-style navigation to feel snappy without fighting the DOM took a few iterations.&lt;/p&gt;

&lt;p&gt;If you're into this kind of thing — keyboard-driven UIs, focus management, accessibility — I'd love to hear how you've solved similar problems.&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%2Fx1p8hsx6z9zkuc8xtgkc.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%2Fx1p8hsx6z9zkuc8xtgkc.png" alt="Kodiak Sudoku Home Page" width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Honest leaderboards
&lt;/h3&gt;

&lt;p&gt;This one bugged me about every other sudoku leaderboard I've seen: hint-assisted times compete directly against pure runs. Someone who used three hints finishing in 4 minutes shouldn't outrank someone who solved it clean in 5.&lt;/p&gt;

&lt;p&gt;Kodiak separates the two. Pure runs and hint-assisted runs are ranked independently. It's a small thing but it changes how the leaderboard actually feels — it's something you can be proud of, not just a number.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interesting technical bits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Daily puzzle state at the edge
&lt;/h3&gt;

&lt;p&gt;The daily puzzle is the same for everyone, every day. I'm using Vercel Edge Config to store and serve the active puzzle, which means the puzzle data is globally distributed and available with essentially zero latency. State per-user (progress, timer, hints used) lives in Supabase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drizzle + Supabase
&lt;/h3&gt;

&lt;p&gt;I'd used Prisma before and wanted to try Drizzle. The type inference is excellent and the query builder feels closer to writing SQL, which I prefer. The integration with Supabase was smooth — I'm using Supabase for auth and RLS policies, with Drizzle handling the query layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Puzzle generation vs. curation
&lt;/h3&gt;

&lt;p&gt;I ended up curating puzzles rather than generating them on the fly. Generated puzzles can technically be valid but feel mechanically similar. Hand-selected (or at least filtered) puzzles have more character. For the daily puzzle specifically, I wanted something people would actually enjoy solving, not just a valid grid.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I used Claude for
&lt;/h2&gt;

&lt;p&gt;Claude was my pair-programmer throughout. Not in a "it wrote the whole thing" way — more like having a senior dev available to rubber-duck architecture decisions, catch edge cases in my input handling logic, and help me move faster on the parts I found tedious (writing marketing copy, mostly).&lt;/p&gt;

&lt;p&gt;The parts that required actual judgment — how to structure the leaderboard, how to handle timer state across tab visibility changes, how to make the keyboard UX feel right — those were conversations, not prompts. Worth being honest about the difference.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The site is live now at &lt;strong&gt;&lt;a href="https://kodiaksudoku.com" rel="noopener noreferrer"&gt;kodiaksudoku.com&lt;/a&gt;&lt;/strong&gt;. Daily puzzles, leaderboards, full keyboard support including vim bindings.&lt;/p&gt;

&lt;p&gt;Things I'm thinking about for v2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile experience improvements (touch is functional but not delightful yet)&lt;/li&gt;
&lt;li&gt;Puzzle difficulty ratings based on solving technique, not just time&lt;/li&gt;
&lt;li&gt;A "practice mode" for specific techniques (naked pairs, X-wing, etc.)&lt;/li&gt;
&lt;li&gt;A Loom walkthrough once I've seen how real users actually play&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build keyboard-first apps, care about honest UX, or just play a lot of sudoku — I'd genuinely love to hear from you. What would make this better?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Next.js, Supabase, Drizzle, Tailwind, and Vercel. Pair-programmed with Claude.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Console Wrangling</title>
      <dc:creator>Raz Chiriac</dc:creator>
      <pubDate>Sun, 14 Aug 2022 16:15:05 +0000</pubDate>
      <link>https://forem.com/raz_chiriac_b5dbbe8de7932/console-wrangling-2k2p</link>
      <guid>https://forem.com/raz_chiriac_b5dbbe8de7932/console-wrangling-2k2p</guid>
      <description>&lt;h2&gt;
  
  
  Have you ever console logged the console?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---c5xEVXh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihj627kwiml7gk0gqo80.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---c5xEVXh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihj627kwiml7gk0gqo80.jpg" alt="Thanos console" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you know the good ol’ &lt;code&gt;console.log()&lt;/code&gt; ? &lt;/p&gt;

&lt;p&gt;Well, that’s not the only thing the console can do, it’s only the most basic. &lt;/p&gt;

&lt;p&gt;Have you ever used the console to log the console? No? You should try it. &lt;/p&gt;

&lt;p&gt;The console will show you what the console can do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KxdmH5d1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0q4ve5ihlkpof7x4q5bj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KxdmH5d1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0q4ve5ihlkpof7x4q5bj.png" alt="console console" width="560" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today we’ll talk about two of the functions available on the console object:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;console.table()&lt;/code&gt; and &lt;code&gt;console.trace()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Lets say we have two objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;carA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ford&lt;/span&gt;&lt;span class="dl"&gt;'&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="s1"&gt;F150&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2018&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;white&lt;/span&gt;&lt;span class="dl"&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;carB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;make&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ford&lt;/span&gt;&lt;span class="dl"&gt;'&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="s1"&gt;F250&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2016&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&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;If we wanted to do some debugging and had to print these objects out to the console we could use &lt;code&gt;console.log()&lt;/code&gt; but it wont look too nice.&lt;/p&gt;

&lt;p&gt;Look how neat &lt;code&gt;console.table()&lt;/code&gt; can display the two objects:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YmmVbIg4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bs04i5n1ekoact10x5fb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YmmVbIg4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bs04i5n1ekoact10x5fb.png" alt="console table" width="536" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can even sort the two rows by column just by clicking on the headers!&lt;/p&gt;

&lt;p&gt;One other console function we’ll talk about today is &lt;code&gt;console.trace()&lt;/code&gt;. This will output a stack trace to the console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3CvRRt1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ag4apxwdgre4514w38l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y3CvRRt1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ag4apxwdgre4514w38l9.png" alt="console trace" width="512" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s all for today.&lt;/p&gt;

&lt;p&gt;I hope I’ve helped make your debugging a little less boring. Now go show off your new console skills in your next stand-up.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Object Equality in JavaScript</title>
      <dc:creator>Raz Chiriac</dc:creator>
      <pubDate>Sat, 13 Aug 2022 16:33:00 +0000</pubDate>
      <link>https://forem.com/raz_chiriac_b5dbbe8de7932/object-equality-in-javascript-2k96</link>
      <guid>https://forem.com/raz_chiriac_b5dbbe8de7932/object-equality-in-javascript-2k96</guid>
      <description>&lt;p&gt;Howdy! Here’s a simple way to check for object equality.&lt;/p&gt;

&lt;p&gt;Let’s say we have two objects:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const carA = {make: 'Ford', model: 'F150', year: '2018', color: 'white'}&lt;br&gt;
const carB = {make: 'Ford', model: 'F150', year: '2018', color: 'white'}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can see that &lt;code&gt;carA&lt;/code&gt; is the same as &lt;code&gt;carB&lt;/code&gt;. So we would expect &lt;code&gt;carA === carB&lt;/code&gt; to return true but it does not. This is because JavaScript checks for equality on non-primitive types like Objects, Arrays and Dates by reference.  This means, JavaScript checks if the two objects point to the same place in memory, not if they have the same key-value pairs.&lt;/p&gt;

&lt;p&gt;One way to check for key-value equality of two objects, without the use of an external  library or much coding is by converting the objects to strings using &lt;code&gt;JSON.stringify()&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;&lt;code&gt;carA == carB // =&amp;gt; false&lt;/code&gt;&lt;br&gt;
&lt;code&gt;carA === carB // =&amp;gt; false&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;JSON.stringify(carA) === JSON.stringify(carB) // =&amp;gt; true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that this approach only works if the key-value pairs are in the same order on both objects. We could go into sorting object entries by key and other approaches in future posts.&lt;/p&gt;

&lt;p&gt;Cheers! 🫖&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
