More from Jim Nielsen’s Blog
Authentication on the web is a complicated problem. If you’re going to do it yourself, there’s a lot you have to take into consideration. But odds are, you’re building an app whose core offering has nothing to do with auth. You don’t care about auth. It’s an implementation detail. So rather than spend your precious time solving the problem of auth, you pay someone else to solve it. That’s the value of SaaS. What would be the point of paying for an authentication service, like workOS, then re-implementing auth on your own? They have dedicated teams working on that problem. It’s unlikely you’re going to do it better than them and still deliver on the product you’re building. There’s a parallel here, I think, to building stuff in the browser. Browsers provide lots of features to help you deliver good websites fast to an incredibly broad and diverse audience. Browser makers have teams of people who, day-in and day-out, are spending lots of time developing and optimizing new their offerings. So if you leverage what they offer you, that gives you an advantage because you don’t have to build it yourself. You could build it yourself. You could say “No thanks, I don’t want what you have. I’ll make my own.” But you don’t have to. And odds are, whatever you do build yourself, is not likely to be as fast as the highly-optimized subsystems you can tie together in the browser. And the best part? Unlike SasS, you don’t have to pay for what the browser offers you. And because you’re not paying, it can’t be turned off if you stop paying. @view-transition, for example, is a free API that’ll work forever. That’s a great deal. Are you taking advantage? Email · Mastodon · Bluesky
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. Email · Mastodon · Bluesky
(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 We need one of those “bless this mess” signs that we can hang over the World Wide Web. Email · Mastodon · Bluesky
I’ve written previously about reloading a document using only HTML but that got me thinking: What are all the values you can put in an anchor tag’s href attribute? Well, I looked around. I found some things I already knew about, e.g. Link protocols like mailto:, tel:, sms: and javascript: which deal with specific ways of handling links. Protocol-relative links, e.g. href="//" Text fragments for linking to specific pieces of text on a page, e.g. href="#:~:text=foo" But I also found some things I didn’t know about (or only vaguely knew about) so I wrote them down in an attempt to remember them. href="#" Scrolls to the top of a document. I knew that. But I’m writing because #top will also scroll to the top if there isn’t another element with id="top" in the document. I didn’t know that. (Spec: “If decodedFragment is an ASCII case-insensitive match for the string top, then return the top of the document.”) href="" Reloads the current page, preserving the search string but removing the hash string (if present). URL href="" resolves to /path/ /path/ /path/#foo /path/ /path/?id=foo /path/?id=foo /path/?id=foo#bar /path/?id=foo href="." Reloads the current page, removing both the search and hash strings (if present). Note: If you’re using href="." as a link to the current page, ensure your URLs have a trailing slash or you may get surprising navigation behavior. The path is interpreted as a file, so "." resolves to the parent directory of the current location. URL href="." resolves to /path / /path#foo / /path?id=foo / /path/ /path/ /path/#foo /path/ /path/?id=foo /path/ /path/index.html /path/ href="?" Reloads the current page, removing both the search and hash strings (if present). However, it preserves the ? character. Note: Unlike href=".", trailing slashes don’t matter. The search parameters will be removed but the path will be preserved as-is. URL href="?" resolves to /path /path? /path#foo /path? /path?id=foo /path? /path?id=foo#bar /path? /index.html /index.html? href="data:" You can make links that navigate to data URLs. The super-readable version of this would be: <a href="data:text/plain,hello world"> View plain text data URL </a> But you probably want data: URLs to be encoded so you don’t get unexpected behavior, e.g. <a href="data:text/plain,hello%20world"> View plain text data URL </a> Go ahead and try it (FYI: may not work in your user agent). Here’s a plain-text file and an HTML file. href="video.mp4#t=10,20" Media fragments allow linking to specific parts of a media file, like audio or video. For example, video.mp4#t=10,20 links to a video. It starts play at 10 seconds, and stops it at 20 seconds. (Support is limited at the time of this writing.) See For Yourself I tested a lot of this stuff in the browser and via JS. I think I got all these right. Thanks to JavaScript’s URL constructor (and the ability to pass a base URL), I could programmatically explore how a lot of these href’s would resolve. Here’s a snippet of the test code I wrote. You can copy/paste this in your console and they should all pass 🤞 const assertions = [ // Preserves search string but strips hash // x -> { search: '?...', hash: '' } { href: '', location: '/path', resolves_to: '/path' }, { href: '', location: '/path/', resolves_to: '/path/' }, { href: '', location: '/path/#foo', resolves_to: '/path/' }, { href: '', location: '/path/?id=foo', resolves_to: '/path/?id=foo' }, { href: '', location: '/path/?id=foo#bar', resolves_to: '/path/?id=foo' }, // Strips search and hash strings // x -> { search: '', hash: '' } { href: '.', location: '/path', resolves_to: '/' }, { href: '.', location: `/path#foo`, resolves_to: `/` }, { href: '.', location: `/path?id=foo`, resolves_to: `/` }, { href: '.', location: `/path/`, resolves_to: `/path/` }, { href: '.', location: `/path/#foo`, resolves_to: `/path/` }, { href: '.', location: `/path/?id=foo`, resolves_to: `/path/` }, { href: '.', location: `/path/index.html`, resolves_to: `/path/` }, // Strips search parameters and hash string, // but preserves search delimeter (`?`) // x -> { search: '?', hash: '' } { href: '?', location: '/path', resolves_to: '/path?' }, { href: '?', location: '/path#foo', resolves_to: '/path?' }, { href: '?', location: '/path?id=foo', resolves_to: '/path?' }, { href: '?', location: '/path/', resolves_to: '/path/?' }, { href: '?', location: '/path/?id=foo#bar', resolves_to: '/path/?' }, { href: '?', location: '/index.html#foo', resolves_to: '/index.html?'} ]; const assertions_evaluated = assertions.map(({ href, location, resolves_to }) => { const domain = 'https://example.com'; const expected = new URL(href, domain + location).toString(); const received = new URL(domain + resolves_to).toString(); return { href, location, expected: expected.replace(domain, ''), received: received.replace(domain, ''), passed: expected === received }; }); console.table(assertions_evaluated); Email · Mastodon · Bluesky
After shipping my work transforming HTML with Netlify’s edge functions I realized I have a little bug: the order of the icons specified in the URL doesn’t match the order in which they are displayed on screen. Why’s this happening? I have a bunch of links in my HTML document, like this: <icon-list> <a href="/1/">…</a> <a href="/2/">…</a> <a href="/3/">…</a> <!-- 2000+ more --> </icon-list> I use html-rewriter in my edge function to strip out the HTML for icons not specified in the URL. So for a request to: /lookup?id=1&id=2 My HTML will be transformed like so: <icon-list> <!-- Parser keeps these two --> <a href="/1/">…</a> <a href="/2/">…</a> <!-- But removes this one --> <a href="/3/">…</a> </icon-list> Resulting in less HTML over the wire to the client. But what about the order of the IDs in the URL? What if the request is to: /lookup?id=2&id=1 Instead of: /lookup?id=1&id=2 In the source HTML document containing all the icons, they’re marked up in reverse chronological order. But the request for this page may specify a different order for icons in the URL. So how do I rewrite the HTML to match the URL’s ordering? The problem is that html-rewriter doesn’t give me a fully-parsed DOM to work with. I can’t do things like “move this node to the top” or “move this node to position x”. With html-rewriter, you only “see” each element as it streams past. Once it passes by, your chance at modifying it is gone. (It seems that’s just the way these edge function tools are designed to work, keeps them lean and performant and I can’t shoot myself in the foot). So how do I change the icon’s display order to match what’s in the URL if I can’t modify the order of the elements in the HTML? CSS to the rescue! Because my markup is just a bunch of <a> tags inside a custom element and I’m using CSS grid for layout, I can use the order property in CSS! All the IDs are in the URL, and their position as parameters has meaning, so I assign their ordering to each element as it passes by html-rewriter. Here’s some pseudo code: // Get all the IDs in the URL const ids = url.searchParams.getAll("id"); // Select all the icons in the HTML rewriter.on("icon-list a", { element: (element) => { // Get the ID const id = element.getAttribute('id'); // If it's in our list, set it's order // position from the URL if (ids.includes(id)) { const order = ids.indexOf(id); element.setAttribute( "style", `order: ${order}` ); // Otherwise, remove it } else { element.remove(); } }, }); Boom! I didn’t have to change the order in the source HTML document, but I can still get the displaying ordering to match what’s in the URL. I love shifty little workarounds like this! Email · Mastodon · Bluesky
More in literature
The anti-marketing aspect of the Gemini network And why your niche should stay a niche I like Gemini. Not the Google Chatbot but the network that existed before Google named its product. If you are enough of a geek, you may want to give it a try. Creating a Blog in Gemini:// (brennan.day) Eoin is counting the number of "capsules" on the Gemini network. A capsule is the equivalent of a "website" on the Web or a "hole" on Gopher. It’s a personal space, a bunch of pages. Blogs become "gemlogs". Eoin’s first observation is that the number of active capsules is slowly growing. The network is healthy. But his second observation is really interesting: there’s absolutely no correlation between the number of new capsules in a given month and articles about the Gemini protocol being promoted on the first page of Hacker News. Counting Capsules (spool-five.com) same content but on the Web This is a confirmation of what I’ve benn trying to explain for years: your metrics and web statistics are completely useless. The number of visitors, the number of clicks are worthless if you are not a marketer trying to extract money from a big brand. They don’t mean anything. By the way: you should really avoid Hacker News these days. The front page is heavily manipulated to promote pro-AI stuff and bury everything that might question wether fascism-capitalism is good. Why is Hacker News like that? (drewdevault.com) A Mozilla employee is leaving after 15 years and comes to a similar conclusion. Mozilla is awesome because it has always been abnormal. It is mostly used by abnormal people. This means that hiring "normal managers" who try to make it "normal" and chase "normal" metrics is, of course, a recipe for failure. jr conlin's ink stained banana (blog.unitedheroes.net) I always failed to understand those marketing people who try to be "like others but a bit cheaper". While all the value is being different enough to nurture your own niche. I completely removed statistics from my blog in 2013. At the time, I had a very strong realisation that the blog posts that mattered most, those I was proudest of, and those that prompted people to mail me even years later all had mediocre statistics. How I Learned to Stop Worrying and Love the Web (ploum.net) On the contrary, most of my hit, my "buzz", were quickly forgotten. But, being addicted to statistics, I was unconsciously adapting my writing style to improve those numbers. I was scrolling through social media posts that were highly popular while trying to create some myself. Closing my social media accounts and, instead, procrastinating on Gemini had the opposite effect: making me read other ideas without caring about reactions, about popularity. Jsreed5 made the same observation while he was trying the Nostr protocol. While Nostr’s technicalities may be interesting by themselves, the content on it is similar to the one found of mainstream social networks. And our smolnet Gemini-formatted brains cannot take it anymore! A Geminaut's View of Nostr (jsreed5.org) We really need a simple Gemini hosting provider that would also provide a Web version, to welcome less technical-minded people who still may want to access a quieter, non-commercial, out-of-time virtual space. Lot of people are trying Gemini and find that there’s no content on it, that they can’t have their news and dopamine hit on it. Well, that’s exactly the point. There Is No Content on Gemini (ploum.net) The fact that only a niche minority want to use it is also part of the point. Gemini might no be for you. That’s fine. The modern Web and even the real world (where every device is actively working against me and every human interaction seems to be about AI) is increasingly not for me. So let me have my strange niche space. About the author I’m Ploum, a writer and an engineer. I like to explore how technology impacts society. You can subscribe by email or by rss. I value privacy and never share your adress. I write science-fiction novels in French. For Bikepunk, my new post-apocalyptic-cyclist book, my publisher is looking for contacts in other countries to distribute it in languages other than French. If you can help, contact me!
A wave builds into a tsunami. I went for a run just over two years ago. Then, it was to shake out the results of the 2024 election. A deep disappointment, but also acceptance knowing what this country is capable of. Come Tuesday, November 3rd, 2026. Jen and I are in the living room, watching news come in on YouTube, and we’re both refreshing social media and news sites to glean the latest. We’re alert. We had voted two weeks prior, dropping our ballots off downtown. Ever since becoming a citizen in 2012, I have taken my ability to vote with respect — a responsibility and duty I don’t take for granted. You betcha I voted. As results started coming in, a feeling we hadn’t felt in a long while crept in. Was that...hope? Texas, Kansas, Iowa, Ohio, South and North Carolina, and Maine went blue. Indigenous representation happened. Queer and trans representation happened. Young people turned the fuck out — Asians and Latinos too. Black voters always turn up. White men turned up. In two years of depravity, deportation, and death, people have come to realize that they don’t want a world in which negativity, polarization, and survival are the only modes we exist in. This is a reprieve, a moment to take a breath, before we continue the marathon. The work continues. → This looks better on the web → Reply via email Thanks for reading and using RSS!
The process of writing has something infinite about it. Even though it is interrupted each night, it is one single notation. – Elias Canetti 1 I am not one for reading projects. I have never planned to read a play of Shakespeare every day until I have read them all or set out to read every novel by a particular author in order of their publication. There are people who every year read all six novels on the Booker Prize shortlist to make a judgement to compare to the jury's or ask "Where to start?" with an author when they (the author) has recently died or been awarded the Nobel Prize (which amounts to the same thing).2 I wonder what they hope to achieve. Despite this, I did plan to read Harvard UP's four-volume Selected Writings of Walter Benjamin in order. And not just in book order but each essay in each book in order. I believed this would enable to me to get a handle on Walter Benjamin's thought better than knowing well the odd essay like The Image of Proust or The Storyteller, both found in a battered copy of Illuminations I've had since the late 1980s, and as a result become a more informed and learned person, able to reach for an idea or example with the nonchalance of one who knows. It started with great promise with consecutive essays on Trauerspiel moving me with unexpected force 3, only then to reach the 69-page essay On the Concept of Criticism in German Romanticism. Not only was it like walking across the Atacama Desert, it was like eating it as well. Of course, I could skip the essay and carry on but, no, it had to be every essay and in order. This is also why I resent books with long introductions by a third party. While they may be full of useful information, with their spin and spoilers they diminish the enthusiasm one had to read the main text, yet skipping them feels like one cannot claim to have read the book.4 Unread books promise, cajole, weep and sneer. Beside the four volumes on the bookshelf is the translation of Benjamin's Arcades Project. Twenty-five years on it remains untouched. (Note the peekaboo on the dust jacket, a design inspired by Albert Angelo by BS Johnson perhaps.) And then on a coffee table there are the toppling piles of partly read books promising a glorious future of enlightenment: Joseph Leo Koerner's Reformation of the Image, Hans Urs Von Balthasar's Mysterium Paschale and Georges Poulet's Metamorphosis of the Circle are just three.5 Instead I have been picking up books I have read and reading them for the second, third or even fourth time. Is this comfort reading? How do we understand this phrase? Akin to comfort blanket, it suggests a retreat into warmth and familiarity. The number of adults reading or, rather, rereading the Harry Potter novels suggests as much, and more: reading as passivity; reading as sleep. If fantasy novels arouse wonder in a child, they numb an adult. I recognise this need even if I could never read that kind of book. There is a longing only to submerge oneself in a river of prose, to find a rhythm as one sentence follows another on the way perhaps to that fabled infinity, even if that is now diminished into the complete absorption of a complete work. I have long been aware of the dangers of dilettantism, the tendency to be satisfied with a passing understanding of what one is dealing with, manifest in the haphazard nature of blogging. Recently, I saw an advert for IndieScholars and, keen to find a way to read and write more productively, I downloaded its free practical guide. When I saw the advice to get rid of my TV, I found the answer...and got rid of the guide instead. It is for the unexpected and perhaps for the revelatory that I read and risk dilettantism as their possibility. I take comfort in desultory reading. The process of reading has something infinite about it. Notes 1. From The Secret Heart of the Clock. 2. This urge is worth noting: it suggests respect for authority and the impulse to question that authority without questioning the conditions generating the need for authority. If they did, their reviews may become more interesting. 3. As a result I began to read Origin of the German Trauerspiel but stalled at the seductively entitled Epistemo-Critical Foreword. 4. That said, Hannah Arendt's 58-page introduction to Illuminations contains a quotation I have always valued: 'Truth, so Benjamin said shortly before he became fully aware of the irreparable break in tradition and the loss of authority, is not "an unveiling which destroys the secret, but the revelation which does it justice."' If one wants to know where the quotation comes from, the publishers in their infinite wisdom have provided a reference to a page number in the German Gesammelte Schriften. Were they not aware that Illuminations is a translation for people who cannot read German! Anyway, by coincidence it's from the Epistemo-Critical Foreword. 5. Blanchot says Poulet's book prompted him to ask himself why "along with this book, the very history of criticism and culture closed".
The post Substitute Torture appeared first on The Perry Bible Fellowship.