Welcome to James Brown's blagoblag! This contains various thoughts and opinions, mostly wrong, going back a couple of decades. All of the opinions are my own, and probably not my employer's. Feel free to visit the about page for more useless interesting facts about me.

cool companies don't invalidate links

Yesterday, a former coworker reached out to ask if I had a copy of a piece of software that we'd open-sourced at easypost, which he couldn't find. I investigated and found out that not only was that repository mysteriously deleted, but a bunch of the things we open-sourced there. This comes about a year after I learned that all of the engineering blog posts had been deleted and the entire corporate blog was just obvious low-quality AI SEO slop now.

Look, I recognize that that corporations aren't your friends, unless it's written in a contract somewhere it doesn't exist, and yadda yadda.

JFC, though – if you are in any position of power at a software company, don't do this!

Your employees give you their 401 hours a week because they get paid for it. If you want them to put in the extra effort and write blog posts2 or write open-source software that boosts your standing in the community? You'd better not goddamn delete it with no notice a couple of years later. If you really can't afford the $0/repo it costs to host something on Github, then reach out to the original authors and let them take it over. This isn't just bad stewardship, it's the worst kind of shortsightedness3. The implied social contract is that when employees produce extracurricular public content for you, you keep it around to be part of their portfolio.

Anyhow, if you were looking for it, ferrous-socks is back online with the latest clone I had, and even with some new fixes and updates. I hope it brings someone out there some value or joy.

  1. In this industry, more often 50 or 60, plus 24/7 on-call coverage

  2. Especially high-quality ones that multiple candidates will cite during interviews

  3. A few people I've talked to have suggested that the executive team has succumbed to AI psychosis and believes that they don't need to try to hire/retain software engineers any more, because LLMs are going to take over the world or something. I guess I can imagine what that means for my stock value...

trying out aerospace, a macos window manager

Back when I used Linux on the desktop, I used to use this great window manager called "awesome". It's probably the thing I've missed the most since switching to macOS, especially when on small screens like the 14" display of this MacBook I'm typing on. Awesome was what is known as a "tiling window manager". This means that rather than having a bunch of windows overlapping on some number of virtual desktops, windows are arranged into nonoverlapping segments called "tiles". Tiling window managers have been around for a long time; Windows 1.01 was a tiling window manager and competed with the overlapping-window desktop metaphor of the original Macintosh OS2. Tiling window managers require a lot of persnicketey window management to be useful (because otherwise you just end up with a bunch of unusably-tiny rectangles vanishing into the distance), but if you're willing to put up with it they can give you a super-fast way to organize and manage your applications. Tiling window managers often lean heavily onto the concept of "virtual desktops", allowing you to quickly switch between different sets of windows.

Macintosh windowing isn't bad. The original concept3 of overlapping windows whose order can be micromanaged is a great way to scale from one to several windows. Mac OS X 10.5 Leopard added a virtual desktop feature called "Spaces" that isn't terrible (especially once you add the "Displays have separate Spaces" option), but the animations are insufferably long and keyboard accessibility is pretty limited.

Anyhow, it turns out that nowadays, there are a few options for tiling window managers on macOS. The top few seem to be

There's also a few very new ones:

  • yashiki looks cool (and is actually very close to the design of awesome), but it appears to be heavily "vibe-coded" and I wasn't able to get it to work.
  • rift also looks cool, but crashed Dock.app when I tested it

I played with them all, but for the last week or so I've been using AeroSpace exclusively.

Desktop under AeroSpace
My desktop right now under AeroSpace, showing several windows in an accordion on the left and two windows tiled on the right

Setting Up

Installation (presuming you're using Homebrew) is very straightforward:

brew install --cask nikitabobko/tap/aerospace

You'll have to go to System Settings → Privacy & Security → Accessibility and add both /Applications/AeroSpace.app. At this point they can control your whole computer, so, uh, here's hoping they aren't malware.

AeroSpace is configured via a TOML file at ~/.aerospace.toml.

config-version = 2

# You can use it to add commands that run after AeroSpace startup.
# Available commands : https://nikitabobko.github.io/AeroSpace/commands
after-startup-command = []

start-at-login = true

# Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#normalization
enable-normalization-flatten-containers = true
enable-normalization-opposite-orientation-for-nested-containers = true

# See: https://nikitabobko.github.io/AeroSpace/guide#layouts
# The 'accordion-padding' specifies the size of accordion padding
# You can set 0 to disable the padding feature
accordion-padding = 30

# Possible values: tiles|accordion
default-root-container-layout = 'tiles'

# Possible values: horizontal|vertical|auto
# 'auto' means: wide monitor (anything wider than high) gets horizontal orientation,
#               tall monitor (anything higher than wide) gets vertical orientation
default-root-container-orientation = 'auto'

# Mouse follows focus when focused monitor changes
# Drop it from your config, if you don't like this behavior
# See https://nikitabobko.github.io/AeroSpace/guide#on-focus-changed-callbacks
# See https://nikitabobko.github.io/AeroSpace/commands#move-mouse
# Fallback value (if you omit the key): on-focused-monitor-changed = []
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']

# Also see: https://nikitabobko.github.io/AeroSpace/goodies#disable-hide-app
automatically-unhide-macos-hidden-apps = false

# List of workspaces that should stay alive even when they contain no windows,
# even when they are invisible.
# This config version is only available since 'config-version = 2'
# Fallback value (if you omit the key): persistent-workspaces = []
persistent-workspaces = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "Z"]

# A callback that runs every time binding mode changes
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
# See: https://nikitabobko.github.io/AeroSpace/commands#mode
on-mode-changed = []

# Possible values: (qwerty|dvorak|colemak)
# See https://nikitabobko.github.io/AeroSpace/guide#key-mapping
[key-mapping]
    preset = 'qwerty'

# Gaps between windows (inner-*) and between monitor edges (outer-*).
# Possible values:
# - Constant:     gaps.outer.top = 8
# - Per monitor:  gaps.outer.top = [{ monitor.main = 16 }, { monitor."some-pattern" = 32 }, 24]
#                 In this example, 24 is a default value when there is no match.
#                 Monitor pattern is the same as for 'workspace-to-monitor-force-assignment'.
#                 See:
#                 https://nikitabobko.github.io/AeroSpace/guide#assign-workspaces-to-monitors
[gaps]
    inner.horizontal = 0
    inner.vertical =   0
    outer.left =       0
    outer.bottom =     0
    outer.top =        0
    outer.right =      0

# 'main' binding mode declaration
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
# 'main' binding mode must be always presented
# Fallback value (if you omit the key): mode.main.binding = {}
[mode.main.binding]

    # All possible keys:
    # - Letters.        a, b, c, ..., z
    # - Numbers.        0, 1, 2, ..., 9
    # - Keypad numbers. keypad0, keypad1, keypad2, ..., keypad9
    # - F-keys.         f1, f2, ..., f20
    # - Special keys.   minus, equal, period, comma, slash, backslash, quote, semicolon,
    #                   backtick, leftSquareBracket, rightSquareBracket, space, enter, esc,
    #                   backspace, tab, pageUp, pageDown, home, end, forwardDelete,
    #                   sectionSign (ISO keyboards only, european keyboards only)
    # - Keypad special. keypadClear, keypadDecimalMark, keypadDivide, keypadEnter, keypadEqual,
    #                   keypadMinus, keypadMultiply, keypadPlus
    # - Arrows.         left, down, up, right

    # All possible modifiers: cmd, alt, ctrl, shift

    # All possible commands: https://nikitabobko.github.io/AeroSpace/commands

    # See: https://nikitabobko.github.io/AeroSpace/commands#layout
    alt-slash = 'layout tiles horizontal vertical'
    alt-comma = 'layout accordion horizontal vertical'

    # See: https://nikitabobko.github.io/AeroSpace/commands#focus
    alt-h = 'focus --boundaries-action wrap-around-the-workspace left'
    alt-j = 'focus --boundaries-action wrap-around-the-workspace down'
    alt-k = 'focus --boundaries-action wrap-around-the-workspace up'
    alt-l = 'focus --boundaries-action wrap-around-the-workspace right'

    alt-q = 'focus-monitor left'
    alt-e = 'focus-monitor right'

    # See: https://nikitabobko.github.io/AeroSpace/commands#move
    alt-shift-h = 'move left'
    alt-shift-j = 'move down'
    alt-shift-k = 'move up'
    alt-shift-l = 'move right'

    # See: https://nikitabobko.github.io/AeroSpace/commands#resize
    alt-minus = 'resize smart -50'
    alt-equal = 'resize smart +50'

    # See: https://nikitabobko.github.io/AeroSpace/commands#workspace
    ctrl-1 = 'workspace 1'
    ctrl-2 = 'workspace 2'
    ctrl-3 = 'workspace 3'
    ctrl-4 = 'workspace 4'
    ctrl-5 = 'workspace 5'
    ctrl-6 = 'workspace 6'
    ctrl-7 = 'workspace 7'
    ctrl-8 = 'workspace 8'
    ctrl-9 = 'workspace 9'
    ctrl-0 = 'workspace Z'

    ctrl-left = 'workspace prev'
    ctrl-semicolon = 'workspace prev'
    ctrl-right = 'workspace next'
    ctrl-quote = 'workspace next'

    ctrl-shift-semicolon = 'focus-monitor prev'
    ctrl-shift-quote = 'focus-monitor next'

    # See: https://nikitabobko.github.io/AeroSpace/commands#move-node-to-workspace
    ctrl-shift-1 = 'move-node-to-workspace 1'
    ctrl-shift-2 = 'move-node-to-workspace 2'
    ctrl-shift-3 = 'move-node-to-workspace 3'
    ctrl-shift-4 = 'move-node-to-workspace 4'
    ctrl-shift-5 = 'move-node-to-workspace 5'
    ctrl-shift-6 = 'move-node-to-workspace 6'
    ctrl-shift-7 = 'move-node-to-workspace 7'
    ctrl-shift-8 = 'move-node-to-workspace 8'
    ctrl-shift-9 = 'move-node-to-workspace 9'
    ctrl-shift-0 = 'move-node-to-workspace Z'

    alt-tab = 'workspace-back-and-forth'
    alt-shift-tab = 'move-workspace-to-monitor --wrap-around next'

    alt-space = 'fullscreen'

    alt-shift-semicolon = 'mode service'

    ctrl-shift-enter = "exec-and-forget kitty -1 --detach -d $HOME"

# 'service' binding mode declaration.
# See: https://nikitabobko.github.io/AeroSpace/guide#binding-modes
[mode.service.binding]
    alt-shift-semicolon = 'mode main'

    esc = ['reload-config', 'exec-and-forget osascript -e "display notification \"aerospace config reloaded\" with title \"aerospace\""', 'mode main']
    r = ['flatten-workspace-tree', 'mode main'] # reset layout
    f = ['layout floating tiling', 'mode main'] # Toggle between floating and tiling layout

    alt-shift-q = 'move-workspace-to-monitor left'
    alt-shift-e = 'move-workspace-to-monitor right'

    alt-shift-h = ['join-with left', 'mode main']
    alt-shift-j = ['join-with down', 'mode main']
    alt-shift-k = ['join-with up', 'mode main']
    alt-shift-l = ['join-with right', 'mode main']

[[on-window-detected]]
    if.app-id = "com.markmcguill.strongbox"
    run = ['layout floating']

[[on-window-detected]]
    if.app-id = "com.1password.1password"
    run = ['layout floating']

[[on-window-detected]]
    if.app-id = "com.iconfactory.Tot"
    run = ['layout floating']

[[on-window-detected]]
    if.app-id = "com.apple.mail"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.apple.MobileSMS"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.gather.GatherV2"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.tinyspeck.slackmacgap"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "net.shinyfrog.bear"
    run = ["move-node-to-workspace Z"]

[[on-window-detected]]
    if.app-id = "com.mimestream.Mimestream"
    run = ["move-node-to-workspace Z"]

Add-Ons

There are a few useful addons I'd recommend; the first is SwipeAeroSpace, which lets you remap 3- or 4-finger swipes to switch virtual desktops in AeroSpace. If you already use BTT or equivalent, you don't need this.

Install it with

brew install --cask mediosz/tap/swipeaerospace

Then add it to Accessibility in System Settings (just like you did for AeroSpace) and launch the app.

The next thing I've been working on is an Alfred workflow to control Aerospace. You can download a prototype of it at 📁 aerospace.workflow. It supports the new alfred triggers asp to bring up the aerospace command menu, and aw to quickly switch workspaces.

Alfred workspace
Alfred showing the 'asp' action from this workflow

It's still super-janky, so I haven't published it to the workflow gallery.

A lot of people online use tools like SwiftBar and SketchyBar with these alternate WMs, but I haven't seen any reason to do so yet.

Anyhow, that's where I have it today. Maybe I'll blog some more if this sticks for a few months.

  1. Circa 1985

  2. Macintosh System Software

  3. Faithfully borrowed from Xerox, of course

  4. Technically, Niri/Paneru are "sliding" window managers, which represent the desktop as an infinitely-long one-dimensional space that your viewport slides across

what makes programming great?

Most of the time, when I'm actually doing it, I love my job. There's a reason that computer programming (or "software engineering" if you're highfalutin) attracts so many people, and it's not just the unsustainably high salaries at overvalued tech companies or the promise of free gogurt in a corporate cafeteria. I want to take this post to try and make a case for what makes it so great.

The first aspect I wanted to discuss, which I think is pretty well covered, is that programming is fundamentally a creative act. Even in the worst slop-house where you're writing boring Java or Go code that converts one form of ProtoBuf to another, you are making the decisions on how to do that, you are structuring the code, and you get to enjoy the satisfaction of building something yourself. Creating and building something is one of the most essential human joys there is, and is essential to human satisfaction1. Most of the time, there's more than one way to do it, and even for the simplest program, the design space is enormous enough that no two people will come up with the same approach. Exploring this space and deciding how to tackle a problem is beautiful.

The next aspect that I want to cover is that computers are close to perfect, something that's true in so few other fields. Computers have ridiculously low error rates2, and are perfectly deterministic unless you go out of your way to make them do something pseudorandom. If you write a program correctly, it'll respond the same way every time. Now, of course, you may still have bugs based on different inputs, or different states of the machine; I'm not trying to say that we all live in a strongly-typed pure-functional utopia. But compare this to other creative endeavors — no two pieces of wood that you cut will be the same every time; even if every ingredient looks the same, the dish is always going to be different. But when you program computers, you have this wonderful opportunity to hone a single project without worrying about variance or materials degradation.

Finally, and most important to me, everything in computing is knowable. Computer science is a small, shallow field that is deliberate about building reliable layers of abstractions. There was a blog post going around a few days ago whose conclusion is that, basically, slopcoding only makes things slightly worse because nobody actually understands the whole stack already. I couldnt't disagree with this more vociferously. Yes, obviously nobody knows everything! But somebody knows each thing (because these are all systems built by humans), and the beautiful thing about working on computers is that you can know any thing! I've had times in my career when I've had to work on kernel interrupt scheduling code; I do have a pretty good idea how the memory model of ARM processors works; I have built processors by drawing MOSFETs in Magic. And, yes, I don't know everything listed off in that blog post, but I know how to learn them if and when I need to. Because my job isn't just to produce widgets of output for some corporate masters; it's to grow and improve as a person and as at my career, so knowing how to learn new things and improve myself is perhaps the most key skill.

Obviously, this is a post about LLMs, which are pseudorandom lie factories which require their users to do you do the least-creative mind-numbing activities on the planet3 and encourage humans to adopt a kind of learned helplessness — to convince us all that the unexamined job is worth working. Maybe this is the future of my industry, the inevitable enshittification of creative work into a satire-of-a-satire, where former knowledge workers pull levers on an expensive slot machine until something that they don't understand comes out that meets a Business Need and makes some executive 0.003% richer, but I damn well hope it's not a future I'll ever participate in.

  1. people keep telling me that there are huge classes of people, called "managers", who get more satisfaction from ordering someone else to do something than from doing it themselves. This seems like a defect if true, but based on the general level of dissatisfaction and alcoholism in all the managers I know, I kind of think it isn't true.

  2. although obviously high enough that you can become a prominent computer person by working around them

  3. "prompt engineering", a.k.a. trying to randomly guess a series of english words whose highest-probability autocompletion will be the output you want

Some Good Stuff in 2025

I'm trying to get into a habit of reflecting on some good stuff from the past year at the end of the year; I did it last year and the year before, at least. It continues to be hard to get into the mood to think about good things while the world's burning, jack-booted gestapo are shooting fellow citizens, and my entire industry has been brain-rotted by the great "AI" scam, but, you know, gotta try to look on the bright side.

read more

⤭ Row Level Security: Defense in Depth

I wrote a blog post over on my employer's blog about how to use row-level security patterns in postgres and clickhouse and I think it's pretty neat. Every company I've worked at has done multitenancy inside of SQL databases, and the approach to prevent cross user access has basically boiled down to git gud; they've also all had at least one incident where some endpoint forgot to check permissions and you could access other users' data (sometimes just by incrementing an auto-incrementing ID in a URL). This is the first attempt I've seen to comprehensively fix that, so I wrote it up. Enjoy!

COVID-19 Update

It's been five years since the pandemic hit, and three years since the last time COVID hit our family, but it's hit for real now. My five year perfect-streak has been ended1.

My daughter came home from day-care sick on Monday and I started feeling bit crummy late Thursday afternoon. Lo and behold, not only am I positive for COVID, but so is everyone else in the family. The kids are mostly okay — the baby had a fever on Tuesday but has been fine since then, and my son has no symptoms — but my wife and I both have fevers and aches and fatigue and coughs and the whole nine yards. Let me tell you, having two healthy and rambunctious children home from school while the adults have COVID is the absolute worst.

Anyhow, I got a Paxlovid prescription. So far (< 12 hours in) it's not doing much for the symptoms, but everything tastes like rusty nails now. Yay.

Stay safe out there, nerds.

  1. Note: While I have not, to the best of my knowledge, had COVID since testing became available, I actually think there's a good chance that my wife and I both had COVID already — in late January 2020. Eva came back from a prison trip and we both came down with a virulent and miserable flu. This is before COVID was officially in the US, but it still seems likely to me.

Cooking is better than computers

I don't have time for a lot of hobbies these days, but one of my favorites is preparing food. Whether that's cooking four courses out of Julia Child1, grilling some meat, or just tossing a salad together, there's something deeply satisfying at turning ingredients into a meal. In some ways, it's not unlike programming; anyone can do a passable job using inferior tools and following other peoples' recipes, but there's almost infinite space to tinker, invest in better tools, and learn to make your own recipes. Also, as long as you follow some simple rules, the worst outcome you're likely to have is something that doesn't taste great.

More than that, though, it's something that's just fundamentally human. Despite what some of my Soylent-chugging former executive leaders might thing, making and eating food is an activity that connects us all. The forces of capitalism are trying as hard as they can to give us a world where everybody eats soulless chain-restaurant slop delivered by underpaid Gig Economy workers, but you can still make something all your own; you can still find joy in something that has no product-market fit, no CSAT, and nothing to optimize2.

Some good things

I got a new knife recently (a 200mm Senzo bunka knife). Does it make the food taste better than if I'd cut it with my 15-year-old chef's knife? No, but it makes the act of prepping food a bit more fun. It's wildly too fancy for my skill level, but ¯\(ツ)/¯.

I also replaced my 15-year-old All-Clad stainless steel frying pan with a new 11" Demeyere frying pan3. The old one's rivets were starting to come apart, so I got one without rivets!

The original Moosewood Cookbook continuous to be joyous; any recipe you cook is sure to include some novel ingredients and techniques.

Some good fresh spices can make all the difference to a simple dish — some tellicherry peppercorns and Calabrian chile powder (and oil and salt, of course) is really all you need for some quick sautéed or grilled vegetables.

The Temescal farmer's market is a great place to blow $50 on berries that your 5-year-old will eat all of on the walk home. And lots of other good produce, too. Oh! And the Garden Variety Cheese Hollyhock is so good.

Some bad things

Fuck Samsung appliances. Our house came with two of them: a range and a fridge. They're both stupid.

The range (which doesn't have a model number on it, but looks like the NX60A6511SS/AA) has capacitive touch buttons on top of it. Which are triggered by steam. Which means that if I boil something on the stovetop, it will turn on random settings on the oven. It's infuriating to be making a sauce and suddenly have the oven turn on at 700°F for no reason. Also, if they're damp, then don't respond to finger touches, which means that you then can't turn the oven off without getting a towel and drying the buttons. Such a fucking stupid design.

The fridge is a four-door model with a "Flex Zone", which means you can convert half of the freezer from a freezer into more fridge space. I have no idea why you'd want this (it leaves you with an absolutely minuscule freezer), but it's mapped to a single touch of a capacitive button, which means once in a while someone will brush by it and not notice and then half of your freezer will melt. There's a "control lock" feature, but it also completely disables the ice maker4 and water dispenser, so it's not really usable.

Outside the realm of Samsung appliance crap? It's become impossible to find recipes or blog posts about cooking on the Internet that haven't become LLM-slop-filled-nonsense. Thankfully, there are hundreds of years of writing about food that predate Truthiness as a Service.

Auf Wiedersehen and good night

  1. not really in the cards with a six-month-old and a five-year-old

  2. unless you want to for fun, I guess? I'm not the optimization police.

  3. no, I did not pay $190 for it, I'm not crazy

  4. which is very bad: if you let the ice maker go for more than about a week without using all the ice, it'll freeze over solid, the plastic parts will crack, and the whole thing will have to be replaced. This has happened to us twice so far!