MicroPosts

  • AI bias

    Comment in a PR; "I use do end but I've missed that here due to Cursor Al generating this code"

    I shared in Why Good Solutions Block Better Ones why the first idea that comes to your mind may make you ignore other ones.

    And here we are!

  • Dialog with Rails & Hotwire

    Clicking on Invite User opens a dialog that contains a form and an X button in the top-right corner to dismiss it

    At the end of <body>:

    <dialog data-controller="modal-dialog" class="tw-w-[calc(100%-0.5rem)] tw-max-w-xl tw-rounded-lg">
      <div class="tw-p-8">
        <button
          data-action="click->modal-dialog#close"
          class="tw-absolute tw-top-6 tw-right-8 tw-text-5xl tw-opacity-40 hover:tw-opacity-90 tw-select-none"
        >&times;</button>
    
        <%= turbo_frame_tag 'modal_dialog', data: { action: 'turbo:frame-load->modal-dialog#open' } %>
      </div>
    </dialog>
    

    Stimulus controller:

    import { Controller } from "@hotwired/stimulus";
    
    export default class extends Controller {
      connect() {
        this.element.addEventListener("close", () => {
          this.element.classList.remove('open')
        });
      }
    
      disconnect() {
        this.close();
      }
    
      open() {
        this.element.showModal();
        this.element.classList.add('open')
      }
    
      close() {
        this.element.close();
        this.element.classList.remove('open')
      }
    }
    

    CSS:

    // This scrolls the page to the top, which is a problem if the
    // user opens the dialog not at the top of the page.
    //
    // The following would work but causes an horizontal layout
    // shift when the scrollbar disappears:
    //   .no-scroll {
    //     overflow: hidden;
    //   }
    //
    // Keep an eye on the following thread for better solutions:
    // https://github.com/whatwg/html/issues/7732
    body:has(dialog[open]) {
      overflow-y: scroll;
      position: fixed;
      width: 100%;
    }
    
    dialog {
      opacity: 0;
      transition: opacity 0.3s ease-out;
      box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.8);
    }
    
    dialog.open {
      opacity: 1;
    }
    
    dialog::backdrop {
      display: none;
    }
    

    Button that opens the dialog:

    <%= link_to(
      fa_icon('plus', text: 'Invite user'),
      new_dashboard_organization_team_member_path(@organization),
      class: '!tw-py-2 !tw-px-3 !tw-text-base btn btn-default',
      data: { turbo: true, turbo_frame: 'modal_dialog' }
    ) %>
    

    Form that gets rendered into the dialog:

    <div class="container tw-py-20">
      <div class="tw-max-w-xl">
        <p class="text-muted">
          <span data-toggle="tooltip" title="Organization name"><%= @organization.name %></span> / <%= link_to 'Team members', dashboard_organization_team_members_path(@organization) %>
        </p>
    
        <%= turbo_frame_tag 'modal_dialog' do %>
          <h1 class="tw-m-0 tw-mb-6">Invite team member</h1>
    
          <%= render partial: 'form', locals: {
            team_member_form: @team_member_form,
            form_method: :post,
              action_url: dashboard_organization_team_members_path,
              submit_value: 'Invite'
            } %>
        <% end %>
      </div>
    </div>
    

    Controller:

    def create
      team_member_form = TeamMemberForm::NewForm.new(new_team_member_params.merge(organization: organization, inviter_email: current_user.email))
    
      if team_member_form.save
        flash[:success] = 'The user has been added to the team members.'
    
        respond_to do |format|
          format.turbo_stream do
            render turbo_stream: turbo_stream.action(:redirect, dashboard_organization_team_members_path(organization))
          end
          format.html do
            redirect_to dashboard_organization_team_members_path(organization)
          end
        end
      else
        @team_member_form = team_member_form
        render :new
      end
    end
    

    Redirect:

    // Workaround for redirecting from within a Turbo Frame:
    // https://github.com/hotwired/turbo-rails/pull/367
    Turbo.StreamActions.redirect = function () {
      Turbo.visit(this.target);
    };
    
  • endoflife.date

    End-of-life (EOL) and support information is often hard to track, or very badly presented. endoflife.date documents EOL dates and support lifecycles for various products.

    Example:

    Need to check what's the latest version of Ruby? Or until when it's supported?

    endoflife.date/ruby

  • stdout vs stderr

    Use stdout for the output of your program: the log in git log, the matching files with find, or the list from ls.

    Use stderr for errors AND ANYTHING ELSE: the progress messages, the warns/info, and the jokes (if you really need to).

    For example, given the following output (on stdout):

    I, [2024-12-10T18:59:45.134882 #7388]  INFO -- : Crystalball starts to glow...
    W, [2024-12-10T18:59:45.138463 #7388]  WARN -- : Maps are outdated!
    I, [2024-12-10T18:59:45.311953 #7388]  INFO -- : Starting RSpec.
    {"version":"3.13.2","messages":["No examples found."],"seed":41594,"examples":[],"summary":{"duration":6e-05,"example_count":0,"failure_count":0,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"0 examples, 0 failures"}
    

    I had to remove manually the first lines to parse the JSON:

    command | tail -n1 | jq
    
  • Ruby: default gems

    Ruby's stdlib is composed of 3 different elements:

    • Standard libraries that are embedded in the language's source
    • Default gems that can be required, are maintained by the core team, and cannot be removed
    • Bundled gems that come installed with Ruby and can be removed

    Full list on stdgems.org.

  • Ruby gem activation

    This is how you use a Ruby gem usually:

    require 'foo'
    Foo.bar
    

    For the require to work, the gem needs to be available on the LOAD_PATH:

    ruby -e 'puts $LOAD_PATH'
    

    The act of adding a gem to the LOAD_PATH is called "activating a gem".

    In a vanilla Ruby situation, you can add a directory to the LOAD_PATH with:

    ruby -I ./foo/lib -e "puts(require 'foo')"
    # true
    

    More often, you deal with a bundle that contains a Gemfile:

    gem 'foo'
    

    To activate the gems from the Gemfile (taking into account the versions from Gemfile.lock):

    require 'bundler/setup'
    require 'foo'
    

    or (equivalent):

    require 'bundler'
    Bundler.setup
    require 'foo'
    

    Notice that the setup step will first clear the LOAD_PATH and activate only the gems in the bundle.

    For example, Rails does it here and here.

    If you've ever wondered what the hell is up with bundle exec ..., the answer is simple: Bundler activates the gems in the bundle with something like

    RUBYOPT="-rbundler/setup" ruby ...
    

    Of course, things are more complex than that. But this should give you a starting point.

    Bundler: How to use Bundler with Ruby

  • Hi, thanks, bye

    When travelling, strive to learn how to say three words: hi, thanks, bye.

    You'll be surprised of what happens when you stop being "just another tourist."

  • Convert RDA to CSV with a script

    Throw the following into rda2csv.r:

    #!/usr/bin/env Rscript
    
    argv <- commandArgs(TRUE)
    inFile <- toString(argv[1])
    print(paste("Reading:", inFile))
    
    outFile <- gsub(".rda$", ".csv", inFile)
    print(paste("Writing:", outFile))
    
    inData <- get(load(inFile))
    write.csv(inData, file=outFile)
    

    Make it executable: chmod +x rda2csv.r

    And run it on an RDA file: ./rda2csv.r path/to/file.rda

  • Risking millions on coin flips

    Using the Binomial Distribution to tilt the odds in your favor.

    I found this pearl in Naked Statistics (chapter 5):

    In 1981, the Joseph Schlitz Brewing Company spent $1.7 million for what appeared to be a shockingly bold and risky marketing campaign for its flagging brand, Schlitz. At halftime of the Super Bowl, in front of 100 million people around the world, the company broadcast a live taste test pitting Schlitz Beer against a key competitor, Michelob. Bolder yet, the company did not pick random beer drinkers to evaluate the two beers; it picked 100 Michelob drinkers.

    It turns out, Schlitz's marketing department was not a bunch of idiots. And here's why.

    If you toss a coin twice you can get either one of the following:

    1. heads, heads
    2. tails, tails
    3. heads, tails
    4. tails, heads

    Therefore, the probability of getting one tails out of two flips is 2/4 (ie, case 3. and 4.).

    That is described by the Binomial Distribution where n is the number of trials (coins) and k is the number of successes (tails):

    p = n! / [k! * (n - k)!] * (1/2)^n
        2! / [1! * (2 - 1)!] * (1/2)^2
        2  / 1               *  1/4
        1/2 (same as 2/4 from above)
    

    The statisticians at Schlitz considered that:

    • commercial beers taste the same
    • in a blind tasting, a Michelob drinker would say that Schlitz tastes better 50% of the times (it's a coin flip)
    • getting 40% or more of Michelob drinkers to say Schlitz is better is worth $1.7 million

    But what's the probability?

    If you take 10 Michelob drinkers, you need at least 4 successes. In other words, you want to sum the probability of 4, 5, 6, 7, 8, 9, and 10 tails with coin flips:

    p = 10! / [4! * (10 - 4)!] * (1/2)^10 +
        10! / [5! * (10 - 5)!] * (1/2)^10 +
        ...
        10! / [10! * (10 - 10)!] * (1/2)^10
      = 0,82
    

    So there's a 82% chance of succeeding.

    And if you raise the number of drinkers to 100, you get to a shocking 98%!

  • The "opposite" test

    When "positioning" something, if the opposite of what you claim is bullshit, what you claim is bullshit.

    If you've ever heard any of the following statements:

    • Our company is customer-first
    • Our product is fast & easy-to-use

    Ask yourself:

    • Would any company pick customer-last as a strategy?
    • Would any product go for slow & hard-to-use?

    Nope. Because that's bullshit!

  • How big things get done

    How big things get done published the following breakdown:

    • 100% of the mega-projects analyzed
    • 47.9% finished on budget
    • 8.5% finished on bugdet and time
    • 0.5% finished on budget, time, and produced the expected benefits

    Later, the book analyzes what are the prerequisites to get into the 0.5% Olympus:

    • Know why all the time: is this code rewrite benefitting the product?
    • Think slow, act fast:
      • Make mistakes in the planning phase when screw-ups are cheap
      • The more time the execution phase takes, the more you expose yourself to black swans (ie, catastrophic and unexpected events)
    • Mitigate risks:
      • Spike code to understand if and how something can be done
      • Ask people who worked on similar features what went wrong/could have been done better
    • Estimate using anchors:
      • Hofstadter’s Law: “It always takes longer than you expect, even when you take into account Hofstadter’s Law.”
      • We always estimate with the happy case in mind and, even the most paranoid, cannot prepare for unknown unknowns
      • Ask others what was their experience building a similar thing and use their numbers as a starting point
    • Lean on experience:
      • Involve people who've worked on similar features in the past
      • Learn from other similar projects—and no, you are not working on anything so unique that cannot draw from experience
      • Use boring tech: "remove the words custom and bespoke from your vocabulary"
    • Build a team
    • Iterate:
      • "we're terrible at getting things right the first time"
      • "I have not failed ten thousand times," Thomas Edison said. "I’ve successfully found ten thousand ways that will not work."
      • "If something works, you keep it in the plan. If it doesn’t, you “fail fast,"
      • When talking about the success of the Empire State Building, "workers didn’t build one 102-story building, they built 102 one-story buildings."

    The following sentence made me stop and think about my fuck-ups:

    When delivery fails, efforts to figure out why tend to focus exclusively on delivery. That’s understandable, but it’s a mistake, because the root cause of why delivery fails often lies outside delivery, in forecasting, years before delivery was even begun.

  • Learning requires sweating

    Learning is not supposed to be fun. It doesn't have to be actively not fun either, but the primary feeling should be that of effort.

    If you are not sweating, it's entertainment, not learning.

    Source: Andrej Karpathy on X

  • Horizontally scroll to center an element

    <div class="width-full overflow-x-auto">
      <div>...</div>
      <div>element</div>
      <div>...</div>
    </div>
    
    const viewportWidth =
      Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
    
    container.scrollLeft =
      element.offsetLeft // scroll to the left edge of element
      + element.offsetWidth / 2 // scroll to center of element
      - viewportWidth / 2 // scroll back half the viewport
    
  • Comment your regular expressions

    # ❌
    usa_postal_code_pattern = /\A\d{5}(-\d{4})?\z/
    
    # ✅
    usa_postal_code_pattern = %r{
      \A # Beginning of string
      \d{5} # 5 digits
      ( # ZIP+4
        - # Hyphen
        \d{4} # 4 digits
      )? # ZIP+4 is optional
      \z # End of string
    }x
    

    Source: Comment your regular expressions

  • Dreamweaving unforgettable experiences

    Will Guidara hires Dreamweavers in his restaurant.

    Their job is to pull legends: creating, automating, and scaling unforgettable experiences.

    Source: The Hospitality Solution in Your Business

  • The 95/5 rule

    Balancing out what's best for the user vs what's best for the business

    Every decision I made seemed to expose the natural tensions between improving the quality of the experience the guests were having and doing what was best for the business. Restaurant-smart meant leading with trust—including allowing the people who worked for me to do what they felt was best for the guests. Corporate-smart meant running a tight ship. Which was right?

    Will Guidara's answers his own question:

    Manage 95% of your financials down to the penny while spending the remaining 5% “foolishly”, and “splurging” the 5% on the guest experience to create an unforgettable experience.

    Source: Will Guidara: Restaurant Smart vs Corporate Smart

  • Find unused indexes in Postgres

    How to use the `pg_stat_user_indexes` table

    SELECT * FROM pg_stat_user_indexes WHERE relname = 'TABLE_NAME':

    • idx_scan: how many times the index was used
    • idx_tup_read: how many index entries were returned using the index
    • idx_tup_fetch: how many rows were returned using the index

    You want:

    • idx_scan to move up (ie, the index is used)
    • idx_tup_read/idx_tup_fetch to move up but not too much (ie, the index is highly selective)

    Source: sgerogia.github.io/Postgres-Index-And-Queries/

  • How to find content ideas

    A cheap trick for unlimited inspiration

    1. Find a relevant subreddit
    2. Google: "how to" site:reddit.com/r/SUBREDDIT/

    Here are the top results for "how to" site:https://www.reddit.com/r/javascript/:

    • "How to actually Learn from the Documentation?"
    • "How do I practice JavaScript?"
    • "How did you learn Javascript?"
    • "How to become a more well rounded dev"
    • "How to develop android apps with javascript?"
  • Html `<template>`

    Hidden and re-usable pieces of Html

    In some situations, you may want to render some hidden Html that can be later accessed by JavaScript to use as a template.

    For example, a form to create a survey with a title and multiple questions:

    <form>
      <input type="text" name="title" placeholder="Enter title" />
      <button type="button" onclick="addQuestion()">Add question</button>
      <p>Question</p>
      <input type="text" name="question" placeholder="Enter question" />
    </form>
    
    <template>
      <p>Question</p>
      <input type="text" name="question" placeholder="Enter question" />
    </template>
    
    <script>
      function addQuestion() {
        const template = document.querySelectorAll("template")[0].content;
        const clone = template.cloneNode(true);
        document.querySelectorAll("form")[0].appendChild(clone);
      }
    </script>
    

    What I used in the past is less elegant:

    <form>
      <input type="text" name="title" placeholder="Enter title" />
      <button type="button" onclick="addQuestion()">Add question</button>
      <p>Question</p>
      <input type="text" name="question" placeholder="Enter question" />
    </form>
    
    <div class="template" style="display: none;">
      <p>Question</p>
      <input type="text" name="question" placeholder="Enter question" />
    </div>
    
    <script>
      function addQuestion() {
        const template = document.querySelectorAll(".template")[0];
        const clone = template.cloneNode(true);
        clone.style.display = "block";
        document.querySelectorAll("form")[0].appendChild(clone);
      }
    </script>
    
  • Logarithm laws

    A quick recap of some fundamental properties of logarithms

    Power law

    logbpn = n * logbp

    Proof:

    logbp = a -> ba = p

    Raise to k -> bk*a = pk

    Apply logb -> k * a = logbpk

    Replace a with logbp -> k * logbp = logbpk

    Product law

    logbm*n = logbm + logbn

    Proof:

    logbm = i -> bi = m & logbn = j -> bj = n

    Multiply -> bi * bj = m * n

    Simplify -> bi+j = m * n

    Apply logb -> i + j = logbm*n

    Replace i and j -> logbm + logbn = logbm*n

    Quotient law

    logbm/n = logbm - logbn

    Proof:

    logbm = i -> bi = m & logbn = j -> bj = n

    Divide -> bi / bj = m / n

    Simplify -> bi-j = m / n

    Apply logb -> i - j = logbm/n

    Replace i and j -> logbm - logbn = logbm/n

  • Video to Gif oneliner

    Using `ffmpeg` to transform an .mp4 into a .gif

    This is how I created the demos for 3v0k4/exit.nvim:

    • Recorded the screen with QuickTime into an .mp4
    • Transformed into a .gif with ffmpeg
    ffmpeg \
      -i input.mp4 \
      -vf "fps=10,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
      -loop 0 \
      output.gif
    

    Source: SuperUser

PinkLetter

It's one of the selected few I follow every week – Mateusz

Tired of RELEARNING webdev stuff?

  • A 100+ page book with the best links I curated over the years
  • An email once a week full of timeless software wisdom
  • Your recommended weekly dose of pink
  • Try before you buy? Check the archives.