TIL.

Things I didn’t know yesterday but thought were worth sharing today.

  1. The browser console’s hidden helpers

    The DevTools console comes with a handful of built-in helpers. The one I’ve found most useful is $0, which references the currently inspected element. In Chromium-based browsers, the four previously inspected elements are also available as $1$4, making it easy to jump among them without repeatedly calling document.querySelector().

    A few other shortcuts are surprisingly handy, too:

    • $_ — the result of the previous expression
    • $(selector) — shortcut for document.querySelector(selector)
    • $(selector) — shortcut for document.querySelectorAll(selector)
    • $x(path) — evaluates an XPath expression and returns matching elements

    The selector helpers accept an optional second argument—a startNode to search within instead of the entire document. Since they return DOM nodes, they compose nicely: $("li", $("ul")) returns all list items within the first unordered list, and $("button", $0) finds a button within the currently inspected element.

    View "The browser console’s hidden helpers"
  2. Bah, heisenbug!

    A heisenbug is a software bug that seems to disappear or alter its behavior the moment you try to study it. Sometimes adding a log statement, stepping through a debugger, or making an unrelated code change is enough to make the problem vanish.

    The name puns on Werner Heisenberg’s uncertainty principle—the rule in quantum mechanics that certain pairs of physical properties, such as a particle’s position and momentum, cannot both be known precisely at once. It’s also a reference to the observer effect, which states that the act of measuring a system inevitably disturbs it. So it is with a heisenbug—the moment you try to observe it, you change the conditions that produced it, and the bug slips away.

    View "Bah, heisenbug!"
  3. Cleaner conditional CSS classes with Rails’ class_names

    class_names (Rails 6.1+, from ActionView::Helpers::TagHelper) builds CSS class lists from strings, arrays, and hashes: hash keys are class names, and their values decide whether each is included. It’s an alias of token_list, so use whichever reads better to you.

    Instead of cramming conditionals into a string, you pass a hash:

    # Before: the pattern you see in a lot of Rails apps
    "nav-link #{active ? "nav-link-active" : ""}"
    # => "nav-link nav-link-active"  (when active is true)
    # => "nav-link "                 (when active is false) — note the trailing space
    
    # After:
    class_names("nav-link", "nav-link-active": active)
    # => "nav-link nav-link-active"  (when active is true)
    # => "nav-link"                  (when active is false) — no trailing space
    

    And the best part? Rails’ tag builder applies the same logic to any class: option, so tag-producing helpers—tag, content_tag, link_to, button_to, and friends—accept conditional hashes for free. Here’s a real example from JobJournal’s _filter_dropdown partial:

    link_to "...", class: [ "dropdown-option", "md:hidden": link[:mobile_only] ]
    
    View "Cleaner conditional CSS classes with Rails’ class_names"
  4. Humans are surprisingly sensitive to audio-visual lag

    Our senses forgive a late sound far more readily than an early one. Sound arriving before its image unsettles us; sound arriving slightly after barely registers. Most people start to notice the mismatch when audio leads the image by roughly 20–50 milliseconds—yet audio can lag as much as 80–150 milliseconds behind the image before it’s equally jarring. That’s roughly three times the tolerance in the other direction.

    The asymmetry comes from how the brain assembles a “moment”. It doesn’t judge each sound and image independently; it briefly holds signals from the eyes and ears, fusing those that arrive close together into a single perceived event. Inside that window, everything feels perfectly in sync, and a small audio delay goes unnoticed. Push past that window and the illusion breaks.

    This bias runs one way by design: the brain is calibrated to a world where sound always arrives a step behind. Light travels 874,000 times faster than sound—about 300 million m/s versus 343 m/s in air—so we always see an event before we hear it. Thunder trails lightning; the bang of a distant firework follows its flash. Sound arriving late is the pattern our senses already expect.

    View "Humans are surprisingly sensitive to audio-visual lag"
  5. Overmind: A better foreman

    I’ve used Foreman for years to run Rails applications from a Procfile, but recently switched to Overmind.

    Overmind is largely compatible with Foreman, so most existing Procfile-based setups work without modification:

    brew install tmux overmind
    overmind start
    

    The main advantage I’ve found is debugging. Since Overmind runs each process in its own tmux pane, you can connect directly to a process when needed:

    overmind connect web
    

    This is particularly useful when a Rails process hits a debugger breakpoint.

    A few other commands I’ve found useful:

    overmind restart
    overmind quit
    

    Overmind also provides single-letter aliases for many commands:

    overmind s      # start
    overmind r      # restart
    overmind c web  # connect
    overmind q      # quit
    

    I also added an om shell alias for overmind to make the commands even quicker:

    om s
    om r
    om c web
    om q
    

    For Rails applications that already use foreman start, replacing Foreman with Overmind has been mostly seamless.

    View "Overmind: A better foreman"
  6. Case-insensitive string comparison in Ruby

    You can’t call yourself a programmer if you’ve never needed to compare strings without regard to case. Most Ruby developers (myself included) reach for String#downcase to normalize both strings before comparing:

    string.downcase == another_string.downcase  # => true
    

    It works, but Ruby already has built-in methods made for exactly this purpose: String#casecmp and String#casecmp?.

    If you just need a boolean, casecmp? is the cleanest option:

    "image".casecmp?("IMAGE")  # => true
    "image".casecmp?("video")  # => false
    

    casecmp behaves similarly, but returns comparison values like the spaceship operator (<=>):

    "image".casecmp("IMAGE")   # => 0
    "apple".casecmp("banana")  # => -1
    "zebra".casecmp("apple")   # => 1
    

    They’re more expressive than manually normalizing case and avoid allocating a lowercase copy of the string. A small Ruby feature, but a nice one to know.

    View "Case-insensitive string comparison in Ruby"
  7. Oh My Zsh’s take command

    The Oh My Zsh take command is basically a shortcut for “set something up and immediately cd into it.” At its simplest, it wraps mkdir -p + cd, but it extends the same idea to git repositories and remote archives.

    For local work, it behaves like a smarter mkdir that keeps you in context. So instead of creating a folder and then moving into it manually, you just do:

    take my/new/project
    

    This creates the full path if needed and drops you into the final directory.

    It also supports git repositories. If you pass a git URL, it will clone the repo and immediately enter it:

    take [email protected]:rails/rails.git
    

    You end up inside the cloned repository right away, which removes the usual “git clonecd” sequence. It’s especially useful when quickly inspecting or experimenting with a repo.

    There’s similar handling for remote archives. If the argument looks like a .tar.gz, .tar.xz, or similar tarball, it will download it, extract it, and then move into the extracted directory:

    take https://example.com/project.tar.gz
    

    This is basically a “download source distribution and enter workspace” shortcut, assuming the archive has a sensible top-level directory structure.

    ZIP files are handled similarly. A ZIP URL gets downloaded and unzipped into the current directory, and take then tries to infer the extracted root folder and cd into it:

    take https://example.com/project.zip
    

    This one is a bit more heuristic than the tar handling, since ZIP archives are less consistent in how they structure top-level directories.


    Under the hood, take is just a dispatcher that decides which of these behaviors to trigger based on the input pattern: local paths go to mkcd / takedir, git URLs go to takegit, tar archives go to takeurl, and zip archives go to takezip. The consistent idea across all of them is eliminating the repeated setup steps, so every operation ends with you already inside the working directory.

    View "Oh My Zsh’s take command"
  8. Imaginary and complex numbers in Ruby

    Ruby has built-in support for complex numbers via the Complex class. It’s a small feature, but it makes working with imaginary numbers feel surprisingly natural.

    You can create complex numbers explicitly using the Complex constructor. This is the most direct and readable approach when both real and imaginary parts are variables or come from calculations.

    Complex(2, 3)  # => (2+3i)
    

    Ruby also supports a literal syntax using the i suffix, which makes imaginary numbers feel first-class in the language. This is often the most convenient form when writing quick expressions or math-like code.

    2 + 3i  # => (2+3i)
    

    Once created, complex numbers behave like numeric types and support standard arithmetic operations out of the box. Addition, subtraction, multiplication, and division all follow mathematical rules without needing any special handling.

    # math with Complex numbers is easy
    (2 + 3i) + (1 - 2i)  # => (3+1i)
    (2 + 3i) - (1 - 2i)  # => (1+5i)
    (2 + 3i) * (1 - 2i)  # => (8-1i)
    (2 + 3i) / (1 - 2i)  # => ((-4/5)+(7/5)*i)
    
    # works with Integers and Floats too
    (2 + 3i) + 5         # => (7+3i)
    (2 + 3i) - 1.5       # => (0.5+3i)
    (2 + 3i) * 0.5       # => (1.0+1.5i)
    (2 + 3i) / 2         # => (1+(3/2)*i)
    

    Ruby also includes some useful methods for switching between rectangular and polar representations of a complex number.

    Calling polar on a complex number returns an array containing the magnitude (distance from the origin) and angle in radians, effectively decomposing the complex number into its polar coordinates:

    z = 2i  # shorter form of z = 0 + 2i
    z.polar
    # => [2, 1.5707963267948966]
    

    If you want to go the other direction, Complex.polar constructs a complex number from a magnitude and angle:

    z = Complex.polar(2, Math::PI / 2)
    # => (0.0+2i)
    

    These two methods are essentially mirrors of each other:

    • z.polar decomposes a complex number into [magnitude, angle]
    • Complex.polar builds a complex number from [magnitude, angle]
    z = 2i
    magnitude, angle = z.polar
    Complex.polar(magnitude, angle)  # => (0.0+2i)
    

    You can also get the rectangular coordinates of a complex number using rectangular (or its shorter alias, rect), which returns the real and imaginary parts as an array:

    z = Complex.polar(2, Math::PI / 2)
    z.rect
    # => [0.0, 2]
    

    For the record, Ruby also exposes Complex.rectangular (along with its alias, Complex.rect) for constructing complex numbers from rectangular coordinates, but these are really just explicit versions of the default Complex(real, imaginary) constructor:

    Complex(2, 3)              # => (2+3i)
    Complex.rectangular(2, 3)  # => (2+3i)
    Complex.rect(2, 3)         # => (2+3i)
    

    The Complex class is part of Ruby’s core library, so no external dependencies are required.

    View "Imaginary and complex numbers in Ruby"
  9. Ruby’s % notation is more powerful than you think

    I already knew the basics—%w for word arrays, %i for symbol arrays, %q/%Q for strings, %r for regexps—but there’s more to the % notation than I realized.

    %() defaults to interpolated strings

    %Q is the interpolated string literal, but you can drop the Q entirely. %() is equivalent to %Q(), which is equivalent to double-quoted strings:

    path = "/some/path"
    %(filename="#{path}")
    # => "filename=\"/some/path\""
    

    This is great when your string contains quotes and you don’t want to escape them.

    Capital letters = interpolation

    The lowercase/uppercase pattern is consistent: lowercase is non-interpolated, uppercase is interpolated.

    language = "Ruby"
    
    # Non-interpolated (lowercase)
    %w[hello #{language}]   # => ["hello", "\#{language}"]
    %i[hello #{language}]   # => [:hello, :"\#{language}"]
    
    # Interpolated (uppercase)
    %W[hello #{language}]   # => ["hello", "Ruby"]
    %I[hello #{language}]   # => [:hello, :Ruby]
    

    %x for shell commands

    %x works like backticks but with the % delimiter flexibility:

    %x(echo "hello from the shell")
    # => "hello from the shell\n"
    

    %s for non-interpolated symbols

    %s(hello world)   # => :"hello world"
    %s(#{nope})       # => :"\#{nope}"
    

    Any non-alphanumeric character works as a delimiter

    I’d only ever used (), [], {}, ||, and <>, but you can use any single non-alphanumeric character—even whitespace:

    %w#foo bar#  # => ["foo", "bar"]  (array of words with hashes as delimiters)
    %i!foo bar!  # => [:foo, :bar]    (array of symbols with exclamation marks as delimiters)
    %r.foo bar.  # => /foo bar/       (regex with periods as delimiters)
    % test       # => "test"          (string with spaces as delimiters)
    % foo\ bar   # => "foo bar"       (string with spaces as delimiters, with an escaped space inside—please don't)
    

    Paired delimiters allow unescaped nesting

    If you use (), [], {}, or <>, you can include those same characters unescaped inside the literal—as long as they appear in balanced pairs:

    %(string (syntax) is pretty flexible)
    # => "string (syntax) is pretty flexible"
    
    %w[one [two three] four]
    # => ["one", "[two", "three]", "four"]
    
    View "Ruby’s % notation is more powerful than you think"
  10. Ruby’s gsub accepts a block via &: shorthand syntax

    You may already know you can pass a block to gsub like this:

    "the html spec and some css tricks".gsub(/\b(html|css)\b/) { it.upcase }
    # => "the HTML spec and some CSS tricks"
    

    But you can make this even simpler by using the &: shorthand syntax:

    "the html spec and some css tricks".gsub(/\b(html|css)\b/, &:upcase)
    # => "the HTML spec and some CSS tricks"
    

    Isn’t Ruby just beautiful?

    View "Ruby’s gsub accepts a block via &: shorthand syntax"
  11. Ruby allows passing a Hash to gsub

    Instead of chaining multiple gsub calls, you can pass a hash as the replacement. Each match is replaced by its corresponding value (or removed if no key exists).

    Say you’re normalizing “smart” punctuation into plain ASCII. (This is a job tr can’t do: an em dash becomes two characters.)

    text = "He said — “that’s ‘fine’…”"
    
    # Before:
    text.gsub("“", '"').gsub("”", '"').gsub("‘", "'").gsub("’", "'").gsub("—", "--")
    # => "He said -- \"that's 'fine'…\""
    
    # After:
    replacements = { "“" => '"', "”" => '"', "‘" => "'", "’" => "'", "—" => "--" }
    text.gsub(/[“”‘’—]/, replacements)
    # => "He said -- \"that's 'fine'…\""
    
    # Matches with no corresponding key are removed (here, the ellipsis):
    text.gsub(/[“”‘’—…]/, replacements)
    # => "He said -- \"that's 'fine'\""
    

    You can also define the hash inline. In typical Ruby fashion, the outer {} can be omitted:

    text.gsub(/[“”]/, { "“" => '"', "”" => '"' })
    text.gsub(/[“”]/, "“" => '"', "”" => '"')
    

    Cleaner, more readable, and more performant than chaining multiple gsub calls.

    View "Ruby allows passing a Hash to gsub"
  12. Subtle dark-mode surfaces with color-mix()

    The blog post cards on this site need a card background that’s a touch lighter than the page in dark mode, but plain white in light mode. Instead of defining a third color variable, I mix the existing --color-surface halfway into --color-bg in the dark slot of light-dark():

    .blog-post-card {
      background: light-dark(
        var(--color-bg),
        color-mix(in oklch, var(--color-surface) 50%, var(--color-bg))
      );
    }
    

    The in oklch interpolation keeps the perceived lightness even, so the card reads as “a step up from the page” without ever looking muddy. Change the theme’s surface or background tokens and every card adjusts for free.

    View "Subtle dark-mode surfaces with color-mix()"