The Destructors, or, Yet Another Rant About That Basecamp Post

First, here’s a link to the lazy Googlers among you who don’t know what I’m referring to.

At a historical/philosophical level, the entirety of that Basecamp post is antithetical to the prevailing values of American workplace culture today, at least among white-collar workers who demand that their employers make deliberate, overt efforts to effect social and political change. Whether or not blue-collar workers wish to make those same demands of their employers is a moot point. The gutting of unions have left them without any bargaining power. White-collar workers — “skilled” labor, a perniciously false term — enjoy the privilege to bargain by virtue of being competed-for by multiple prospective employers.

All this is received wisdom by now, received gravely and with simmering, powerless anger. What is new and fascinating to me is not that Basecamp is experiencing such a backlash from the Twitteratti. From a superficial (and true) perspective the backlash is almost entirely deserved. Rather, the interesting thing is to understand why there is such a backlash. To understand that requires acknowledging the unusual role that the American workplace has taken in our lives, unusual relative to previous centuries of Western culture.

Neoliberalism, the prevailing ideology of our times, continues to eat the world. Under neoliberalism, “the market” and an illusory “freedom of choice” are the organizing principles governing human bodies. Employment/employer have seized the scepter that was once held by religion/church. “What do you do?” is the de rigeur ice-breaker question of our times. Whether we like it or not, the tides of Western culture, at least in the US, have plunged us into a worldview (usually unspoken and unexamined) that makes work the center of one’s life. It’s not a surprise that most workplaces are flowing along with that tide. It is in the nature of tides that few can resist them. At a large enough company, you can practically live your entire life on the company campus: eat, exercise, shower, get child care, sleep, play, relax, do yoga, get medical attention. There was a time when this kind of lifestyle was viewed as dystopic. It’s a relatively recent invention (the last century or so) that we expect the average person not only to work, but to have a vocation. For most of the previous millenia, it was viewed as a kind of doom or failure to be employed by an employer (serfdom). Attitudes have shifted over the past century, coinciding with the loss of influence from historically powerful religious and secular institutions. That power vacuum was filled by work. Work as the center of one’s life. Work as an identity. Work as the only place that people gather with folks outside their immediate circle of family and friends.

As I reread that Basecamp post, it strikes me as extremely at odds with the status quo of the culture and values of the American workplace. Each bulleted decision and change moves the company away from being central to employee’s lives and instead “back” to a more restrained, vintage view of the role of the workplace. I’m not arguing for or against their views here, I’m pointing out that if you can’t put a finger on why the post is bothersome to you, it is in large part due to the fact that the entire thing is countercultural.

In Graham Greene’s short story The Destructors, a group of idle teenage boys systematically dismantle a working man’s house that had barely survived the German bombings of London. They boys moved meticulously through the house, prying up every plank and tile, sawing down every interior wall and joist, until the only thing left standing was a wythe of brick along the perimeter of the house. A single wood pole remained outside propping up the house, a remnant from the war, which the boys tied to the bumper of a neighbors car.

At seven next morning the driver came to fetch his lorry. He climbed into the seat and tried to start the engine. He was vageuly aware of a voice shouting, but it didn’t concern him. At last the engine responded and he backed the lorry until it touched the great wooden shore that supported Mr Thomas’s house. That way he could drive right out and down the street without reversing. The lorry moved forward, was momentarily checked as though something were pulling it from behind, and then went on to the sound of a long rumbling crash. The driver was astonished to see bricks bouncing ahead of him, while stones hit the roof of his cab. He put on his brakes. When he climbed out the whole landscape had suddenly altered. There was no house beside the car-park, only a hill of rubble.

That house is Western social order, and those boys destroying it are the forces of neoliberalism and capital eroding every form of social belonging and power except for one’s employer. Your employer, if you’re a white-collar worker, is that lone wooden strut propping up the house. When a company like Basecamp announces that they’re trying to retreat to a yesteryear posture of detachment, I feel complex emotions. On the one hand, I bitterly despise living in a world that has been so thoroughly gutted that we have to go groveling to our employer to effect social change. Isn’t this supposed to be a goddamn democracy? Why should Basecamp employees be reliant on motherfucking Basecamp to exert sociopolitical influence? On the other hand, I recognize that this shitty world, in all it’s blistering shittiness, is the only world we have, the only world that actually exists. If all we have is that one rickety strut propping up the whole edifice, we had better guard it with our lives.

|  3 May 2021

Deep Fake

(The following is an excerpt from a short story. Read the whole thing here.)

On weekday mornings the Safespace product leadership gathers in an airy conference room for triage. Usually it’s forgettable stuff, scaling issues with a server cluster or the homepage stumbling over a program error, but today’s different. Ralgo, the founder and CEO of Safespace, mounts the dais and says there’s good news and bad news. The bad news is Vencent, our senior Distressing Content Moderator, jumped off a balcony in the rotunda and burst open on the foosball table. The good news is Vencent’s replacement is already lined up so none of our deadlines will slip.

The news of Vencent’s suicide lands on the leadership team in uneven ways. Glarry is sobbing with Aimie in the Quality Assurance pit, grieving the loss of their Borg LARPing comrade. Stavros in Image Analysis says nothing, dons headphones and resumes tweaking the scrotal recognition model. As for me, well, I suspect I’m in for a reaming when Ralgo tells me to meet him in the Privacy Cylinder.

Vencent’s death marks the third time I’ve lost a Distressing Content Moderator in the last eighteen months. The previous one flung himself in front of the Palo Alto BART, and the one before that self-immolated at the annual company acid trip, taking an investor’s dog with her. I thought I’d never stop being the butt of the What’s your burn rate? wisecracks. Content moderation is a rough job, don’t get me wrong. A moderator has to sit in front of a grid of displays all day, double-checking the media that our A.I. flags as too disgusting, incendiary, or violent for human consumption. There’s gonna be some turnover. But the spate of suicides is a big problem for me personally because I’m the corporate Vice President of Spiritual Health and, at least on paper, I’m supposed to be the load-bearing wall that keeps morale from caving so badly.

Ralgo seals the red anodized walls of the Privacy Cylinder around the two of us and tears into me. He says it would be cheaper to install jumper nets in the rotunda than it would be to keep me on staff another six months. He wants to see Vencent’s spiritual performance review notes. I tell him I don’t have any. I’ve stopped taking notes because the distraction interferes with intersoul harmonics. Oh boy, does he ever not like hearing that.

“W. T. Fuck?” he says. “I’ve got senators crawling up my ass over these suicides, and the investors are spooked another public incident will tank the valuation. If they hear about Vencent and come snooping and we don’t have a liability paper trail, we’re one-hundred percent fucked. Do you hear me? Eternally. By thorny cocks.”

I vow that I will recommit myself to diligent notekeeping and he says he’ll believe it when he sees it. I ask him what he meant during standup RE: the good news about Vencent’s replacement.

“About that,” he says. “I’ve hired a Deep Phakes consultant to replace Vencent. And don’t get pissy that I didn’t loop you into the candidate selection process, there wasn’t one. Their salespeople won’t allow it.”

The instant I hear Ralgo mention Deep Phakes I realize why we’re having this conversation in the Privacy Cylinder and not in the breezeway he uses for public humiliation. Deep Phakes is one of the darkest secrets in the Valley. You’ll never hear anyone admit publicly they’re working with Deep Phakes. What they do isn’t entirely legal, and for my money it’s morally fuzzy, but their product is legendary. A Deep Phake shows up at your office a nameless nonentity of programmable meatware, unencumbered by personality or desire. They’ll become anyone you want, utterly and wholly. I’ve heard they’ll even swap genders if you spring for the surcharge.

Ralgo says we should count our blessings that Vencent offed himself on company property, out of the reach of journalists, and that all the witnesses are on the payroll.

“It needs to be like Vencent never jumped off that balcony,” Ralgo says.

“I’m not sure I get it,” I say.

“I’m saying the Deep Phake is New Vencent. We dump him in the same cubicle, have him pick up wherever Old Vencent left off. He’s got to become Vencent, sans, obviously, the suicidal tendencies.”

“Obviously,” I echo, and he gives me a look that says don’t be cute, fuckwad.

“Against my better judgement,” he says, “I’m putting you in charge of New Vencent’s onboarding. Don’t make me regret it.”

We emerge from the Privacy Cylinder and Ralgo grabs a compostable dry erase marker and scribbles NEW VENCENT on the Kanban board over the same column of post-its that Old Vencent had been assigned.

Read the whole thing here.

|  12 Apr 2021


Beulah treads carefully across the church lot. Frozen slicks cover the asphalt and sidewalks. The dogwoods are bare and ice coats them down to the last twig like blown glass. It seems to her that a single clap of her red hands might bring the whole thing crashing down like a severed chandelier. She grips the cold door handle and pulls. It’s Saturday. The church is empty. Only the smell, threadbare carpet and stale sugar, greets her. She’s come to see the pastor. All week long she’s pictured how their conversation might go, but now, here, surrounded by actual walls and floors and ceilings, self doubt floods her. Her questions seem selfish, her motives childish. She considers fleeing, but what good would it do? Imagine how embarassing it would be to see the pastor tomorrow in the foyer before the Sunday service. Besides the eerie red glow of an emergency exit sign overhead, the only light in the church comes from the pastor’s office door which waits ajar at the end of the hall. She draws a breath and raps it with her knuckles.

“Come on in,” the pastor says in his calm practiced voice.

Beulah smiles awkwardly and takes a seat. The pastor’s hair is grayer and thinner than it looks in the framed photos on the walls, Kodachrome handshakes with district leaders in smoky prescription lenses and camel-brown corduroy. Books bear witness from floor-to-ceiling shelves on all sides, Wesleyan hymnals, outdated biblical translations, the complete life works of forgotten theologians bound in blue and gold bindings, old paperbacks for new fathers needing permission to punish their children, devotionals to guide the mouths of the spiritually blank.

“So tell me what’s been going on,” the pastor says.

She swallows some doubt and goes into it, “During service, after we sing hymns, when it’s time to give testimonies, I hear people give thanks to God for so many things, for their father’s cancer going into remission, for closing on a new house, for their grandbaby getting out of the NICU. They say these things are answers to prayer. But how could that be? Is God infusing the chemo? Is God changing the tape on the feeding tube? Well, they say that God is working through these doctors and nurses, that they’re human vessels carrying out his plan.”

The pastor is touching his short gray beard, listening.

She continues, “But why then is God helping only these people? Is it because they prayed? What’s so special about them? What makes them deserving when others aren’t? Is God reaching his mighty arm past whole continents of raped villages and mudslides and starving children only to help Bob and Judy get a favorable fixed rate on a thirty-year term? Don’t those villages pray? Aren’t those children begging God every gray morning that today will be the day food comes? Is this what we’re supposed to believe? It’s absurd. It’s obscene.”

The pastor puts an elbow on an armrest and props up his chin. “First of all,” he says, “I want to thank you for coming here and sharing this with me. I assure you that you are not alone in these vexing questions. The Bible says that Jacob wrestled with God. I myself wrestled with these same questions, years ago, when I was about your age. Can I tell you a story?”

She nods, trying her best not to be too open to conviction.

“You’re driving down the highway in a car. It’s your car. It’s not new, it’s a used car, an old car, a gift from family. It’s your first car. It’s ugly, has a rusted door, rattles at top speed, but it gets you where you need to go. Suddenly one day it breaks down and you’re late for work. You get it repaired at great expense. It fails again and you miss a job interview. This can’t go on. What do you do?” he asks rhetorically. “It’s time for a new car.”

Against her best judgement, she lets the metaphor soften her anger. Maybe it’s the pastor’s dulcet baritone, or the faded comforts of the vintage furniture and shag carpeting, but she finds herself sinking into it like a medicinal bath, unpleasant but not entirely unwelcome.

“I believe what you’re feeling is that you have outgrown the simple faith of childhood, that you’re ready for something different, something more…” he seems to be searching delicately for the appropriate phrasing, “…nuanced. It is possible to have gratitude in this life without ascribing every fortunate moment to divine intervention or blaming every tragedy on a lack of devotion. We know so little, down here, and have to be contented by our faith that in the fullness of time his plan will be revealed.”

She considers for a moment. Some of the heat that brought her here flares back.

“Then why,” she asks, “do you permit all those testimonies, Sunday after Sunday, thanking God for the many uneven kindnesses he capriciously doles out among so few? Why don’t you intervene, set an example? You called it the faith of childhood. Are these adults or children?”

He tents his hands by the finger tips and shifts in his chair. His words are even more delicate now.

“There are some lessons,” he replies, “that a person may never be ready to hear.”

He asks her if they can pray together, and she assents. He holds her hands across the desk and closes his eyes. She keeps her eyes open, watches the way he bows his gray head, notices the whiskers in his ears and the flakes on his shirt. She sees his wedding band, his finger so swollen now around the metal that it would be impossible to remove it, and remembers how it was only a few years ago that his wife died when a drunk teenager entered the freeway from the wrong direction.

There are no rings on Beulah‘s fingers, nor would there be in any forseeable future. Her mother’s condition leaves no room for a life outside of work. Oh, how Momma’s hands feel now when she holds them, gnarled and stiff like dried roots, how they tremble with age and pain, how Momma’s fragile skin breaks open over the knuckles. It’s nearly dinner time in the nursing home. Momma has to be fed like a child. The nurses there don’t feed her well. They leave too much food on the tray, impatient with how slowly she chews. They leave green beans smeared in the corners of her mouth and bread crumbs on her gown. Water dribbles down her wagging chin and soaks her bedsheets because no one places a napkin on Momma’s lap, not like Beulah does. She can’t bear to leave Momma alone in that place. Beulah leaves the house by quarter-to-five every morning so she can spend two hours before work bathing her, changing her linens, feeding her breakfast and coffee. Beulah will tell her about the weather, pass along news from distant cousins many timezones away. Momma won’t speak, she’ll just lie there making a wet bup-bup-bup-bup sound and squint at the drop-tile ceiling. Beulah will kiss Momma’s forehead and leave for work. The young nurse will stick out her lower lip and blow her dyed bangs when she thinks Beulah can’t see. It will be the same after work. Beulah will return and clean. She’ll wipe bits of lunch from Momma’s cheeks, brush her dentures, scrub the bedpan, disinfect the shower chair, fluff the pillows, let in some light, make fresh coffee, and feed Momma her dinner. They’ll watch whatever is on TV together, anything with a laugh track. Momma will fall asleep before the evening news, and Beulah will sit there a while, holding Momma’s knotted silky hand.

The pastor squeezes Beulah‘s hands and says “Amen,” and Beulah echoes it. He offers to see her out, but she declines. She walks through the dim red light of the emergency exit sign and emerges onto the slick cold of the parking lot. Freezing rain has started up again and it’s beginning to stick to her windshield. She gets it cleared away and drives carefully to the nursing home.

There’s an ambulance out front. Its lights are spinning wildly, scattering red and blue panic in the rain. Beulah rushes into the building. She ignores the front desk nurse who’s demanding she sign the guest log. Another nurse is walking out of her mother’s room and Beulah almost tackles her as she sprints in. Her mother is lying there, alive and awake, making the bup-bup-bup-bup sound. Beulah throws her arms around her mother, hugging her through a pile of hospital blankets.

“Oh, Momma,” Beulah says.

The nurse has left the television tuned to a talk show at full volume. Every other word is bleeped out and someone is throwing a chair at the audience. Beulah switches it off and goes into the bathroom to wash Momma’s glasses. From the bathroom she hears Momma softly screaming, like a newborn deer bleating for its mother. Beulah comes out and sees an old man, another patient, has wandered in, lost and confused. He’s trying to climb into the bed. “My darlin!” he says. “My darlin!” Beulah yanks him out of the bed and sends him down the hall. He’s missing a slipper and wobbles back and forth with each each step like a wind-up soldier.

“Paw-Paw?” a voice croaks from Momma’s bed.

Beulah turns.

“Maw-Maw? Paw-Paw? Is that you?” her mother says.

Beulah holds her hand and says, “Yes, it’s me.”

Momma hasn’t spoken since nine months ago when she looked out the window and said, “Is it gonna rain?” Momma raises her trembling hand, and Beulah helps guide it. She brings Beulah‘s hand close to her mouth and kisses it.

“Paw-Paw,” Momma says. “Please pray for me.”

“Yes, Momma,” Beulah says. “What shall we pray for?”

“Please pray that God won’t let me lose faith.”

“Okay, Momma, we’ll pray.” Beulah begins praying aloud and her mother squints her eyes at the ceiling again and again starts making the bup-bup-bup-bup sound through her wagging jaw. A tear traces a wrinkle in her old cheek. She falls asleep without eating her supper.

The drive home takes Beulah past the church. A heavy hauler has spun out on the ice and is jack-knifed up ahead. Traffic in both directions is at standstill. Beulah waits there idling for a long time. She looks over at the church and sees the light switch off in the pastor’s office. A minute later he’s tip-toeing across the slippery parking lot to his car. It’s a factory white 1965 Ford Mustang. The paint is still fresh and brilliant all these decades later, except along the passenger side where the drunk teenager’s car swiped it, crunching the door. The door looks desperately hammered back into shape, but is imperfect. It’s rippled and repainted in a color that doesn’t exactly match the original. The windshield is caked with solid ice. The pastor is hacking away at the ice with a long scraper, wildly stabbing with it like a bayonette. He swings the scraper too hard and his feet slip. The scraper tumbles across the engine hood. He pulls himself up and examines every inch of the hood as if frantically looking for nicks in the paint. He picks up the scraper and hurls it again into the ice, chip by tiny chip.

|  1 Apr 2021

Four Years Ago

Four years ago today I was in Cupertino for the eve of what was supposed to be a small day of personal achievement. I was part of a special project for the fruit company that I can’t discuss. The kickoff was in the morning, November 4th. I scarcely contained my giddy excitement.

I spent the evening at a hotel nearby, watching the returns roll in. The fuck is all that red on the map. When denial turned into dawning realization, and dawning realization decayed into despair, I violently switched off the television. I needed a drink, and not to be alone.

The plan was simple: wander until I encountered a bar. Any bar. It didn’t take long to stumble into one. Cramped inside, a handful of patrons. The horror show glared at us from the TV hanging under a drop-tile ceiling. Inescapable. What’re you gonna do.

“Jack. Neat.”

One shot glass turned into two, then three. It wasn’t enough. I once got booted in the nuts with so much force that I was propelled into the air. This felt worse. It was like watching God die on live TV, a melancholy accident, a wave function collapsing the wrong direction.

I wasn’t alone. An assembly line of grief had gathered there, nursing our shared world-historical wound. Time slipped off on a conveyer belt of booze. The bar tender had stopped charging for drinks and was robotically refilling everyone’s shot glasses as soon as they’d emptied.

The hangover the next morning was like none before. The hazy California sky was corpse-blue and relentless. Our little fruit project group assembled and rolled into the parking lot at the mothership. Where was the joy I’d anticipated all these years?

Friendly corporate busy-bees escorted us through electronically sealed doors, through gray hallways, past giant portraits of products in brushed metal and curved glass, and into a windowless room. Not enough chairs, so I sat legs-crossed in the corner leaning against a couch.

The next four hours demanded more of me than any software project I’ve ever been on, not in itself, but in context. We weren’t even writing code that day, merely setting up accounts and emails and corporate network access. Mind-numbing work, but nearly impossible that morning.

The obscenity of the night before clouded my vision. The sensible pale walls and polite gray carpeted floors mixed with the mind-numbing dullness of awaiting two-factor authentication codes, all giving way to a pall of grief, every keystroke a miracle of survival instincts.

Four years later I’m girding myself for another boot to the nuts, tallying the liquor stocks in the house, keeping my mind’s eye on the shelf where the shot glass waits patiently for a call to arms. Let’s hope it remains there this time around.

(Originally a Twitter thread.)

|  3 Nov 2020

Every Skill is a Liability

I think the hardest skill to learn in programming is recognizing when you’re not communicating enough.

We’re often the first people to spot change, to feel the rumble of trouble over the horizon, but we’re also inclined to reflect on approaching danger internally, as a puzzle to solve without assistance, rather than externally by sharing the burden with our team.

Every skill is also a liability. Programming is anticipating the future and solving problems before anyone else has to know that a problem would have occurred. Every day we practice that skill and get better at it, but sometimes the right call is to resist the urge to problem solve, to instead simply report on what I’ve seen, to let my team know that a problem is coming, and then wait for the team to collaboratively decide who’ll solve the problem, and how, and when.

I wish I knew a secret that unlocks this ability without effort, but I don’t. The only way I know of is to dedicate some utility thread of my mind that periodically checks in to see if there’s anything that pattern-matches against mistakes I’ve made in the past, and quickly course correct by communicating with the right people.

|  21 Oct 2020

Dagmar Chili Pitas: The Official Unofficial Mirror

“…and with the microscopic diligence of a Leuwenhoeck submits to the inspection of a shivering world ninety-six facsimiles of magnified Arctic snow crystals.”


Have you ever heard of Dagmar Chili Pitas? It’s twenty years old. It, sadly, went offline a few years back. It is, I can say without hesitation or reservation, the greatest website ever made, at least in my own flawed estimation.

I have no idea who made it. Or why. Or what substances they were under the influence of. It doesn’t matter, I enjoy the mystery. If I had to pick a back story, it would be: Thomas Pynchon got bored and spent two years working on an anonymous website whenever he was drunk.

Although the site is available at the Internet Wayback Machine, I have been anxious, whenever I think about it, that the site will eventually get lost or purged from there. Thanks to this very, very helpful tool, I was able to easily download the entire site and make it available as the Official Unofficial Mirror on my site. Enjoy your symptom!


|  28 May 2020

When Should I Use @State, @Binding, @ObservedObject, @EnvironmentObject, or @Environment?

SwiftUI introduced a laundry list of new property wrappers that your code can use to bridge the gap between program state and your views:


That’s only a partial list. There are other property wrappers for Core Data fetch requests and gesture recognizers. But this post isn’t about these other wrappers. Unlike the wrappers above, the use cases for @FetchRequest and @GestureState are unambiguous, whereas it can be very confusing how to decide when to use a @State versus @Binding, or @ObservedObject versus @EnvironmentObject, etc.

This post is an attempt to define in simple, repeatable terms when each wrapper is an appropriate choice. There’s a risk I’m being overly prescriptive here, but I’ve gotten some decent mileage from these rules already. Besides, being overly prescriptive is a time-honored programmer blog tradition, so I’m in good albeit occasionally obnoxious company.

All of the code samples that follow are available in this GitHub repo. Note for posterity: this post was written using Swift 5 and iOS 13.

Cheat Sheet

  1. Use @State when your view needs to mutate one of its own properties.

  2. Use @Binding when your view needs to mutate a property owned by an ancestor view, or owned by an observable object that an ancestor has a reference to.

  3. Use @ObservedObject when your view is dependent on an observable object that it can create itself, or that can be passed into that view’s initializer.

  4. Use @EnvironmentObject when it would be too cumbersome to pass an observable object through all the initializers of all your view’s ancestors.

  5. Use @Environment when your view is dependent on a type that cannot conform to ObservableObject.

  6. Also use @Environment when your views are dependent upon more than one instance of the same type, as long as that type does not need to be used as an observable object.

  7. If your view needs more than one instance of the same observable object class, you are out of luck. You cannot use @EnvironmentObject nor @Environment to resolve this issue. (There is a hacky workaround, though, at the bottom of this post).

Your view needs a @State property if…

…it needs read/write access to one of its own properties for private use.

A helpful metaphor is the isHighlighted property on UIButton. Other objects don’t need to know when a button is highlighted, nor do they need write access to that property. If you were implementing a from-scratch button in SwiftUI, your isHighlighted property would be a good candidate for an @State wrapper.

struct CustomButton<Label>: View where Label : View {
    let action: () -> Void
    let label: () -> Label

    /// it needs read/write access to one of 
    /// its own properties for private use
    @State private var isHighlighted = false

…it needs to provide read/write access of one of its properties to a descendant view.

Your view does this by passing the projectedValue of the @State-wrapped property, which is a Binding to that value1. A good example of this is SwiftUI.Alert. Your view is responsible for showing the alert by changing the value of some @State, like an isPresentingAlert boolean property. But your view can’t dismiss that alert itself, nor does the alert have any knowledge of the view that presented it. This dilemma is resolved by a Binding. Your view passes the Alert a Binding to its isPresentingAlert property by using the compiler-generated property self.$isPresentingAlert, which is syntax sugar for the @State wrapper’s projected value. The .alert(isPresented:content:) modifier takes in that binding, which is later used by the alert to set isPresentingAlert back to false, in effect allowing the alert to dismiss itself.

struct MyView: View {

    /// it needs to provide read/write access of 
    /// one of its properties to a descendant view
    @State var isPresentingAlert = false

    var body: some View {
        Button(action: {
            self.isPresentingAlert = true
        }, label: {
            Text("Present an Alert")
        .alert(isPresented: $isPresentingAlert) {
            Alert(title: Text("Alert!"))

Your view needs a @Binding property if…

…it needs read/write access to a State-wrapped property of an ancestor view

This is the reverse perspective of the alert problem described above. If your view is like an Alert, where it’s dependent upon a value owned by an ancestor and, crucially, needs mutable access to that value, then your view needs a @Binding to that value.

struct MyView: View {
    @State var isPresentingAlert = false

    var body: some View {
        Button(action: {
            self.isPresentingAlert = true
        }, label: {
            Text("Present an Alert")
        .customAlert(isPresented: $isPresentingAlert) {
            CustomAlertView(title: Text("Alert!"))

struct CustomAlertView: View {
    let title: Text
    /// it needs read/write access to a State-
    /// wrapped property of an ancestor view
    @Binding var isBeingPresented: Bool

…it needs read/write access to a property of an object conforming to ObservableObject but the reference to that object is owned by an ancestor.

Boy that’s a mouthful. In this situation, there are three things:

  1. an observable object
  2. some ancestor view that has an @-Something wrapper referencing that object
  3. your view, which is a descendant of #2.

Your view needs to have read/write access to some member of that observable object, but your view does not (and should not) have access to that observable object. Your view will then define a @Binding property for that value, which the ancestor view will provide when your view is initialized. A good example of this is any reusable input view, like a picker or a text field. A text field needs to be able to have read/write access to some String property on another object, but the text field should not have a tight coupling to that particular object. Instead, the text field’s @Binding property will provide read/write access to the String property without exposing that property directly to the text field.

struct MyView: View {
    @ObservedObject var person = Person()

    var body: some View {
        NamePicker(name: $

struct NamePicker: View {

    /// it needs read/write access to a property 
    /// of an observable object owned by an ancestor
    @Binding var name: String

    var body: some View {
        CustomButton(action: {
   = names.randomElement()!
        }, label: {

Your view needs an @ObservedObject property if…

…it is dependent on an observable object that it can instantiate itself.

Imagine you have a view that displays a list of items pulled down from a web service. SwiftUI views are transient, discardable value types. They’re good for displaying content, but not appropriate for doing the work of making web service requests. Besides, you shouldn’t be mixing user interface code with other tasks as that would violate the Single Responsibility Principle. Instead your view might offload those responsibilities to an object that can coordinate the tasks needed to make a request, parse the response, and map the response to user interface model values. Your view would own a reference to that object by way of an @ObservedObject wrapper.

struct MyView: View {

    /// it is dependent on an object that it 
    /// can instantiate itself
    @ObservedObject var veggieFetcher = VegetableFetcher()

    var body: some View {
        List(veggieFetcher.veggies) {
        }.onAppear {

…it is dependent on a reference type object that can easily be passed to that view’s initializer.

This scenario is nearly identical to the previous scenario, except that some other object besides your view is responsible for initializing and configuring the observable object. This might be the case if some UIKit code is responsible for presenting your SwiftUI view, especially if the observable object can’t be constructed without references to other dependencies that your SwiftUI view cannot (or should not) have access to.

struct MyView: View {

    /// it is dependent on an object that can
    /// easily be passed to its initializer
    @ObservedObject var dessertFetcher: DessertFetcher

    var body: some View {
        List(dessertFetcher.desserts) {
        }.onAppear {

extension UIViewController {

    func observedObjectExampleTwo() -> UIViewController {
        let fetcher = DessertFetcher(preferences: .init(toleratesMint: false))
        let view = ObservedObjectExampleTwo(dessertFetcher: fetcher)
        let host = UIHostingController(rootView: view)
        return host


Your view needs an @EnvironmentObject property if…

…it would be too cumbersome to pass that observed object through all the initializers of all your view’s ancestors.

Let’s return to the second example from the @ObservedObject section above, where an observable object is needed to carry out some tasks on behalf of your view, but your view is unable to initialize that object by itself. But let’s now imagine that your view is not a root view, but a descendant view that is deeply nested within many ancestor views. If none of the ancestors need the observed object, it would be painfully awkward to require every view in that chain of views to include the observed object in their initializer arguments, just so the one descendant view has access to it. Instead, you can provide that value indirectly by tucking it into the SwiftUI environment around your view. Your view can access that environment instance via the @EnvironmentObject wrapper. Note that once the @EnvironmentObject’s value is resolved, this use case is functionally identical to using an object wrapped in @ObservedObject.

struct SomeChildView: View {

    /// it would be too cumbersome to pass that 
    /// observed object through all the initializers 
    /// of all your view's ancestors
    @EnvironmentObject var veggieFetcher: VegetableFetcher

    var body: some View {
        List(veggieFetcher.veggies) {
        }.onAppear {

struct SomeParentView: View {
    var body: some View {

struct SomeGrandparentView: View {
    var body: some View {

Your view needs an @Environment property if…

…it is dependent on a type that cannot conform to ObservableObject.

Sometimes your view will have a dependency on something that cannot conform to ObservableObject, but you wish it could because it’s too cumbersome to pass it as a initializer argument. There are a number of reasons why a dependency might not be able to conform to ObservableObject:

In cases like these, your view would instead use the @Environment wrapper to obtain the required dependency. This requires some boilerplate to accomplish correctly.

struct MyView: View {

    /// it is dependent on a type that cannot 
    /// conform to ObservableObject
    @Environment(\.theme) var theme: Theme

    var body: some View {
        Text("Me and my dad make models of clipper ships.")

// MARK: - Dependencies

protocol Theme {
    var foregroundColor: Color { get }
    var backgroundColor: Color { get }

struct PinkTheme: Theme {
    var foregroundColor: Color { .white }
    var backgroundColor: Color { .pink }

// MARK: - Environment Boilerplate

struct ThemeKey: EnvironmentKey {
    static var defaultValue: Theme {
        return PinkTheme()

extension EnvironmentValues {
    var theme: Theme {
        get { return self[ThemeKey.self]  }
        set { self[ThemeKey.self] = newValue }

…your views are dependent upon more than one instance of the same type, as long as that type does not need to be used as an observable object.

Since @EnvironmentObject only supports one instance per type, that idea is a non-starter. Instead if you need to register multiple instances of a given type using per-instance key paths, then you will need to use @Environment so that your views’ properties can specify their desired keypath.

struct MyView: View {
    @Environment(\.positiveTheme) var positiveTheme: Theme
    @Environment(\.negativeTheme) var negativeTheme: Theme

    var body: some View {
        VStack {

// MARK: - Dependencies

struct PositiveTheme: Theme {
    var foregroundColor: Color { .white }
    var backgroundColor: Color { .green }

struct NegativeTheme: Theme {
    var foregroundColor: Color { .white }
    var backgroundColor: Color { .red }

// MARK: - Environment Boilerplate

struct PositiveThemeKey: EnvironmentKey {
    static var defaultValue: Theme {
        return PositiveTheme()

struct NegativeThemeKey: EnvironmentKey {
    static var defaultValue: Theme {
        return NegativeTheme()

extension EnvironmentValues {
    var positiveTheme: Theme {
        get { return self[PositiveThemeKey.self]  }
        set { self[PositiveThemeKey.self] = newValue }

    var negativeTheme: Theme {
        get { return self[NegativeThemeKey.self]  }
        set { self[NegativeThemeKey.self] = newValue }

Workaround for Multiple Instances of an EnvironmentObject

While it is technically possible to register an observable object using the .environment() modifier, changes to that object’s @Published properties will not trigger an invalidation or update of your view. Only @EnvironmentObject and @ObservedObject provide that. Unless something changes in the upcoming iOS 14 APIs, there is only one recourse I have found: a hacky but effective workaround using a custom property wrapper.

With this set up in place, your view can observe multiple objects of the same class:

struct MyView: View {

    @DistinctEnvironmentObject(\.posts) var postsService: Microservice
    @DistinctEnvironmentObject(\.users) var usersService: Microservice
    @DistinctEnvironmentObject(\.channels) var channelsService: Microservice

    var body: some View {
        Form {
            Section(header: Text("Posts")) {
                List(postsService.content, id: \.self) {
            Section(header: Text("Users")) {
                List(usersService.content, id: \.self) {
            Section(header: Text("Channels")) {
                List(channelsService.content, id: \.self) {
        }.onAppear(perform: fetchContent)

// MARK: - Property Wrapper To Make This All Work

struct DistinctEnvironmentObject<Wrapped>: DynamicProperty where Wrapped : ObservableObject {
    var wrappedValue: Wrapped { _wrapped }
    @ObservedObject private var _wrapped: Wrapped

    init(_ keypath: KeyPath<EnvironmentValues, Wrapped>) {
        _wrapped = Environment<Wrapped>(keypath).wrappedValue

// MARK: - Wherever You Create Your View Hierarchy

    .environment(\.posts, Microservice.posts)
    .environment(\.users, Microservice.users)
    .environment(\.channels, Microservice.channels)
    // each of these has a dedicated EnvironmentKey

Sample Code

All of the code above is available in an executable form here.

  1. Every @propertyWrapper-conforming type has the option of providing a projectedValue property. It is up to each implementation to decide the type of the value. In the case of the State<T> struct, the projected value is a Binding<T>. It behoves you, any time you’re using a new property wrapper, to jump to its generated interface to discover in detail what it’s projected value is. 

|  7 May 2020

Addicted to Jesus

I was pushing forty before I was able to articulate this:

Religion and philosophy are bad for me.

Not “are bad”, but “are bad for me”, specifically me, in the way the same liquor that drags one person down passes quietly over the next. I don’t drink. I’ve another poison. I’m fatally attracted to unanswerable questions. I’m always slouching toward a downward spiral of obsessive philosophical and religious thought, trying to resolve irresolvable thought problems, torturing myself over that failure by trying ever harder, until I develop old-fashioned clinical depression. From my teenage years onward, throughout all of my twenties, I was transfixed by big, vexing questions. The usual suspects: the problem of evil, the existence of god, blah blah blahd nauseam. But not in a casual way. Never in a pot-fueled like, what if we’re all in a simulation, man reverie. This pain cut deep, pervaded every moment of my life. I tore apart books, filled journals with angsty blather, changed jobs and majors like clothes, looking for answers to questions that have none. I was addicted.

"The Upper Zoom" by @MythAddict.

That notion — those words even: religion and philosophy are bad for me — is not a thing I’ve ever heard before. It’s not a part of any tradition I know of. There are no shortage of true unbelievers who’ll blame the human condition on religion1. This ain’t that. I’m saying that they’re bad for me, and it doesn’t matter whether they’re a cause of or the cure for society’s ills. I can’t safely be around them. I was raised in an evangelical subculture that was profoundly committed to a certain view of right living, often harmful, sometimes heart-breakingly beautiful, spotlit by a few passages of truly moving literature. One thing evangelicals and atheists and whateverists of all stripes have in common is the assumption that the nuts n’ bolts of religio-philosophical thought are value-neutral. Abstracted from any conclusions drawn, the practical mechanics of the thing aren’t considered a threat. “Assumption” is too strong a word. It’s simply not under consideration.

When I finally figured out that I am not like other people, that sustained religious and philosophical thinking poses a perpetual threat to my mental health, it was liberating. In practical terms, I was able to spot the warning signs and put myself out of harm’s way. Put down that book, jackass, go do something grounded.

I can’t say for sure, but I attribute at least some of my predicament to my evangelical roots. That contradictory tension of “you must believe this but you had better not actually live this,” the impossible sacrifice we were all expected to recite but never required to follow, — so many contradictions — it’s inevitable that it’s going to churn out some kids like me, kids who go into adulthood feeling like they have no home, can’t go back to the bullshittery of the past, but are still addicted to Jesus.

  1. Faith traditions are powerful motivators both for good and for evil. Don’t @-me with your religion-is-the-cause-of-evil bullshit. 

|  1 Apr 2020

Dr. Jekyll and Mr. Tumblr

Yesterday morning this tweet from Mark Sands showed up in my mentions:

@jaredsinclair Looks like Tumblr deleted your blog. Any hope for reviving it?

Up until yesterday, my blog was hosted via Tumblr with a CNAME redirect from to the appropriate Tumblr domain. I set everything up seven or eight years ago, and it had worked fine all that time. I tried visiting the site and, like Mark said, the blog was gone. I got the Tumblr equivalent of a 404 instead of my blog. Next I tried logging into my Tumblr account to figure out what was wrong. Here’s what I saw:

W. T. Fuck.

I thought perhaps I’d missed a warning from Tumblr, but I had no recollection of receiving anything. I double and triple checked all my email accounts. Nada. There wasn’t anything from Tumblr since one day in March when I used a magic link to sign into my account rather than username and password. Nothing archived, nothing in spam. Without warning Tumblr had terminated my account for no discernable reason. I tried logging in from a desktop browser to be sure there wasn’t some mobile-rendering goof. Same thing. Your account has been terminated in classic Tumblr chunky white.

Wait, back up.

Ever since I slung screwdrivers around repairing Macs at a third-party Mac store, I’ve lived in mortal fear of losing data. Practically every day we had to tell somebody your baby pictures, your wedding pictures, your dissertation: all gone. There’s only so many times you can watch folks sob over the consequences of a spilled cup of coffee before you’re sobbing with them. For the most part, my backup strategy has been comfortably paranoid:

Yet my blog was the one corner of my digital life where I had gotten lazy. I didn’t have a backup of my posts anywhere, only scattered draft versions in a Dropbox folder. The canonical versions of all my blog posts were whatever Tumblr had saved for my account. The Tumblr versions had lots of small edits and corrections that weren’t saved anywhere, even if there was a draft copy in Dropbox. Now suddenly, everything that Tumblr had was gone.

Moving to Jekyll

I used the spartan contact form to ask Tumblr why my account was terminated. But rather than wait for a response that might never come, I decided it was past time to bring my blogging setup in line with the rest of my backup paranoia.

I decided to move everything over to Jekyll, backed by a comprehensive GitHub repository. I’ve had a Media Temple account for years and have been really happy with them. I knew once I figured out how to actually use Jekyll in a comfortable way, my Media Temple Grid Service would be able to host the static files easily. Why Jekyll? The short answer is that it’s used by GitHub Pages. In a sea of alternatives, I’m content to follow the smart folks at GitHub wherever they go.

If you’re reasonably comfortable with basic web programming, and know enough about the shell to get yourself in trouble, using Jekyll isn’t so bad. I got a proof-of-concept site up and running pretty quickly. Since Media Temple is also the domain registrar for, it was easy to replace the Tumblr CNAME record for with an A record pointing to the same IP address as (this was a necessary part of ensuring that old blog post links resolved to their new Jekyll permalink). Within hours, anyone that wanted to visit this blog was able to see something here. The real problem was recovering all my old posts.

Recovering all my old posts

Felix Lapalme recommended a command-line tool that downloads an entire site’s content from the Internet Wayback Machine. Before anything else could go wrong, I immediately ran that tool, which worked as advertised (isn’t it great when things work like they say on the tin?). This archive was missing a lot of posts, particularly my more recent stuff, but something was better than nothing. I was pretty worn out from getting Jekyll set up so I went to bed, putting off figuring out how to transform this backup into something formatted for Jekyll.

When I woke up this morning, I had a pleasant email in my inbox. It wasn’t from Tumblr, natch. It was from Ben Ubois, the founder of Feedbin, the RSS aggregator:

Hey Jared,

I saw on Twitter about Tumblr closing your account. That sounds lame!

Feedbin has posts from your blog going back to 2012. I’ve attached all 234 of them as JSON.

The structure looks like:

{ title: “Title”, url: …

Hope it helps!

Thanks to Ben, I now had everything I needed. Unlike the Internet Archive backup, in which each post would need to be heavily transformed to unsleeve the post content from all the page chrome that got captured with each crawl, the RSS backup was already free of such chrome. Better still, the JSON data structure would make it possible to automate the capture of the critical metadata for each page, namely the title and published date.

Using the Swift Package Manager, I made a quick n’ dirty utility that transforms a JSON-encoded file of Feedbin posts into a directory of HTML files formatted for Jekyll. After running this utility, all I had to do to republish all my old content (in correct chronological order, too) was to copy those HTML files into the _posts directory in my Jekyll project, run jekyll build, and upload them to the right directory on my Media Temple server.

The one truly unfortunate downside of moving to Jekyll is that all existing links to my Tumblr-hosted blog are now defunct. Tumblr blog post URL paths take one of the following forms:


Whereas Jekyll links use a date-based path:


At least that’s the Jekyll default. I like this default and have decided to keep it and fix broken Tumblr links on a case-by-case basis. For the posts that I care about most (the ones that at one time or another got a lot of traffic, like this one or this one), I’ve updated the .htaccess file on my Media Temple server with redirects like this:

RedirectMatch 301 /post/97655887470.${html`*`}$ /2014/09/16/good-design-is-about-process-not.html

I don’t know if I’ll do it this way forever (there might be a better way that Jekyll supports), but this was effective and took only a few minutes.

For all the posts that I haven’t redirected, I’ve updated the 404 with a blurb about what happened to my blog this weekend, with a link to my archive page.

Looking ahead

To make day-to-day life easier going forward, I added a script to my Jekyll project that uses rsync to upload the _sites directory to my Media Temple server, authenticated with ssh. From the root directory of my Jekyll repo, I can just run to rebuild and upload everything. I’m continually impressed by how efficient rsync is. Small changes to the site, like correcting a typo in some markup, are published in seconds.

Update: Tumblr replies

By the time I finished writing this post, I received a reply from Tumblr:


We’ve restored your account.

Thank you for bringing this problem to our attention. We’re sorry that it occurred, and we’ll do our best to make sure that it doesn’t happen again.

You should now be able to log in just fine with your email address and password.

Please let me know if there’s anything else I can help you with!


Community Manager

Too little, too late, Drew. I hope Tumblr understands that I simply cannot trust them anymore. I’m grateful that they responded to my contact request and that my account has been reopened, but it’s unacceptable that a years-old account still in good standing can be terminated without any advance warning or preventative recourse. This weekend’s debacle is a textbook case for why folks should own their own data. It’s also a good reminder that no single service provider can be wholly trusted.

If something isn’t backed up in more than one place, it’s not backed up at all.

|  7 Apr 2019

Please Pardon Our Mess

Since Tumblr decided to terminate my account without warning or explanation, I’ve decided it’s past time to move my blog to something under my control. Unfortunately I don’t have a backup of my old posts ready to go. In lieu of anything better, I’ll fill my Jekyll queue with my favorite lorem ipsum alternative, the “Clipper Ships” poem from Little Man Tate.

|  6 Apr 2019