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.

Zola 0.23 and Tera 2

I just finished updating this site from Zola 0.22.0 to 0.23.2. It doesn't seem like this should be a big deal, but it turns out that Zola 0.23 ported the templating engine from Tera 1 to Tera 2. The biggest change in Tera 2 is the replacement of macros by hygenic function-like "components" which, to put it lightly, fucked my shit up.

I have a bunch of templates that make this site more pleasant; for example, if I want to insert an image I used to use a Zola shortcode called "figure" that I wrote:


{% if src is starting_with("http") %}
{% set url=src %}
{% elif src is starting_with("/") %}
{% set url=src %}
{% elif page %}
{% set url=get_url(path=page.path ~ src) %}
{% else %}
{% set url=get_url(path=section.path ~ src) %}
{% endif %}
{% if self_href %}
{% set href = url %}
{% endif %}
{% if thumbnail %}
{% set resized = resize_image(path=page.colocated_path ~ src, width=800, height=800, op="fit") %}
{% set url = resized.url %}
{% endif %}
<figure>
  {% if href %}
  <a href="{{href | safe}}">
    {% endif %}
    <img src="{{url | safe}}" {% if classes %}class="{{classes}}" {% endif %}{%if alt %}alt="{{alt}}" {%endif%} />
    {% if href %}
  </a>
  {% endif %}
  <figcaption>{{caption | markdown(inline=true) | safe}}</figcaption>
</figure>

This was called like {{figure(src="foo.png", caption="Whatever", thumbnail=true)}} and would figure out the right source for the image.

The replacement component looks like


{% component blog.figure(src: string, caption: string, alt: string = "", self_href: bool = false, thumbnail: bool = false, page: map = {}, section: map = {}) %}
	{% if src is starting_with(pat="http") %}
	{% set url=src %}
	{% elif src is starting_with(pat="/") %}
	{% set url=src %}
	{% elif page %}
	{% set url=get_url(path=page.path ~ src) %}
	{% else %}
	{% set url=get_url(path=section.path ~ src) %}
	{% endif %}
	{% if self_href %}
	{% set href = url %}
	{% endif %}
	{% if thumbnail %}
	{% set resized = resize_image(path=page.colocated_path ~ src, width=800, height=800, op="fit") %}
	{% set url = resized.url %}
	{% endif %}
<figure>
{% if href %}
<a href="{{href | safe}}">
{% endif %}
<img src="{{url | safe}}" {% if classes %}class="{{classes}}" {% endif %}{%if alt %}alt="{{alt}}" {%endif%} />
{% if href %}
</a>
{% endif %}
<figcaption>{{caption | markdown(inline=true) | safe}}</figcaption>
</figure>
{% endcomponent %}

You use this as {{<blog.figure src="foo.png" caption="whatever" thumbnail={true} page />}}

A couple of important changes:

  • The page / section global needs to be passed in explicitly now because there's no way to reference globals any more (I filed getzola/zola#3223 about this)
  • You can't indent any more, because components run before markdown processing, so they have to output valid markdown (which means any inline HTML needs to be un-indented; I filed getzola/zola#3224 about this)
  • The new fake-HTML syntax really screws up syntax highlighting and has a totally unnecessary JSX-style curly-bracket wrapping of non-text values (even though you also need to quote string values, so, again, this serves no purpose).

Another change that's in Zola 0.23 that drove me a bit mad is that the group_by filter now outputs an unsorted hash map instead of a sorted map, and the workaround is gross and requires using {% set_global %}. Hoping that upstream adds the ability to sort maps at some point (or just switches to a BTreeMap instead of a HashMap).

Anyhow, it took me about an hour to port everything over and there are almost certainly some screwy pages still on the site. Drop me a line if you notice any!

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 40[1] hours a week because they get paid for it. If you want them to put in the extra effort and write blog posts[2] 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 shortsightedness[3]. 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.0[1] was a tiling window manager and competed with the overlapping-window desktop metaphor of the original Macintosh OS[2]. 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 concept[3] 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 satisfaction[1]. 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 rates[2], 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 planet[3] 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!