justin․searls․co

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.

The Terms of Service are coming from inside the house!

I just received this e-mail directly from my Synology NAS, to alert me to a change in its data collection policy. Who is the “our”, here?

If a device in my house e-mails me from inside my house, kinda seems like the data it’s collecting should also be in my house?

Unifi has a neat way of sorting dates

This UI, which appears to be the result of a query like ORDER BY year DESC, date ASC is a violence.

Enter your iPhone passcode to set up your Mac

Gun-to-head, there’s no way in hell I would successfully draw the flowchart of account states and security responses necessary for me to land on this screen.

(I have no idea where this iPhone 11 Pro even is!)

visionOS Algorithm Failure Detected

Neat.

Epic Inbox Zero

I am generally extremely on top of my inbox, but I don’t know if I can get through 18 quintillion e-mails by end-of-day.

Captchas have really gotten away from us

Thanks to recent advancements in AI, the only way to pass this captcha and prove you’re a human is to mutter, "what the fuck are we even doing here anymore," into your device’s microphone.

Better 404 design

There are exactly three things I want from a 404 page:

  1. Something approaching an apology
  2. An easy way to report the broken link
  3. Copy that might elicit a sensible chuckle

This page only took a few minutes to make. The "let us know" link pre-populates the entire e-mail and is implemented in a Rails view thanks to a helper method that makes constructing mail_to links a little nicer:

def mail_to_url(email, **options)
  "mailto:#{email}?" + options.map { |k, v|
    "#{k}=#{ERB::Util.url_encode(v)}"
  }.join("&")
end
<%= link_to "let us know", mail_to_url("support@betterwithbecky.com",
  subject: "I got a 404!",
  body: "I clicked a link that led me here, but got a 404 message:\n\n#{request.url}"
) %>

Wish people spent more time sweating details like this.

GPT 3.5 is a lot worse than GPT 4

It should shock no one to learn that Open AI's newer, better language model is an improvement over the old one, but if you aren't an active user of any of this newfangled AI stuff, it can be easy to lose track of just how much better things are getting and how quickly.

If you subscribe to GPT Plus, ChatGPT will also implement the ReAct pattern for requests it thinks can be formalized, which is one way to mitigate hallucinations.

Pictured here, asking "days between 12/10 and 2/11":

  1. GPT 3.5, which gets it completely wrong with a nonsensical reasoning
  2. GPT 4, which gets it right and even has a little terminal prompt link
  3. Opening up that link will actually show you a Python script GPT 4 used to compute the answer

Cool beans. 🫘

Vision Pro with the Good Strap

Following up on my post from this morning on how to use a 3rd party "halo" strap for Vision Pro, my incredible brother Jeremy printed and sanded these adapters for me. The experience wearing this strap is night-and-day better than either of Apple's built-in straps. The headset's weight is finally where it should be.

Still got it

10 pounds. 19 liters. Vision Pro, MacBook Air, spare battery, and all the clothes and toiletries I need to travel indefinitely.

Still got it.

Neat HomePod Update

The latest batch of 17.3 updates appear to have nuked all my HomePods. Very neat.

Trying VS Code’s Terminal Loader instead of foreman or overmind

I did a video about debugging Rails in Visual Studio Code a couple years ago that showed off how to use the remote interface of the debug gem with Rails 7's Procfile-based bin/dev workflow. Using foreman or overmind and the remote debugger interface is fine but it's honestly no replacement for the developer experience of running binding.irb directly against a dedicated terminal running nothing other than rails server.

So I decided to give this Terminal Loader extension a try to see if I could have my cake and eat it too: a one-shot way to run all of my development servers across multiple terminals. The verdict? It works!

  1. Install the extension
  2. Run the command TLoader: Load Terminals (via Command-Shift-P) once, which will:
    • Launch a couple dummy terminals with split-views, which you can feel free to kill manually
    • Create a workspaceConfiguration/LoadTerminal.json, which you can (and should, IMO) add to .gitignore
  3. Edit the LoadTerminal.json file to specify which terminal groups you want to open, how many columns per group, and the commands to run for each one

This is my config for a straightforward Rails 7 app that runs the Rails server, the tailwind CLI, a Rails console, and Solid Queue. Because I don't typically need to interact with tailwind or my queue daemon, I relegated those to a shared terminal group. And while I didn't have need for it in this case, I appreciate that the extension allows you to set a different working directory for each terminal, which will be a huge boon to my projects that embed sub-libraries and example apps.

Here's my first crack at a LoadTerminal.json for this project:

{
  "version": "1.2.1",
  "groups": [
    {
      "name": "Rails Server",
      "description": "Rails Server",
      "enabled": true,
      "terminals": [
        {
          "name": "server",
          "path": ".",
          "cmd": [
            "env RUBY_DEBUG_OPEN=true bin/rails server -p 3000"
          ],
          "num": 0
        }
      ]
    },
    {
      "name": "Rails Console",
      "description": "Rails Console",
      "enabled": true,
      "terminals": [
        {
          "name": "console",
          "path": ".",
          "cmd": [
            "bin/rails console"
          ],
          "num": 0
        }
      ]
    },
    {
      "name": "Other",
      "description": "Tailwind / Queue",
      "enabled": true,
      "terminals": [
        {
          "name": "tailwind",
          "path": ".",
          "cmd": [
            "bin/rails tailwindcss:watch"
          ],
          "num": 0
        },
        {
          "name": "queue",
          "path": ".",
          "cmd": [
            "bin/rake solid_queue:start"
          ],
          "num": 0
        }
      ]
    }
  ]
}

Seems to work fine! Nice change of pace not having to juggle virtual-terminals-within-an-electron-wrapper-within-a-terminal anymore.