Hardscrabble šŸ«

By Maxwell Jacobson

See also: the archives and an RSS feed

generating opengraph social preview images for blog posts automatically in jekyll

December 3, 2024

Some time last year I thought it might be nice to have opengraph preview images on my blog posts, so if I posted them to Mastodon theyā€™d show a cool little preview image and more people would click on them.

Itā€™s easy enough, you just need something like this in the <head>:

<meta property="og:image" content="https://www.hardscrabble.net/img/preview/2024-10-03-colors.png">

Because I use Jekyll to generate my blog, it wasnā€™t too hard to add a little conditional logic that includes that meta element if a post specifies a preview image filename in its front matter.

But itā€™s sort of annoying to have to prepare an image whenever you want to blog something. I donā€™t necessarily have time for all that.

Then, yesterday, I was poking around on Bluesky and saw this post:

I'm starting my 2024 #blogvent series where I post a blog a day in December! Blogvent day 1 is about fighting spam in your open source repos: cassidoo.co/post/oss-int...

[image or embed]

— Cassidy (@cassidoo.co) December 1, 2024 at 4:07 PM

And I got so jealous of how nice and clean that preview image is, and I had a few thoughts:

  • Sigh I should blog more
  • Dang can you imagine blogging that much?
  • No, I mean, it would be so annoying to generate all those preview images
  • Wait, how did she manage to auto-generate the preview images with text for each blog post??? Thatā€™s so cool!!!

I saw that her blog post is open source and I tracked down the lines of code, which look sort of like this:

---
const { title, description, image = "/home-blog-card.png" } = Astro.props;
---

<head>
  <meta property="og:image" content={new URL(image, Astro.url)} />
</head>

I sat and stared at that for several minutes, trying to puzzle out how it works. Iā€™ve never used Astro, but it seems very powerful. I look at the docs.

Eventually I realized that actually itā€™s just a static image, and it isnā€™t dynamically changing the text for each blog post. I went back to her page and scrolled down past several links, and I saw that they all have the same text.

Lol.

But hey, I thought, maybe it is possible to generate custom images with text in them? And if it is, maybe it is possible to hook that into the jekyll build process?

And indeed it is. And Iā€™ve done a somewhat basic version of that. And hereā€™s how you can do that too.

First, create a plugin by creating a file called _plugins/opengraph.rb. Ruby files in the _plugins folder are loaded during the build process.

Within that plugin, register some jekyll hooks:

Jekyll::Hooks.register :posts, :pre_render do |post|
  unless post.data["preview_image"]
    preview = GeneratePreview.new(post)
    preview.write
    post.merge_data!({"preview_image" => preview.path }, source: "opengraph plugin")
  end
end

Jekyll::Hooks.register :site, :after_init do
  FileUtils.mkdir_p "tmp/img/preview"
end

Jekyll::Hooks.register :site, :post_write do
  Dir.glob("tmp/img/preview/*").each do |path|
    FileUtils.cp path, "_site/img/preview"
  end
end

The idea here is that for any post that doesnā€™t already have a preview image specified in the front matter, weā€™ll

  • generate a preview image and write it to a folder
  • modify the in-memory post object to know that it has a preview image with a particular filename. Because this is a :pre_render hook, the HTML for that post has not yet been written to disk, so itā€™s not too late to modify its metadata

And then, after the whole site has been written, copy all of those generated image files into place.

In this design, I donā€™t need to commit all of the generated files to the repository, I just generate them at build time. I need to regenerate them each time I deploy. This is a bit slow and ineffecient. I might decide to just commit them down the line.

The actual image generation code is sort of hacky. Itā€™s a bit of ruby glue code that shells out to the venerable imagemagick CLI tool. It executes commands like this:

magick \
    -background "#f2d8b2" \
    -font "Helvetica" \
    -fill "#248165" \
    -size 1200x630 \
    -gravity SouthWest \
    "caption:The\ easiest\ way\ to\ indent\ paragraphs\ online,\ not\ that\ you\ necessarily\ should" \
    tmp/img/preview/2012-03-21-indenting-paragraphs-online.png

Which produces images like this:

example preview image with green text on beige background saying "The easiest way to indent paragraphs online, not that you necessarily should"

Kind of obnoxious right? A little brat maybe? You tell me.

Getting this to run in GitHub Actions presented a few challenges. The ubuntu-latest machine where the builds run had imagemagick installed, but it was imagemagick 6, not 7. sudo apt-get install -y imagemagick just complained that hey, itā€™s already installed baby, nothing for me to do. But I had gotten all of this working with imagemagick 7 and I wasnā€™t about to redo anything.

Once I figured out how to install imagemagick 7, I had the next issue, which was that no fonts were installed on the machine, at least as far as imagemagick could tell. After installing the gsfonts package, it could see some fonts, but only the off brand, Linux versions of them. By running magick -list font in the CI context, I could see what those were. Eventually I worked out that something called Nimbus-Sans-L is basically knockoff Helvetica, which was what I had randomly picked locally. Great. Close enough. Perhaps you can tell but I have never really been a Font Guy. But I wanted this to work in both contexts, locally and in GitHub Actions. To work around that, I added some conditional logic to the ruby script to default the font to Helvetica, but allow overriding it via an environment variable.

And with thatā€¦ everything was working, and I went to bed, criminally late, having forgotten what I originally had been planning to blog about. I remembered over lunch today and finished that post. And now hereā€™s this.

Hereā€™s the commit with all of the code, if youā€™re curious. It kind of works.

How untrustworthy is that?

December 3, 2024

In Waitress, a musical I love, thereā€™s a song called ā€œWhen He Sees Meā€ that I love. But thereā€™s one line that gives me pause.

Hereā€™s the verse (emphasis mine):

Sorry girls

But he could be criminal, some sort of psychopath

Who escaped from an institution

Somewhere where they donā€™t have girls

He could have masterminded some way to find me

He could be colorblind

How untrustworthy is that?

He could be less than kind

The context here is that Dawn is single and hesitant about putting herself out there. Most of all sheā€™s afraid of being hurt, and the joke of the song is that she canā€™t quite admit that fact, and she chooses instead to enumerate all of her other excuses for not going on a date: minor infractions like calling the waiter by his first name or eating Oreos wrong, for example. Itā€™s a breathless and very funny stream of consciousness. Ha ha, right?

But hold on, whatā€™s her problem with colorblind people? How is it untrustworthy to be colorblind? I love this song, but I always trip over that line. In addition to being a bit ableist, itā€™s also such a non sequitor.

Iā€™m reminded of the classic 2008 parody of Deep Blue Somethingā€™s Breakfast at Tiffanyā€™s from the sketch comedy group Olde English:

Thatā€™s me right now.

Dawnā€™s characterization is probably strengthened by her being a little bit shitty about this. Sheā€™s such a sweet character that for her to be randomly biased against a protected group is funny, in a dark way. In the chorus, she asks, ā€œWhat if when he knows me, heā€™s only disappointed?ā€ The audience is inclined to love and root for Dawn, but in order for the song to resonate, she needs to show some human flaws.

The sloughing off of hacks

October 3, 2024

I was recently looking over a post of mine from a few years ago, Learning to love vim colorschemes, which goes over how I managed to get my favorite color scheme, Smyck, to take effect in several contexts:

  • as a vim colorscheme
  • as a bat / delta theme
  • as some accent colors in tmux

Honestly most of this has held up! I still use and love Smyck, mostly in exactly the same ways.1

In order to get all that to work, I mentioned this:

In my ~/.vimrc:

if $COLORTERM == 'truecolor'
  set termguicolors
  let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
  let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
  colorscheme smyck
endif

The conditional is there because this configuration only works in terminals that support true color. Iā€™m definitely sold on iTerm2 now, but I donā€™t want everything to look wacky if I did try and use vim in Terminal.app.

Those funky &t_8f and &t_8b things are there for tmux compatibility. I have no idea what they mean. I copied them from the internet.

It kind of bugs me having configuration that I donā€™t actually understand what itā€™s doing, so I tried to learn what it actually does. I didnā€™t quite figure it out. So then I tried removing it, and simplifying that whole passage to simply:

set termguicolors
colorscheme smyck

Forget the hack! Forget the conditional!

And to my pleasant surprise, everything seems to work fine?

When writing software we often accumulate piles of weird hacks, and sometimes if youā€™re lucky, they stop being necessary. Maybe tmux fixed something. Maybe vim did. I donā€™t know. They have updates all the time which I dutifully install. Why shouldnā€™t things get better?

Thereā€™s this idea called Chestertonā€™s Fence that people often invoke in the context of software. Hereā€™s a quote Iā€™m copying from Wikipedia:

In the matter of reforming things, as distinct from deforming them, there is one plain and simple principle; a principle which will probably be called a paradox. There exists in such a case a certain institution or law; let us say, for the sake of simplicity, a fence or gate erected across a road. The more modern type of reformer goes gaily up to it and says, ā€œI donā€™t see the use of this; let us clear it away.ā€ To which the more intelligent type of reformer will do well to answer: ā€œIf you donā€™t see the use of it, I certainly wonā€™t let you clear it away. Go away and think. Then, when you can come back and tell me that you do see the use of it, I may allow you to destroy it.ā€

This might as well have been written about my &t_8f and &t_8b and I might be the less intelligent type of reformer happily clearing it away. But hell, I put up the fence, and Iā€™ll live with the consequences.

I suspect many software teams are tolerating many such fences in their codebases. By all means, try to get to the bottom of the question. Make an effort. And then let the mystery be.

  1. The main difference is that Iā€™ve migrated from iTerm 2 to Alacritty. I had been using the Smyck.itermcolors theme file to use the Smyck colorscheme in my shell sessions ā€“ I want those sweet pastels to be used for my prompt and to color the output from the various command line programs Iā€™m running. BUT the official Smyck repository doesnā€™t offer any Alacritty theme. One kind soul has opened a pull request which might one day get merged. Before I saw that, I had already adapted it myself. Itā€™s not too bad.Ā 

Ten years of Hardscrabble

November 6, 2023

This website launched ten years ago today.

Itā€™s a little hard to define the exact birthday of the blog. There are a handful of posts in the archive that are older than ten years, but those ones were originally published on older iterations of my blog that werenā€™t called Hardscrabble, so I donā€™t count that. Thereā€™s the post announcing that the blog is now powered by Jekyll and open source, which is still a few weeks away from its ten year anniversary, but Iā€™ve done a little archaeology, and Iā€™ll point to this commit on November 6, 2013 at 10:41pm as the canonical moment. In that commit, I added the CNAME file that tells GitHub Pages that the site will be served from www.hardscrabble.net.

Incidentally, this site is still powered by Jekyll and open source and hosted by GitHub Pages. Jekyll and GitHub Pages have both iterated steadily over the years. I feel sort of like Iā€™ve been getting away with something to have this reliable, simple free hosting service. There have been times Iā€™ve been tempted to move the hosting elsewhere but itā€™s never become necessary.

Hereā€™s a screenshot of what the site looked like in February 2014, from the internet archive:

screenshot of a very plain homepage

(I tried to check out an old version of the source code and boot it up but it proved a bit challenging. If I were a more committed nostalgist Iā€™d probably try to make a docker image with the older dependencies, like Ruby 2.0.0, but this will have to do for now)

This has never been the most beautiful website and the design has not evolved very much. I kept the dashed lines and that shade of green for links. I changed the background color.

Before Hardscrabble, I bounced around between a bunch of different blogs and blogging tools, including WordPress, Tumblr, Octopress, Calepin, Livejournal, Facebook Notes. Maybe some others. I remember my friend Corey would poke gentle fun at me for this restlessness. I remember when I started Hardscrabble I wanted to stick with it if only to prove him wrong. Iā€™ll have to ask.

Thanks for reading.

Programming my macropad

November 3, 2023

I recently wrote about a gadget that Iā€™ve started using, a DOIO KB04-01 Macro Keyboard 4 Keys + 1 Knob Macro Pad. Weirdly enough, not long after I posted that, it stopped working. The buttons still worked, but the knob didnā€™t, and for me, the knob was basically the whole point. I had no idea how to debug this either. I had never figured out how to program the macropad at all and was using its out of the box settings. There was some discussion online about how it can be programmed using Via, the popular app, but you needed to install an old version of it and load in a special JSON file in order for that old version of Via to recognize the device. Reader, I tried.

After hitting some dead ends, distraught, I decided to shop around a bit, and to look for a more reputable brand. I ordered a Keychron Q0 Plus QMK Custom Number Pad, because Keychron is a good brand, and the product page has extensive details about how to program it with Via. I donā€™t really need a full numpad1 but I donā€™t mind having one, and more importantly, itā€™s got a beautiful knob and some keys that are reserved for doing whatever you want them to do.

When it came, I was thrilled to find that it worked out of the box. It controlled the volume when I turned the knob. And I was able to get Via to recognize the device by following the instructions on Keychronā€™s product page. Great success.

Itā€™s neat actually. Iā€™m simply using the web app in Chrome (it doesnā€™t work in Firefox or Safari) rather than the downloadable app. This is powered by the experimental WebHID API.

But, uh, now what?

I had five physical keys reserved for running macros but no immediate ideas what to have them do.

I spent the next several days happily turning the knob to adjust volume and keeping one eye open for ideas for things to turn into a macro. Eventually, inspiration struck.

I mostly use my Apple Studio Displayā€™s speakers, but every now and then I like to use headphones, if Iā€™m really zoning in and focusing. I keep my headphones permanently plugged in to my Mac Studio, and every now and then Iā€™ll click open Control Center and adjust the output device like so:

Gif showing me tapping on the macOS Sonoma Control Center menu bar icon and changing my audio output to my headphones

If I can find a way to script that, that would be a fine macro: a button that toggles the audio output between my headphones and my studio display.

I hoped that would be easy to script using Shortcuts but I couldnā€™t find any shortcut actions that update the audio output device.

I googled around a bit and came upon this open source project: switchaudio-osx which is easily installed with Homebrew with a command line interface. Sigh, okay, letā€™s try that.

Hereā€™s the script I came up with2, depending on that tool, to toggle the output device:

set -e

# N.B. this depends on https://github.com/deweller/switchaudio-osx

# Fully qualified path to SwitchAudioSource
SAS="/opt/homebrew/bin/SwitchAudioSource"

# Get the current audio output device
current_device=$($SAS -c)

# Check if the current device is "External Headphones"
if [ "$current_device" == "External Headphones" ]; then
    # Set the audio output to "Studio Display Speakers"
    $SAS -s "Studio Display Speakers"
else
    # Set the audio output to "External Headphones"
    $SAS -s "External Headphones"
fi

This worked reliably when invoked from the command-line. I also wrapped it up in a Shortcut and configured the shortcut to run when I type āŒƒāŒ„ā‡§āŒ˜H.

Screenshot showing the Shortcuts app using the Run Shell Script action to run that shell script

I had always wondered what does it actually mean to program a macropad to run a macro? Like, can we load a shell script onto the keyboard?? Does it remember things??

The main screen in Via is called ā€œConfigureā€, and within that is the Keymap section. There, you can map each physical key (including the knob) to type whatever character you want, or to invoke a macro. It seems that for this device, there are sixteen slots for macros, named named M0, M1, M2, ā€¦ M15.

After the Keymap section is the Macro section where you can define what those sixteen macros actually are. The way it works is that you click ā€œRecord keystrokesā€, and then you type some stuff, and then you click ā€œStop recordingā€ and then ā€œSave changesā€. Now, whenever you invoke that macro, it will type in exactly what you typed in during the recording. That could be your billing address or a keyboard shortcut or whatever else you want. In this gif, Iā€™m recording a macro that will perform the keyboard shortcut āŒƒāŒ„ā‡§āŒ˜H.

Recording a macro to invoke the shortcut

Once the macro exists, I just need to go back to the Keymap section and map that physical key to that macro:

Mapping the key to the macro

Iā€™m happy to learn that this is all nicely decoupled. The keyboard doesnā€™t need to know anything about my computer or any particular behavior I want my computer to have. Itā€™s simply an input device, and I can program it to send the inputs I want it to send. Then I can program my Mac to respond to those inputs how I want it to respond. The device does seem to remember how Iā€™ve programmed it.

And now I can happily jam that button to toggle my audio output.

There may be better or simpler ways to do some of this stuff, and Iā€™d be happy to hear about them if youā€™d like to share. Iā€™m pretty new to all this.

  1. the numbers are already on my regular keyboard and Iā€™ve never had any issue just using those, but if youā€™re a numpad person I wish you all the peace in the worldĀ 

  2. Iā€™ll be real with you, I used ChatGPT to actually write thisĀ 

My tmux aliases (2023 edition)

October 21, 2023

My tmux setup, with some panes showing a vim editor process, server logs, and test output

In 2015, I wrote a blog post called some helpful tmux aliases explaining a bit about how I use tmux in my development workflow. Iā€™ll confess itā€™s not my most coherent blog post Iā€™ve written, and Iā€™ve iterated a bit since then, so I thought Iā€™d take another run at it.

Wait what is tmux even all about?

When Iā€™m working on a project, I usually need a bunch of separate shells. Right now, as I work on this blog post, Iā€™m using three:

  1. One to run my text editor, vim, where Iā€™m writing these words
  2. Another to run my exe/serve helper script which runs the jekyll server, so I can preview the blog post in a browser and make sure it looks right
  3. Another for miscellaneous use, like running git operations to commit the changes or using ripgrep to search the project for references to things Iā€™ve blogged about already

It would be fine and reasonable to just open three separate windows or tabs in my terminal emulator to run those various things, but instead I use tmux. I basically never have more than one actual tab or window, even when Iā€™m bouncing between multiple projects, because my tmux workflow makes that unnecessary.

tmux is a ā€œmultiplexerā€, which is to say that it lets you run multiple shells in one shell.

It can split your screen vertically and horizontally to run as many shells as you want. In the screenshot at the top of this post, there are three shells1. In tmuxā€™s jargon, those are called ā€œpanesā€, like how a window may be made up of multiple panes of glass, this window is made up of multiple shells.

When I open too many panes, it can start to feel a little cramped. I often resize them by dragging the little border line2. I also will often maximize the current pane, hiding the others, if I just need to focus on one task for a bit:

Resizing tmux panes and zooming into one

You can also run multiple windows, and switch between those. This is nice because sometimes you want something running in the background and you donā€™t really want to look at it all the time. These are called ā€œwindowsā€ but I tend to think of them as ā€œtabsā€, because they function sort of like tabs in a GUI app. You can even rename windows to help you remember what that windowā€™s purpose is:

Switching between windows and renaming them

The last major bit of tmux terminology, after ā€œpaneā€ and ā€œwindowā€, is ā€œsessionā€. A session is a group of windows. You can have many sessions running at once, and switch between them. I have one session per project that Iā€™m working on. If I initiate a session in the directory for my project seasoning, then every new pane and window will start a new shell in that directory, which is really convenient.

So thatā€™s the overview.

Aliases

Creating a new session

You can start a new session by simply running tmux. If you do that, your session will automatically be given a name of 0. Your next session will be called 1, and so on. Because I run one session per project, and sometimes work on multiple projects at the same time, I like to name my sessions after the project. Thatā€™s easy enough to do. Instead of running simply tmux to start a session, I can run, for example:

tmux new-session -s seasoning

That will start a new session called seasoning in the current directory.

Back in 2015, I came up with a clever alias that would automatically infer the session name from the name of the current directory. It looked like this:

alias 't'='tmux new-session -A -s $(basename $PWD | tr -d .)'

Running basename $PWD | tr -d . when youā€™re in a directory like /Users/max/src/gh/maxjacobson/seasoning prints the text seasoning, which seems like a perfect session name for when Iā€™m working on that project.

The -A bit will attach to an existing session with that name, if one exists, and otherwise create it.

With this alias, I can happily run simply t in any directory and feel confident that Iā€™ll be in a nicely-named session.

This held up pretty well over the years, but it has one flaw: every now and then (and this is pretty rare) Iā€™ll have more than one ā€œprojectā€ with the same name. For example, Iā€™ll often clone other peopleā€™s dotfiles repos and rummage around for inspiration. Theyā€™re almost always called dotfiles. If I clone wfleming/dotfiles and start a new session by running t, Iā€™ll get a new session called dotfiles in Willā€™s dotfiles. If I then clone pbrisbin/dotfiles and run t, tmux will see that there is already a running session called dotfiles and attach to that instead of creating a new one.

This has only come up a very small handful of times but every time is a little papercut that has bugged me. So, recently, I revised my t alias for the first time in ages. It now looks like this:

alias 't'='tmux new-session -A -s "$(basename $PWD) $(echo $PWD | shasum -a 256 | cut -c1-4)"'

With this new version, the derived session name when Iā€™m in /Users/max/src/gh/maxjacobson/seasoning is seasoning 3f2c. That bit at the end generates a unique3 four character hash based on the absolute path to the project. It will always come up with the same hash, so it will be possible to run t in as many dotfiles repos as I want and start up independent sessions.

Listing sessions

You can run tmux list-sessions to print out a list of the running sessions, plus some interesting metadata about them:

$ tmux list-sessions
hardscrabble_github_io afd6: 4 windows (created Sat Oct 21 15:06:31 2023) (attached)
seasoning 3f2c: 1 windows (created Sat Oct 21 15:06:24 2023)

Iā€™ve had that aliased to tl for years. Today I learned you can format the output to include whatever information you want, and spent several minutes exploring various ideas.

Some ideasā€¦

If I want to correct the pluralization error of ā€œ1 windowsā€4:

$ tmux list-sessions -F '#{session_name} (#{session_windows} #{?#{==:#{session_windows},1},window,windows})'
hardscrabble_github_io afd6 (4 windows)
seasoning 3f2c (1 window)

That one is uh, pretty gnarly. In English, itā€™s saying ā€œIf the session_windows variable is equal to 1, say ā€˜windowā€™ otherwise say ā€˜windowsā€™ā€.

If I want to include the sessionā€™s directory:

$ tmux list-sessions -F '#{session_name} (#{session_path})'
hardscrabble_github_io afd6 (/Users/max/src/gh/hardscrabble/hardscrabble.github.io)
seasoning 3f2c (/Users/max/src/gh/maxjacobson/seasoning)

$ tmux list-sessions -F '#{session_name} (#{d:session_path})'
hardscrabble_github_io afd6 (/Users/max/src/gh/hardscrabble)
seasoning 3f2c (/Users/max/src/gh/maxjacobson)

$ tmux list-sessions -F '#{session_name} (#{b:session_path})'
hardscrabble_github_io afd6 (hardscrabble.github.io)
seasoning 3f2c (seasoning)

As you can see, there are modifiers to just say the parent directory, or just say the basename of the directory.

If I want to scrub out the unsightly trailing hash from the session name:

$ tmux list-sessions -F '#{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}'
hardscrabble_github_io
seasoning

I feel like I should be able to simplify that regular expression to something like / [a-f0-9]{4}$/ but I canā€™t quite figure out how to escape it. So it goes.

I think Iā€™m going to keep it minimalist and use this last one when I run tl. That means my next recommended alias looks like this:

alias 'tl'="tmux list-sessions -F '#{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}' 2>/dev/null || echo 'no sessions'"

Youā€™ll notice that thereā€™s a little bit of error handling in there. Thatā€™s because, by default, tmux list-sessions throws a kind of unsightly error when there are no sessions to list. We can make that a little nicer.

Attaching to an existing session

One of the benefits of tmux is that if you accidentally close your terminal emulator app (e.g. Terminal or iTerm 2 or Alacritty or whatever), your session is still running in the background, and you can reattach to it. Itā€™ll keep running until all of the shells within the session exit. I normally press Ctrl + d to exit shells, but you can also type exit and hit enter.

You can attach to a session by running tmux attach-session, which will attach to the most recently used session. I have this aliased to ta like so:

alias 'ta'='tmux attach-session'

This is usually what I want. From there, if I happen to have multiple sessions going, I might switch to another session like so:

switching between tmux sessions

(Eagle-eyed readers might notice that the hash has disappeared from the status bar in the lower right in that gif, because I realized I can use the same format string trick to scrub it from there too. And now you know that I am a bit too lazy to redo the earlier gifs for consistencyā€™s sake.)

Itā€™s also possible to attach to a specific session, rather than the most recently used one. You can do that by running a command like this:

$ tmux attach-session -t seas

Thankfully you donā€™t need to specify the full name. tmux can figure out that when I specify seas I mean seasoning 3f2c.

I have that aliased as to, so I can simply run:

$ to seas

That alias looks like this:

alias 'to'='tmux attach-session -t'

Wrapping it all up

Alright, thanks for coming on this journey. Hereā€™s the aliases all together:

alias 't'='tmux new-session -A -s "$(basename $PWD) $(echo $PWD | shasum -a 256 | cut -c1-4)"'
alias 'tl'="tmux list-sessions -F '#{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}' 2>/dev/null || echo 'no sessions'"
alias 'ta'='tmux attach-session'
alias 'to'='tmux attach-session -t'

And hereā€™s the relevant bits of config in my ~/.tmux.conf

set -g status-interval 1
set -g status-left ""
set -g status-right "%b %d %H:%M:%S #{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}"

Happy tmuxing. See you in another eight years.

  1. It sort of looks like four, but itā€™s really three. The one in the top left is running vim, which has its own splitting mechanism, and is showing two different files.

    It can get a bit fractal when you have vim inside tmux, but you get used to it.Ā 

  2. This requires you to enable mouse support in your tmux configuration, which I heartily recommend.

    set-option -g mouse on
    

  3. I guess itā€™s possible to have a hash conflict, and thatā€™s much more likely because Iā€™m truncating the hash to just four characters, but uh, fingers crossed.Ā 

  4. This is something that has apparently bothered me for a long time. I added the Rails/PluralizationGrammar rule to rubocop many years ago, and now itā€™s referenced in thousands of repos, something that genuinely delights me.Ā 

Multi-paragraph footnotes in Markdown

October 1, 2023

It can be challenging to write about Markdown in Markdown, but Iā€™m going to try. The hard part is showing examples of the syntax without that syntax getting converted into HTML. For example, did you know that if you want to bold some text, you do it like this? Shit, that got bolded. Let me try again. You can do it **like this**. OK, great.

So what about footnotes? Adding a simple footnote is fairly straightforward: you put [^1] in the main flow of your prose to indicate that there is a footnote. Then you can add your footnote like this:

[^1]: My great footnote

Hereā€™s how that looks1.

John Gruber, the inventor of Markdown, recently published a recap of Appleā€™s recent iPhone 15 event which contained three footnotes. When I saw that one of them is three whole paragraphs, my eyes widened. You can do that???

If we look at the generated HTML, it looks like this:

<li id="fn2-2023-09-15">
<p>On the eve...</p>

<blockquote>
  <p>Apple has also...</p>
</blockquote>

<p>Huaweiā€™s geopolitical travails...</p>
</li>

Nothing magical going on at all. Just some normal-looking HTML.

But, I wondered, how the hell do you represent that in Markdown? When I generate footnotes with Markdown, as soon as I finish the first paragraph, the footnote is done.

I happen to know that Daring Fireball has a trick, where you can append .text to any URL and see the Markdown source code for that article. So I took a look at that articleā€™s source Markdown, and hereā€™s what I saw:

<li id="fn2-2023-09-15">
<p>On the eve...</p>

<blockquote>
  <p>Apple has also...</p>
</blockquote>

<p>Huaweiā€™s geopolitical travails...</p>
</li>

Yep: the exact same thing. Heā€™s not using any special Markdown syntax to generate the footnotes, heā€™s doing it manually by writing HTML. And thatā€™s fair enough; itā€™s totally valid to include bits of HTML in your Markdown source.

Butā€¦ does that mean that itā€™s not possible to have multi-paragraph footnotes in HTML-free Markdown? Wellā€¦ unfortunately, itā€™s time that we start to get into some nuance (and a bit of drama).

I should note that Iā€™m referring to ā€œMarkdownā€ as though Markdown is one thing. Itā€™s not. Gruberā€™s original version of Markdown doesnā€™t support footnotes at all (so itā€™s not a surprise that his blog implements them without any non-HTML syntax). There are many, many different implementations of Markdown. The one that I tend to use is called GitHub Flavored Markdown, which is the version of Markdown used in GitHub text fields. Itā€™s also the version of Markdown that I use to build this blog. So thatā€™s the one Iā€™ll focus on today.

This diaspora of implementations can make it hard to find good information about what features you should expect to have access to. GitHub publishes a spec for GitHub flavored Markdown but it doesnā€™t describe their implementation of footnotes. Elsewhere, they publish a doc on ā€œBasic writing and formatting syntaxā€ and its section on footnotes includes these two examples:

Here is a simple footnote[^1].

A footnote can also have multiple lines[^2].

[^1]: My reference.
[^2]: To add line breaks within a footnote, prefix new lines with 2 spaces.
  This is a second line.

Oh God, is that the best we can do? That seems to generate one paragraph, with <br /> tags breaking it up into multiple lines. Thatā€™s not really what I want.

But, clicking around, I found some reason for hope. The 2021 changelog post that introduced footnotes to GitHub embeds a gif that, hooray, includes a multi-paragraph footnote example, which looks like this:

Some text.[^bignote]

[^bignote]: Here's one with multiple paragraphs and code.

    Indent paragraphs to include them in the footnote.

    `{ my code }`

    Add as many paragraphs as you like

As the gif looped and this little miracle flashed on the screen momentarily before flickering away again, I did my best to see what was there, and eventually the carousel looped around enough times that I got it. So thatā€™s easy enough: you can add more paragraphs to your footnote as long as you indent them (with four spaces). Easy.2 And hopefully this will actually continue to work, even though itā€™s barely documented.

I did tease a little drama, but Iā€™m actually not super invested in it. So, suffice it to say that Gruber is occasionally a bit salty about the various takes on Markdown that exist.

  1. My great footnoteĀ 

  2. Just to show it off here, Iā€™m doing another footnote, and this is the first paragraph of it.

    and wow hereā€™s a blockquote and itā€™s still in the same footnote

    And hereā€™s another paragraph thatā€™s still, magically, in the same footnote.

    The blank lines between the paragraphs can just be blank, they donā€™t need to have four spaces in them for no reason, donā€™t worry.Ā 

The Apple Studio Display's Missing Volume Knob

October 1, 2023

EDIT Nov 3, 2023: Iā€™ve replaced that gadget with a different, similar gadget that Iā€™ve written a new post about.

Iā€™ve been using the Apple Studio Display as a computer monitor for over a year now and hereā€™s my review: itā€™s pretty good, but itā€™s missing a volume knob.

I use the built-in speakers, which are pretty good. And, of course, itā€™s possible to adjust the volume. My goofy mechanical keyboard1 has a row of function keys, and maybe one of them is supposed to control the volume, but I canā€™t figure it out (Iā€™ve tried for like two whole minutes). So for the first year or so of using this monitor, any time I wanted to adjust the volume, I moused up to the little Control Center icon in the menu bar, clicked, and dragged the little slider.

Adjusting volume with Control Center

And reader, forgive me, maybe this is intuitively obvious to you, but it must be said: this experience sucks!

If Iā€™m ā€œfeeling myselfā€ and want to turn up the tunes, that should be as easy as possible to do. I donā€™t want to scan through a mess of tiny monochromatic icons and have to think. If Iā€™m in the middle of a tense chess position and I want to turn down the music and concentrate, again, that needs to be so easy to do without even taking my eyes off the chess.com chess board.

This sucky experience was kind of simmering below the threshold of conscious annoyance for a while, but a few months ago I finally put my finger on it and had the thought: damn, I wish I had a little volume knob I could turn right now, I wonder if that exists somewhere? So I started doing some google queries like ā€œstandalone volume knobā€ that yielded some very interesting products.

Some products are literally what I imagined: a standalone volume knob. For example this handmade walnut one from Etsy seller ZiddyMakes that I came very close to ordering:

Walnut knob

A bunch more of them were small knobs that are not usable standalone electronics, but meant to be somehow fastened to a mechanical keyboard. An appealing idea! If my keyboard had a little knob, Iā€™d be thrilled. But my keyboard doesnā€™t, and I do not dare attempt to give it one.

Eventually, in this research, I found my way to the term ā€œmacropadā€, which is basically like a little standalone keyboard with a few keys on it that you can program to do whatever you want, like kick off some automation, or act as simple media controls to pause your music or adjust your volume. Some of them even have knobs on them.

I ordered this one: DOIO KB04-01 Macro Keyboard 4 Keys + 1 Knob Macro Pad

DOIO macropad

Hot yellow! Nice.

When it arrived, it basically worked out of the box. The knob controlled the volume. The first button worked like a play/pause button. The second button like a previous track button. The third one like a next track button. The fourth one, uh, didnā€™t seem to do anything.

I spent a few minutes trying to figure out how to customize what those buttons would do, but nothing seemed to work. Then I remembered the classic Mitch Hedberg joke:

I write jokes for a living, I sit at my hotel at night, I think of something thatā€™s funny, then I go get a pen and I write it down. Or if the pen is too far away, I have to convince myself that what I thought of ainā€™t funny.

And so I convinced myself that actually having play/pause, previous track, next track, and a no-op button was what I wanted.

I did order some media keycaps from WASD to replace the blank keycaps that came with it, so I could remember what each key does. I was planning to leave the fourth key blank, but the WASD keycaps were a bit taller than the keycaps that came with the macropad, and it felt weird for them not to all be the same height, so I just put the square ā€œstopā€ keycap there, even though it doesnā€™t actually stop anything.

Iā€™ve had this little guy on my desk for a few months now and I really love it. I almost never press the keys, but I turn the volume knob all the time. It has a nice uh, knob feel. It isnā€™t an entirely smooth spin; it sort of turns in notches. As you turn it, you can feel exactly how many notches youā€™re turning it, and each notch is equivalent to pressing the ā€œvolume upā€ or ā€œvolume downā€ key on a keyboard that has those buttons.

Nice little gizmo.

  1. FWIW, Iā€™ve been very happily using the REALFORCE R2 KEYBOARD MID SIZE (IVORY) keyboard for almost two years and itā€™s my favorite keyboard Iā€™ve ever used. It has the excellent topre switches from the iconic Happy Hacker Keyboard, but in a normal keyboard layout that asks very little of you.Ā 

Streaming sites and their bad URLs

October 1, 2023

Why do streaming sites have such poor URLs?

Hereā€™s a quick survey of the field:

Site Example series URL
Amazon Prime Gen V https://www.amazon.com/gp/video/detail/B0CBFTRGPZ/
Apple TV+ Ted Lasso https://tv.apple.com/us/show/ted-lasso/umc.cmc.vtoh0mn0xn7t3c643xqonfzy
Disney+ Bluey https://www.disneyplus.com/series/bluey/1xy9TAOQ0M3r
Hulu Only Murders in the Building https://www.hulu.com/series/ef31c7e1-cd0f-4e07-848d-1cbfedb50ddf
Max Friends https://play.max.com/show/52dae4c7-2ab1-4bb9-ab1c-8100fd54e2f9
Netflix One Piece https://www.netflix.com/title/80217863
Paramount+ Star Trek: The Original Series (Remastered) https://www.paramountplus.com/shows/star_trek/
Peacock Killing It https://www.peacocktv.com/watch/asset/tv/killing-it/5156438808822262112
Tubi Hannibal https://tubitv.com/series/300000159/hannibal

Of these, the best one is clearly Paramount+, because itā€™s human-readable and free of any junk. If I were feeling uncharitable, I might ding it for using snake case rather than kebab case, but Iā€™m willing to concede thatā€™s a matter of taste.

In the second tier are Apple TV+, Disney+, Peacock, and Tubi which all contain the series name somewhere in their URL. They donā€™t make it easy for you to read them, because thereā€™s some amount of junk mixed in there too, but itā€™s possible.

When I see a URL like that, I want to test if they actually validate the human-readable bit. Letā€™s see:

Site Example series Fake URL
Apple TV+ Ted Lasso https://tv.apple.com/us/show/sad-soccer-show/umc.cmc.vtoh0mn0xn7t3c643xqonfzy
Disney+ Bluey https://www.disneyplus.com/series/sad-dog-show/1xy9TAOQ0M3r
Peacock Killing It https://www.peacocktv.com/watch/asset/tv/silly-snake-scenarios/5156438808822262112

And indeed, two out of those three work. Good for Peacock going that extra mile.

In a distant last place, of course, are Hulu, Netflix, Max, and Amazon Prime, which donā€™t make an effort to be human-readable at all. Boo.

Michael in the Bathroom

September 3, 2023

Hereā€™s some more musical theatre. I think a lot of peopleā€™s introduction to the musical Be More Chill is this song, because itā€™s a real powerhouse performance.

I wonder how many of them, like me, were disappointed to learn that Michael is not the main character of the musical. Heā€™s the best friend of the main character, Jeremy.

Jeremy is being a kind of shitty friend, and Michael feels abandoned. Thatā€™s all you need to know to enjoy this song.

George Salazar performs this song again at the Tiny Desk Concert performance the production did:

Cā€™mon, what a voice!

Be More Chill is based on a young adult novel by Ned Vizzini which I havenā€™t read. When I was a young adult I recall enjoying his memoir Teen Angst? Naaahā€¦, which he published in 2000 while still a teenager. He died, apparently by suicide, in 2013. The reference to suicide in this song hits harder knowing that.

On a related note, perhaps the other great example of a musical theatre piece about debilitating anxiety in recent years is Surface Pressure from Encanto:

Fair warning to readers with siblings that this one might fuck you up a little, depending on whatever dynamics yā€™all are working with.

On another tangentically related note, hereā€™s one more terrific George Salazar performance, this one a duet with Michaela JaĆ© Rodriguez performing Suddenly Seymour: