A Love Letter to CoffeeScript and HAML: When Rails Frontend Development Was Pure Joy

The author reflects on the nostalgia of older coding practices, specifically with Ruby on Rails, CoffeeScript, and HAML. They appreciate the simplicity, conciseness, and readability of these technologies compared to modern alternatives like TypeScript. While acknowledging TypeScript’s superiority in type safety, they express a longing for the elegant developer experience of the past.

There’s something bittersweet about looking back at old codebases. Recently, I found myself diving into a Ruby on Rails project from 2012, and I was immediately transported back to an era when frontend development felt different. Better, even. The stack was CoffeeScript, HAML, and Rails’ asset pipeline, and you know what? It was glorious.

I know what you’re thinking. “CoffeeScript? That thing died years ago. TypeScript won. Get over it.” And you’re right. TypeScript did win. It’s everywhere now, and for good reasons. But let me tell you why, after all these years, I still get a little nostalgic pang when I think about writing CoffeeScript, and why part of me still thinks it was the better language.

The Rails Way: Opinionated and Proud

First, let’s set the scene. This was the golden age of Rails, when “convention over configuration” wasn’t just a tagline. It was a philosophy that permeated everything. The asset pipeline handled all your JavaScript and CSS compilation. You’d drop a .coffee file in app/assets/javascripts, write your code, and Rails would handle the rest. No webpack configs, no Babel presets, no decision fatigue about which bundler to use.

Your views lived in HAML files that looked like this:

.user-profile
  .header
    %h1= @user.name
    %p.bio= @user.bio
  
  .actions
    = link_to "Edit Profile", edit_user_path(@user), class: "btn btn-primary"
    = link_to "Delete Account", user_path(@user), method: :delete, 
      data: { confirm: "Are you sure?" }, class: "btn btn-danger"
Code language: JavaScript (javascript)

And your JavaScript looked like this:

class UserProfile
  constructor: (@element) ->
    @setupEventListeners()
  
  setupEventListeners: ->
    @element.find('.btn-danger').on 'click', (e) =>
      @handleDelete(e)
  
  handleDelete: (e) ->
    return unless confirm('Really delete?')
    
    $.ajax
      url: $(e.target).attr('href')
      method: 'DELETE'
      success: => @onDeleteSuccess()
      error: => @onDeleteError()
  
  onDeleteSuccess: ->
    @element.fadeOut()
    Notifications.show 'Account deleted successfully'

$ ->
  $('.user-profile').each ->
    new UserProfile($(this))
Code language: CSS (css)

Look at that. It’s beautiful. It’s concise. It’s expressive. And it just works.

Why HAML Was a Breath of Fresh Air

Let’s talk about HAML first. If you’ve never used it, HAML (HTML Abstraction Markup Language) was a templating language that let you write HTML without all the angle brackets. Instead of this:

<div class="container">
  <div class="row">
    <div class="col-md-6">
      <h1>Welcome</h1>
      <p class="lead">This is my website</p>
    </div>
  </div>
</div>
Code language: HTML, XML (xml)

You wrote this:

.container
  .row
    .col-md-6
      %h1 Welcome
      %p.lead This is my website
Code language: CSS (css)

The difference is striking. HAML forced you to write clean, properly indented markup. You couldn’t forget to close a tag because there were no closing tags. The structure was defined by indentation, Python-style. This meant your templates were always consistently formatted, always readable, and always correctly nested.

HAML also integrated beautifully with Ruby. Want to interpolate a variable? Just use =. Want to add a conditional? Use standard Ruby syntax. The mental model was simple: it’s just Ruby that outputs HTML.

- if current_user.admin?
  .admin-panel
    %h2 Admin Controls
    = render partial: 'admin/controls'
- else
  .user-message
    %p You don't have access to this section.
Code language: PHP (php)

No context switching between template syntax and programming language syntax. It was all Ruby, all the way down.

CoffeeScript: JavaScript for People Who Don’t Like JavaScript

Now, let’s get to the controversial part: CoffeeScript. For those who missed it, CoffeeScript was a language that compiled to JavaScript, created by Jeremy Ashkenas in 2009. It took heavy inspiration from Ruby and Python, offering a cleaner syntax that eliminated much of JavaScript’s syntactic noise.

Here’s the thing people forget: JavaScript in 2011 was terrible. No modules, no classes, no arrow functions, no destructuring, no template strings, no const or let. You had var, function expressions, and pain. So much pain.

CoffeeScript gave us:

Arrow functions (before ES6):

numbers = [1, 2, 3, 4, 5]
doubled = numbers.map (n) -> n * 2

Class syntax (before ES6):

class Animal
  constructor: (@name) ->
  
  speak: ->
    console.log "#{@name} makes a sound"

class Dog extends Animal
  speak: ->
    console.log "#{@name} barks"
Code language: CSS (css)

String interpolation (before ES6):

name = "Ivan"
greeting = "Hello, #{name}!"
Code language: JavaScript (javascript)

Destructuring (before ES6):

{name, age} = user
[first, second, rest...] = numbers

Comprehensions (still not in JavaScript):

adults = (person for person in people when person.age >= 18)

CoffeeScript didn’t just add syntax sugar. It changed how you thought about JavaScript. The code was more expressive, more concise, and more Ruby-like. For Rails developers, it felt like home.

The Magic of the Asset Pipeline

What made this stack truly shine was how it all fit together. The Rails asset pipeline was like magic. Possibly black magic, but magic nonetheless.

You’d organize your code like this:

app/assets/
  javascripts/
    application.coffee
    models/
      user.coffee
      post.coffee
    views/
      users/
        profile.coffee
      posts/
        index.coffee

In your application.coffee, you’d require your dependencies:

#= require jquery
#= require jquery_ujs
#= require_tree ./models
#= require_tree ./views
Code language: PHP (php)

Rails would automatically compile everything, concatenate it in the right order, minify it for production, and serve it with cache-busting fingerprints. You didn’t think about build tools. You just wrote code.

The same applied to stylesheets. Drop a .scss file in app/assets/stylesheets, and it would be compiled and served. Want to use a gem that includes assets? Add it to your Gemfile, and its assets would automatically be available. No CDN links, no manual script tags.

Was it perfect? No. Was it sometimes confusing when assets weren’t loading in the order you expected? Yes. But the developer experience was smooth. You could go from idea to implementation incredibly quickly.

Why CoffeeScript Still Feels Better Than TypeScript

Okay, here’s where I’m going to lose some of you. TypeScript is objectively the right choice for modern JavaScript development. It has type safety, incredible tooling, massive community support, and it’s actively developed by Microsoft. CoffeeScript is essentially dead, with minimal updates and a dwindling community.

And yet… CoffeeScript still feels better to write.

Let me explain. TypeScript added types to JavaScript, which is fantastic. But it kept JavaScript’s verbose syntax. You still have curly braces everywhere, you still need semicolons (or don’t, which becomes its own debate), you still have visual noise.

Compare these:

TypeScript:

interface User {
  name: string;
  email: string;
  age: number;
}

class UserService {
  constructor(private apiClient: ApiClient) {}
  
  async getUser(id: string): Promise<User> {
    const response = await this.apiClient.get(`/users/${id}`);
    return response.data;
  }
  
  filterAdults(users: User[]): User[] {
    return users.filter((user) => user.age >= 18);
  }
}
Code language: JavaScript (javascript)

CoffeeScript:

class UserService
  constructor: (@apiClient) ->
  
  getUser: (id) ->
    response = await @apiClient.get "/users/#{id}"
    response.data
  
  filterAdults: (users) ->
    users.filter (user) -> user.age >= 18
Code language: CSS (css)

The CoffeeScript version is cleaner. There’s less visual noise, less ceremony. The @ symbol for instance properties is brilliant. It’s immediately obvious what’s a property and what’s a local variable. The implicit returns mean you’re not constantly writing return statements. The significant whitespace enforces good formatting.

Yes, TypeScript gives you type safety. That’s huge. But CoffeeScript gives you readability. And in my experience, readable code is maintainable code. I can glance at CoffeeScript and immediately understand what it’s doing. TypeScript requires more parsing, more mental overhead.

The list comprehensions in CoffeeScript are particularly beautiful:

# CoffeeScript
evenSquares = (n * n for n in numbers when n % 2 == 0)

# TypeScript
const evenSquares = numbers
  .filter((n) => n % 2 === 0)
  .map((n) => n * n);
Code language: PHP (php)

Both work, but the CoffeeScript version reads like English: “n squared for each n in numbers when n is even.” It’s declarative and expressive.

The Existential Operator: A Love Story

One of CoffeeScript’s best features was the existential operator (?). It was like optional chaining before optional chaining existed, but more powerful:

# Safe property access
name = user?.profile?.name

# Default values
speed = options?.speed ? 75

# Function existence check
callback?()

# Existence assignment
value ?= "default"
Code language: PHP (php)

That last one, ?=, was particularly great. It means “assign if the variable is null or undefined.” It’s cleaner than value = value || "default" and more correct (because it doesn’t overwrite falsy-but-valid values like 0 or "").

TypeScript eventually got optional chaining (?.) and nullish coalescing (??), which is great. But it took years, and CoffeeScript had it from the start.

What We Lost

When the JavaScript community moved from CoffeeScript to ES6 and then TypeScript, we gained a lot. Type safety, better tooling, standardization. But we also lost something.

We lost the joy of writing concise code. We lost the elegance of Ruby-inspired syntax. We lost the community that valued readability and expressiveness over completeness and type safety.

Modern JavaScript development often feels like you’re fighting the tools. Configuring TypeScript, setting up ESLint, configuring Prettier, choosing between competing libraries, debugging sourcemaps, dealing with module resolution issues. It’s powerful, but it’s exhausting.

With CoffeeScript and Rails, you just wrote code. The decisions were made for you. The tools were integrated. The conventions were clear. It was opinionated, and that was a feature, not a bug.

The Verdict

Would I start a new project with CoffeeScript and HAML today? Probably not. The ecosystem has moved on. TypeScript has won, and for most use cases, it’s the right choice. React and Vue have replaced server-rendered templates. The world has changed.

But do I miss it? Absolutely.

I miss the simplicity. I miss the elegance. I miss being able to write beautiful, concise code without worrying about types and interfaces and generics. I miss HAML’s clean markup and the way it forced you to write good HTML. I miss the Rails asset pipeline just working without configuration.

Most of all, I miss the developer experience of that era. We were moving fast, building things quickly, and having fun doing it. The code was readable, the stack was coherent, and everything felt like it fit together.

Maybe that’s just nostalgia talking. Maybe I’m romanticizing the past and forgetting the pain points. But when I look at that old CoffeeScript code, I don’t see technical debt. I see craft. I see code that was written with care, that values clarity over cleverness, that respects the reader’s time.

And honestly? I still think CoffeeScript’s syntax is better. TypeScript is more powerful, more practical, and more maintainable at scale. But CoffeeScript is more beautiful.

Sometimes, that matters too.


What are your thoughts? Did you work with CoffeeScript and HAML back in the day? Do you miss them, or are you glad we’ve moved on? Let me know in the comments or reach out on Twitter.

Artisanal Coding (職人コーディング): A Manifesto for the Next Era of Software Craftsmanship

Artesanal coding emphasizes the importance of craftsmanship in software development amidst the rise of AI and “vibe coding.” It advocates for intentional, quality-driven coding practices that foster deep understanding and connection to the code. By balancing AI assistance with craftsmanship, developers can preserve their skills and create sustainable, high-quality software.

In an age where code seems to write itself and AI promises to make every developer “10x faster,” something essential has quietly started to erode our craftsmanship. I call the counter-movement to this erosion artisanal coding.

Like artisanal bread or craft coffee, artisanal coding is not about nostalgia or resistance to progress. It’s about intentionality, quality, and soul; things that can’t be automated, templated, or generated in bulk. It’s the human touch in a field that’s rushing to outsource its own intuition.

What Is Artisanal Coding (職人コーディング)?

Artisanal coding is the conscious resistance to that decay.
It’s not anti-AI, it’s anti-carelessness. It’s the belief that the best code is still handmade, understood, and cared for.

Think of an artisan carpenter.
He can use power tools but he knows when to stop and sand by hand. He knows the wood, feels its resistance, and adjusts. He doesn’t mass-produce he perfects.

Artisanal coding applies that mindset to software. It’s about:

  • Understanding the problem before touching the code.
  • Writing it line by line, consciously.
  • Refactoring not because a tool says so, but because you feel the imbalance.
  • Learning from your errors instead of patching them away.

It’s slow. It’s deliberate. And that’s the point.

Artisanal coding is the deliberate act of writing software by hand, with care, precision, and understanding. It’s the opposite of what I call vibe coding the growing trend of throwing AI-generated snippets together, guided by vibes and autocomplete rather than comprehension.

This is not about rejecting tools it’s about rejecting the loss of mastery. It’s a mindset that values the slow process of creation, the small victories of debugging, and the satisfaction of knowing your code’s structure like a craftsman knows the grain of wood.

Why We Need Artisanal Coding

  1. We’re losing our muscle memory.
    Developers who rely too heavily on AI are forgetting how to solve problems from first principles. Code completion is helpful, but when it replaces thought, the skill atrophies.
  2. Code quality is declining behind pretty demos.
    Vibe coding produces software that “works” today but collapses tomorrow. Without deep understanding, we can’t reason about edge cases, performance, or scalability.
  3. We risk becoming code operators instead of creators.
    The satisfaction of crafting something elegant is replaced by prompt-tweaking and debugging alien code. Artisanal coding restores that connection between creator and creation.
  4. AI cannot feel the friction.
    Friction is good. The process of struggling through a bug teaches lessons that no autocomplete can. That frustration is where true craftsmanship is born.

The Role (and Limitations) of AI in Artisanal Coding

Artisanal coding doesn’t ban AI. It just defines healthy boundaries for its use.

Allowed AI usage:

  • Short code completions: Using AI to fill in a few lines of boilerplate or repetitive syntax.
  • Troubleshooting assistance: Asking AI or community-like queries outside the codebase similar to how you’d ask Stack Overflow or a mentor for advice.

🚫 Not allowed:

  • Generating entire functions or components without understanding them.
  • Using AI to “design” the logic of your app.
  • Copy-pasting large sections of unverified code.

AI can be your assistant, not your replacement. Think of it as a digital apprentice, not a co-author.


The Future Depends on How We Code Now

As we rush toward AI-assisted everything, we risk raising a generation of developers who can’t code without help. Artisanal coding is a statement of independence a call to slow down, think deeply, and keep your hands on the keyboard with intent.

Just as artisans revived craftsmanship in industries overtaken by automation, we can do the same in tech. The software we write today shapes the world we live in tomorrow. It deserves the same care as any other craft.

Artisanal coding is not a movement of the past, it’s a movement for the future.
Because even in the age of AI, quality still matters. Understanding still matters. Humans still matter.

If vibe coding is the fast food of software, artisanal coding is the slow-cooked meal: nourishing, deliberate, and made with care.
It takes more time, yes. But it’s worth every second.

Let’s bring back pride to our craft.
Let’s code like artisans again.

In many ways, artisanal coding echoes the Japanese philosophies of Shokunin [職人] (the pursuit of mastery through mindful repetition), Wabi-sabi [侘寂] (the acceptance of imperfection as beauty), and Kaizen [改善] (the quiet dedication to constant improvement). A true craftsperson doesn’t rush; they refine. They don’t chase perfection; they respect the process. Coding, like Japanese pottery or calligraphy, becomes an act of presence a meditative dialogue between the mind and the material. In a world driven by automation and speed, this spirit reminds us that the deepest satisfaction still comes from doing one thing well, by hand, with heart.

Final Thoughts

This post marks a turning point for me and for this blog.
I’ve spent decades building software, teams, and systems. I’ve seen tools come and go, frameworks rise and fade. But never before have we faced a transformation this deep one that challenges not just how we code, but why we code.

Artisanal coding is my response.
From this point forward, everything I write here every essay, every reflection will revolve around this principle: building software with intention, understanding, and care.

This isn’t just about programming.
It’s about reclaiming craftsmanship in a world addicted to shortcuts.
It’s about creating something lasting in an era of instant everything.
It’s about remembering that the hands still matter.

“職人コーディング – Writing software with heart, precision, and purpose.”

The AI Detox Movement: Why Engineers Are Taking Back Their Code

In 2025, AI tools transformed coding but led developers to struggle with debugging and understanding their code. This sparked the concept of “AI detox,” a period where developers intentionally stop using AI to regain coding intuition and problem-solving skills. A structured detox can improve comprehension, debugging, and creativity, fostering a healthier relationship with AI.

The New Reality of Coding in 2025

Over the last year, something remarkable happened in the world of software engineering.

AI coding tools Cursor, GitHub Copilot, Cody, Devin became not just sidekicks, but full collaborators. Autocomplete turned into full functions, boilerplate became one-liners, and codebases that once took weeks to scaffold could now appear in minutes.

It felt like magic.

Developers were shipping faster than ever. Teams were hitting deadlines early. Startups were bragging about “AI-assisted velocity.”

But behind that rush of productivity, something else began to emerge a quiet, growing discomfort.


The Moment the Magic Fades

After months of coding with AI, many developers hit the same wall.
They could ship fast, but they couldn’t debug fast.

When production went down, it became painfully clear: they didn’t truly understand the codebase they were maintaining.

A backend engineer told me bluntly:

“Cursor wrote the service architecture. I just glued things together. When it broke, I realized I had no idea how it even worked.”

AI wasn’t writing bad code it was writing opaque code.
Readable but not intuitive. Efficient but alien.

This is how the term AI detox started spreading in engineering circles developers deliberately turning AI off to reconnect with the craft they’d begun to lose touch with.


What Is an AI Detox?

An AI detox is a deliberate break from code generation tools like Copilot, ChatGPT, or Cursor to rebuild your programming intuition, mental sharpness, and problem-solving confidence.

It doesn’t mean rejecting AI altogether.
It’s about recalibrating your relationship with it.

Just as a fitness enthusiast might cycle off supplements to let their body reset, engineers are cycling off AI to let their brain do the heavy lifting again.


Why AI Detox Matters

The longer you outsource cognitive effort to AI, the more your engineering instincts fade.
Here’s what AI-heavy coders have reported after several months of nonstop use:

  • Reduced understanding of code structure and design choices.
  • Slower debugging, especially in unfamiliar parts of the codebase.
  • Weaker recall of language and framework features.
  • Overreliance on generated snippets that “just work” without deeper understanding.
  • Loss of flow, because coding became about prompting rather than creating.

You might still be productive but you’re no longer learning.
You’re maintaining an illusion of mastery.


The Benefits of an AI Detox

After even a short AI-free period, developers often notice a profound change in how they think and code:

  • Deeper comprehension: You start to see the architecture again.
  • Better debugging: You can trace logic without guesswork.
  • Sharper recall: Syntax, libraries, and idioms return to muscle memory.
  • Creative problem solving: You find better solutions instead of the first thing AI offers.
  • Reconnection with craftsmanship: You take pride in code that reflects your thought process.

As one engineer put it:

“After a week without Cursor, I remembered how satisfying it is to actually solve something myself.”


How to Plan Your AI Detox (Step-by-Step Guide)

You don’t need to quit cold turkey forever.
A structured plan helps you recoup your skills while keeping your work flowing.

Here’s how to do it effectively:


Step 1: Define Your Motivation

Start by asking:

  • What do I want to regain?
  • Is it confidence? Speed? Understanding?
  • Do I want to rebuild my debugging skills or architectural sense?

Write it down. Clarity gives your detox purpose and prevents you from quitting halfway.


Step 2: Choose Your Detox Duration

Different goals require different lengths:

Detox LevelDurationBest For
Mini-detox3 daysA quick reset and self-check
Weekly detox1 full weekRebuilding confidence and recall
Extended detox2–4 weeksDeep retraining of fundamentals

If you’re working on a production project, start with a hybrid approach:
AI-free mornings, AI-assisted afternoons.


Step 3: Set Clear Rules

Be explicit about what’s allowed and what’s not.

Example rules:

✅ Allowed:

  • Using AI for documentation lookups
  • Reading AI explanations for existing code
  • Asking conceptual questions (“How does event sourcing work?”)

❌ Not allowed:

  • Code generation (functions, modules, tests, migrations)
  • AI refactors or architecture design
  • Using AI to debug instead of reasoning it out yourself

The stricter the rule set, the greater the benefit.


Step 4: Pick a Suitable Project

Choose something that forces you to think but won’t jeopardize production deadlines.

Good choices:

  • Refactor an internal service manually.
  • Build a small CLI or API from scratch.
  • Rewrite a module in a different language (e.g., Ruby → Rust).
  • Add integration tests by hand.

Bad choices:

  • Complex greenfield features with high delivery pressure.
  • Anything that will make your manager panic if it takes longer.

The goal is to practice thinking, not to grind deadlines.


Step 5: Journal Your Learning

Keep a daily log of what you discover:

  • What took longer than expected?
  • What concepts surprised you?
  • What patterns do you now see more clearly?
  • Which parts of the language felt rusty?

At the end of the detox, you’ll have a personal reflection guide a snapshot of how your brain reconnected with the craft.


Step 6: Gradually Reintroduce AI (With Boundaries)

After your detox, it’s time to reintroduce AI intentionally.

Here’s how to keep your skills sharp while benefiting from AI assistance:

Use CaseAI Usage
Boilerplate✅ Yes (setup, configs, tests)
Core logic⚠️ Only for brainstorming or reviewing
Debugging✅ For hints, but reason manually first
Architecture✅ As a sounding board, not a decision-maker

You’ll quickly find a balance where AI becomes an amplifier not a crutch.


Example AI-Detox Schedule (4-Week Plan)

Here’s a simple structure to follow:

Week 1 – Awareness

  • Turn off AI for 3 days.
  • Focus on small, isolated tasks.
  • Note moments where you instinctively reach for AI.

Goal: Realize how often you rely on it.


Week 2 – Manual Mastery

  • Full AI-free week.
  • Rebuild a module manually.
  • Write comments before coding.
  • Practice debugging from logs and stack traces.

Goal: Relearn problem-solving depth.


Week 3 – Independent Architecture

  • Design and code a feature without any AI input.
  • Document design decisions manually.
  • Refactor and test it by hand.

Goal: Restore confidence in end-to-end ownership.


Week 4 – Rebalance

  • Reintroduce AI, but only for non-critical parts.
  • Review old AI-generated code and rewrite one section by hand.
  • Evaluate your improvement.

Goal: Reclaim control. Let AI assist, not lead.


Practical Tips to Make It Work

  • Disable AI in your editor: Don’t rely on willpower remove temptation.
  • Pair program with another human: It recreates the reasoning process that AI shortcuts.
  • Keep a “questions log”: Every time you’re tempted to ask AI something, write it down. Research it manually later.
  • Revisit fundamentals: Review algorithms, frameworks, or patterns you haven’t touched in years.
  • Read real code: Open-source repositories are the best detox material real logic, real humans.

The Mindset Behind the Detox

The purpose of an AI detox isn’t to prove you can code without AI.
It’s to remember why you code in the first place.

Good engineering is about understanding, design, trade-offs, and problem-solving.
AI tools are brilliant at generating text but you are the one making decisions.

The best developers I know use AI with intent. They use it to:

  • Eliminate repetition.
  • Accelerate boilerplate.
  • Explore ideas.

But they write, refactor, and debug the hard parts themselves because that’s where mastery lives.


The Future Is Balanced

AI isn’t going away. It’s evolving faster than any tool in tech history.
But if you want to stay valuable as a developer, you need to own your code, not just generate it.

The engineers who thrive over the next decade will be those who:

  • Think independently.
  • Understand systems deeply.
  • Use AI strategically, not passively.
  • Keep their fundamentals alive through intentional detox cycles.

AI is a force multiplier not a replacement for your mind.


So take a week. Turn it off.
Write something from scratch.
Struggle a little. Think a lot.
Reignite the joy of building with your own hands.

When you turn the AI back on, you’ll see it differently not as your replacement, but as your apprentice.