Thoughts on Doing Better

This is the script from a YouTube video I posted today.

These are not normal times. I can’t just do the same things I normally do, and pretend nothing is happening.

I. Freedom and Responsibility

I believe in the innate freedom of every person to choose how they want to live their lives. (And every person is a person, regardless of their race, religion, sexual orientation, gender expression, or ability.) I also believe that freedom comes with responsibility.

If we choose to live in a society — which is not really a choice in the 21st century — then we also have a responsibility to the others around us, to respect and defend their freedoms. The civil rights movement was born of the principle that no one is free, unless we all are free. Or, to put it another way: injustice anywhere is a threat to justice everywhere.

But it’s not just about individual freedoms. Working together, we can do things that none of us can do alone. Collective action allows us to create a better, more just, more meaningful world, for everyone.

II. Power and Money

The late, great senator from my home state of Minnesota, Paul Wellstone, famously said it best: “We all do better when we all do better.”

Or maybe you prefer the words of Jesus: “So in everything, do to others what you would have them do to you, for this sums up the Law and the Prophets.” (Matthew 7:12 [NIV])

Power and money are corrupting forces. Yes, complex organizations need some kind of hierarchy, to make decisions and maintain order, but everyone should still have a voice. And money is an essential tool for an economy to function, when we remember its fundamental purpose: storing the value of human effort, to facilitate trade.

But there are those among us who ignore the social contract; those who choose to hoard wealth and power. Their insatiable desire for more drives a wedge between us. Great empires of injustice are built on the backs of the masses, whose labor is essential to the construction of those empires, but who do not receive their fair share of the value they produce.

Every large-scale economic system, across the spectrum, from communist totalitarianism to laissez faire capitalism, creates an imbalance of wealth and power. It is the power of the people, the purpose of democracy, to provide a counterbalance, to ensure liberty and justice for all.

III. The Internet

The Internet exploded in the 1990s, at a unique time of international openness and realignment, following the collapse of the Soviet Union. There had never in human history been such a powerful tool for the free exchange of ideas, with anyone in the world, nor such an opportune moment for it to appear.

Fast forward to 2026, and obviously that utopian vision for the Internet has become severely corrupted by power and money… as almost all large-scale human endeavors inevitably do.

Today, misinformation and abuse are everywhere. Corporations have acquired, or squashed — often acquired in order to squash — many of the once-independent voices. Countries that were once exploring a new openness have slammed the door shut. Social media echo chambers encourage people to indulge their worst impulses. Every website seems to be drowning in ads and AI slop.

And yet, individual, self-hosted blogs still exist. Independent voices can still speak freely to the world on YouTube… even if the mysterious algorithm ultimately decides which voices rise to the surface in the vast ocean of “content.”

One word (OK, it’s a portmanteau/neologism) shows, better than any other, that the Internet can still live up to its earliest ideals: Wikipedia.

The Internet hasn’t completely lost its potential for the kind of collective action that allows us to do better — to create a more informed, more interesting, more just world — for everyone. But we have to work harder at it.

IV. Minneapolis

I live in Minneapolis. I love Minneapolis. It is a city that embraces the spirit of Paul Wellstone.

It’s not a perfect place. We all know that. But it’s a place that is trying hard to do better. And that’s made it a desirable place to be, for a lot of people.

A place that celebrates nature, with its award-winning park system. A place that celebrates the arts in their many forms, especially with its thriving theater and music scenes. A place that celebrates the diversity of its people, with a vast array of thriving restaurants and small businesses. A place that celebrates learning and innovation, with the University of Minnesota and numerous smaller colleges.

A place that is in jeopardy, in the present moment.

Minneapolis is a city of immigrants, built on stolen Dakota land.

I say that not because I believe it can somehow be “unstolen,” or as some kind of token white guilt self-flagellation. We live in the world we live in. We can’t erase the past — as much as some of us may try — but we can choose how we write the future. Choosing a future where we do better requires understanding how we got here in the first place.

People from all over the world choose to live in Minneapolis, because it is a place where they are welcome. Where they find opportunities. And where they become a part of the fabric of the community, making the city better for all of us by sharing their talents, their hard work, and the things that make their cultures unique. This is what the city has always been.

But, of course, Minneapolis is not perfect. No place is. Fort Snelling is a painful reminder of that, from its use as a concentration camp after the Dakota War of 1862, to the use of the nearby Whipple Building by ICE in 2026. (The Whipple Building is not part of Historic Fort Snelling, which is managed by the Minnesota Historical Society.)

Another painful reminder is the history of redlining, which still shapes the character of our neighborhoods more than a half century after the practice was banned. (The city has a program for homeowners to remove the now-unenforceable racial covenants from their property deeds, which my wife and I did in 2021.) And just six years ago, we experienced one of the most difficult times we’ve ever faced as a community, with the police murder of George Floyd.

But despite — maybe because of — our flawed history, and our imperfect present, we are always striving to make Minneapolis a place where “we all do better.”

The civic spirit of our city is a threat, to greed and corrupt power. So we’ve become a target. But our humble reputation for “Minnesota nice” belies the fierceness with which we will defend the things that make this place special.

That quote from Paul Wellstone, “we all do better when we all do better,” is not just an empty platitude. It’s a foundational pillar of the kind of progressive politics that has held strong here over the decades. And it’s something that those corrupted by wealth and power will never understand.

Here, we actually believe in something beyond ourselves. Our moral compass has not been skewed by the pull of avarice and cynical nihilism.

The thing is, we know we’re not especially unique in that regard. We believe most people, everywhere, feel the way we do. That everyone deserves a fair chance to live their own lives, and that they should treat their neighbors with care and respect. It’s just something that most of us go about quietly.

But. Now is the time to not be so quiet about it.

V. Do Better

These are not normal times… in Minneapolis, in the United States, in the world. I can’t just do the same things I normally do and pretend nothing is happening. I have to speak up. I have to do better.

I believe in the innate freedom of every person to choose how they want to live their lives. I also believe that freedom comes with responsibility. Individual freedom comes with the responsibility of helping to ensure and defend the individual freedom of others, and to engage meaningfully with others, to do the things we can’t do alone.

“We all do better when we all do better.”

But… what, exactly, does it mean to “do better?”

Is it a state of being? Or a call to action?

It’s both. And you can’t have one without the other.

How to get WordPress REST API self-requests working in a local development environment, i.e. Docker

I’ve only just started using Docker to run a local development environment for testing my WordPress plugin development. (Yeah, don’t get started. I’ve been working successfully for nearly 3 decades by testing my code on a remote development server.)

Using Docker is great! It’s so much faster to be able to just save my code and hit refresh to see the changes immediately, without having to sync the changes to the server… and local load times are lightning-fast!

But there’s a problem. ICS Calendar Pro‘s main functionality is to retrieve and process data from external URLs (specifically iCalendar subscription feeds), using the wp_remote_get() function. Once I added the ability to create and manage similar data locally, the obvious solution to me was to still retrieve and process that local data as if it were an external source, so ICS Calendar Pro generates ICS feeds for the internal event data, and then it makes wp_remote_get() requests to itself.

This was not working in Docker, and it took me several hours of wading through a sea of AI slop on the WordPress “how-to” websites that have spread across the web like a film of toxic algae, to finally find an answer.

Port forwarding.

I access the local Docker site at the URL http://localhost:8888 but it didn’t occur to me immediately that since Docker is running a virtualized Linux system inside a container, it’s Docker that is listening on port 8888 of my Mac system, but then Docker is routing those requests to Apache listening on port 80 inside the container.

Since the plugin’s REST API requests are coming from inside the container, Apache inside the container also needs to be listening on the port Docker is using outside the container.

This involved going into the file system of the container within Docker and editing the /etc/apache2/ports.conf file, to add the second line shown below:

Listen 80
Listen 8888

I restarted the container and boom! Everything was working.

Well… I think that’s because I had already taken another step that was probably essential for this. Since the WP REST API is using URLs with the wp-json/ path, WordPress’s URL routing needs to be activated. By default, the WordPress installation in the Docker container had the Permalink structure set to “Plain.” Changing that to anything else (on the Settings → Permalinks page) makes the REST URLs work.

Building a custom WordPress block without a React/JSX build process

I spent two solid days trying to make this work. And it’s not purely because I’m too stubborn to learn React/JSX. That’s part of it.

It’s more that I’m too stubborn to introduce an arbitrary build process into my development workflow. I have my own idiosyncratic ways of doing things, and I’m coding solo, so I generally don’t need to conform to someone else’s standard practices. As long as what I’m doing has its own internal consistency and safeguards, and produces a reliable product, who cares how I get there?

I actually have introduced a few “build” processes into my own workflow, using Git (out of necessity) for version control of my WordPress Plugin Directory plugins, my own custom versioning process for non-repository plugins, and “minifying” all of my JavaScript and CSS (using online minifiers).

But I am not doing any significant amount of block development. Certainly not enough to learn JSX and implement the arbitrary build process into my workflow, adding thousands of unnecessary dependency files to my development codebase, etc.

For the latest version of ICS Calendar Pro (version 6), currently in development, I just needed to add a simple custom block that lets users insert a calendar (a Custom Post Type) by selecting one from a dropdown list. It’s a more user-friendly option than what they’ve had to do up to this point: copying a small code snippet from the CPT’s admin page and pasting it into a Shortcode block.

Ideally my new block would render a preview of the calendar in the Block Editor, but that’s secondary to just having a way to insert the block itself and pick from the list of saved calendars.

I found the official list of block development examples, and made some progress with the “no build” example. I also found a great article from Cello Expressions (maker of the Sheet Music Library plugin), called Dynamic Blocks with Block.json, Vanilla JS, and No Build Process. I really thought that was going to get me exactly what I needed… until I realized that their particular needs did not involve having any kind of configuration options in the Block Editor itself.

Then I found DahmaniAdame/block.js on GitHub Gist. It was exactly what I needed. A straightforward, no-build example, using Vanilla JavaScript, of a simple block with a dropdown list of posts in the inspector sidebar.

The only problem: it worked perfectly if I stuck with built-in post types, but it would cause a vaguely defined block error when I switched it to using my CPT.

Eventually I realized the problem was here:

const title = post.meta.some_meta1 && post.meta.some_meta1 !== ''
		? post.meta.some_meta1
		: post.title.rendered;

For an as-yet undetermined reason, I’m not able to retrieve meta data about my CPT. (Yes, I changed some_meta1 to the actual key of my meta data field, view.) I decided I didn’t really need to retrieve that meta data, so I just removed the conditional, i.e.:

const title = post.title.rendered;

With that change (and a few others to change the post type I’m querying and remove a few other things I didn’t really need), my block was working perfectly! So much so, in fact, that I decided to try to push it to do more things. There, I ran into some trouble, because my block does a lot of JavaScript stuff when it renders, and I haven’t (yet) figured out how to get that JavaScript to run when the content renders in the Block Editor, so the HTML output of the block does get inserted into the Block Editor when the user selects from the dropdown, but the JavaScript that actually reveals it on the front-end page isn’t running. (Yes, I used enqueue_block_editor_assets to load my JavaScript and CSS files. And I also added logic to try to force my plugin’s JavaScript initialization event to trigger, but something more complicated is going on.)

Anyway… it’s still not 100% perfect, but I really wish I had found that Gist before I spent two full days banging my head against the wall over this!

How to get Safari not to display the disclosure triangle on an HTML5 <details> <summary> tag in 2025

I hate blog posts with “in (insert current year here)” in the title, but it seems important here, because none of the stuff I’m finding in the usual locations, even StackOverflow, CSS-Tricks, or the Mozilla Developer Network really seems to be getting this right.

So, you’re using the HTML5 <details> tag, and you want to customize the disclosure toggle element on the <summary> tag? Most browsers play nicely with summary::marker in CSS, but not Safari.

Forget this (the old rule everyone has been recommending but that doesn’t seem to do anything, at least, not anymore):

details summary::-webkit-details-marker { display: none; }

Fortunately, the solution (at least, what seems at this very moment to be working for me) is even easier, once you find it:

details summary { list-style: none; }

(OK I did actually find this solution on the MDN link above, but it also includes the bit that I said doesn’t seem to do anything.)

A practical example

Let’s say you want to get rid of the triangle and replace it with a plus sign (+) that changes to a minus sign (-) when toggled open. You can certainly improve the aesthetics of this with color, or images, or some margin/padding tweaks, but this is the essence. Confirmed to be all you need in both Safari and Firefox (the only browsers I currently have installed on my Mac), but it should work in Chrome too.

details summary {
    list-style: none;
}
details summary::before {
    content: "+";
}
details[open] summary::before {
    content: "−";
}

How to replace line break tags (<br>) with spaces in CSS

You can’t. This post on TimonWeb with the same title as mine is wrong. As is this one from Gallagher Website Design, which of course it is, because it’s exactly the same.

And now that all of the search engines (even my beloved DuckDuckGo) are using AI to gather answers, the LLMs are permanently ensconcing this incorrect answer to the question.

It doesn’t work.

I get why it might seem like it works. In fact it’s kind of not really doing anything… at least in terms of adding the space. The only reason there’s a space between the words where the <br> tag got removed is because there’s a space there in the HTML.

HTML honors spaces. Well, one space. If there’s a line break, that’s the space. If you get rid of the line break and there’s still space in the HTML, then HTML renders a space. But if removing the <br> tag pushes two words together, they’re really going to get pushed together.

The only way (at least, the only way I’ve been able to find, and I’ve spent more time on this than it’s worth) to get there to be a space when you remove the <br> tag, is to actually have a space in the HTML.

Since I’m dealing with WordPress, which is the reason it’s a problem, I have also come up with a WordPress-specific solution. The reason it’s a problem in WordPress, at least in the Block Editor (a.k.a. Gutenberg) is that if you insert a line break in your text, i.e. by typing Shift-Enter, it inserts a <br> tag without any space between the words.

I guess Gutenberg is trying to be efficient by not adding an unnecessary space. You know, just like how it makes efficient use of CSS class names and inline styles like this:

<div class="wp-block-columns are-vertically-aligned-center is-layout-flex wp-container-core-columns-is-layout-7d06e0e1 wp-block-columns-is-layout-flex" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)">

I’d insert the eyeroll emoji here, but I use my No Nonsense plugin to prevent WordPress from unnecessarily… uh, I mean efficiently, loading a bunch of emoji-related JavaScript on every page of every one of the 42% of the websites in the world it powers.

Let’s see, now where was I? You can use the the_content filter to force WordPress to add a space after <br> tags in page content:

function my_the_content($content) {
    $content = str_replace('<br>', '<br> ', $content);
    return $content;
}
add_filter('the_content', 'my_the_content', 11, 1);

(Yes, this might be adding an extra space in places where there’s already a space after the <br> but it doesn’t matter… as I said, HTML honors the space, but only one space.)

Once you’ve got that in place, then you can write some CSS to selectively remove <br> tags from display. In my case, I want to remove them in Cover blocks on mobile devices, and my mobile breakpoint is 1024 pixels. So here’s the CSS I wrote:

@media screen and (max-width: 1024px) {
    .wp-block-cover__inner-container br { display: none; }
}

Note here that I’m just hiding the <br> tags altogether, which is all you really can do. But as long as that <br> tag is followed by an actual space (or an actual line break) in the HTML code itself, then you’ll get a space between the words.