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.
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.
In this industry, more often 50 or 60, plus 24/7 on-call coverage ↩
Especially high-quality ones that multiple candidates will cite during interviews ↩
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... ↩
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
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.
My desktop right now under AeroSpace, showing several windows in an accordion on the left and two windows tiled on the right
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/commandsafter-startup-command = []start-at-login = true# Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#normalizationenable-normalization-flatten-containers = trueenable-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 featureaccordion-padding = 30# Possible values: tiles|accordiondefault-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 orientationdefault-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-appautomatically-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#modeon-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"]
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 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.
Technically, Niri/Paneru are "sliding" window managers, which represent the desktop as an infinitely-long one-dimensional space that your viewport slides across ↩
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.
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. ↩
although obviously high enough that you can become a prominent computer person by working around them↩
"prompt engineering", a.k.a. trying to randomly guess a series of english words whose highest-probability autocompletion
will be the output you want ↩
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.
I wrote another blog post on my employer's blog about a little
side quest I did to replace part of our system which used v8 to use QuickJS-NG
instead. Nothing earth-shattering, but it's always nice to make slow things go faster.
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!
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.
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. ↩
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.
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.
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
not really in the cards with a six-month-old and a five-year-old ↩
unless you want to for fun, I guess? I'm not the optimization police. ↩
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! ↩