Occam’s Razor as it applies to DevOps

A client emailed me today about an unusual problem. The “Create an Account” link on their website was not pointing to a page of their site, but instead to an IP address. An IP address that, when I ran a whois on it, turned out to be owned by China Telecom.

That was disconcerting. I’m not passing any judgment on anyone in particular in any country in particular, but the fact is, a hugely disproportionate number of online attacks against U.S. sites come from a small number of foreign countries, China being one of them. So, I was alarmed.

My first guess, since I’ve seen WordPress sites get hacked… a few times (much less often since I started using Wordfence as standard practice on every site I build), was that hackers had somehow hijacked this link and were trying to route my client’s users over to their systems in some kind of spoofing/phishing/whatever attack.

Only, the link didn’t actually do anything. There’s no web server listening on that IP address. Hmm.

Next up I looked at the HTML source, since I wasn’t quite sure if the code in question was being generated by their theme (which I built) or by a plugin like WooCommerce, so I wanted to see the CSS classes on the wrapper. Sure enough, it was in code I had built.

When in doubt, always blame your own code first.

But the fact that it was my code wasn’t what really caught my attention. It was the fact that the URL in the HTML wasn’t the IP address at all. It was just a string of 8 digits.

Suddenly it was clear to me that this was not a hack attempt, and China Telecom (or anyone using its services) had precisely zero to do with whatever was going on. This was a peculiar code problem, and it was at least indirectly my fault.

That 8-digit number… it looked very familiar. I knew that, due to some of the peculiarities of this client’s site, the ID values in their wp_posts table are on that order of magnitude. Bingo. This wasn’t a URL at all. It was a WordPress post ID, being incorrectly inserted here because Advanced Custom Fields was outputting the raw ID value instead of using it to look up the corresponding post URL.

Why was that happening? Well, that was ultimately my fault too, having to do with some logic that was designed to tell ACF where to load my custom field groups. When the site was built, I had that code running (technically) too early, then ACF released an update that started throwing up admin notices if you did that, so I rewrote it to run later… only I didn’t realize that my change was causing my code to get applied to a hook after it had already run, so… it wasn’t working. It was a very isolated set of circumstances, so it went unnoticed for months. As far as I know, the only user-facing issue it created was this weird integer URL on the “Create an Account” link on a page few people ever visit.

The simplest explanation is the most likely.

Even if the details are anything but simple. Was someone in China trying to hack my client’s site to phish for their customers’ data, or did I just write some flawed code? Hmm.

On Apple products and the subjectivity of “expensive”

Until the Apple Watch came along, I had gone 20 years without wearing a watch. And before that, the most I had ever spent on one was $50. A watch was not an object I was willing to spend more money on than that. But then Apple released a very compelling product, and I went for it. (Even if, in retrospect, the original iteration of the Apple Watch was… underwhelming.)

I’ve owned three Apple Watches: the original (a.k.a. “Series 0”), a Series 3, and my current one, a Series 5. But after visiting a local Apple Store yesterday and looking at the latest models, I decided it was time to upgrade my Apple Watch experience once again, so today I ordered a Series 8… and for the first time, I sprung for the extra $100 to get one with cellular. Now I can finally go out running without bringing my phone. This will transform my running experience… once the snow melts and I’m back outside, that is. (But actually it will transform my experience on the indoor track too. I hate having my phone in my pocket when I run!)

Of course, running without my iPhone means that also means I need wireless earbuds. I may be willing to drop a lot of cash on a lot of Apple products, but AirPods are one area where I feel their options are way overpriced. Well… maybe not overpriced for what they offer, but more than I am willing to spend, for features I don’t really care about.

What’s worse, I can’t stand hard plastic earbuds, and Apple’s only option with silicone tips is the $250 “Pro” models. There’s no way I’m paying that much of a premium for something so easily lost. I’d rather stick with my tried-and-true $9 wired Panasonic earbuds. (But of course you can’t use wired earbuds with the watch!)

Anyway, I found these no-name Bluetooth earbuds on Amazon for $20. I’m going to give them a try before I go for anything higher-end. Hopefully they won’t explode while I’m wearing them!

Bootstrap 5 Carousel: position captions outside (i.e. below) the images

(If you want to get right to the point, a full code example is provided at the bottom of this post.)

I’m not a Bootstrap expert. During most of its evolution, I’ve mostly ignored it in favor of rolling my own… everything. I finally really embraced Bootstrap when I had a rush project in October 2022 that was way too precisely designed to work with WordPress, especially Gutenberg. (And the client didn’t need editing capabilities.) So I decided to hand-code it, but to use Bootstrap to… uh… bootstrap my HTML/CSS layouts.

I think Bootstrap 5 is excellent. I wish Gutenberg was built on top of it instead of the idiosyncratic house of cards it’s actually built on, but whatever.

As it happens, I’m actually now using Bootstrap 5 with Gutenberg for some custom blocks, specifically a Carousel block. One of the options I want to provide in my block is the ability to show the carousel’s captions and controls outside of the image, but apparently, at least with Bootstrap 5, that’s not an option.

I decided to Google for a quick solution before creating my own and I came across… this. I’m sure it gets the job done, but it seems severely over-engineered, so here I’m presenting my own comparatively simple, CSS-only solution.

Basically there are two things you need to do: 1) move the absolute-positioned caption text below the image, and 2) add padding to the bottom of the container, so the caption has somewhere to go instead of just overlapping the content below it.

Let’s start with the second item first:

.carousel { padding-bottom: 4rem; }

There may be some trial and error here, as you need to make sure you’re accommodating captions of varying length. I will admit this is not fully thought out here, and unlike the rest of what is about to follow, it may be a deal breaker under certain circumstances. But let’s assume your captions are a fairly consistent length, and you can determine how much padding you need.

Getting the caption pushed below the images is easy…

.carousel-caption { top: 100%; }

…except, oops, vertical overflow is hidden. Let’s fix that:

.carousel-inner { overflow: visible; }

Of course, if you have your transition effect set to slide (which is the default), that now spews stuff all over the page in an ugly way. But we can fix that by hiding overflow on the outer carousel element instead:

.carousel { overflow: hidden; }

You might, at this point, wonder why I didn’t just set overflow-y: visible on .carousel-inner which seems perfectly reasonable, and which, of course, I tried. But for reasons I couldn’t be bothered with investigating, that ended up causing .carousel-inner to just show a vertical scrollbar and not display the caption unless you scrolled it. Ugh. No matter, the above takes care of it.

That’s pretty much it, as far as the captions go. But if you’re using the controls (previous/next arrows) or indicators (dots/lines for the number of slides and current selection), you’ll notice there’s some weirdness to their placement, so let’s fix that too. The indicators just get shoved to the bottom of the container, so your bottom padding can accommodate that. But if you want to move them back up onto the image, you just need to offset that extra padding, like this:

.carousel-indicators { bottom: 4rem; }

Make that value the same as the bottom padding you added to .carousel itself.

As for the controls, since you’ve made the overall container taller, they’re now a bit too low rather than being vertically centered on the image. Guess what… setting their bottom value to match the extra bottom padding fixes their placement too!

.carousel-control-next, .carousel-control-prev { bottom: 4rem; }

So, putting it all together as concisely as possible, here’s what we have:

.carousel { overflow: hidden; padding-bottom: 4rem; }
.carousel-caption { top: 100%; }
.carousel-inner { overflow: visible; }
.carousel-control-next, .carousel-control-prev, .carousel-indicators { bottom: 4rem; }

On the Futility of Naming Colors

I posted this over on the ICS Calendar blog, but it’s probably of some interest to the audience of this blog, dealing with the intersection of code and design.

HTML has “named colors” which are… weird. And I have finally given up on using them in the plugin, which was a bit of a silly thing to do anyway, when there are 16 million RGB colors at one’s disposal. The post ends with swatches of both the old and the newly revised color palettes.


How I learned to stop worrying and embrace being a middle-aged curmudgeon

Reading John Gruber’s thoughts on the potential future existence of an iPhone SE 4, which necessarily included a note on the lack of an ongoing slot in the iPhone lineup for my beloved 13 mini, got me once again thinking more generally about something that’s been on my mind a lot lately, as my 49th birthday approaches in 2023.

That thing I have been thinking is, this is not the future I signed up for. I’m beginning to understand why older people get set in their ways and cranky about change, in ways that probably could not have occurred to me until I had lived enough to experience a lot of things firsthand.

I remember lots of things, both good and bad. Most things, in general, improve and get better over time. But some things get worse — at least, worse by my subjective standards, because there are certain things I don’t want, like a phone that’s too big for me to use with one hand, or fit comfortably in my jeans pocket.

Possibly even more frustrating than things that get worse are things that just stop existing. Things I really liked that are now gone, especially if they are gone for reasons that I do not think are sound. That happens a lot for me with styles of music or genres of video games. I want new synthwave music* that I can get as excited about as when I first heard Tycho and Com Truise in 2012, for instance, or new Castlevania games that are as good as Aria of Sorrow.

True, sometimes those glories do return, and 2021 gave me a double whammy, in the form of Mitch Murder’s Then Again album and the all-time classic Metroid Dread. But more often than not, I just have to move on and give up on dreaming of a decent modern SimCity game, or a computer Scrabble that will ever be even a fraction as “smart” as the GameHouse version I loved back in 2006.

The world is change. I get that. It’s just hard to let go of things that I know are better than what has replaced them, and that makes me cling desperately to the old things I have that I’m still able to enjoy, whether that’s 40-year-old vinyl records on my turntable or 20-year-old GameBoy Advance ROMs in an emulator.

The true nature of the curmudgeon, I think, is not borne of pessimism. The curmudgeon is not purely a crank. It’s optimism, idealism. Belief in a world that could and should be better than the one we’re living in, because you remember something that’s gone. And beyond that, you remember the trajectory those past things suggested we were on. But somehow we never got where we were going.

Now, let me be clear: I’m not one of those MAGA types who longs for a return to their fictional, idealized version of what the 1950s were (at least, for white people). I’m inspired by the progress we are making towards a more equitable society for everyone — even as I see how far we still have to go. I’m longing for a future world that never was, but that I believed we could — and would — have by now.

Ironically, it was the sci-fi dystopias that were so popular in my childhood in the 1980s, that seemed to get many of the worst things about the 21st century right. The techno-fascism of corporations more powerful than governments, spying on our every action as a way to make more money. Aggregations of the intimate details of millions of people’s personal lives have become the most valuable commodity around. I didn’t believe this was the future we would have, but here we are.

I just want a small phone, some good music to listen to on it (ideally through wired headphones and a 1/8″ jack), and maybe a couple of games that are actually mentally stimulating instead of just “idle” ways to give up money or personal information in an endless stream of “microtransactions.”

Is that too much to ask?

* Of course, synthwave itself is an appeal to the future-should-be-better-than-it-is nostalgia of people (like me) who grew up in the 1980s.