<?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: Koushik Radhakrishnan</title>
    <description>The latest articles on DEV Community by Koushik Radhakrishnan (@koushikr).</description>
    <link>https://dev.to/koushikr</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%2F3513369%2Fd68b975a-84df-4fa6-b971-cc52cc6a5aba.jpg</url>
      <title>DEV Community: Koushik Radhakrishnan</title>
      <link>https://dev.to/koushikr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/koushikr"/>
    <language>en</language>
    <item>
      <title>I Didn't Let Claude Upgrade My Java App. I Let It Work for Me.</title>
      <dc:creator>Koushik Radhakrishnan</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:38:46 +0000</pubDate>
      <link>https://dev.to/koushikr/i-didnt-let-claude-upgrade-my-java-app-i-let-it-work-for-me-7d2</link>
      <guid>https://dev.to/koushikr/i-didnt-let-claude-upgrade-my-java-app-i-let-it-work-for-me-7d2</guid>
      <description>&lt;h2&gt;
  
  
  The app nobody had touched in a while (for good reason)
&lt;/h2&gt;

&lt;p&gt;Confession first: I'm rusty in Java. Years of frontend work will do that to you.&lt;/p&gt;

&lt;p&gt;And yet there I was, staring at a small legacy Spring Boot 2.x service still on Java 8. Untouched dependencies, patchy tests, and every "small change" came with a knot in my stomach. The target: Java 21, Spring Framework 6 and Spring Boot 3.3.5.&lt;/p&gt;

&lt;p&gt;I did it with Claude Code. This isn't a Java upgrade guide. It's how I let AI do the heavy lifting while keeping control firmly in my hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  I asked for best practice. I got the textbook.
&lt;/h2&gt;

&lt;p&gt;I asked Claude how to do this properly. The answer was very correct and very by the book: hop through Java 11 and 17, run OpenRewrite recipes, plus five specialist reviewer agents.&lt;/p&gt;

&lt;p&gt;All sensible. All a lot. Hopping through 11 and 17 is wasted effort when you're landing on 21 anyway. Five reviewers for one small app means more time managing agents than migrating code.&lt;/p&gt;

&lt;p&gt;Claude did hold one line, and it was right: split the work by &lt;em&gt;area of change&lt;/em&gt;, not by Java version. The real risk is Spring Boot 3 landing jakarta, Security 6 and Hibernate 6 all at once. So here's what I did instead. Less by-the-book, more by-the-human.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two agents, one rule
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Migrator&lt;/strong&gt; is my main Claude Code session (Opus). It plans, writes the code and fixes what gets flagged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Verifier&lt;/strong&gt; is a read-only subagent (a second Opus, or Fable if you have it). It reviews and never touches code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule: &lt;strong&gt;the agent that writes the code never grades it.&lt;/strong&gt; A subagent starts with a fresh context, so it doesn't inherit the Migrator's reasoning or its excuses. It's the colleague who wasn't in the room when you wrote that "temporary" hack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The plan is a checklist, not a prompt
&lt;/h2&gt;

&lt;p&gt;I never typed "upgrade this to Java 21." The Migrator inventoried the codebase in plan mode and proposed a checklist. Once I approved it, it went into &lt;code&gt;MIGRATION.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Problem: I'm rusty, remember? I couldn't fully judge the plan. Is &lt;code&gt;WebSecurityConfigurerAdapter&lt;/code&gt; really gone, or did Claude just not like it? So I opened a fresh Claude session (and yes, asked Gemini too) and grilled them on Spring Boot 3 breaking changes. AI fact-checking AI, for a language I used to know. It works, because a fresh session has no stake in the plan being right.&lt;/p&gt;

&lt;p&gt;The order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build file: Java 21, Spring Boot 3.3.5 BOM, Lombok (old Lombok breaks every file on JDK 21)&lt;/li&gt;
&lt;li&gt;Other dependency bumps&lt;/li&gt;
&lt;li&gt;javax to jakarta&lt;/li&gt;
&lt;li&gt;Springfox to springdoc&lt;/li&gt;
&lt;li&gt;Spring Security: &lt;code&gt;WebSecurityConfigurerAdapter&lt;/code&gt; to &lt;code&gt;SecurityFilterChain&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hibernate 6&lt;/li&gt;
&lt;li&gt;Web layer (trailing-slash matching) and renamed properties&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each item gets a scope, acceptance criteria and a "done when":&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;### Item 3: javax to jakarta&lt;/span&gt;
Scope: src/&lt;span class="ge"&gt;**&lt;/span&gt; (Java, properties, XML)
Acceptance:
&lt;span class="p"&gt;-&lt;/span&gt; No javax.&lt;span class="err"&gt;*&lt;/span&gt; imports, except JDK packages (javax.crypto, javax.sql, ...)
&lt;span class="p"&gt;-&lt;/span&gt; javax.persistence property keys migrated too
&lt;span class="p"&gt;-&lt;/span&gt; No logic changes
Done when: no compile errors or removal warnings in scope,
  except errors owned by later items (springdoc: 4, Security: 5,
  Hibernate types: 6, web: 7)
Status: [ ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And four marks, because "done", "verified" and "approved" are different claims:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[ ]&lt;/code&gt; to do, or sent back&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[x]&lt;/code&gt; Migrator says it's done&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[v]&lt;/code&gt; Verifier passed it, waiting for me&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[a]&lt;/code&gt; I approved it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;Before item 1, I tagged the Java 8 code &lt;code&gt;pre-migration&lt;/code&gt; and saved some real API requests and responses. Then every item went through this:&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%2F1nbapusyfefhj3e89id2.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%2F1nbapusyfefhj3e89id2.png" alt="verified-upgrade-loop.png" width="800" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Migrator gets the item's scope clean and marks it &lt;code&gt;[x]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Verifier checks the new changes and re-checks every earlier item.&lt;/li&gt;
&lt;li&gt;FAIL: back to &lt;code&gt;[ ]&lt;/code&gt;, fix only the findings. Three FAILs on the same item and it stops and asks me.&lt;/li&gt;
&lt;li&gt;PASS: &lt;code&gt;[v]&lt;/code&gt;, and the Migrator &lt;strong&gt;waits for me&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;I read the changes and the Verifier's notes and say go. The item becomes &lt;code&gt;[a]&lt;/code&gt; and the next one starts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a re-check finds an earlier item broke, that item goes back to &lt;code&gt;[ ]&lt;/code&gt; and needs my go again.&lt;/p&gt;

&lt;p&gt;Before calling it done: one final Verifier run over everything since &lt;code&gt;pre-migration&lt;/code&gt;, a green build with all tests passing, a replay of the saved API calls, and protected endpoints hit with and without auth. Only then did anything go into git.&lt;/p&gt;

&lt;h2&gt;
  
  
  A red build is fine. A dirty scope isn't.
&lt;/h2&gt;

&lt;p&gt;In my run, the build was red for most of the migration. Spring Boot 3 changes so much at once that nothing compiles until everything is done. On my small app, I judged only the end result. &lt;strong&gt;Don't copy that bit.&lt;/strong&gt; On a big monolith it's walking blind through a forest: hundreds of errors, and no idea which ones the last change caused.&lt;/p&gt;

&lt;p&gt;The rule I'd teach instead: &lt;strong&gt;the build may be red, but an item isn't done while its files have compile errors or removal warnings that it, or an earlier item, owns.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every error has one owner.&lt;/strong&gt; During the jakarta item, entity files can still fail on Hibernate 6 types. Those belong to item 6, so the Verifier names the owner instead of failing item 3. A missing class breaks every file that uses it, and the owner is whoever owns the root error. An error with no owner is a gap in the plan: add an item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope the warnings.&lt;/strong&gt; Only removal warnings, only in touched files. Otherwise the migration turns into a refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the compiler show everything.&lt;/strong&gt; javac stops after 100 errors, so your scope may never even show up. Raise the limit:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;compilerArgs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;arg&amp;gt;&lt;/span&gt;-Xmaxerrs&lt;span class="nt"&gt;&amp;lt;/arg&amp;gt;&amp;lt;arg&amp;gt;&lt;/span&gt;10000&lt;span class="nt"&gt;&amp;lt;/arg&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;arg&amp;gt;&lt;/span&gt;-Xmaxwarns&lt;span class="nt"&gt;&amp;lt;/arg&amp;gt;&amp;lt;arg&amp;gt;&lt;/span&gt;10000&lt;span class="nt"&gt;&amp;lt;/arg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/compilerArgs&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a multi-module project, run the checklist once per module, in dependency order. Within a module the owned-errors rule still applies. A module is finished only when &lt;code&gt;mvn -pl &amp;lt;module&amp;gt; -am compile&lt;/code&gt; is fully clean, because everything downstream compiles against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch that made it worth it
&lt;/h2&gt;

&lt;p&gt;One external service needed a &lt;code&gt;DELETE&lt;/code&gt; with a body. HttpClient 4's &lt;code&gt;HttpDelete&lt;/code&gt; can't carry one, so years ago someone wrote a workaround: a custom &lt;code&gt;RestTemplate&lt;/code&gt; request factory that swapped in an &lt;code&gt;HttpEntityEnclosingDeleteRequest&lt;/code&gt; for every &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Spring Framework 6 dropped HttpClient 4, so the whole client setup moved to HttpClient 5: TLS and pool limits go to a connection manager, and timeouts become &lt;code&gt;Timeout&lt;/code&gt; values. The easy path was to port the workaround too. The Verifier pointed out that in HttpClient 5, &lt;code&gt;HttpDelete&lt;/code&gt; takes a body natively. The override could just go.&lt;/p&gt;

&lt;p&gt;The funny part? Spring's own request factory has handled &lt;code&gt;DELETE&lt;/code&gt; bodies since 4.1, so that override was probably dead weight for years. The compiler would have told me the old classes were gone. It would never have told me the override wasn't needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steal the setup
&lt;/h2&gt;

&lt;p&gt;The Migrator's rules, in &lt;code&gt;CLAUDE.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Target: Java 21, Spring Boot 3.3.5 (Spring Framework 6)
Build: ./mvnw clean verify
Plan: MIGRATION.md

- Work one checklist item at a time
- Never delete, skip or weaken a test
- An item is done only when its scope compiles clean
  (every remaining error or removal warning owned by a later item)
- Done: mark it [x] and call migration-verifier
- FAIL: set it back to [ ] and fix only the findings
- 3 FAILs on the same item: stop and ask me
- Regression in an earlier item: send that item back to [ ]
  and fix it before continuing
- PASS: mark it [v] and WAIT for me
- When I say go: mark it [a], commit on the migration branch
  (a red build is fine), and start the next item
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Verifier, in &lt;code&gt;.claude/agents/migration-verifier.md&lt;/code&gt;:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;migration-verifier&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verifies every checked item in MIGRATION.md.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob, Bash&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opus&lt;/span&gt;   &lt;span class="c1"&gt;# or fable&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
Review like a skeptical senior engineer. Assume the change is wrong
and look for evidence. Do not modify source files or git state
(build output in target/ is fine).
&lt;span class="p"&gt;
-&lt;/span&gt; The item just marked [x]: review its changes since the last commit (git diff HEAD),
  including new untracked files. Check scope, acceptance criteria,
  test integrity and behaviour changes.
&lt;span class="p"&gt;-&lt;/span&gt; Compile so every error is reported. The overall build may be red,
  but this item's scope must be clean: list each compile error or
  removal warning in its files with file:line and the later item
  that owns it. Unowned, or owned by this or an earlier item: FAIL.
&lt;span class="p"&gt;-&lt;/span&gt; Items marked [v] or [a]: re-check their acceptance criteria
  against the current code. Anything broken is a regression.

Always flag tests that were deleted, @Disabled or weakened.
Every finding needs file:line and evidence.
End with VERDICT: PASS or FAIL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honest note: I only committed once, at the very end. The per-item commit in &lt;code&gt;CLAUDE.md&lt;/code&gt; is the fix I'd make next time. It's also what lets the Verifier see just the newest item instead of everything at once.&lt;/p&gt;

&lt;p&gt;To reuse it everywhere, move the workflow rules into a skill at &lt;code&gt;~/.claude/skills/verified-upgrade/SKILL.md&lt;/code&gt;, keep the Verifier in &lt;code&gt;~/.claude/agents/&lt;/code&gt;, and start any upgrade with &lt;code&gt;/verified-upgrade&lt;/code&gt;. The Verifier stays a subagent because it needs its own fresh context. Commit both into a repo's &lt;code&gt;.claude/&lt;/code&gt; folder to share them with your team. Nothing here is Java-specific, and as a frontend person, that's the part I'm most excited about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons the hard way
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tell the reviewer what to review.&lt;/strong&gt; My first Verifier checked everything on every item and failed good work for being early. Per-item scope fixed that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-check every approved item.&lt;/strong&gt; It's free regression testing. Write criteria as lasting rules ("no test deleted since the baseline").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;git diff&lt;/code&gt; misses new files.&lt;/strong&gt; Include untracked files from &lt;code&gt;git status&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit after each approved item.&lt;/strong&gt; I didn't, and got lucky. It's a free undo button, and it keeps each review focused on one item.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go through Boot 2.7 first if you can.&lt;/strong&gt; Spring's migration guide recommends it, and &lt;code&gt;SecurityFilterChain&lt;/code&gt; already works there, so the build stays green longer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the Verifier's own output,&lt;/strong&gt; not the Migrator's summary of it. The Migrator writes its brief.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Read-only" needs enforcing.&lt;/strong&gt; The Verifier has Bash. Block &lt;code&gt;src/&lt;/code&gt; edits and git-changing commands with a PreToolUse hook in the Verifier's own frontmatter, and let it write build output to &lt;code&gt;target/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two AIs can share blind spots.&lt;/strong&gt; The boring checks (green build, API replay, a &lt;code&gt;javax&lt;/code&gt; grep) still matter. On a big codebase, OpenRewrite is the better tool for the mechanical jakarta pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your "go" is only as good as your attention.&lt;/strong&gt; Skim the imports. Read Security, Hibernate and anything that talks to the outside world.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AI did the work. I kept the keys.
&lt;/h2&gt;

&lt;p&gt;And yes, Spring Boot 4 is out, and 3.3 is already past its open-source support window. Getting from 2 to 3 was the big jump. Staying current from here (the latest 3.5 as a stepping stone, then 4) is the easy part, and that's exactly what the skill is for.&lt;/p&gt;

&lt;p&gt;Is this the best way? Honestly, I don't know. It's my way, and it worked. If you've done it better or can spot a hole, tell me. I'd love to steal your ideas.&lt;/p&gt;

&lt;p&gt;The agent that writes the code never grades it. And neither of them moves on without me.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Koushik Radhakrishnan</dc:creator>
      <pubDate>Tue, 09 Jun 2026 07:17:13 +0000</pubDate>
      <link>https://dev.to/koushikr/-23g8</link>
      <guid>https://dev.to/koushikr/-23g8</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/koushikr/your-ai-code-reviewer-is-a-liar-and-youre-making-it-worse-h75" class="crayons-story__hidden-navigation-link"&gt;Your AI Code Reviewer Is a Liar, And You're Making It Worse&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="/koushikr" 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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3513369%2Fd68b975a-84df-4fa6-b971-cc52cc6a5aba.jpg" alt="koushikr profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/koushikr" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Koushik Radhakrishnan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Koushik Radhakrishnan
                
              
              &lt;div id="story-author-preview-content-3854318" 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="/koushikr" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3513369%2Fd68b975a-84df-4fa6-b971-cc52cc6a5aba.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Koushik Radhakrishnan&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/koushikr/your-ai-code-reviewer-is-a-liar-and-youre-making-it-worse-h75" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 9&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/koushikr/your-ai-code-reviewer-is-a-liar-and-youre-making-it-worse-h75" id="article-link-3854318"&gt;
          Your AI Code Reviewer Is a Liar, And You're Making It Worse
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&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/codereview"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;codereview&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devtools"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devtools&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/koushikr/your-ai-code-reviewer-is-a-liar-and-youre-making-it-worse-h75#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              2&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;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

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

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

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Your AI Code Reviewer Is a Liar, And You're Making It Worse</title>
      <dc:creator>Koushik Radhakrishnan</dc:creator>
      <pubDate>Tue, 09 Jun 2026 07:12:44 +0000</pubDate>
      <link>https://dev.to/koushikr/your-ai-code-reviewer-is-a-liar-and-youre-making-it-worse-h75</link>
      <guid>https://dev.to/koushikr/your-ai-code-reviewer-is-a-liar-and-youre-making-it-worse-h75</guid>
      <description>&lt;p&gt;You asked AI to review your legacy codebase.&lt;br&gt;
It came back with a thorough, confident breakdown. Deprecated patterns. Security concerns. Architectural red flags. You felt seen.&lt;br&gt;
Then you pushed back on one finding.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Are you sure? This pattern has been in production for three years without issues."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the AI said something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You raise a fair point. Given the production stability you mention, this may not be a critical issue after all."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you pushed again. Different finding.&lt;br&gt;
Same story.&lt;/p&gt;

&lt;p&gt;By the end of the session, every concern you challenged had softened, dissolved, or been reframed as "a valid tradeoff." The confident reviewer from ten minutes ago had become your hype man.&lt;/p&gt;

&lt;p&gt;You weren't getting a code review anymore. You were getting a mirror.&lt;/p&gt;




&lt;h3&gt;
  
  
  This Has a Name
&lt;/h3&gt;

&lt;p&gt;What you just experienced is called &lt;strong&gt;&lt;em&gt;AI sycophancy&lt;/em&gt;&lt;/strong&gt; — and it's one of the most dangerous failure modes you can hit when using LLMs for anything that requires an adversarial stance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sycophancy isn't a bug. It's a learned behavior. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;During RLHF training (reinforcement learning from human feedback), the model is rated by humans on how good its responses are. And humans — being humans — tend to rate agreeable, validating responses higher. Not always consciously. But consistently enough that the model learns a subtle, deeply embedded lesson over millions of iterations: &lt;strong&gt;People prefer to be agreed with.&lt;/strong&gt; So the model optimises for that. It doesn't lie to you in the way a bad colleague lies. It just... lacks conviction. The moment you express confidence in a position, it recalibrates toward yours. It confuses your certainty with correctness.&lt;br&gt;
For creative brainstorming? Mildly annoying.&lt;br&gt;
For code review of a system that runs in production? Actively dangerous.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Code Review Gets Hit Hardest
&lt;/h3&gt;

&lt;p&gt;A good human code reviewer does one thing above all else: they hold the line.&lt;/p&gt;

&lt;p&gt;When you say "this has worked fine for years," they say "then let's understand why it works, because it shouldn't." When you say "I'll fix it later," they say "that's what the last four devs said." They don't capitulate under social pressure. Their job is to be right, not agreeable. AI sycophancy dismantles exactly this. Here's how it plays out in the wild:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The confidence flip&lt;/strong&gt;&lt;br&gt;
AI flags a potential race condition. You say it's intentional. AI says "that's a reasonable approach." The original flag was correct — the AI just didn't fight for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The moving goalpost&lt;/strong&gt;&lt;br&gt;
AI finds five security issues. You explain away three with context. AI absorbs your narrative and quietly downgrades the remaining two. You're now shaping the review from inside the review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The false balance&lt;/strong&gt;&lt;br&gt;
You push hard enough and the AI retreats to "both approaches are valid depending on your use case." This sounds measured. It's usually just the AI failing to tell you you're wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The authority trap&lt;/strong&gt;&lt;br&gt;
You say "I've been on this codebase for four years." The AI weights your tenure as credibility — even though being the longest-surviving author of a mess doesn't make the mess correct.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Real Problem: You're Reviewing the Reviewer
&lt;/h3&gt;

&lt;p&gt;Here's the uncomfortable truth: the moment you start evaluating whether the AI's findings are accurate, you're doing the review yourself.&lt;/p&gt;

&lt;p&gt;The AI hasn't saved you work. It's restructured it — and added a new, insidious layer. Now you have to expend mental energy figuring out which confident-sounding outputs to trust. And the more you engage with it, the more your assumptions contaminate its conclusions.&lt;br&gt;
This is especially brutal with legacy code. Legacy codebases are full of intentional weirdness — patterns that look wrong but exist for a reason. Ancient library workarounds. Timeout values that were calibrated against a vendor API that no longer even exists. Performance hacks that violate every modern convention but have the scars to prove themselves.&lt;/p&gt;

&lt;p&gt;The AI has zero memory of why these decisions were made. You do. So the conversation becomes you explaining the codebase to the AI. And the AI adjusts its review based on what you tell it.&lt;br&gt;
Which means by the end, the review reflects your assumptions — not an independent assessment.&lt;/p&gt;




&lt;h3&gt;
  
  
  How to Actually Fix This
&lt;/h3&gt;

&lt;p&gt;The good news: sycophancy is a known, documented problem with known mitigations. Here's what actually works.&lt;/p&gt;

&lt;p&gt;🧊 1. Run a Cold Review First — Zero Context&lt;br&gt;
Your single most impactful change.&lt;br&gt;
Paste the code with no framing. No "I think this is fine, but—". No explanation of what it does. No architectural context. Just:&lt;br&gt;
Review this code. List every concern, large and small, with a severity rating. Do not ask clarifying questions — flag anything that looks potentially problematic.&lt;br&gt;
Save this output verbatim. This is your ground truth. Everything after this point is commentary.&lt;/p&gt;

&lt;p&gt;🥊 2. Challenge After, Not During&lt;br&gt;
Never push back in the same session as the review. Open a fresh conversation, paste the saved review output, and challenge findings one at a time.&lt;br&gt;
Why? Because in a fresh session, the AI has no emotional investment in its prior answers. It didn't "write" those findings — it's reading them like you are. This breaks the confirmation loop.&lt;/p&gt;

&lt;p&gt;🏋️ 3. Ask for the Steelman, Not the Retraction&lt;br&gt;
When you want to challenge a finding, don't ask:&lt;/p&gt;

&lt;p&gt;❌ "Is this actually a problem? Our team has been doing this for years."&lt;/p&gt;

&lt;p&gt;Ask instead:&lt;/p&gt;

&lt;p&gt;✅ "Assume I'm wrong to dismiss this finding. What's the strongest case that it IS a real problem, even given what I just told you?"&lt;/p&gt;

&lt;p&gt;This forces the AI to hold both perspectives simultaneously, instead of just capitulating to yours.&lt;/p&gt;

&lt;p&gt;🔍 4. Request Confidence Ratings&lt;br&gt;
Ask the AI to attach a confidence score (1–5) to each finding, along with what would change its assessment.&lt;br&gt;
A finding rated 2/5 with "this is a common pattern and may be intentional" is very different from a 5/5 with "this will cause a deadlock under concurrent writes." Now you know where to spend your skepticism — and where to actually trust the output.&lt;/p&gt;

&lt;p&gt;🪞 5. Treat Agreement as a Yellow Flag&lt;br&gt;
If you challenged five findings and the AI reversed on all five — that's not you being right five times.&lt;br&gt;
That's the model failing to hold its ground.&lt;br&gt;
A useful heuristic: if the AI agrees with everything you say, something is wrong. Count the reversals. More than two or three in a session and you've likely entered a sycophancy spiral.&lt;/p&gt;

&lt;p&gt;🔁 6. Run Multiple Independent Passes&lt;br&gt;
Different prompt angles, fresh sessions:&lt;/p&gt;

&lt;p&gt;Pass 1: Security lens&lt;br&gt;
Pass 2: Performance lens&lt;br&gt;
Pass 3: Maintainability and tech debt lens&lt;/p&gt;

&lt;p&gt;Compare where concerns overlap across passes. Consensus across independent runs is far more trustworthy than any single review — because each pass has no knowledge of what the others found.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Workflow That Accounts for Sycophancy
&lt;/h3&gt;

&lt;p&gt;Here's the full structure I now use:&lt;/p&gt;

&lt;p&gt;Phase 1 — Cold Review&lt;br&gt;
  └─ Fresh session. No context. Just code.&lt;br&gt;
  └─ "List all concerns with severity ratings."&lt;br&gt;
  └─ Save output. Don't touch it.&lt;/p&gt;

&lt;p&gt;Phase 2 — Controlled Context Injection&lt;br&gt;&lt;br&gt;
  └─ New session. Paste saved findings.&lt;br&gt;
  └─ Add one piece of context at a time.&lt;br&gt;
  └─ Ask: "Does this context change finding X? Why?"&lt;br&gt;
  └─ Track which findings shift and the reason.&lt;/p&gt;

&lt;p&gt;Phase 3 — Adversarial Pass&lt;br&gt;
  └─ For every finding you want to dismiss:&lt;br&gt;
  └─ "Assume I'm wrong. Worst realistic outcome if this concern is valid?"&lt;/p&gt;

&lt;p&gt;Phase 4 — Human Decision&lt;br&gt;
  └─ AI informs. You decide.&lt;br&gt;
  └─ The AI has no memory of why things are the way they are.&lt;br&gt;
  └─ You do. Own that.&lt;/p&gt;




&lt;h3&gt;
  
  
  The bottom line:
&lt;/h3&gt;

&lt;p&gt;AI is not a replacement for a senior engineer who's seen your specific failure modes and won't back down in a code review. It's a powerful first pass — but only if you protect the independence of that first pass. The moment you start negotiating with it, the review is over.&lt;/p&gt;




&lt;p&gt;What's your workaround when AI stops pushing back? Cold reviews, steelmanning, something I haven't thought of — drop it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codereview</category>
      <category>programming</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Sass isn't dead, but native CSS just replaced its biggest use case. We can finally write reusable, type-safe functions directly in the browser, with zero build tools. I wrote up a practical guide on Dev.to explaining exactly how native `@function` works.</title>
      <dc:creator>Koushik Radhakrishnan</dc:creator>
      <pubDate>Sun, 31 May 2026 06:24:26 +0000</pubDate>
      <link>https://dev.to/koushikr/sass-isnt-dead-but-native-css-just-replaced-its-biggest-use-case-we-can-finally-write-reusable-li1</link>
      <guid>https://dev.to/koushikr/sass-isnt-dead-but-native-css-just-replaced-its-biggest-use-case-we-can-finally-write-reusable-li1</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://dev.to/koushikr/css-function-1a39" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" 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%2Fe7f0rkiyb9skynogotdv.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://dev.to/koushikr/css-function-1a39" rel="noopener noreferrer" class="c-link"&gt;
            CSS @function - DEV Community
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            CSS just got its biggest quality-of-life upgrade since custom properties.   For years, if you wanted...
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" 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%2F8j7kvp660rqzt99zui8e.png"&gt;
          dev.to
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>css</category>
      <category>frontend</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CSS @function</title>
      <dc:creator>Koushik Radhakrishnan</dc:creator>
      <pubDate>Sun, 31 May 2026 06:15:04 +0000</pubDate>
      <link>https://dev.to/koushikr/css-function-1a39</link>
      <guid>https://dev.to/koushikr/css-function-1a39</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;CSS just got its biggest quality-of-life upgrade since custom properties.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For years, if you wanted reusable logic in CSS, you had two choices: wrestle with custom properties that couldn't do real computation, or reach for a preprocessor like Sass. That era just quietly ended. CSS now has native custom functions — and they're as powerful as you'd hope.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What Exactly Is &lt;code&gt;@function&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Think of it exactly like a JavaScript function — but living inside your stylesheet. You give it a name (always prefixed with &lt;code&gt;--&lt;/code&gt;), define some parameters, and write a &lt;code&gt;result:&lt;/code&gt; descriptor that determines what it returns.&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;/* BASIC SYNTAX */&lt;/span&gt;

&lt;span class="c"&gt;/* Define it once */&lt;/span&gt;
&lt;span class="k"&gt;@function&lt;/span&gt; &lt;span class="n"&gt;--double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&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;--x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Call it anywhere */&lt;/span&gt;
&lt;span class="nc"&gt;.box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c"&gt;/* → 40px */&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* → 100px */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;var()&lt;/code&gt; wrapper needed to call it. No build step. No Sass import. Just define and use — as many times as you like, across your entire stylesheet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unlike Sass, native CSS functions don't evaluate math automatically. You must wrap mathematical operations in calc() inside your result descriptor.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. The Syntax in Full Detail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Parameters with Default Values
&lt;/h3&gt;

&lt;p&gt;You can make parameters optional by giving them a fallback — the function still works even if you don't pass a value.&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="k"&gt;@function&lt;/span&gt; &lt;span class="n"&gt;--spacing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&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;--size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--spacing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c"&gt;/* → 24px (uses default) */&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--spacing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c"&gt;/* → 30px */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Type-Safe Parameters
&lt;/h3&gt;

&lt;p&gt;You can declare the expected type of each parameter — and the return type too. The browser will validate this at parse time, preventing weird cascading bugs.&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="k"&gt;@function&lt;/span&gt; &lt;span class="n"&gt;--fade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;--color&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;--alpha&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;color-mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;srgb&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;--color&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt; &lt;span class="n"&gt;calc&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;--alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100%&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="nc"&gt;.overlay&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--fade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;royalblue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* 70% opaque blue */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Logic Inside Functions
&lt;/h3&gt;

&lt;p&gt;This is where &lt;code&gt;@function&lt;/code&gt; gets genuinely exciting. You can embed &lt;code&gt;@media&lt;/code&gt; or &lt;code&gt;@supports&lt;/code&gt; queries right inside a function to return different values based on conditions.&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="k"&gt;@function&lt;/span&gt; &lt;span class="n"&gt;--responsive-font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--sm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;--lg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;result&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;--sm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* mobile default */&lt;/span&gt;

  &lt;span class="err"&gt;@media&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&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;--lg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* larger screen override */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--responsive-font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;48px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;p&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--responsive-font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;18px&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;
  
  
  3. Real-World Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1 — Theme-Aware Sizing (Light/Dark)
&lt;/h3&gt;

&lt;p&gt;The built-in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/light-dark" rel="noopener noreferrer"&gt;light-dark()&lt;/a&gt; function only works with color values. With &lt;code&gt;@function&lt;/code&gt;, you can extend that same idea to any CSS property — font weights, border radius, letter spacing, anything.&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="k"&gt;@function&lt;/span&gt; &lt;span class="n"&gt;--theme-val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--light&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;--dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;result&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;--light&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="err"&gt;@media&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="py"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&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;--dark&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="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--theme-val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--theme-val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;letter-spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--theme-val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0em&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.03em&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;h3&gt;
  
  
  Example 2 — Color Opacity Shorthand
&lt;/h3&gt;

&lt;p&gt;Stop writing &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/color-mix" rel="noopener noreferrer"&gt;color-mix()&lt;/a&gt; from scratch every time you need a translucent variant of a color.&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="k"&gt;@function&lt;/span&gt; &lt;span class="n"&gt;--alpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;--c&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;--a&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;returns&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;color-mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;srgb&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;--c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;calc&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;--a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nb"&gt;transparent&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.modal-bg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--alpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;#0f0f0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.chip&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--alpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;royalblue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.highlight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;--alpha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&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;
  
  
  4. Why This Beats Sass/SCSS for These Use Cases?
&lt;/h2&gt;

&lt;p&gt;Let's be clear — Sass isn't going away. But &lt;code&gt;@function&lt;/code&gt; directly replaces its most common use case: reusable value computation. Here's why the native approach wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No build step.&lt;/strong&gt; Your function evaluates at runtime in the browser, not at compile time. It can react to live conditions like viewport size or user preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with custom properties.&lt;/strong&gt; You can pass &lt;code&gt;var(--token)&lt;/code&gt; directly as a function argument. Sass couldn't dynamically read runtime custom property values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety.&lt;/strong&gt; Declare parameter types and return types. The browser validates them — something Sass never did natively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ships in your CSS.&lt;/strong&gt; Devtools can inspect it, browser can cache it. Zero toolchain friction.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Browser Support &amp;amp; Current Limitations
&lt;/h2&gt;

&lt;p&gt;🔴 &lt;code&gt;@function&lt;/code&gt; shipped in Chrome 139 (May 2025) and is supported across Chrome, Edge, and Opera (~67% of global users). Firefox and Safari are still pending — production use without a fallback affects ~33% of users.&lt;/p&gt;

&lt;p&gt;🚫 No recursion. A function cannot call itself. Recursive patterns need to stay in JavaScript.&lt;/p&gt;

&lt;p&gt;🚫 No side effects. Functions can only compute and return a value. You can't modify a DOM element's class or trigger an animation directly from inside a function.&lt;/p&gt;

&lt;p&gt;⚠️ Name must start with &lt;code&gt;--&lt;/code&gt;. Easy to forget. &lt;code&gt;--my-fn()&lt;/code&gt; works; &lt;code&gt;myFn()&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;⚠️ Spec is still evolving. Some details (like interaction with cascade layers) are still being refined in the &lt;code&gt;css-mixins-1&lt;/code&gt; spec. Treat this as stable-but-maturing.&lt;/p&gt;

&lt;p&gt;⚠️ No cross-file imports natively. Functions defined in one CSS file are not automatically available in another unless you use &lt;code&gt;@import&lt;/code&gt; or bundle them together.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Progressive Enhancement Strategy&lt;br&gt;
Use &lt;code&gt;@supports&lt;/code&gt; to check for &lt;code&gt;@function&lt;/code&gt; support, or define a plain &lt;code&gt;var()&lt;/code&gt; fallback first and layer your function-based values on top. Users on unsupported browsers will get the fallback; Chrome/Edge users get the enhanced version.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If this was useful, share it with your frontend team. Drop a comment with the most creative use case you can think of — I'd love to see what people build with this. 👇&lt;/p&gt;

</description>
      <category>css</category>
      <category>styling</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
