TIL: Show the application switcher on all screens

I have multiple monitors and I use the cmd + tab application switcher often. I find the current behavior of macOS pretty confusing. By default, it only shows the switcher on the most recently used screen. But that’s not the screen I’m looking at when switching to another application!

After getting confused a bunch of times, I found this setting which lets you show the switcher on all screens:

defaults write com.apple.dock appswitcher-all-displays -bool true
killall Dock # you need to restart the Dock for settings to take effect

Screenshot of two screens with the same App Switcher active on each

It’s wonderful.

TIL: Control less options globally with LESS

While researching interactive flags in less I learned about the LESS env var. This lets you set less’s startup flags globally. For example, if you always want ANSI color escape sequences to be interpreted (so you see colors instead of control characters), put LESS=-R in your shell config.

export LESS=-R
less request.log

Screenshot of HTTP request log with HTTP status codes in color.

export LESS=
less request.log

Screenshot of HTTP request log with HTTP status codes surrounded by control characters like ESC[33m.

Printing a Shoebox Divider

We have piles of shoes in our entryway. We have kids who help the piles grow. We have a dog whose love language is taking shoes, nibbling them, leaving them in unexpected places, and returning for more until our living room contains two different flip flops, a sequined sneaker, a hiking boot, and one adult Croc (I’m guilty of piling too). We have a problem.

I’m a problem solver. I found some very nice, clear, stackable shoeboxes with drop fronts from The Container Store. I also found that they are $20 each and I don’t want to spend $250 on shoe storage (in fairness, they are less during sales). I found a cheaper version on Amazon and after 20 minutes of assembly I had them arranged in our closet. The boxes are large enough that they can accommodate two pairs of kids’ shoes or women’s flats, but it’s a little messy. I’ve been looking for opportunities to design and 3D print something custom and a divider for these shoeboxes seemed low stakes.

My plan was to design an I-beam to sit in the middle of the box, held in place with pressure from the top and bottom. First lesson: the parts of an I-beam are the flange (top and bottom) and the web (the tall part). It took me awhile to prototype the dimensions for the divider. The interior height of the box was hard to measure accurately. I ended up doing more test prints than I would have liked. I figured I would measure, print a sample, and adjust. Instead I printed five samples and made adjustments each time. By the end, I realized I could prototype more quickly with cardboard and used that to figure out the final height.

Screenshot of I-beam samples of various heights in Bambu Studio

I-beam samples of various heights that I printed to check the measurements.

I used Autodesk Fusion to design this simple part. It’s a very complicated program! The target audience is clearly professionals who know what they’re doing and likely have some training in engineering. Neither of those is me. That’s not to say it’s impossible to use—there are tons of tutorials and videos online—just that I could not open the program and figure out what to do without assistance. Fusion costs $700–$2000 a year but there is a personal use version that is free.

Fusion is a parametric modeling program, which is different from other programs I’ve used like SketchUp. Everything you define in your model is a “parameter” which can be adjusted in the future. For example, an object has such-and-such width and height, extruded by another dimension, offset from another object by this amount, and so on. You can use variables for these values, you can do math like divider_width_mm + border_mm, etc.

To make my I-beam I drew a rectangle with my height and width and extruded the depth. Then I added the flanges, setting the width I wanted to fit into the shoebox. I read that I should add a fillet between the web and the flange. So I planned to do that, except first I needed to figure out what a fillet was.

It’s fun to learn new stuff (including—or maybe even especially?—a complicated program like Fusion). I had heard the term “chamfer” before but didn’t actually know what it was. I had not heard the term fillet, but it’s related and what I needed. A fillet is a rounded transition on an edge. Two pieces that join at a sharp, 90° angle can be fragile, so you add a fillet to strengthen the part. In Fusion, this is easy: you select an edge and select Modify → Fillet.

Screenshot of three rectangular prisms in Fusion, one with an angled edge (a chamfer), one with a rounded edge (a fillet).

A rectangle with square edges (left), with a chamfer on one edge (middle), and with a fillet on one edge (right).

I decided to add a patterned perforation to the panel so that it looks nicer, uses less filament, and I learn how to do it. In Fusion, you do this by drawing your shape, constraining to a certain distance from the edge, and then turning it into a repeating pattern.

Screenshot of Fusion specifying parameters for the hexagon pattern on the side of the I-beam flange

Creating hexagon cutouts on the web of the I-beam by specifying parameters.

Screenshot of finished 3D model in Fusion

The finished model in Fusion.

After exporting from Fusion I brought the part into Bambu Studio to slice and print. I ended up printing the structure by laying it down on the bed so it looks like a capital letter I with serifs from the top view. This way it doesn’t need supports to print the upper part of the I (you can’t print into midair). It’s hard to think in 3D!

This was when I realized that the hexagons I picked had a steep angle that, in theory, has the same support problem. Instead of going back to the model and changing to, say, diamonds, I read that I might be able to get away with my 60°-off-vertical overhangs by printing at a smaller layer height, slower speed, and with more ventilation.

3D print of the divider in progress inside a Bambu Lab X2D printer
Finished printed divider, fit inside a clear plastic shoebox

It took about 5 hours to print. The edges of the hexagon were a little messy, but it worked! I’m pretty happy with the way this thing looks given that I had no idea how to use Fusion at the beginning of the project.

TIL: less lets you toggle startup flags interactively

I love the less pager. You can use it to read, search, tail, and more. As with many versatile programs, however, the man page for less has a bazillion different options:

$ man less

NAME
    less - opposite of more

SYNOPSIS
    less [-[+]aABcCdeEfFgGiIJKLmMnNqQrRsSuUVwWX~]

There’s hardly a letter it doesn’t use!

Of these, my favorites are:

  • -S or --chop-long-lines. Truncate lines rather than wrapping them.
  • -N or --LINE-NUMBERS. Show line numbers at the start of each line.
  • -R or --RAW-CONTROL-CHARS. Show control characters vs. interpret them.

You can supply these as flags when starting less. But you often don’t start less directly; it’s set as your pager and then gets invoked when you run commands that produce lots of output, like man or git. No matter because you can type the startup flags while less is running to toggle their behavior!

If lines are wrapped and you want to truncate them, just type -S. You’ll see a message at bottom describing the change and then you press enter to accept. Want to see what line number something is on? Type -N, enter.

Screen recording of opening an HTTP request log with less, then toggling escape codes to color, toggling wrapped long lines to be truncated, then enabling line numbers

As an aside: the dominant pager that preceded less was more. Despite the names, less has more features. On macOS, more is now less (or more precisely, /usr/bin/more and /usr/bin/less are the same file):

ls -lih /usr/bin/more /usr/bin/less
1152921500312523552 -rwxr-xr-x  2 360K Nov 22  2025 /usr/bin/less
1152921500312523552 -rwxr-xr-x  2 360K Nov 22  2025 /usr/bin/more

Whether you get more’s impoverished capabilities or less’s expansive set is determined by the name you use to invoke it.

TIL: Mojibake

I learned a new word today: mojibake!

Some of my old blog posts have random gibberish in them like ñ or ’. I always assumed this was the result of some character encoding mismatch but I didn’t know exactly what was going on. While I was fixing this issue via find/replace technology, I did some reading to learn more.

The short version is a mismatch between UTF-8 and Latin-1 (ISO-8859-1) encodings. As an example, this is how ñ becomes ñ.

First, when I type ñ in my CMS it is represented in UTF-8:

>> "ñ".codepoints
=> [241]
>> "ñ".bytes
=> [195, 177]

In UTF-8, codepoints above 127 are represented with more than one byte. You can see here the codepoint 241 (U+00F1) corresponds to two bytes, 195 and 177.

Then, when these bytes get written to the DB, they are not converted from UTF-8 to Latin-1. We store [195, 177] but believe they are Latin-1. Later, we re-encode these bytes as UTF-8 to convert from not-actually-Latin-1:

bytes = "ñ".bytes               # => [195, 177]
latin1_str = bytes.pack("C*").force_encoding("ISO-8859-1")
puts latin1_str.encode("UTF-8") # => ñ

I was fixing these bugs in old posts and one AI result said, “Oh yeah, this is a classic mojibake problem.” My first reaction was, “I don’t know man–How ‘classic’ can it be if I’m hearing it for the first time after working with strings and encodings for years?” But then again, you have to learn everything sometime. Maybe it is classic!

Mojibake or 文字化け is a Japanese word for exactly this kind of garbled text. Before UTF-8, the dominant character encoding in Japan was Shift JIS, and this kind of character mangling was probably common when text interacted with systems that did not convert encodings properly.

The “moji” in mojibake (文字) means text and, yes, it’s the same moji found in emoji (絵文字)! I knew that emoji originated in Japan but I did not know that the name was actually Japanese. I assumed that emo was related to emotion since the first emoji were all faces showing different emotions. Nope! E (絵) means pictures, moji (文字) means text.

Now that UTF-8 is the dominant text encoding, I’m pretty confident you’ll see all the text here correctly and not as a “classic” mojibake issue.

TIL: Spotlight can do math and unit conversions

You can open macOS’s Spotlight with cmd + space. Then you can type in math like (4 + 23) * 92 and it will show you the answer. I’ve been using this feature for awhile but I just learned that it can do a lot more than I realized! This came up because I typed 2**256 and it gave me the answer. Interesting…what else can it do?

Screenshot of Spotlight answers to math like round(4723/125) and sin(pi/2), as well as unit conversions like 95F and 10 miles in feet. The answer to each query is selected in blue so it can be copy/pasted.

I couldn’t find a comprehensive list of supported features. This Stack Overflow answer has a long list; one other commenter says it’s everything the macOS calculator can do. All of these things work:

  • Exponents, written as any of 2**3, 3^2, or pow(2, 10)
  • Factorials, written as 5! or fact(7)
  • Trigonometry, functions like sin, cos, tan, and constants like pi.
  • Lots of other math. You can round(), pow(), abs(), sqrt(), ceil(), floor(), and more.

The absolutely best part is that it does unit conversions. Historically this is one of the things I have always typed into Google search. I’m sure I’m going to use this all the time now. Type 95F and it tells you that’s 35°C. Type 170 lbs and it says 77 kg. You can specify the output unit like 4 Tbsp in mL or 10 miles in feet (it does not, however, support unhinged unit conversion like “1 mile in furlongs”).

One really nice UX touch is that Spotlight automatically selects the answer, so you can easily copy it with cmd + C without dragging.

TIL: pkill to stop a process by name

Sometimes when I learn about a tool I think “wow, that would have been useful [when I had some problem in the past]”. That happened with pkill, which is a command you can use to stop a process accurately.

Many years ago I was slogging through debugging a Selenium test failure in a Rails application with my coworker, Ron. Every time we made a change we had to stop the server, restart it, and re-run the test suite. This had a bunch of steps so naturally we wrote a script to automate them. At the end of the script, we killed the Rails server by looking for the process using port 3000 with kill -9 $(lsof -t -i:3000).

  • lsof lists open files. In unix/linux, everything is a file including things like network connections (via sockets).
  • -t terse output–just the pid
  • -i:3000 process using port 3000

It worked! Until one run when, all of a sudden, my entire browser disappeared. It was unexpected, abrupt, and rude. We were using that browser! Ron and I were really baffled … until we realized that there were two processes using the port: the server and the browser (maybe with a keep alive connection?).

This is what the lsof output looks like when you look for processes using a port:

$ lsof -i:3000 -P

COMMAND  PID    TYPE  NODE  NAME
ruby3.2  42727  IPv6  TCP   *:3000                           (LISTEN)
ruby3.2  42727  IPv6  TCP   localhost:3000->localhost:57537  (ESTABLISHED)
Google   6439   IPv6  TCP   localhost:57537->localhost:3000  (ESTABLISHED)

Notice that process ID 42727 is using :3000 in the LISTEN state, while that same process and Chrome are both using it for a connection in the ESTABLISHED state. So we got unlucky on one run, grabbed the browser’s process ID, and unceremonously killed it. At the time, we worked around this by adding another flag, -sTCP:LISTEN. This filters to TCP sockets in the LISTEN state, so we’d always get the server process.

pkill would have been perfect for this! It stops processes by matching the process name. For example, pkill ruby would stop all running ruby processes. That’s a little aggressive if you only want to stop one process. With pkill -f you match the program arguments as well, like ruby bin/rails so pkill -9 -f rails would do it. (You could also argue that I should have the server process itself write its pid to a file to later stop it. You’re probably right!)

One thing I love is programs that have two modes to either (1) take action or (2) preview that action. Many systems expose this as a “dry run”. In the case of pkill, the dry-run equivalent is pgrep, which works the same way, but just outputs the pid instead of sending a kill signal.

pkill has a ton of other options so be sure to look at the tldr or man page.

Twitter Hall of Fame

A poorly understood aspect of male culture is that we express affection by curating content on the Internet to share with the people we love. It’s a bit like being a modern hunter-gatherer. So instead of “Why is Doug showing me this dog video?” you should perhaps think “Wow, of all the content in the world Doug picked this for me. He really cares.”

Screenshot of Twitter Hall of Fame mini-site

Now honoring the inaugural 267 inductees.

Circa 2008 to 2023, I spent too much time on Twitter. Was it good for me? No. But did I love every minute of it? Absolutely not. I will say this, however: I curated the hell out of some tweets. Over 10,000 gems. Consider for a moment how many tweets a person has to read in order to find 10,000 good ones. A million? Ten million? That amount of curation takes a tremendous toll, but it’s just something the modern man does for his loved ones.

Thankfully we were able to close that chapter, but the long-term effects on my mind remain. Under certain conditions I am physiologically compelled to recall specific tweets. Poor financial decisions? Please help me budget this my family is dying. My past actions return to me? Me sowing, me reaping. My kids’ latest pile of plastic junk? Marie Kondo in tears. Some random stimulus I haven’t quite figured out? Uh your dragon really should be trained by now.

As these once-bright stars of Twitter fade, I opened the Twitter Hall of Fame to capture the dying light.

  • I crawled through the GraphQL API with credentials sucked out of my browser to download 10,064 old likes.
  • I put them in a little local UI.
  • I took these already curated tweets and ran them back again to find the ones that are (1) text-only, (2) not replies, and (3) made me laugh.
  • Then I took the 267 tweets that passed that filter and put them into a little single-page archive.

Yes, you read that right. I took already curated content and re-curated it. Please remember this is a great sign of respect and affection in my culture. Enjoy the tweets.

(Right after I finished everything, I remembered The Verge did the same project. If your thirst for content cannot be sated, I’m sorry, but definitely check it out.)

TIL: cmd + shift + G to Go To Folder

GUIs are great! But I’m used to typing cd, and sometimes I prefer that to clicking around to get somewhere.

MacOS merges these worlds with cmd + shift + G. In the Finder it’s under GoGo to Folder.... This opens a dialog where you can type a path to navigate to. It supports tab completion, it understands that ~ is your home directory, and it shows recently used options that you can navigate with your arrow keys.

Screenshot of Go to Folder window with ~/code/ in the input box and a list of possible destinations below

It also works in the open/save dialog boxes! This is more hidden; as far as I know there is no menu that shows “Go To Folder” so you just have to know the cmd + shift + G shortcut.

Screenshot of Go to Folder being used with an open/save dialog box in the background

Now when I’m not navigating to the Desktop, Documents, or Downloads (which each have their own keyboard shortcuts), I often use cmd + shift + G to get there.

TIL: badssl.com lets you test bad certificates

While working on Slack’s unfurl previews I needed to test what happens when the fetcher cannot access a site. People put a lot of different URLs into Slack; some of them are going to be unfetchable for various reasons. Pointing the fetcher at a URL that doesn’t resolve, or one that results in a timeout, is straightforward, but I don’t know enough to set up various bad SSL/TLS scenarios. Surely one of them is going to have a bad config at some point.

That’s when I found https://badssl.com. It’s a project from some security folks that provides tons of misconfigured sites for exactly this purpose. Want to see what happens with an expired cert? Go to https://expired.badssl.com. How about a cert that’s been revoked? That’s https://revoked.badssl.com/.

For example, this is how curl behaves when the certificate doesn’t match the domain name:

$ curl https://wrong.host.badssl.com/
curl: (60) SSL: no alternative certificate subject name matches target host name 'wrong.host.badssl.com'
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

This is what it looks like when you navigate to a site where the certificate has expired:

Screenshot of Chrome showing a warning for expired.badssl.com that says "Your connection is not private" and the error code ERR_CERT_DATE_INVALID.

I love that badssl.com gives you a quick way to check these behaviors interactively. For example, curl https://tls-v1-1.badssl.com:1011 works fine; going to that URL in Chrome presents a warning (because TLS v1.1 is deprecated).

There are more than 70 subdomains in 13 categories so you can trigger mixed content warnings, HTTP form behaviors, HSTS, and more.

TIL: Find shared lines with comm

diff is a well-known tool to see what lines are different between files; comm is a less-known tool to see the common lines.

I found comm when I was looking for a way to identify lines that are shared (or not) between files. Specifically, I had two lists: one with all the APIs in my codebase, and another with all the tested APIs. From this I wanted to get a list of untested APIs.

As an aside, my go-to in this situation is to drop in a Ruby one-ish-liner:

ruby -e "puts (File.readlines('all') - File.readlines('tested'))"

In Ruby, Array.- (minus) returns a new array after removing the elements from the right-hand side. IMO, this is pretty nice. Ruby has a wide variety of methods for combining arrays (+ to merge, - for difference, & for intersection, | for union). The only caveat is that this is trickier if you haven’t written the lines to a file. That’s pretty common in pipelines and was, in fact, exactly the situation in which I found myself.

Let’s say we have these two lists: mammals.txt and pets.txt:

$ cat mammals.txt
cat
dog
lion
rabbit
whale
$ cat pets.txt
cat
dog
fish
rabbit
snake

We can compare the files with comm:

$ comm mammals.txt pets.txt
		cat
		dog
	fish
lion
		rabbit
	snake
whale

The first time I saw this output I was pretty confused. Is this working? It kind of looks like I just dumped all the lines with weird indentation? comm produces output in three columns:

  1. lines that are only in the first file
  2. lines that are only in the second file
  3. lines that are in both files

Here’s a labeled version:

$ comm mammals.txt pets.txt
just          | just       |
mammals.txt   | pets.txt   | both
--------------+------------+-------
              |            | cat
              |            | dog
              | fish       |
lion          |            |
              |            | rabbit
              | snake      |
whale         |            |

You can use -1 to hide the first column, -2 to hide the second column, and, yes, -3 to hide the third column. In this case, if you want just animals that are mammals and pets: comm -12 mammals.txt pets.txt:

$ comm -12 mammals.txt pets.txt
cat
dog
rabbit

If you want pets that aren’t mammals, that would be comm -13 mammals.txt pets.txt.

To get back to my original use case, if I want a list of untested APIs, you write:

comm -23 all_apis.txt tested_apis.txt

comm works with process substitution so you can also write:

comm -23 <(command to find all APIs) <(command to find tested APIs)

One caveat: the input lines need to be sorted, so you may need to add | sort.

Summer of Shrub 2.0

Years ago I was at a coffee shop with my friend Nick. On a lark I ordered something on the menu that I had never seen before: a raspberry shrub. It was delicious! (and not a plant). I took a mental note to learn more about them, and when I got home I immediately did nothing. As time passed occasionally the thought would resurface like an itch. What was that drink called again? A shrub? Right, like a bush. I really should look it up. But the moment would pass. I repeated this process of not taking any action for quite some time until one day I was scrolling and the algorithm brought shrubs back to me. In fact it brought them back so often that I became A Guy Who Has Watched Too Many Videos On The Subject.

Three bottles of shrub syrups labeled “Sweet Bay-Peppercorn Shrub”, “Strawberry Balsamic Shrub”, and “Blackberry-Lime Shrub” Three shrubs I made at the beginning of the Summer of Shrub 2.0.

A shrub, I learned, is a mixture of a fruit syrup with an acid, usually vinegar, sometimes fruit juice. The combo is part of what makes it so refreshing. Instead of cloying sweetness, you get a more balanced taste. Shrubs are an acidulated beverage, sometimes called a drinking vinegar, that hit their peak popularity in the American colonial era. You can use them to mix cocktails or add them to sparkling water. Once you make a shrub, you typically let it rest in the fridge for a week or two for the flavors to combine and mellow. After that they’ll keep for months (this was historically one reason to make them–food preservation).

Armed with this knowledge I declared it the Summer of Shrub. Why dip your toe in a hobby when you can dive in? I had more shrubs than I could fit into the fridge. I bullied friends and coworkers into making their own. “Stop trying to make shrub happen!” I imagined naysayers naysaying. But none could be found: everyone else just sipped from a glass I foisted upon them and enjoyed a remarkably refreshing drink.

After taking a summer off, I’m back with the reboot: Summer of Shrub 2.0. This is a more demure edition, enthusiasm tamed by wisdom. You’re interested in trying some shrubs of your own? Oh sure, I’d be happy to share some tips.

If you’re getting started, two recipes I recommend are Christina Chaey’s Any-Berry Shrub or Michael Dietsch’s Cold-Processed Shrub at Serious Eats. In typical Serious Eats fashion, Michael’s recipe has background on what shrubs are, the various types, techniques, and more. Michael also wrote the book on shrubs. Naturally, I bought it. A few of my faves are the Strawberry Balsamic Vinegar Shrub and the Blackberry-Lime Shrub.

I really love Rebekah Peppler’s Sweet Bay-Peppercorn Shrub. It’s the backbone of a convincing non-alcoholic French 75. It has a lovely citrus + botanical flavor.

You absolutely do not need additional equipment to make shrubs–you almost certainly have everything you need in your kitchen now. But! I would hate to pass up an opportunity to recommend Crew Supply’s bottles. I keep my shrubs in them and they’re lovely.

As we hit the midpoint of summer, there’s still plenty of time to make shrubs (and make shrub happen).

TIL: Reorder columns, print the last with awk

awk is a whole programming language, but 90% of what I use it for is printing a subset of columns. This is pretty much covered by cut with two exceptions: re-ordering columns and printing the last column. Both cut and awk call columns “fields”.

Here’s some sample tab-separated data (formatted with column -t!):

FirstName  LastName  FavoriteColor  Pet
Elena      Kowalski  yellow         hamster
Marcus     Rivera    green          snake
Nadia      Ferreira  coral          rabbit
Yuki       Tanaka    pink           cat
Zara       Okonkwo   indigo         chinchilla

If you just want a list of favorite colors, you select the third field with cut -f3:

cat data.tsv | cut -f3
FavoriteColor
yellow
green
coral
pink
indigo

But if you want last name then first name, no dice. You can run cut -f2,1 but it will output them in the original order. For this I learned to use awk. You have to remember to specify the field separator with -F, then you put the program to run on each line in braces:

cat data.tsv | awk -F'\t' '{ print $2, $1 }'
LastName FirstName
Kowalski Elena
Rivera Marcus
Ferreira Nadia
Tanaka Yuki
Okonkwo Zara

If you want the last field in the input (in my case I was looking at log lines), you have two options: count the number of fields and use cut (no thanks, I’m too lazy) or use awk. The variable you want is $NF for “number of fields”. Since the fields are one-indexed, this will give you the last field.

cat data.tsv | awk -F'\t' '{ print $NF }'
Pet
hamster
snake
rabbit
cat
chinchilla

I usually have to look up -F'\t' even though I should probably remember “field separator” is -F.

TIL: Number.MAX_SAFE_INTEGER is 2**53 - 1

I was filling out a form today and a friend joked, “You should put Number.MAX_SAFE_INTEGER in the budget field.” Woah! I didn’t know we had that constant.

Number.MAX_SAFE_INTEGER was added to JavaScript in 2015 as part of ES6. The constant is new to me, but the value is a number I know by heart: 2**53 - 1.

When I worked at Twitter, we switched from 32-bit IDs generated by MySQL to 64-bit IDs generated by Snowflake. Late in the project–just a few weeks before launch–we hit a small road bump: we could not use the new IDs in the browser.

With the Snowflake IDs, sometimes you would like a tweet and the request would fail, “tweet not found”. You would scroll the timeline and duplicate tweets would appear. These bugs and others were caused by the way JavaScript represents numbers.

JavaScript implents IEEE-754, which uses 64 bits to represent all its numbers–big, small, floating point, integer. Practically this means that you do not get 64 bits to represent integers. Of the 64 bits, 1 bit represents the sign, 11 bits are used to represent the exponent of a number, and the remaining 52 bits are what’s left for the mantissa (what you typically think of as the actual number). This means you can only represent integers faithfully up to 2**53 - 1, Number.MAX_SAFE_INTEGER, 9007199254740991. (This was 2010 and support for bigints didn’t land until 2018-2020.)

The memory format of an IEEE 754 double-precision floating-point value. Illustration from Wikipedia

Above that limit, the numbers behave in unexpected ways:

> Number.MAX_SAFE_INTEGER
9007199254740991
> Number.MAX_SAFE_INTEGER + 1
9007199254740992
> Number.MAX_SAFE_INTEGER + 2
9007199254740992
> Number.MAX_SAFE_INTEGER + 3
9007199254740994

Tweets in the API came back like this:

{
  "id": 915710611442491393,
  "text": "..."
}

but the browser would parse the id as 915710611442491400. That’s close! But close isn’t usually what you want for IDs.

At the time, we solved the problem by adding "id_str" with a string representation of the ID. We couldn’t change or remove "id" because it was a widely used public API, but that vestigial field was a perennial cactus underfoot. People would do things like use the field, and get unexpected results when the ID was not the ID.

See also the archived source for snowflake and the archived twitpocalypse.com.

TIL: Pathname for file operations

I used Ruby extensively at Twitter and it remains one of my favorite languages, especially for tools and scripts. That said, my knowledge of production Ruby is largely from the 1.8.7 / 1.9.3 era, which is quite old. We’re now on Ruby 4.0.

I still write a lot of scripts and these days I ask Claude to write many of them for me. I noticed it wrote some stuff using the Pathname module. I assumed it was a newer addition to the language, but it’s extremely old–it was created in 2003.

Pathname wraps a path and provides an OO facade for lots of common file operations from different modules like File, Dir, and FileUtils. Without Pathname, you pass the path around and call methods:

dir = "./config"
path = File.join(dir, "app.json")

File.exist?(path)
File.read(path)
File.write(path, "new content")
File.directory?(path)
f = File.open(path)

With Pathname, you wrap the path and then can call methods on it directly:

dir = Pathname.new("./config")
path = dir + "app.json"

path.exist?
path.read
path.write("new content")
path.directory?
f = path.open

The facade for methods that typically return string paths wraps them as Pathname so it’s easy to chain:

dir = "./config"
Dir.glob(File.join(dir, "*.json")).each {|p| File.read(p) }

dir = Pathname.new("./config")
dir.glob("*.json").each {|p| p.read }

I wish I had known about Pathname sooner because I definitely would have used it more. Especially in Ruby, which emphasizes everything-is-an-object, it’s a nice improvement to my previous handling of paths.

A Year of Mondays

I started doing the New York Times crossword in their app. Once I got on a bit of a streak I thought “well, it would be a shame to let this arbitrary accomplishment end.”

Screenshot of NYT Games app: Congratulations! You finished a Monday puzzle in 7:04. 52 Mondays In A Row. 5:33 Faster Than Your Average.

Today I hit 52 weeks of solved puzzles! Writing about it is a little self indulgent, but Mondays are the easiest puzzles each week so we can agree not to be all that impressed.

I look up answers from time to time. I try to do it mostly in Wikipedia and learn something along the way. Here are some of my faves from my notes:

  • ARIANA is the flag carrier and largest airline of Afghanistan (and also obviously Ari).
  • There are spring tides and NEAP tides, which each happen twice a month.
  • 5.5 yards is a ROD.
  • “People are wrong when they say that the OPERA is not what it used to be. It is what it used to be–that’s what’s wrong with it” (Noël Coward, in one of his plays).
  • Nobelist IRENE Joliot-Curie synthesized radioactive isotopes. Her mother, father, husband, and brother-in-law also won Nobel prizes bringing the Curie family total to five.
  • OLIO is a mishmash or miscellaneous mixture.
  • NERTS is an old-timey oath (a 1930s euphemism for “nuts”).
  • SOU is a pittance (named for an old, low-value French coin).
  • Eric Arthur BLAIR wrote under the pen name George Orwell.
  • IDEE fixe is an idea or desire that dominates the mind; an obsession (This was my favorite solve of the year).

TIL: 8b/10b encoding

Programmers are forever coming up with clever ways to represent data more compactly. Like storing the entire English dictionary using a trie; or compressing fax transmissions 10x using run-length encoding; or storing thousands of values in a few kilobytes using probabilistic data structures. I might not come up with one of these schemes myself but when I read how they’re implemented I’m usually able to come away with an understanding of how they work, even if they feel a bit magical when you first hear about them.

I was on a random Wikipedia walk when I stumbled on 8 bit/10 bit encoding. My first reaction was complete bafflement. In what world can you represent 10 bits with just 8 bits? Are you tapping into some hidden quantum realm or something? But actually it’s the opposite: the encoding uses 10 bits to represent 8 bits of data. If anything that’s more interesting. Of course you can do it–no one wonders how you can fit 8 numbers into 10 slots–but why?

The answer is a reminder that I don’t really know much about the physical world of computers. It largely boils down to the fact that it’s problematic to transmit lots of 1s or 0s in a row. But sometimes we’re going to do that! If someone writes an email that has a bunch of consecutive @ signs (ASCII 64, 01000000), then we’re going to send tons of zeros over the wire. The issue with lots of 1s and 0s is that it causes problems with the voltage in the line, and problems with clock synchronization.

The voltage drift is called DC bias. Even after reading the article on it, I was a little lost.

The clock problem is easier for me to understand. When you’re receiving a message, you use a clock to decide when to sample the voltage to see if it’s high (1) or low (0). I imagine it like a metronome and on each tick you read a bit. But, of course, clocks are hard! It’s easy for them to drift and get out of sync. So you use transitions between 0 and 1 to re-synchronize (see “Clock Recovery”). If one of those transitions happens before your metronome ticks, you know that you’re running a little behind and can adjust. If you don’t have those transitions (because lots of 0s are being transmitted), then your clock can drift. When that happens the data is going to be wrong because you’re reading the voltage at the wrong time.

One solution to the problem of too many 1s or 0s is 8b/10b encoding. Because you have extra bits, you can use them to create multiple representations of the underlying bytes. Some of them are already balanced enough and you can send them as-is. For others you create different representations then you alternate between them to achieve balance between the number of 1s and 0s transmitted. In fact, 8b/10b makes that guarantee: if you send at least 20 bits, you will have practically the same number of 1s and 0s.

8b/10b encoding is used for some forms of Gigabit ethernet, USB 3.0, DisplayPort, and a variety of other protocols. Other systems use related line codes for similar reasons: Fast Ethernet (100BaseT) uses 4B5B; 10 Gbps Ethernet uses 64b/66b.

I enjoy living in software land where I don’t spend a lot of time thinking about these problems but it was fascinating to learn about.

TIL: brew bundle and Homebrew Casks replaced my bespoke scripts

Several years ago I tried to automate my new computer setup so that I could configure a new work laptop or home computer the way I like it easily. It went something like this:

  • Put a list of Homebrew formula in a file.
  • Write a script to loop over those and run brew install for each of them.
  • Keep a list of apps like 1Password, Sublime Text, VS Code. Download and install those.

The last time I revisited my setup, I found that almost everything I wrote could be replaced with built-in stuff (great!).

Homebrew added support for installing a bunch of formulas from a Brewfile by running brew bundle install. You can bootstrap a Brewfile from existing stuff you have installed with brew bundle dump. You can have multiple files–I keep a base Brewfile, and separate ones for work-only and home-only formulas in my dotfiles repo.

Homebrew also added support for Casks, which are packages for pre-compiled artifacts like applications and fonts. Now instead of searching for some program, downloading it, running the installer (or dragging to Applications), unmounting, and trashing the disk image, you can simply run brew install 1password.

Neither of these things are super new! I might even have been able to avoid writing my own bootstrapping scripts in the first place if I was paying attention. Support for bundles was added in 2013. Casks started in 2012 and eventually migrated to the main toolchain in 2016. Over time, Brewfiles have gained support for a ton of other things: casks, installation from the Mac App Store, VS Code extensions, go tools, python tools, and more.

My current Brewfile looks like this (edited for length):

# Core formula
brew "fzf"
brew "gh"
brew "git"
brew "jq"
brew "ripgrep"
...

# Apps
cask "1password"
cask "google-chrome"
cask "rectangle"
cask "sublime-text"
cask "visual-studio-code"
...

# Apps from the Mac App Store
mas "Pixelmator Pro", id: 1289583905
...

# VS Code extensions
vscode "braver.vscode-solarized"
vscode "golang.go"
vscode "shopify.ruby-lsp"
...

I deleted a bunch of code from my bootstrapping process. Now when I want to download and install an app, I start with brew search [name] and install via Homebrew so I can track install, uninstall more easily.

TIL: Use process substitution to tag stdout and stderr

I learned something new about shells today! You can use >(...) to write output to another process.

You are almost certainly familiar with command substitution: ls -l $(which git) runs which git first and substitutes that as an argument to ls -l (you can also use backticks but they can’t be nested like $(..)).

You might know about <(...) which lets you turn the output of a command into a pseudo file. This is process substitution. It’s helpful for commands that only operate on files. For example diff, which compares two files:

diff <(printf "abc\ndef\n") <(printf "abc\n123\ndef\n")
1a2
> 123

The way this works is that the output of <(cmd) becomes a file descriptor:

$ ls -l <(echo "abc")
prw-rw---- 0 ryan staff 8 Jan 28 11:21 /dev/fd/11

The p that you see in the ls output indicates that this is a pipe or “FIFO”; on Linux you’ll see slightly different output. Not all commands play nicely with these fds, zsh provides =(...) which writes the output to an actual temporary file:

ls -l =(echo "abc")
-rw------- 1 ryan wheel 8 Jan 28 11:22 /tmp/zshAnZjWQ

I wrote all of this up for the #cli-pro-tips channel I created at Slack many years ago (and rewrote it now because I did not put the content I wrote for that channel anywhere else–I wish I had started recording TILs long ago!).

Today I learned there is an entirely new way to compose this: >(...)

<(command)   # read from command's stdout
>(command)   # write to command's stdin

Specifically, I was wondering how I could tag the output of a command as going to stderr vs stdout without writing something that wraps the whole command. It turns out it’s easy with this technique.

Here’s a Ruby command that writes even numbers to stdout, odd numbers to stderr. By default, you can’t tell what is going where:

ruby -e '10.times {|i| (i.even? ? STDOUT : STDERR).puts i}'
0
1
2
3
4
5
6
7
8
9

You can redirect stdout and stderr to a program that will read the input and append the source.

ruby -e '$stdout.sync = true; 10.times {|i| (i.even? ? STDOUT : STDERR).puts i}' \
  > >(sed $'s/^/[STDOUT] /') \     # write stdout to one sed program
  2> >(sed $'s/^/[STDERR] /' >&2)  # write stderr to another
[STDOUT] 0
[STDERR] 1
[STDOUT] 2
[STDERR] 3
[STDERR] 5
[STDOUT] 4
[STDERR] 7
[STDOUT] 6
[STDOUT] 8
[STDERR] 9

Here > >(sed $'s/^/[STDOUT] /') redirects stdout to one sed program, and 2> >(sed $'s/^/[STDERR] /' >&2) redirects stderr to another.

(I added $stdout.sync = true to this example because it circumvents some output buffering that is a whole separate discussion.)

Here’s a version that’s harder to read but nice because it uses colors:

ruby -e '
    $stdout.sync = true;
    10.times {|i| (i.even? ? STDOUT : STDERR).puts i}' \
  > >(sed $'s/^/\e[32m[STDOUT]\e[0m /') \
  2> >(sed $'s/^/\e[31m[STDERR]\e[0m /' >&2)

Screenshot of program output where odd numbers are prefixed with STDERR in red and even numbers are prefixed with STDOUT in green

The uses for this technique seem pretty limited because in almost every other case I can imagine you would be better off with a regular pipe.

TIL: cmd + option + L to navigate to Downloads

For years–maybe decades–I have repeated these steps when facing an open/save dialog box to select a file.

If the file I want is on the Desktop, I use cmd + shift + D to navigate there. If it’s in my Downloads folder, which is common, I use this keyboard Rube Goldberg machine:

  • Press cmd + shift + H to navigate to my home directory
  • Start typing “Dow” to select Downloads
  • Press cmd + down to open the selected folder.

“Ryan,” you might say gently, “There’s a button right there. You know you could just click it?”

Screenshot of macOS open/save dialog box with “Downloads” in the sidebar, circled in red

The heart wants what it wants (to keep its hands on the keyboard).

But it turns out that every shortcut listed in the Finder’s Go menu also works in Open/Save dialog boxes.

Screenshot of macOS Finder’s Go menu showing destinations and their corresponding keyboard shortcuts. Documents: cmd + shift + O; Downloads: cmd + option + L; Desktop: cmd + shift + D; etc.

I must have missed cmd + option + L (presumably for downLoads). Now I use it all the time.