Jim Nielsen’s Blog

You found my HTML feed — I also have an XML feed and a JSON feed.

I ♄ HTML

Subscribe to my blog by copy-pasting this URL into your RSS reader.

(Learn more about RSS and subscribing to content on the web at aboutfeeds.)

Recent posts

Contrast Is Clarifying

View

Which is best?

  • Generalist or specialist?
  • Native or web?
  • Web site or web app?
  • JavaScript or Typescript?
  • Framework or library?
  • Server side or client side?
  • Photoshop or Sketch or Figma?
  • Designing in a tool or design in the browser?
  • Skueomorphic or flat?
  • Mac or PC or Linux?

This list could go on forever. Zoom in to just the JavaScript ecosystem and its overwhelming:

  • Modules: ESM, CJS, AMD, UMD
  • Package managers: npm, yarn, pnpm, bower
  • Bundlers: Webpack, Rollup, Parcel, Bun, Vite
  • Compilers: Babel, TypeScript, esbuild, swc
  • Runtimes: Node, Deno, Bun
  • UI frameworks: React, Vue, Angular, Svelte, Lit

Even here, the list could go on forever: db libraries, task runners, testing libraries, UI metaframeworks, server frameworks, state management libraries, etc.

Module systems — ESM, CJS, AMD, UMD — are a great example: how could we truly understand any of them individually without having had all of them?

We need opposing options. They exist not solely in opposition to one another but as contrast.

We must have a diversity to understand and discuss which is most fit for a given context. The web is big! “It depends”!

How do you understand one thing without the contrast of its opposite? What is white without black? How do you understand salty without sweet? One, by definition, excludes the other, which gives form and shape to the definition of each.

And guess what? Whichever you choose, you’ll likely choose poorly. That’s ok. Choosing poorly is where growth happens — if you let it.

Silver bullets are for killing werewolves not building technology.


Reply via: Email :: Mastodon :: Twitter

Nothing Is Something

View

There’s a post on htmx.org about why htmx wasn’t the right fit for a particular project (which is dope, we need more websites that admit their thing might not be the right thing all the time).

The bit on AI being unfamiliar with their tool choice piqued my interest:

It’s worth noting that AI tools are intimately familiar with Next.js and not so much with htmx, due to the lack of open-source training data. This is similar to the issue Rails faces. While not a dealbreaker, it did impact our development speed and the ease of finding solutions to problems. When we encountered issues, the wealth of resources available for React/Next.js made troubleshooting much faster.

That’s an interesting phenomenon: a big part of tool choice is popularity because it shapes your experience finding support. The more popular the tool, the more probable your question has already been answered.

“How to get started with Next.js” likely has tons of search results — and therefore, one can assume, a large set of training data to inform the LLM’s answer.

But that same question for an obscure tool yields far fewer search results (if any) — and therefore, one can assume, a sparse set of training data to inform the LLM’s answer.

The point I’m getting to is this: the experience of asking a question on a niche topic or tool is different with an LLM than it is with a search engine.

With a search engine, fewer quality results means something. But an LLM is going to spit back a response regardless of the quality of training data. When the data is thin, a search engine will give you nothing. An LLM will give you an inaccurate something.

For example, an LLM may have no idea how to generate code for a novel syntax on top of JavaScript, but because it looks a lot like JavaScript and borrows a lot of the same idioms, it’ll go ahead and generate something for you — accuracy be damned.

This seems like a case where nothing is better than something because nothing means something.

LLMs — at least in my experience — haven’t really cued in on this. They’ll try their darnedest to give you something when nothing would be more instructive. (What’s that famous quote? “Better to remain silent and be thought a fool than to speak out and remove all doubt.” LLMs didn’t get the memo on that one.)

Rather than saying “I don’t know, there’s not enough on this subject to formulate a working answer” — which is what you could infer from an empty search results page — an LLM will give you something that looks right. Then you have to go shoot yourself in the foot to learn it’s not right, because you didn’t know enough to know it was wrong.


Reply via: Email :: Mastodon :: Twitter

Nabbing macOS Icon Artwork

View

I keep a personal collection of beautiful macOS app icons, which might make you ask: “How does he get those icons?”

Apps in the App Store

For apps in the Mac App Store, I have my ways. I don’t necessarily want to write about them because I’m semi-afraid Apple would frown on my doings and close off my ways.

Don’t get me wrong, I’m not being nefarious. If you spent 30 minutes browsing through the publicly-accessible contents of a link from the App Store, you could probably figure out what I’m doing.

I’ll just leave it at that.

DMG Apps

For apps that aren’t in the Mac App Store, I’ll show you a few of the ways I grab icon artwork.

First off, you need the app itself. If that means you have to pay for it to download the DMG file, well sorry, I have no magic trick there. You just have to pay for it — or email the developer and ask for it.

But once you get your hands on the DMG, you can open that and do some sleuthing.

Opening a DMG will often get you a standard macOS app install screen, like this:

Screenshot of an application installion screen on macOS for TableTool, which shows the application icon on the left and an icon for the macOS Applications folder on the right and an arrow between them, denoting you should drag and drop the app.

From there you can right click and “Get Info”:

Then click-to-focus the application icon in the upper-right of the “Get Info” panel and CMD+C:

Beautifully, this will copy the application’s .icns file to your clipboard. From there, you can open Preview and go “File -> New From Clipboard” and it will open the .icns file in Preview with all the different icon sizes!

For me, I want the biggest one (1024×1024 pixels), so I find that and then go “File -> Export” to save it as a transparent PNG to disk.

And violĂĄ, the app icon in a format I want!

However! Sometimes this won’t get you exactly what you want.

For example, I downloaded the wonderful TablePlus app, opened the DMG, did “Get Info”, and found an app icon that was different from the install app (and the one that shows in your dock).

Sometimes this can happen. Sometimes you won’t be able to find the icon you want using this method.

When I hit this roadblock, I turn to finding the Assets.car file and plugging it into a tool like Asset Catalog Tinkerer by Guilherme Rambo.

With this method, you can right click the app and instead of “Get Info” do “Show Package Contents” and find .car files (I’m not a Mac developer, but it seems like the convention is to stick it at “Contents -> Resources -> Assets.car”). Then you can drag them into Asset Catalog Tinkerer and hopefully find what you’re looking for. In my case, with TablePlus, I was able to find the 1024 pixel icon I was looking for.

The End

If you ever find yourself needing to nab the icon for a macOS app, hopefully this little guide is helpful.

And if you’re just reading this because you love poking around at the internals of things, good on you. I love that stuff too!


Reply via: Email :: Mastodon :: Twitter

Tagged in: #iconGalleries

Captchas Turned Notification Exploits

View

When my site analytics reported a large number of inbound traffic from Hacker News clones, I got curious and started clicking links.[1]

I like to visit links. I am connoisseur of it. I love the feeling of landing on something you didn’t expect — which is precisely what happened.

I landed on a site that had one of those Cloudflare-esque “prove you're human” captchas. That didn’t seem particularly abnormal. Lots of website owners these days use them for protection against malicious activities like DDoS attacks.

Anyhow, the page had a little graphic that said: “Press ‘Allow' to prove you are not a robot.”

Illustration of a fanciful, friendly robot head with the text “Press ‘Allow' to prove you are not a robot.” underneath.

I sat there for a moment looking for a button, but couldn’t find one. “Where’s the “Allow” button?” I thought.

A few seconds later, Safari’s native permission dialog popped up asking for permission to send me notifications!

I immediately thought, “Ah, hell no!” and ran away from that website. That’s sneaky, leveraging tools site owners use to protect themselves — and therefore normalize for their users — as a weapon.

I hate this crap.

But one of the beautiful things about browser security is that a lot of people work really hard to make visiting any website in the world safe. Granted there are caveats to this statement, but it’s cool you can mostly sleep at night doing a GET to any domain. (Whereas, for example, it is very much not safe to install any package in the world from npm.) That’s great news for link hoppers like me.

THANK YOU browser makers!


  1. I initially posted this train of thoughts on Mastodon, but still haven’t been able to stop thinking about it so I wanted to post it on my blog for more permanence. ⏎

Reply via: Email :: Mastodon :: Twitter

Tagged in: #grateful

Named Blogs

View

I think it’s endearing when people name their blog.

I’m not talking about branding, like people do with professional blogs or newsletters.

I’m talking about personal blogs that people name out of care and idiosyncrasy.

It’s endearing, because you brand things you own, you name things you cherish.

We didn’t brand our family cat. And we don’t call him “The Nielsen’s cat” (though that’s probably what our neighbors call him). We named him. We call him “Fluffy”.

Don’t get me wrong, I like how personal it is when a personal blog is just someone’s name (that’s what mine is).

But I also love when folks add a little name or subtitle to communicate their personal feelings about their blog, e.g. “Little nothings: the blog of so-and-so”.

Here’s a few examples out in the wild of people I follow:

My sister had a blog for a long time — though sadly not anymore [insert comment here from old man yelling at cloud about the demise of blogging in the wider public].

I can’t remember the name of her blog, but the subtitle was “tasting life twice; in the moment and in retrospection” which was a nod to Anaïs Nin. I think about that quote probably once a year — I love stuff like that!

Yeah, I know, I’m one to talk. My blog is titled “Jim Nielsen’s Blog” which, hey, at least it’s clear. Maybe I’d name it if I could land on a name I like, but I’m a fickle namer so don’t hold your breath. Perhaps I could start with a subtitle first, something like:

  • “Jim’s Blog: Dialogues With Myself”
  • “Jim’s Blog: A Refinery For Coarse Feelings”
  • “Jim’s Blog: Opinions With a Shelf Life”

Oooof. Maybe I better leave this art to others.

You have a blog you follow whose name you love? Let me know — or even better, blog about it!


Reply via: Email :: Mastodon :: Twitter

Reading and Writing as Human Expression & Connection

View

Why do we write?

We write, in part, because our own reading was given as a gift to us and we want to extend that same magic we received to others. Here’s Mandy Brown (and my notes) in a recent article:

The more compelling and interesting reason that most writers seek out readers is, I think, less utilitarian: we receive our writing as a gift, and so it must be given in turn. We write because something needs to be expressed through us, and only by giving the writing to a reader is that need fulfilled

You write because something needs to be expressed through you, which is something nobody else in the world can do.

(Contrast this with writing that is expressed through an LLM that everyone else in the world has access to.)

By giving our writing to a reader its is purpose fulfilled.

In other words, reading and writing has traditionally been an act that takes place in the context of people. Its purpose is fulfilled through humans. AI bots could read and write to each other all day long, and what is being “fulfilled” in that scenario?

Reading and writing is for expression and connection between humans. Its purpose is fulfilled in that context. Anything other than this and it is purposeless; that is, done in a way that violates the reason for its existence in the first place.

Does that make sense? I’m trying to make sense of it myself. Maybe there’s something here — maybe not. But Mandy’s piece got me thinking.

Reading and writing is a human-centric exercise because it deals with the interpretation, exchange, and expression of consciousness which is an attribute that machines do not possess — “yet” some will say, and to that I say “lol”.


Reply via: Email :: Mastodon :: Twitter

The Beauty of Building

View

Jan Miksovsky has an absolutely tremendous article about how he cobbled together some disparate pieces of hardware and software in order to help improve the quality of life of his mother who has amnesia.

Everything about this article illustrates what got me into making websites.

Everything about this article is what fuels my curiosity and interest in continuing to make boring little websites.

But first, a quick overview of Jan’s story. First, the problem:

the side-effects of a long surgery left my mom with permanent anterograde amnesia
[she] still lives on her own in an apartment. Because she cannot remember things, she goes through each day in a state of low-grade anxiety about where her grown children are and whether they are all right.

To help alleviate this, he dreamed up a solution:

I thought some sort of unobtrusive, always-on device installed in her apartment might be able to show her notes written by my siblings and me.

But technological solutions are famously fickle and rarely easy. How do you introduce a new device into someone’s life when you can’t teach them how to use it because their mind is incapable of remembering?

Not easy stuff. But Jan’s imposed set of constraints on any solution are a great start. The solution has to be:

resilient to network failures

not enshittified with a subscription service or proprietary App Store

He gives insights into the technical details:

Since it’s essentially impossible to debug anything that happens on the device, I made as much use of vanilla HTML and CSS as possible.

Boring is good! Even the “backend” where the family posts notes sounds like just an absolutely lovely little website:

A small web app manifest lets us save the Compose page to a phone’s home screen as an icon for quick access.

The whole site is tiny, entails no build process, and with the exception of the service (below) is just static files.

Jan’s retrospective is wonderful from the human side:

Looking back, the display is essentially the only intervention of any kind we’ve tried that’s actually been successful at improving her quality of life (and ours). One reason it’s worked so well is that it didn’t require her to learn anything new. Without the ability to remember new things, it’s virtually impossible for her to learn a new skill or to form new habits.

And the technical retrospective is wonderful as well:

For my part, keeping the software as simple as possible and sticking to vanilla web technologies surely helped avoid bugs.

It reminds me of the early years of the web, when nobody quite knew what was possible with all this web stuff. If you knew enough about how to patch things together on this big open standard, you could do some pretty magical things for people.

Not magical in the sense of “Hey, I think I uncovered something that I can generalize into a product and then provide as a service to the entire world” — though there was plenty of that coming out of the early days of the web.

But magical more like the way a great cook would improvise a meal for a small group of hungry people based on their needs and what was available in the pantry at the moment.

That magic of using your digital fluency to scrappily cobble together some disparate technologies in service of helping solve the unique problem of an individual loved one or friend. No scale. No monetization. No buzzwords. No grand upside potentiality.

Just building cool shit for people you love on the ethos of an open platform.


Reply via: Email :: Mastodon :: Twitter

Navigations on the Web

View

When trying to define the difference between a link (<a>) and a button (<button>), a general rule of thumb is: links are for navigation, buttons are not.

That can take you pretty far. However, like most things, there’s nuance and that mental model can fall apart under certain scenarios.

Why? Because buttons can be for navigation too.

Where? Buttons in forms trigger navigations by default (without JS).

Maybe I’m showing my naivety when I say this, but it took me a while to fully grok this idea. But doing so helped me change how I think about the basic grain of building for the web.

When it comes to browser APIs, I love to ask myself: How does this thing work, at its most basic level, without JavaScript?

And a <button type=submit> inside a <form> is, by default, a navigation.

For example, if you click “GO” here:

<form action="https://google.com">
  <button type="submit">GO</button>
</form>

The browser will navigate you to the same page as if you click “GO” here:

<a href="https://google.com">GO</a>

Both of those are what I would call “navigations”. Without JS, they trigger a round-trip request to the server for a new document and navigate the user to this new location.

To reiterate: a button inside a form, like a link, triggers a round-trip request to the server for a new document — a navigation.

So links aren’t the only mechanism for navigations, buttons are too! Or, perhaps more accurately, forms are a mechanism for navigations and buttons inside of them are navigation triggers.

As another illustration, imaging making a multiple-choice quiz. Each question is its own HTML page. You can mark that up in two different ways.

Option 1: a form + a button.

<form action='/questions/1/'>
  <label><input type=radio name=answer value=a /> A</label>
  <label><input type=radio name=answer value=b /> B</label>
  <label><input type=radio name=answer value=c /> C</label>
  <label><input type=radio name=answer value=d /> D</label>
  <button type=submit>Submit</button>
</form>

Given the attributes of this form, when the user clicks the “Submit” button, the browser will navigate them to a new page such as:

/questions/1/?answer=a

You can achieve this exact same functionality with links:

<ul>
  <li><a href="/questions/1?answer=a">A</a></li>
  <li><a href="/questions/1?answer=b">B</a></li>
  <li><a href="/questions/1?answer=c">C</a></li>
  <li><a href="/questions/1?answer=d">D</a></li>
</ul>

In both cases, a “navigation” happens by default.

I write this because understanding form submissions (triggered by buttons) as navigations became an empowering idea for me in building for the web.

Maybe it will for you too.


Reply via: Email :: Mastodon :: Twitter

Tagged in: #webPlatform

Persisting State to localStorage in Recoil Across Browser Tabs

View

I was working on a project using Recoil for state management in React.

I needed to persist some state to localStorage, and there’s some info on how to do it in Recoil’s docs.

That works; however it doesn’t respond to state changes from other instances of your app in multiple browser tabs.

If you want that, you have to add the code yourself by leveraging the storage event.

Or, you can just copy the code I made to do it.

import { AtomEffect } from 'recoil';

/**
 * Given a specific key, this effect will sync the value of the atom into localStorage
 * and react to changes to that localStorage value across other tabs/windows.
 *
 * @param key - The key used in localStorage
 */
export const localStorageEffect =
  <T>(key: string): AtomEffect<T> =>
  ({ setSelf, onSet, resetSelf }) => {
    // On load, if there's a value in localStorage, set the atom
    const savedValue = localStorage.getItem(key);
    if (savedValue != null) {
      setSelf(JSON.parse(savedValue));
    }

    // Subscribe to changes in the atom and update localStorage
    onSet((newValue, _, isReset) => {
      isReset ? localStorage.removeItem(key) : localStorage.setItem(key, JSON.stringify(newValue));
    });

    // When the value changes in localstorage (from another tab), update the atom
    const handleStorageChange = (event: StorageEvent) => {
      if (event.key === key) {
        if (event.newValue === null) {
          resetSelf();
        } else {
          const newValue = JSON.parse(event.newValue);
          setSelf(newValue);
        }
      }
    };
    window.addEventListener('storage', handleStorageChange);
    return () => {
      window.removeEventListener('storage', handleStorageChange);
    };
  };

Usage:

const myAtom = atom({
  key: 'myAtom',
  default: 1,
  effects: [
    localStorageEffect('my-atom-local-storage-key'),
  ]
});

Happy coding!


Reply via: Email :: Mastodon :: Twitter

Job Screening Blog Post

View

Take a look at these two animated gifs.

First:

Animated gif of a button being clicked and the UI changing. The text heading in the UI is shifting upwards by 1px.



Second:

Animated gif of a button being clicked and the UI changing. The text heading in the UI doesn’t shift at all.

Can you tell the difference between them?

Do you care?

If not, we might not be a good fit.

#designEngineering


Reply via: Email :: Mastodon :: Twitter

Tagged in: #designEngineer