Web developers: learn how to Google. If no one else has the same problem, the problem is you.

OK, maybe not you per se. This is not a judgment of your merits as a developer, or as a human being. But it does mean the problem is almost certainly something specific to the code you’ve written.

The Hierarchy of Coding Errors

If your code isn’t working, the source of the problem is one of the following, in order from most likely to least likely:

  1. New procedural code you’ve just written
  2. New object-oriented code you’ve just written*
  3. Custom functions or objects you built, but have used before
  4. Third-party/open source add-ons to the software platform you’re using (e.g. WordPress plugins)
  5. Standard functions or objects in the software platform (e.g. WordPress core)
  6. Public code libraries that are included in your chosen software platform (e.g. jQuery)
  7. Browser bugs
  8. OS bugs
  9. Internet protocol bugs
  10. Quantum fluctuations in the fabric of spacetime
  11. Gremlins

You may have guessed correctly at this point that this blog post is not just idle Friday afternoon musings. I’ve spent the majority of the day today troubleshooting a very strange issue with a website I’m currently building. I fixed the problem, but not after being forced to — once again — confront this humbling reality. If something’s not working, it’s probably your own fault. Especially if you’re the only person with the problem.

Googling the issue got me (almost) nowhere… which was the most obvious clue that it was my own fault

Aside from the natural human inclination to deflect blame, the tools we have for troubleshooting these types of problems are not necessarily well suited to forcing us to be honest with ourselves. It’s too easy to blame external forces.

Here’s my scenario. I found out last week while presenting work-in-progress to a client at their office that there was a JavaScript-related problem with the website. It only affected Internet Explorer (and Edge), which I had not yet tested the site in, and, weirdest of all, it didn’t always happen. I’d say maybe 10-20% of the time, the page loaded normally. But the rest of the time, it got an error.

Since this was only affecting one browser, my natural inclination was to start all the way at number 7 on the list, blaming Internet Explorer. But I’ve learned that as much as I want to blame it, issues with IE usually just shine a light on something in my own code that other browsers are more forgiving about. So it was time to walk backwards down the list. (Again… not really, but this is how it played out.)

The error that the browser reported was a “security problem” with jQuery Migrate. First I had to figure out what the hell jQuery Migrate was and why it was being loaded. (Turns out, it’s a place the jQuery team dumped deprecated code it pulled from version 1.9. It’s loaded by default by WordPress.)

With that in mind, this should be affecting every site I’ve built recently, since they’re all in WordPress. But it was only affecting this one site. So I had to try to narrow down where the problem exists. With WordPress, there are two main “variables” in the implementation: themes and plugins. When in doubt, try switching your theme and disabling the plugins you’re using. I started by disabling all of the plugins, one by one. No change. I found the error didn’t occur if I disabled Advanced Custom Fields, but that’s because half of the page didn’t load without it! (That’s another error on my part but let’s ignore that for now, shall we?)

OK, so it’s not a plugin. Next I swapped in the standard Twenty Sixteen theme in place of my custom theme. Not surprisingly, the error didn’t occur, but that didn’t help much because none of my Advanced Custom Fields content was in the pages. I still couldn’t rule out ACF as the culprit. But I tend to reuse field groups from site to site, so once again, if this were attributable to an ACF issue — even something specific to my field groups — it would’ve cropped up on another site.

So now I had little left to do but selectively comment out elements of the theme so I could narrow down where the problem was. (I make this all sound like a logical progression; in fact my debugging process is a lot more chaotic than this description — I actually did this commenting-out process haphazardly and repetitively throughout the afternoon.)

Eventually I pinpointed the troublesome block of code. Yes, it was #1 from the list. But as is usually the case with hard-to-diagnose problems, the complete picture here is that #1 included a combination of #3 and #5, which triggered an error message generated by #6, but only in the context of #7.

Yes. That’s what happened.

In the footer of the page, I had a link to the client’s email address. As is my standard (but by now probably outmoded) practice, I have a custom-built function I wrote years ago to obfuscate the email address by randomly converting most (but not all) of the characters in the string into HTML ampersand entities. My problem was not that function itself, which is tried and true. It’s that in this particular instance I called it on a string that included the mailto: pseudo-protocol, not just the email address itself.

I think the colon in mailto: is particularly significant to the problem, as evidenced by the fact that around 10-20% of the time the problem didn’t occur, and the page loaded normally. Since my obfuscation function randomly leaves characters in the string alone, that’s about how often the colon would have been kept untouched.

But even then, what difference should it make? Browsers decode those entity strings and can handle them in the href attribute of links just fine. However in this particular case I didn’t just use my obfuscation function. Without giving it much thought, in this particular site I had decided to wrap the obfuscated string in the standard WordPress esc_url() function. Trying to properly sanitize things, like a good developer. Right? Except — and I took a quick look at the source code to confirm it — there’s special handling in esc_url() for strings that don’t contain a colon. So the roughly 86% of the time that my string didn’t contain a colon, esc_url() was prepending http:// onto the string.

This situation was causing a particular piece of code in jQuery Migrate to barf… but only in Internet Explorer and Edge, for reasons I still don’t understand, but it has to do with how the different browsers handle security warnings in JavaScript. I found along the way (but before I had pinpointed the real problem) that if I commented out a particular segment of code in jQuery Migrate pertaining to the handling of selectors containing hashtags (see, the HTML ampersand entities again) I could get the page to load normally.

So, like I said, my newly written procedural code (#1), which itself included calls to both an existing custom function I wrote (#3) and a function baked into the WordPress core (#5), caused jQuery Migrate to issue an error (#6) but it was one that only a particular browser (Internet Explorer/Edge) cared to acknowledge (#7).

No wonder it took all afternoon to figure it out.

* The only reason I break out OO from procedural code is that OO has more structured patterns that are less likely to result in sloppy mistakes. Slightly.

WordPress dev tip: How to move the Featured Image box up… to just below the Publish box

Whenever I’m doing development on a WordPress site that makes heavy use of taxonomies (it happens with meta data-rich portfolios for architects, for instance, which seems to be a niche for me), I get really annoyed with how much WordPress devalues the Featured Image meta box. I don’t want it shoved way down below all of the taxonomies, mainly because users will probably forget or never even know that it’s there!

What I really want is to have the Featured Image box near — but not at — the top of the sidebar. Specifically, I want it to come just below the Publish meta box.

I’ve found some resources online that almost got me there, but not quite. However a minor tweak to this example solves the problem for me.

I’m taking some shortcuts here, some of which you may not like. First, most tutorials on manipulating meta boxes encourage you to remove them and then add duplicates with a slightly different ID. I think what’s happening here though (not having inspected the source code!) is that your modifications to the add_meta_boxes action run before the standard WordPress meta boxes get loaded (possibly/probably because, as you’ll see, we’re setting the priority to high), so if you’ve created one with the same ID as a default box, yours takes precedence.

The other shortcut I’m taking, which I suspect will be more controversial (but it’s just the way I like to do this) is that I am creating an anonymous function directly within the add_action() call. That’s just a personal preference, but I like to do it because 1) it keeps the code more compact and 2) it avoids creating a bunch of named functions that have no business ever being called anywhere else anyway.

So what’s happening here? First, I’m creating the Publish meta box. Then I’m creating the Featured Image meta box. By giving them both high priority, WordPress makes them the first two meta boxes in the sidebar. The reason I have to create the Publish meta box is that, if I didn’t, Featured Image would come first, above it. I don’t want that.

I’ve set the $screen parameter to null so this will happen on all editing screens, but if you only wanted to move Featured Image up on posts and not on pages, for example, you could set it to 'post'.

Here’s the full code:

add_action('add_meta_boxes', function() {
  add_meta_box('submitdiv', __('Publish'), 'post_submit_meta_box', null, 'side', 'high');
  add_meta_box('postimagediv', __('Featured Image'), 'post_thumbnail_meta_box', null, 'side', 'high');

For more background, check out the official documentation on the add_meta_box() function.

What’s in my bag?

Sounds like a dare to me. How can I resist?


First, the bag itself. I love Tom Bihn bags. In addition to this backpack and its clip-in accessory pouches, I own two messenger bags and an assortment of other pouches. They are super high-quality and supremely functional. As you’ll see when I show you how much stuff I can cram into this bag and still have plenty of room to spare!

Let’s look at the contents of the red pouch. That’s where I keep adapters and thumb drives.


Oh, and an iPod nano, for some reason.

I need napkins (and/or paper towels) a lot. I can’t stand having a runny nose, or spilling things. Whatever the reason, I carry a few around with me all the time. I know it’s probably not the best for the environment, but I do buy recycled as much as possible. Anyway, that’s what the black pouch is for. And the large black thing is a padded sleeve perfectly sized for the 11-inch MacBook Air.


Next up, another bag-within-a-bag. This is the one non-Tom Bihn case I use. It’s a Roocase for the iPad mini. The iPad mini actually just barely fits in it, which is fine. I take my iPad mini with me to meetings, especially first-time meetings with new clients, because it doesn’t create a wall like having a laptop on the desk does. I have also stopped carrying my laptop when I travel for pleasure, so the iPad in the Roocase is all I bring. In addition to protecting the iPad, the Roocase has a side pocket into which I manage to shove a Field Notes notebook, Space Pen, headphones and some business cards.


And finally… everything else. This is the entirety of what I had in my backpack when I came to work this morning, aside from some running clothes I had also shoved in but don’t really feel I need to show here.


Look at all that stuff! From top left to bottom right, we have:

  • Two copies of my latest CD
  • Victorinox Swiss Army Cybertool S, which is too big to actually carry in my pocket but an essential tool for tech geeks
  • Another Field Notes notebook
  • Sharpie, Field Notes pen, Space Pen
  • Compact travel power strip (highly recommended)
  • More business cards
  • 1/8″ to 1/4″ headphone adapter (not sure why that’s in there)
  • Novelty fan that plugs into iPhone Lightning port
  • Postage stamps
  • iPhone/iPad charger
  • MacBook Air charger
  • $3.01 in change
  • A bunch of Ricola cough drops (original flavor)
  • Spare keys on a special strap that Tom Bihn also makes
  • Anker portable phone charger, its charging cable, a USB-to-Lightning cable, and carrying pouch
  • Roocase with iPad mini and all of that crap inside it
  • 11-inch MacBook Air with padded sleeve
  • Accessory pouch with napkins
  • Accessory pouch with adapters, thumb drives and iPod nano

P.S. Sorry for the quality of the photos… I hastily snapped these on the studio conference table under harsh fluorescent lighting.

Some thoughts on 5 of my favorite Prince songs

Confession: I was never that big of a Prince fan at his peak of popularity in the ‘80s. I watched MTV incessantly, so I had plenty of exposure to most of his big hits. But I was a repressed small-town midwestern Protestant white kid. I thought it was cool when I learned he was from Minneapolis, but I didn’t see that connection as an opportunity to liberate myself from my fear of everything; I was still freaked out by his uninhibited, guiltless sexuality. That’s probably the biggest reason I didn’t pay very much attention to him, if I’m honest with myself about who I was as a 10-year-old.

But even though I didn’t consider myself a fan, and never owned any of his music until I was much (much) older, I still heard his music all the time. Because once MTV realized it was not only OK but necessary for them to play black artists, Prince was on there a lot. Videos like “1999” and “When Doves Cry” are burned into my memory. And honestly, even if I was a little sheepish about it, I liked them. As time went on, my appreciation grew. And so, as my small tribute to the Artist whose music touched so many people, here’s my list of 5 Prince songs. Not necessarily my “top 5” Prince songs, but 5 that have made a big impression on me, in the order that they did.

“1999” from 1999 (1982)

This song was my introduction to Prince. It was probably 1983 when I first saw it, so I would have been 9. I was fascinated by this song and the video. As a kid, I was always interested in the future. I wanted to get there, fast, whether it was a paradise or (as this song suggested) something more ominous. I’ve never paid as much attention to lyrics as to music, but the lyrics of this song definitely got my attention even back then. It was my first exposure to the idea of an apocalypse. And it made a huge impression on me that Prince would sing about confidently, defiantly celebrating in the face of doom. The other thing that impressed me about the video was that it showed Prince’s band performing, and he had women in the band. The music industry is still far too male-dominated today, but back then I didn’t even think to question things like that. So to see a band with both men and women, playing music together, struck me as something unique.

“When Doves Cry” from Purple Rain (1984)

Whoa. This video. The Rorschach-like mirror image effect had me transfixed from the first moment I saw it, but what really struck me was the sound of this song. It was sparse and futuristic and weird. I’d never heard anything like it, and probably still haven’t. As a kid, this was definitely my favorite Prince song. And it was made even better for me as a nerdy college student in the early ‘90s when it was referenced in a Simpsons episode where Milhouse meets his Shelbyville doppelganger.

[Much time passes…]

“Let’s Work” from Controversy (1981)

I spent most of my adult life not really thinking all that much about Prince. As I became more of an accomplished musician myself, my appreciation of his immense skills grew considerably, but I still was not really that engaged with his music. Then, one day in 2010 I was listening to The Current on the radio, and this incredibly funky song came on. I wasn’t really familiar with any of Prince’s pre-1999 work, so I had no idea what it was but I thought, “Oh, that sounds like Prince. I wonder if this is something new.” (Yeah, I’m not proud of that… but I do think it shows how we’ve come full circle that the dry, immediate sound of Prince’s early ‘80s recordings sounds contemporary again today.) I fired up Soundhound on my iPhone to identify the track, and before the song had even finished playing, I’d purchased and downloaded the entire Controversy album. Thus began my exploration of Prince’s early work. I bought all of his first four albums and listened to them — especially Dirty Mind and Controversy — incessantly for the next several weeks. Finally, at the age of 36, I really “got it”. Prince was a visionary genius, virtuoso multi-instrumentalist, and all-around legend. And he was from Minneapolis and stayed here.

“Uptown” from Dirty Mind (1980)

As I delved into the early work of Prince following my “Let’s Work” epiphany, I realized that Dirty Mind was really the album that defined Prince, the Minneapolis sound, 1980s pop music, everything. “Uptown” quickly became one of my favorite tracks on the album. I loved the upbeat funk groove, and the fact that it was a song about a neighborhood I used to live in. In 2011 I decided to record an album as a tribute to Minneapolis, the city where I was born and have spent most of my adult life. I had to include a song about Uptown, and I took inspiration from this Prince track.

“When You Were Mine” from Dirty Mind (1980)

The more I’ve listened to Dirty Mind (which has become my favorite Prince album), the more I’ve grown to love this song. It’s really kind of a weird song. It’s very poppy, with catchy hooks, but it is driven by strummed chords on an electric bass. And the lyrics seem, at first, like a typical pop song about a lost love, but as you listen closely you realize there’s a bizarre undercurrent to the story they tell that can only come from Prince.

I never was the kind to make a fuss
When he was there
Sleeping in between the two of us

Wait, what?

I figured my appreciation of this song was a bit unusual; it wasn’t a single, it was from before he really hit big. But in the days following his death, I discovered on Twitter that a lot of his fans cite it as their favorite Prince song. To me, it kind of symbolizes what Prince was all about: showing us that we’re all a little weird, that’s OK, and we’re not alone.

Star Wars: The Farce of Waiting

I did not see Star Wars last night.

That was not how it was supposed to be. I was supposed to see Star Wars last night.

Never tell me the odds.

Oh, sure, it was kind of a last-minute thing. Although I had seen The Phantom Menace on opening day, and even went to a midnight screening of Attack of the Clones when it was released, I just did not expect to see The Force Awakens for at least a week.

I didn’t even really give it much thought. A midnight screening would be impossible for my kids (ages 9 and 12) on a school night, and besides, they’re both already going to see it next Tuesday on a group outing with their swim team. So I just figured SLP and I would see it, eventually, sometime over the holiday break.

But then yesterday morning I found out screenings were actually going to start at 7 PM, and I realized we all could go to see it on opening night… if I could find tickets.

We live in the city. There’s a movie theater two blocks from our house. It’s a 1950s single-screen, restored to its original midcentury appearance. But it’s a second-run theater, so Star Wars will maybe be there in, like, February. No, for a premiere like this, the proper experience is at a suburban megaplex. But which one?

I fired up Fandango on my iPhone. AMC Southdale? Sold out. St. Louis Park, Roseville, Eagan… sold out. Finally I found some tickets available for an 8:30 showing in Inver Grove Heights. Purchased!

Oh, wait… crap! I bought tickets for a 3D showing. I hate 3D! Whew. You can get a refund. OK, now just to find another 2D screening… yes! The Paragon Odyssey 15 IMAX in Burnsville, Minnesota. (You’ll see why I am calling them out so specifically in a bit. They deserve it.)

The Paragon in Burnsville had several tickets still available for the 8:05 screening. Purchased!

You do have your moments. Not many, but you do have them.

With tickets acquired, I hatched a master plan to surprise the kids. We would be leaving for the movie directly from their swim practice. When the car turned the wrong way to go home, and one of the kids asked where we were going, I would, right on cue, turn on the car stereo, where the opening trumpet blast of the Star Wars Main Theme would be playing.

In my experience, there’s no such thing as luck.

Things started to unravel early. Mid-afternoon, our 9-year-old daughter called from school, sobbing. She had eaten too much for lunch, and was immediately thereafter hit in the stomach with a football on the playground. After some hesitation, I resorted to revealing the surprise as a way to comfort her. Which it did. But now the surprise was 50% blown. Oh well, at least we could still surprise our 12-year-old son…

…who came home from school brooding. He didn’t want to go to swim team, for whatever reason. Turns out he had not really eaten anything all day because the school lunch was “soooo disgusting!!!!” So in an effort to lighten his mood, we told him the surprise too.

If you’re saying that coming here was a bad idea, I’m starting to agree with you.

Burnsville is an outer suburb, about 30 minutes’ drive from our house. We arrived at the Paragon at 7:10. Plenty of time to settle in, get some food, and wait for the show to start. No stress, because the Paragon has assigned seating.

We entered the lobby of the theater. It was quiet. Too quiet. Oh, there were people around, but not the endless queues of plastic lightsaber wielding Jedi knights and cloaked Sith lords I expected. We walked directly to a self-service ticket machine, punched in our confirmation code, got our tickets and entered the theater.

I had never been to the Paragon before. Nor, in fact, to any of the new-style contemporary megaplexes with opulent lobbies, beer and wine, and decadent reclining seats. I had been alarmed when I picked our seats from the online seating chart that we appeared to be seeing the movie in a tiny theater with only five rows of seats. But it turned out to be an average-sized megaplex screen. It was the seats that were out of scale.

So, you’d think this would be an amazing experience, right?

Our troubles started almost immediately. Though the lobby of the theater is huge and ornate, once you’ve handed over your ticket stub you’re thrust into a cramped and poorly laid-out concession area. One that was littered with dropped popcorn kernels and straw wrappers, sticky self-service soda fountains and empty napkin dispensers.

We ordered buttered popcorn for the kids, and two cheese pizzas for us. We were told it would be a few minutes for the pizzas, so we all headed to the door of screen 14.

How you get so big eating food of this kind?

A few minutes later I went back for the pizzas. At least, I think they were pizzas. I saw two small, doughy discs sitting on the end of the conveyor belt of the pizza oven and pointed them out to the cashier. I actually said, “Those are my pizzas… at least I think they’re pizzas. Or are they pretzels?” I really couldn’t tell.

I expressed that sentiment while eating a Papa John’s single-serving pizza at a Twins game this past summer. Last night the Paragon in Burnsville proved me wrong. It’s hard to explain what was wrong with the pizza. I’ve never had anything like it. It was, nominally, a pizza. Crust on the bottom, tomato-based sauce in the middle, a vaguely cheese-like substance on top. Except… the cheese wasn’t melted. At all. It’s not that it was cold. It wasn’t mozzarella, or any other kind of cheese that melts. If it was actually cheese. It might have been parmesan. Nonetheless, that was our dinner, so we ate it.

I’ve got a bad feeling about this.

While we were waiting outside screen 14 for the crew to finish cleaning it, SLP and the kids overheard the manager on a walkie-talkie saying something about the 8:00 screening being canceled. [Foreshadowing.]

Passably sated, and more than comfortably seated, we waited out the remaining half hour to showtime, enduring the usual barrage of annoying, blurry, up-scaled TV commercials and mix of hideous versions of Christmas songs. My favorite love-to-hate-it recording was Darius Rucker’s rendition of “You’re a Mean One, Mr. Grinch.”

None of that mattered though, because we were going to see STAR WARS. I passed the time on social media, posting photos on Instagram, clever quips on Twitter, and exchanging eager comments with friends on Facebook.

8:05 came. And went. At around 8:12, the manager came in and made an announcement. For reasons she only vaguely alluded to, something about the movie being very popular and them having added more showings, our 8:05 screening was actually not going to be able to start until around 10 PM. “It might be 5 minutes to 10, it might be 5 after 10. But it will be around 10:00,” she said. She offered full refunds of our concessions, free passes to future IMAX screenings, and the option to stick around until 10:00 or get our tickets refunded.

People were… mildly perturbed. This is Minnesota, after all.

We discussed it briefly, and decided to wait it out. By this point it was almost 8:30, so I figured another hour and a half would go by quickly. Besides, there was beer.

Did I mention there was beer?

I headed back to the concession stand, and returned with a Fat Tire in each hand. By this point, only one other person besides us was still waiting in the theater. Most of them, not living a half hour away, had decided to go home and come back later, or perhaps hang out in the mezzanine-level bar. (What kind of movie theater is this, anyway? Are we in Amsterdam? Am I Vincent Vega? Sorry… wrong movie.)

After maybe another half hour passed, we decided to check out the arcade. I’m not talking about the typical, pathetic movie theater arcade of the ’90s or early 2000s… two dilapidated arcade cabinets, one of which is unplugged. This was a real arcade, with a claw machine, skee-ball, air hockey, all of your modern video game cabinets and, first thing you see, a combo Ms. Pac-Man/Galaga machine. We had an air hockey tournament and I set a new personal high score in Ms. Pac-Man, and before we knew it, it was 9:30. We headed back to our seats.

It’s a trap!

9:55. 10:00. 10:05. At 10:10, an usher started walking the aisles with a small bucket, and handed each of us a packet of Care Bears-branded fruit snacks. (Really? You make us wait 2 hours and the best you can do is give us the crap even 3-year-olds won’t touch?) Then the manager returned, to inform us that the movie was “here” and would be starting soon. 10:20. 10:30.

At 10:40 I went to the bathroom. Fruit snack boy was now stationed at the entrance with a giant plastic tub full of popcorn and a stack of bags. “I’ve never had to do anything like this,” he said. When I got to the men’s room, I noticed someone had spit chewing tobacco into the urinal. Nice. (And not really relevant to the story at all. I’m just trying to add some local color.)

When I got back, the manager had returned. She informed us that the copy they were setting up on our screen was “at 92%” — whatever that meant — and would be a little while longer. She added, disconcertingly, that there were several seats available for the showing on screen 9 that was going to start at 10:55. Several people left. SLP and the kids stayed behind while I went to screen 9 to see what was available. The only open seats, together, were… the entire front row. With good reason. I sat in one of the seats to test it out, and even in the reclined position it would require craning your neck. I decided we should stay put. I went back to deliver the bad news.

They told me they fixed it! I trusted them. It’s not my fault!

At 11:00, the realization set in that, even if the movie started reasonably soon, it wouldn’t be done until nearly 1:30 in the morning, which meant we wouldn’t get home until at least 2:00, and the kids had school in the morning. It was a split decision, but we finally resigned ourselves that this just wasn’t happening.

I scarcely spoke a word on the long drive home. I was fuming. I hatched a plan. Once I was settled into bed, I would erase every tweet, every Facebook post, every Instagram photo I had posted pertaining to Star Wars. I would forget that this night had happened. I even deleted all of the photos I took at the theater from my phone. They’re gone. Erased… from existence. (Sorry… wrong movie. Again.)

It was partly my fury over how we had been treated. Partly my frustration at having gotten so pumped up to see this movie, spending five hours in total in that effort, and leaving with nothing. But the last straw was a reply to my tweet about my phone being down to 9%. Someone I only know through Twitter replied, in effect, that it was what I deserved for keeping my phone on during the movie, to which I had replied in fuming all-caps that the movie hadn’t even started and I had been sitting in the theater for three hours. I blocked her. And some rando who follows her who liked her tweet.

But still, I woke up this morning almost as frustrated as I felt last night.

The 92%.

It was the 92% that stuck with me. What the hell was that about? I don’t know much about how digital projection in movie theaters works. I do know that movies are distributed on hard drives now, with encryption, but I really had no idea what the process was. I had to find out, so I could make sense of what exactly was happening that took so long last night, and hopefully find some closure to the whole experience. To Wikipedia!

This is some rescue.

Movies are delivered to theaters on what is known as a Digital Cinema Package (DCP). This is often a physical hard drive, which can contain as much as 300 GB of data. I don’t know if you’ve ever tried to copy 300 GB of data from an external hard drive over USB, but… it takes a while. Further explanation from Wikipedia:

Regardless of how the DCP arrives it first needs to be copied onto the internal hard-drives of the server, usually via a USB port, a process known as “ingesting”. DCPs can be, and in the case of feature films almost always are, encrypted. The necessary decryption keys are supplied separately, usually as email attachments and then “ingested” via USB. Keys are time limited and will expire after the end of the period for which the title has been booked. They are also locked to the hardware (server and projector) that is to screen the film, so if the theatre wishes to move the title to another screen or extend the run a new key must be obtained from the distributor.

Italics mine. That is the key to the whole thing. I’m not sure if the scheduled 8:05 showing on screen 14 ever actually took place last night. I don’t know if the management knew it wasn’t going to happen, and were just stringing us along, or if they really believed they were going to work out a solution. But the level of incompetence I witnessed may well have carried over into the projection room.

The “92%” referred, I am assuming, to the progress in copying the movie from the external hard drive to the server’s internal drive, a necessary step to screen it. However, they also needed a new decryption key for this specific projector, and who knows if they actually had that or not? They had added these extra screenings at the last minute. Whether they were just hoping they’d get the keys from the distributor in time, or they had no clue that they’d even need new ones… I don’t know.

All I know is, I did not see Star Wars last night.