Jim Nielsen’s Blog

You found my experimental HTML feed (there are also other ways to subscribe).

I HTML

Recent posts

Doing It Manually

View

I have a standing desk that goes up and down via a manual crank.

I’ve had it for probably ten years.

Every time I raise or lower that thing, it gets my blood pumping.

I often think: “I should upgrade to one of those standing desks that goes up and down with the push of a button.”

Then there’s the other voice in my head: “Really? Are you so lazy you can’t put your snacks down, get out of your comfy chair, in your air conditioned room, and raise or lower your desk using a little elbow grease? That desk is just fine.”

While writing this, I get out of my chair, star the timer, and raise my desk to standing position. 35 seconds.

That’s the cost: 35 seconds, and an elevated heart rate.

As I have many times over the last ten years, I recommit to keeping it — mostly as a reminder that it’s ok to do some things manually. Not everything in my life needs to be available to me at the push of a button.


Reply via: Email · Mastodon · Bluesky

Running Software on Software You’ve Never Run

View

I love a good look at modern practices around semantic versioning and dependency management (Rick Hickey’s talk “Spec-ulation” is the canonical one I think of).

Niki recently wrote a good ‘un at tonsky.me called “We shouldn’t have needed lockfiles”.

What struck me was this point about how package manifests allow version ranges like ^1.2.3 which essentially declare support for future versions of software that haven’t yet been written:

Instead of saying “libpupa 1.2.3 depends on liblupa 0.7.8”, [version ranges] are saying “libpupa 1.2.3 depends on whatever the latest liblupa version is at the time of the build.”

Notice that this is determined not at the time of publishing, but at the time of the build! If the author of libpupa has published 1.2.3 a year ago and I’m pulling it now, I might be using a liblupa version that didn’t even exist at the time of publishing!

The funny thing is, we use version ranges only to go freeze them with lock files:

version ranges end up not being used anyway. You lock your dependencies once in a lockfile and they stay there, unchanged

In other words: we avoid locking ourselves to specific versions in package.json by using version ranges, only to then go lock ourselves to specific versions in package-lock.json — lol!

I mean, that’s funny when you think about it.

But to go back to Niki’s earlier point: version ranges let us declare to ourselves that some code that exists today is compatible with some other future code that has yet to be written.

This idea allows us to create automated build systems that resolve to an artifact whose dependencies have never existed before in that given combination — let alone tested and executed together in that combination.

Now I get it, semantic versioning is an idea not a guarantee. But it’s also pretty wild when you think about it — when you encounter the reality of how semantic versioning plays out in the day-to-day world of building software.

I guess that’s a way of acknowledging out loud that we have normalized shipping production systems on top of the assumption that untested, unwritten combinations of software will behave well together — if not better, since patch updates fix bugs right?

And that’s not even getting into the security side of the equation. Future versions of packages have no guarantee to be as safe as previous ones, as we’ve seen with some of the npm supply chain attacks which rely on version ranges for their exploits. (Funny, isn’t it? Upgrading to the latest version of a package can get you into trouble. The solution? Upgrading to the latest version of a package.)

Anyhow, this all gets me thinking that version ranges and dependency management were the gateway drug to the non-determinism of LLMs.


Reply via: Email · Mastodon · Bluesky

The Risks of NPM

View

There was a time when I could ask, “Did you see the latest NPM attack?” And your answer would be either “Yes” or “No”.

But now if I ask, “Did you see the latest NPM attack?” You’ll probably answer with a question of your own: “Which one?”

In this post, I’m talking about the Qix incident:

  • Prolific maintainer Qix was phished.
  • Qix is a co-maintainer on many packages with Sindre Sorhus, the most popular maintainer on NPM (by download count).
  • Attackers pushed malicious code to packages that are indirectly depended by a huge portion of the ecosystem (hundreds of millions of downloads a week).

When I first heard about it, I thought “Oh boy, better not npm i on the old personal machine for a little while.”

But as details began to emerge, I realized the exploit wasn’t targeting my computer. It was targeting the computers of people downstream from me: end users.

The malicious code didn’t do anything when running npm install. Instead, it laid there dormant, waiting to be bundled up alongside a website’s otherwise normal code and served to unsuspecting end users.

Maybe we should rename “bundlers” to “trojan horses”, lol.

Graphic depicting many source assets on the left, like .js files, passing through a trojan horse in the middle and coming out as singular files on the right.

That’s all to say: you didn’t have to run npm install to be affected by this attack. You just had to visit a website whose code was sourced via npm install. (You needed a bitcoin wallet too, as that was the target of the exploit.)

It’s wild because browsers work really hard to make it safe to visit any webpage in the world — to do a GET to any URL. But attacks like this chip away at those efforts.

So while it’s easy to think NPM can be unsafe for your computer because running npm install allows running arbitrary code, that’s not the whole story. npm install can be unsafe for:

  • Your computer (install time execution)
    • Lifecycle scripts (preinstall, install, postinstall) allow running arbitrary code which can read/write files locally, steal keys and tokens, install malware, and otherwise exfiltrate data.
  • Your dev/CI computer(s) (build time execution)
    • Compilers, bundlers, transpilers, plugins, etc., can all execute arbitrary code and leak secrets, corrupt build artifacts, add hidden payloads, etc.
  • Your application server (server runtime execution)
    • Any dependency runs top-level in production and exposes risk to data exfiltration, unsafe privilege escalation, remote command execution, etc.
  • Your users’ computers (client runtime execution)
    • Bundled dependencies ship with your website, exposing your users to malicious code that runs in their browser and can exfiltrate data, insert hidden trackers/miners, etc.

Reply via: Email · Mastodon · Bluesky

Anti-*: The Things We Do But Not All The Way

View

I was reading Chase McCoy’s article “Antibuildings” where he cites Wikipedia’s entry on the term “Antilibrary” which points to another entry about the Japanese concept of Tsundoku, all of which deal with this idea of things we do with intention but that never make it to fruition.

Antilibraries are the books we buy but never read.

Antibuildings the architect’s version of sketches and plans drafted but buildings never made.

It got me thinking about the stuff I’ve started with intention but never brought to fruition — my own anti-*’s.

To name a few:

  • Antidomains: the domains I bought and had big plans for, but they never progressed beyond being parked at my registrar. (Zach Leatherman recently made a list kinda like this, if you haven’t seen it.)
  • Antiwebsites: the sites I was gonna make, but never shipped.
  • Antilayers: the Photoshop, Sketch, or Figma designs I painstakingly crafted to the level of “completeness”, but then never began building with code.
  • Anticode: the changes I made that functioned to the level of being usable and shippable, but then I never could pull the trigger on ‘em.
  • Antiposts: (also known as “drafts”, lol) all those blog posts I poured time and energy into researching, writing, and editing, but never could take all the way to “published”.
  • Antitweets: all the Tweets/Toots/Skeets I meticulously crafted as witty comebacks or sarcastic quips, but then never posted (honestly, probably for the better).

And last, but certainly not least — in fact, probably grandest of them all:

  • Antitabs: all the browser tabs of articles, videos, recipes, and other good things I collected and was going to read, watch, bake, etc. but never did.

Photo of a bookshelf on top with lots of books, below that a screenshot of a bunch of tabs where all you can see is favicons


Reply via: Email · Mastodon · Bluesky

RIP “Browsers”

View

Richard MacManus just posted “Chrome Switches on AI: The Future of Browsing Begins Now” where he points out that what we think of today as “browsers” is undergoing a radical change. Here’s the lay of the land:

  • Microsoft launched “Copilot Mode” on Edge and promotes it as an “AI-powered browser.”
  • Mozilla is baking AI into Firefox
  • Atlassian is into browsers now with their acquisition of The Browser Company and its AI browser Dia (my computer autocorrected that to “Die” and I reluctantly changed it back).
  • AI-first companies like Perplexity are releasing their own AI browsers.
  • OpenAI hired ex-Chrome engineers and the rumor is they are building a browser.

Safari is notably absent from that list.

Browser logos for Chrome, Firefox, and Edge on a tombstone that says “RIP” (Safari is on the outside of the tombstone). To the right of that is a photo of a bunch of newborn babies with their faces covered by a bunch of AI browser logos like Chrome, Edge, Firefox, Comet, and Dia.

This all leads Richard to ask:

One has to wonder if “browser” is even the right word for what products like Chrome and Edge are evolving into. We are moving further away from curiosity-driven exploration of the web — the modern browser is becoming an automaton, narrowing what we can discover and reducing the serendipity.

The Chrome folks don’t appear to be shying away from the fact that they’re keen on killing evolving this long-held definition of what a “browser” is. From their announcement:

This isn’t just about adding new features; it’s about fundamentally changing the nature of browsing

One of the examples they give is that of a student researching a topic for a paper with dozens of tabs:

Instead of spending hours jumping between sources and trying to connect the dots, your new AI browsing assistant — Gemini in Chrome — can do it for you.

Wait what? Jumping between sources and trying to connect dots is literally the work of research and learning. The paper is merely a proxy to evaluate the success of the work. Can you automate learning?

But I digress.

Look, I like browsers. No, I LOVE browsers.

But it does kinda feel like “browser” is undergoing a similar redefinition as “phone”.

“Phones” used to be these devices that allowed you to speak with other people who weren’t within earshot of you.

Now they do that and a million other things, like let you listen to music, watch videos, order pizza, transfer money, find love, rent a vacation home, be radicalized, purchase underwear, take photos, etc.

However, despite all those added features, we still call them “phones”.

Perhaps I need to start redefining what I mean when I say “browser”.


Reply via: Email · Mastodon · Bluesky

Why Make a Website in 2025?

View

The same reason you would bake a batch of cookies: because you enjoy it — the process itself, but also the result.

And perhaps, if you like, you share the result with others.

Who is out there asking, “Should I bake a batch of cookies? How well can that act be monetized? Should I do something else instead?”

Do it for the fun of the thing itself.

It doesn’t have to be anymore than that. It can be — Dave talks about that — but it doesn’t have to be.

Bake cookies because you like to.

Make websites because you like to.


Reply via: Email · Mastodon · Bluesky

The Mac App Flea Market

View

Have you ever searched for “AI chat” in the Mac App Store?

I have. It’s like strolling through one of those counterfeit, replica markets where all the goods look legit at first glance. But then when you look closer, you realize something is off.

For the query “AI chat”, there are so many ChatGPT-like app icons the results are comical. Take a look at these:

App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app. App icon for Apple’s platform that looks like the ChatGPT app.

The real app icon for the ChatGPT desktop app (from OpenAI) is in that collection above. Can you spot it?

Here they are again in a single image:

A grid of 45 icons. 1 is the real ChatGPT desktop app for Mac from OpenAI. The others are all look-a-likes.

(It’s the one in the 4th row, 3rd column.)

And those are just black-and-white lookalikes. There are other apps riding the AI/OpenAI wave that look like the ChatGPT logo just in different colors.

The funny thing is: the official ChatGPT desktop app from OpenAI is not even in the Mac App Store. It’s only available from their website, so it won’t show up in the “AI chat” results.

There were lots of other “sort of looks like the official one but isn’t” app icons in my search results, like this Claude one, this Grok one, or this Gemini one.

Oh, and these apps’ names were fascinating to look at. They were basically every spacing and casing combination of “AI”, “Chat”, and “Bot” you can image. Just look at this sampling:

I mean, look at this one: they named it “Al Chatbot” (that's the letter l as in “lima”, you can see it better in the URL slug where the letters are lowercase: al-chatbot).

Imagine going to store to grab some Nike gear and you find stuff like this (image courtesy of this post on Reddit):

A bunch of “Nike” logo knock-offs that look like a swoosh but say “Hike” or “Mike” or “NAIK”.

What does that say about the store you’re visiting?

I always wanted a pair of Mike Jordans, just like I always wanted ChatGPP for my Mac.


Reply via: Email · Mastodon · Bluesky

Some Love For Python

View

I really enjoyed watching Python: The Documentary (from CultRepo, formerly Honeypot, same makers as the TypeScript documentary).

Personally, I don’t write much Python and am not involved in the broader Python community. That said, I love how this documentary covers a lot of the human problems in tech and not just the technical history of Python as language. For example:

  • How do you handle succession from a pivotal creator?
  • How do you deal with poor representation?
  • How do you fund and steer open projects?
  • How do you build community?
  • How do you handle the fallout of major version changes?

And honestly, all the stories around these topics as told from the perspective of Python feel like lessons to learn from.

Here are a few things that stood out to me.

Guido van Rossum, Creator of Python, Sounds Cool

The film interviews Drew Houston, Founder/CEO at Dropbox, because he hired Python’s creator Guido van Rossum for a stint.

This is what Drew had to say about his time working with Guido:

It’s hard for me to think of someone who has had more impact with lower ego [than Guido]

For tech, that’s saying something! Now that is a legacy if you ask me.

The Python Community Sounds Cool

Brett Cannon famously gave a talk at a Python conference where he said he “came for the language, but stayed for the community”. In the documentary they interview him and he adds:

The community is the true strength of Pyhon. It’s not just the language, it’s the people.

❤️

This flies in the face of the current era we’re in, where it’s the technology that matters. How it disrupts or displaces people is insignificant next to the fantastic capabilities it purports to wield.

But here’s this language surrounded by people who acknowledge that the community around the language is its true strength.

People are the true strength.

Let me call this out again, in case it’s not sinking in: Here’s a piece of technology where the people around it seem to acknowledge that the technology itself is only secondary to the people it was designed to serve.

How incongruous is that belief with so many other pieces of technology we’ve seen through the years?

What else do we have, if not each other? That’s something worth amplifying.

Mariatta, Python Core Developer, Sounds Cool

I absolutely loved the story of @mariatta@fosstodon.org. If you’re not gonna watch the documentary, at least watch the ~8 minutes of her story.

Watched it? Ok, here’s my quick summary:

  • She loves to program, but everywhere she looks it’s men. At work. At conferences. On core teams.
  • She hears about pyladies and wants to go to Pycon where she can meet them.
  • She goes to Pycon and sees Guido van Rossum stand up and say he wants 2 core contributors to Python that are female.
  • She thinks, “Oh that’s cool! I’m not good enough for that, but I bet they’ll find someone awesome.”
  • The next year she goes to the conference and finds out they’re still looking for those 2 core contributors.
  • She thinks “Why not me?” and fires off an email to Guido.

Here’s her recollection on composing that email:

I felt really scared. I didn’t feel like I deserved mentorship from Guido van Rossum. I really hesitated to send this email to him, but in the end I realized I want to try. This was a great opportunity for me. I hit the send button.

And later, her feelings on becoming the first female core contributor to Python:

When you don’t have role models you can relate to, you don’t believe you can do it.

❤️ Mad respect. I love her story. As Jessica McKellar says in the film, Mariatta’s is an inspiring story and “a vision of what is possible in other communities”.

Python Is Refreshing

I’ve spent years in “webdev” circles — and there are some great ones — but this Python documentary was, to me, a tall, refreshing glass of humanity.

Go Python!


Reply via: Email · Mastodon · Bluesky

Trying to Make Sense of Casing Conventions on the Web

View

(I present to you my stream of consciousness on the topic of casing as it applies to the web platform.)

I’m reading about the new command and commandfor attributes — which I’m super excited about, declarative behavior invocation in HTML? YES PLEASE!! — and one thing that strikes me is the casing in these APIs.

For example, the command attribute has a variety of values in HTML which correspond to APIs in JavaScript. The show-popover attribute value maps to .showPopover() in JavaScript. hide-popover maps to .hidePopover(), etc.

So what we have is:

  • lowercase in attribute names
    • e.g. commandfor="..."
  • kebab-case in attribute values
    • e.g. show-popover
  • camelCase for JS counterparts
    • e.g. showPopover()

After thinking about this a little more, I remember that HTML attributes names are case insensitive, so the browser will normalize them to lowercase during parsing. Given that, I suppose you could write commandFor="..." but it’s effectively the same.

Ok, lowercase attribute names in HTML makes sense. The related popover attributes follow the same convention:

  • popovertarget
  • popovertargetaction

And there are many other attribute names in HTML that are lowercase, e.g.:

  • maxlength
  • novalidate
  • contenteditable
  • autocomplete
  • formenctype

So that all makes sense.

But wait, there are some attribute names with hyphens in them, like aria-label="..." and data-value="...".

So why isn’t it command-for="..."?

Well, upon further reflection, I suppose those attributes were named that way for extensibility’s sake: they are essentially wildcard attributes that represent a family of attributes that are all under the same namespace: aria-* and data-*.

But wait, isn’t that an argument for doing popover-target and popover-target-action? Or command and command-for?

But wait (I keep saying that) there are kebab-case attribute names in HTML — like http-equiv on the <meta> tag, or accept-charset on the form tag — but those seem more like legacy exceptions.

It seems like the only answer here is: there is no rule. Naming is driven by convention and decisions are made on a case-by-case basis. But if I had to summarize, it would probably be that the default casing for new APIs tends to follow the rules I outlined at the start (and what’s reflected in the new command APIs):

  • lowercase for HTML attributes names
  • kebab-case for HTML attribute values
  • camelCase for JS counterparts

Let’s not even get into SVG attribute names

Screenshot of various SVG attribute names, all with mixed casing from lowercase to kebab-case to camelCase.

We need one of those “bless this mess” signs that we can hang over the World Wide Web.

Photograph of Tim Berners-Lee next to his NeXT computer with the first web page on it at CERN. Above the image is a superimposed sign that says “Bless this mess” in cursive type.


Reply via: Email · Mastodon · Bluesky

Successive Prototypes Bridge the Gap Between Idea and Reality

View

Dismissing an idea because it doesn’t work in your head is doing a disservice to the idea.

(Same for dismissing someone else’s idea because it doesn’t work in your head.)

The only way to truly know if an idea works is to test it.

The gap between an idea and reality is the work.

You can’t dismiss something as “not working” without doing the work.

When collaborating with others, different ideas can be put forward which end up in competition with each other.

We debate which is best, but verbal descriptions don’t do justice to ideas — so the idea that wins is the one whose champion is the most persuasive (or has the most institutional authority).

You don’t want that. You want an environment where ideas can be evaluated based on their substance and not on the personal attributes of the person advocating them.

This is the value of prototypes.

We can’t visualize or predict how our own ideas will play out, let alone other people’s. This is why it’s necessary to bring them to life, have them take concrete form. It’s the only way to do them justice.

(Picture a cute puppy in your head. I’ve got one too. Now how do we determine who’s imagining the cuter puppy? We can’t. We have to produce a concrete manifestation for contrast and comparison.)

Prototypes are how we bridge the gap between idea and reality. They’re an iterative, evolutionary, exploratory form of birthing ideas that test their substance.

People will bow out to a good persuasive argument.

They’ll bow out to their boss saying it should be one way or another.

But it’s hard to bow out to a good idea you can see, taste, touch, smell, or use.


Reply via: Email · Mastodon · Bluesky