justin․searls․co

Spot the N+1

In general, it's great that you can call array and enumerable methods on relations in Active Record, but the way that's implemented (cleverly querying on the relation instead of considering if to_a and doing the operation in memory would be more performant) is maddening at times.

Something must be done about the skyrocketing cost of college

Life comes at you fast

Quintessential Oils

Sure, you've tried essential oils, but once you go quintessential you won't go back.

IYKYK

AAAAAwesome company name

I've got nothing but respect for this rando Amazon vendor's alpha-sort-hacking game.

Outed

I hate when the algorithm nails me.

Putting your phone away to focus? Fixed that for you

If you've ever felt distracted and thrown your phone in your bag so you can focus on your Mac, you're going to need a new strategy to achieve self control thanks to macOS Sequoia's new iPhone Mirroring feature.

The #1 app on iPad is a calculator

Saving this for posterity as it seems likely Apple is 90 minutes away from announcing a first-party Calculator app for iPad. Only took 14 years.

If you're the DOJ, this is definitely a sign that they're abusing their market position and stifling competition!

If you're anyone else, you're amazed that Apple let them use an icon so evocative of their own Calculator app on iPhone.

Email Apocalypse Now

Deeply satisfying that Google automatically sent this e-mail to my spam folder.

This iOS Home Screen

I've perhaps had too many idle hours this month riding trains by myself this month, as it's led to silly micro-projects like this: carefully weeding out everything from my iPhone's Home Screen but the most essential apps, each expressed as a monospace link with a name that evokes the styling of my web site's navigation.

Kudos to the Dumbify app for facilitating it and to dumbph.com for the inspiration.

Podcasting from a train!

Well, technically, just editing one. (It was actually recorded in a karaoke joint.)

Computer Bldg.

I walked in to give them a piece of my mind, but the elevator button didn't do anything no matter how many times I pressed it.

RubyKaigi 2025 is heading to Matsuyama

Incidentally, Becky and I just visited Matsuyama for the first time a bit over a week ago, so I was surprised (and delighted!) when RubyKaigi's head organizer Akira Matsuda announced that next year's event will be held in Matsuyama from April 16-18, 2025.

If you've never been, Matsuyama resides in Ehime prefecture, which is on the island of Shikoku, just southwest of Japan's main island. It has one of the most cherished castles in the country atop a mountain at the center of the city and which is accessible by a continuously running cable car. It's also home to Dōgo onsen, which is considered to be the oldest hot springs bath in the country (and one of several inspirations for Studio Ghibli's Spirited Away). Additionally, it's famous for its massive and varied mikan (Japanese clementine oranges) crop—Becky and I got to sample a smattering of varieties from a store that had dozens of local citrus juices on tap, via cute little faucets.

Getting to Matsuyama isn't so bad either. From Osaka, it's about 3.5 hours by train, and a special rapid "Shiokaze" train service runs to Matsuyama from Okayama, making it an easy transfer from the Sanyo Shinkansen.

If you've ever wanted to visit a hot springs resort, or get a glimpse of daily life in a more remote Japanese city than the most well-known tourist destinations, I hope I'll see you next year!

[Translator's note: yama means mountain]

Oops! All Dryers

Had a surprisingly hard time finding a coin laundry in Okinawa this morning. First one I went to only had dryers, which was a first. 💨

Battle Station

A number of readers have asked about my Vision Pro setup since writing about how I've forsaken my desk for an Eames chair.

Well, here it is. I ran the MagSafe charging cable for my MacBook Air along the left armrest and routed the USB-C cable that charges my Vision Pro battery up the back (which itself is affixed via a 3D-printed enclosure and velcro command strips).

At nearly three months in, I'm loving this setup. Went ahead and cancelled a longstanding to-do to find a more comfortable office chair than my Steelcase Leap, because I can't really see myself needing one anytime soon.

Yes, it isn't

A recent trend in GPT-4 over the past few months is that it's started catching hallucinations (or, more charitably, over-eager user affirmation) mid-sentence. At this point, about 20% of the yes/no questions I ask it result in a sudden about-face. As jarring as it is to read, only once has it explicitly acknowledged its own contradiction—which, I'll admit, was impressive.

Because ChatGPT spews fluent bullshit, it has no relationship with the truth and so no apology or reflection typically follows. However, unlike most bullshitters, if you ask for an apology it'll gladly oblige. Silver lining.

Down for everyone because of me?

Just signed up for bandsintown to look for live concerts during my next Japan trip. I created my account using Sign in With Apple and the site immediately started behaving weirdly. Then I opened my devtools and it was spewing an unbelievable number of XHR errors. Within 90 seconds the entire site was down. It didn't come back for about 5 minutes.

I have are a very particular set of skills.

Spatial Personas are terrifyingly cool

Is the Apple Vision Pro "Persona" feature still firmly stuck in the uncanny valley and make me feel like I'm trapped in a Polar Express vortex? Yes.

Is the newly-released Spatial Persona mode way cooler than the default Persona-in-a-box the device shipped with? Yes.

Does all the engineering Apple has poured into the SharePlay API and frameworks finally have a meaningful purpose? Yes.

Yes. Yes. Yes. That earns Spatial Persona's a Certified Cool Stuff™ rating from me.

Super fun experience playing around with this new beta-of-a-beta feature with Aaron today. I'll definitely chat about it on my next podcast.

Takes, re-designed

Today, I updated the design of the short-form takes on this web site to look a little more distinctive and to better conform to the design of other "non-prose" posts. Like each podcast player, the take is rendered "beneath the page" as if the page has been cut out. Each component of the take is then rendered as a roundrect: my avatar, the text of the take, and the action links.

This design is roughly what I'd wanted to make for a while, but figuring out a responsive way to render a two-piece roundrect for takes that extend beyond the height of my avatar took a while to get right.

The approach to achieve this effect requires a lot of absolute positioning, with every component but the prose itself having a fixed height, and the two white-background roundrects that contain the prose being absolutely positioned and pinned relative to all four sides of the widget.

If you're curious how this was implemented, here's the markup and tailwind classes (most of which are stock, but this site uses a custom sizing scale):

<div class="mx-auto max-w-68">
  <div class="relative p-1 md:mt-6 md:mr-2 bg-secondary beneath-the-page list-inside-all">
    <!-- The top portion of the background of each take, pinned to the top and with width adjusted to make room for the avatar -->
    <div class="z-10 bottom-12 absolute w-[calc(100%-124px)] mr-1 top-1 ml-13.5 shadow-lg bg-primary rounded-xl">
    </div>
    <!-- The bottom portion of the background of the take, pinned to the bottom and with fuller width -->
    <div class="z-20 bottom-11.5 absolute w-[calc(100%-16px)] mr-1 top-14.5 shadow-lg bg-primary rounded-tl-xl rounded-bl-xl rounded-br-xl"
      data-min-height="20">
    </div>

    <a href="/about">
      <!-- my face, fixed width/height, floated to the left -->
      <img class="float-left w-12 h-12 mt-px mb-0 mr-2.5 shadow-2xl rounded-xl" src="/img/face.jpg">
    </a>
    <div class="relative z-30 px-1 text-lg pt-sm min-h-11.5 [&amp;>*:first-child]:mt-0 [&amp;>*:last-child]:mb-0">
      <!-- The take's inner HTML, rendered from markdown -->
    </div>
    <div class="flex flex-row justify-between h-5 px-2 mx-auto mt-3 rounded-lg shadow-lg max-w-48 py-sm bg-primary min-w-30">
      <!-- Action links & icons -->
    </div>
    <div class="h-3 pt-1 text-sm text-center mt-sm text-secondary/60">
      <!-- the like count & time -->
    </div>
  </div>
</div>

This was almost enough on its own, but the two take backgrounds needed to overlap to cover the rounded corners on the bottom of the top piece. As a result, sometimes there'd be an extra trailing white doo-dad without any text associated. For this, I needed a bit of JavaScript. So I put a data-min-height="20" on the background and wrote this script:

function enforceRenderingConstraintsFor (el) {
  if (el.dataset.minHeight != null) {
    el.classList.remove('hidden') // have to remove hidden first b/c otherwise el.clientHeight will be 0
    el.classList.toggle('hidden', el.clientHeight < parseInt(el.dataset.minHeight, 10))
  }
}

function enforceRenderingConstraints () {
  const elements = document.querySelectorAll('[data-min-height]')
  elements.forEach(enforceRenderingConstraintsFor)
}

window.addEventListener('DOMContentLoaded', enforceRenderingConstraints);
window.addEventListener('resize', enforceRenderingConstraints);

The design is pretty busy and a little cramped, but I dig it anyway.