Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
2
I was listening to a podcast interview with the Jackson Browne (American singer/songwriter, political activist, and inductee into the Rock and Roll Hall of Fame) and the interviewer asks him how he approaches writing songs with social commentaries and critiques — something along the lines of: “How do you get from the New York Times headline on a social subject to the emotional heart of a song that matters to each individual?” Browne discusses how if you’re too subtle, people won’t know what you’re talking about. And if you’re too direct, you run the risk of making people feel like they’re being scolded. Here’s what he says about his songwriting: I want this to sound like you and I were drinking in a bar and we’re just talking about what’s going on in the world. Not as if you’re at some elevated place and lecturing people about something they should know about but don’t but [you think] they should care. You have to get to people where [they are, where] they do care and where they do...
23 hours ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Jim Nielsen’s Blog

A Few Things About the Anchor Element’s href You Might Not Have Known

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

4 days ago 12 votes
How to Make Websites That Will Require Lots of Your Time and Energy

Some lessons I’ve learned from experience. 1. Install Stuff Indiscriminately From npm Become totally dependent on others, that’s why they call them “dependencies” after all! Lean in to it. Once your dependencies break — and they will, time breaks all things — then you can spend lots of time and energy (which was your goal from the beginning) ripping out those dependencies and replacing them with new dependencies that will break later. Why rip them out? Because you can’t fix them. You don’t even know how they work, that’s why you introduced them in the first place! Repeat ad nauseam (that is, until you decide you don’t want to make websites that require lots of your time and energy, but that’s not your goal if you’re reading this article). 2. Pick a Framework Before You Know You Need One Once you hitch your wagon to a framework (a dependency, see above) then any updates to your site via the framework require that you first understand what changed in the framework. More of your time and energy expended, mission accomplished! 3. Always, Always Require a Compilation Step Put a critical dependency between working on your website and using it in the browser. You know, some mechanism that is required to function before you can even see your website — like a complication step or build process. The bigger and more complex, the better. This is a great way to spend lots of time and energy working on your website. (Well, technically it’s not really working on your website. It’s working on the thing that spits out your website. So you’ll excuse me for recommending something that requires your time and energy that isn’t your website — since that’s not the stated goal — but trust me, this apparent diversion will directly affect the overall amount of time and energy you spend making a website. So, ultimately, it will still help you reach our stated goal.) Requiring that the code you write be transpiled, compiled, parsed, and evaluated before it can be used in your website is a great way to spend extra time and energy making a website (as opposed to, say, writing code as it will be run which would save you time and energy and is not our goal here). More? Do you have more advice on building a website that will require a lot of your time and energy? Share your recommendations with others, in case they’re looking for such advice. Email · Mastodon · Bluesky

a week ago 13 votes
Occupation and Preoccupation

Here’s Jony Ive in his Stripe interview: What we make stands testament to who we are. What we make describes our values. It describes our preoccupations. It describes beautiful succinctly our preoccupation. I’d never really noticed the connection between these two words: occupation and preoccupation. What comes before occupation? Pre-occupation. What comes before what you do for a living? What you think about. What you’re preoccupied with. What you think about will drive you towards what you work on. So when you’re asking yourself, “What comes next? What should I work on?” Another way of asking that question is, “What occupies my thinking right now?” And if what you’re occupied with doesn’t align with what you’re preoccupied with, perhaps it's time for a change. Email · Mastodon · Bluesky

2 weeks ago 14 votes
Measurement and Numbers

Here’s Jony Ive talking to Patrick Collison about measurement and numbers: People generally want to talk about product attributes that you can measure easily with a number…schedule, costs, speed, weight, anything where you can generally agree that six is a bigger number than two He says he used to get mad at how often people around him focused on the numbers of the work over other attributes of the work. But after giving it more thought, he now has a more generous interpretation of why we do this: because we want relate to each other, understand each other, and be inclusive of one another. There are many things we can’t agree on, but it’s likely we can agree that six is bigger than two. And so in this capacity, numbers become a tool for communicating with each other, albeit a kind of least common denominator — e.g. “I don’t agree with you at all, but I can’t argue that 134 is bigger than 87.” This is conducive to a culture where we spend all our time talking about attributes we can easily measure (because then we can easily communicate and work together) and results in a belief that the only things that matter are those which can be measured. People will give lip service to that not being the case, e.g. “We know there are things that can’t be measured that are important.” But the reality ends up being: only that which can be assigned a number gets managed, and that which gets managed is imbued with importance because it is allotted our time, attention, and care. This reminds me of the story of the judgement of King Solomon, an archetypal story found in cultures around the world. Here’s the story as summarized on Wikipedia: Solomon ruled between two women who both claimed to be the mother of a child. Solomon ordered the baby be cut in half, with each woman to receive one half. The first woman accepted the compromise as fair, but the second begged Solomon to give the baby to her rival, preferring the baby to live, even without her. Solomon ordered the baby given to the second woman, as her love was selfless, as opposed to the first woman's selfish disregard for the baby's actual well-being In an attempt to resolve the friction between two individuals, an appeal was made to numbers as an arbiter. We can’t agree on who the mother is, so let’s make it a numbers problem. Reduce the baby to a number and we can agree! But that doesn’t work very well, does it? I think there is a level of existence where measurement and numbers are a sound guide, where two and two make four and two halves make a whole. But, as humans, there is another level of existence where mathematical propositions don’t translate. A baby is not a quantity. A baby is an entity. Take a whole baby and divide it up by a sword and you do not half two halves of a baby. I am not a number. I’m an individual. Indivisible. What does this all have to do with software? Software is for us as humans, as individuals, and because of that I believe there is an aspect of its nature where metrics can’t take you.cIn fact, not only will numbers not guide you, they may actually misguide you. I think Robin Rendle articulated this well in his piece “Trust the vibes”: [numbers] are not representative of human experience or human behavior and can’t tell you anything about beauty or harmony or how to be funny or what to do next and then how to do it. Wisdom is knowing when to use numbers and when to use something else. Email · Mastodon · Bluesky

3 weeks ago 17 votes

More in programming

YouTube has earned its crown

I often give Google a lot of shit for shutting down services whenever they're bored, hire a new executive, or face a three-day weekend. The company seems institutionally incapable of standing behind the majority of the products they launch for longer than a KPI cycle. But when the company does decide that something is pivotal to the business, it's an entirely different story. And that's the tale of YouTube: The King of Internet Archives (Video Edition). I've just revived my YouTube channel after realizing just how often video has become my go-to for learning. This entire Linux adventure I've gotten myself into started by watching YouTube creators like ThePrimeagen, Typecraft, and Bread on Penguins. I learned about mechanical keyboards from Hipyo Tech. Devoured endless mini PC reviews from Level1Techs and Robtech. Oh, and took a side quest into retro gaming handhelds with Retro Game Corps. But it was when putting together the playlists for my own channel that YouTube's royal role in internet archival really stood out. Like with the original Rails Demo from 19 years ago(!), the infamous talk at Startup School from 2009, or my very first RailsConf keynote from 2006. You'd be hard-pressed to find any video content on the internet from those days anywhere else. I notice that with podcast appearances from even just a few years ago that have gone missing already. Decentralization is wonderful in many ways, but it's very much subject to link rot and disappearing content. I love how you can pull in videos from other channels onto your own page as well. I've gathered up a bunch of the many podcast appearances I've done, and even dedicated an entire playlist to the 69(!!) clips from the Lex Fridman interview. The majority of the RailsConf and Rails World keynotes are on a list. So is the old On Writing Software Well series that I keep meaning to bring back. When you're working in small tech, it's really easy to become so jaded with big tech that you become ideologically blind to the benefits they do bring. I find no inconsistency in cheering much of the antitrust agenda against Google while also celebrating their work on Chrome or their stewardship of YouTube. Any company as large as Google is bound to be full of contradictions, ambitions, and behaviors. We ought to have the capacity to cheer for the good parts and boo at the bad parts without feeling like frauds. So today, I choose to cheer for YouTube. It's an international treasure of learning, enthusiasm, and discovery.

9 hours ago 2 votes
The Framework Desktop is a beast

I've been running the Framework Desktop for a few months here in Copenhagen now. It's an incredible machine. It's completely quiet, even under heavy, stress-all-cores load. It's tiny too, at just 4.5L of volume, especially compared to my old beautiful but bulky North tower running the 7950X — yet it's faster! And finally, it's simply funky, quirky, and fun! In some ways, the Framework Desktop is a curious machine. Desktop PCs are already very user-repairable! So why is Framework even bringing their talents to this domain? In the laptop realm, they're basically alone with that concept, but in the desktop space, it's rather crowded already. Yet it somehow still makes sense. Partly because Framework has gone with the AMD Ryzen AI Max 395+, which is technically a laptop CPU. You can find it in the ASUS ROG Flow Z13 and the HP ZBook Ultra. Which means it'll fit in a tiny footprint, and Framework apparently just wanted to see what they could do in that form factor. They clearly had fun with it. Look at mine: There are 21 little tiles on the front that you can get in a bunch of different colors or with logos from Framework. Or you can 3D print your own! It's a welcome change in aesthetic from the brushed aluminum or gamer-focused RGBs approach that most of the competition is taking. But let's cut to the benchmarks. That's really why you'd buy a machine like the Framework Desktop. There are significantly cheaper mini PCs available from Beelink and others, but so far, Framework has the only AMD 395+ unit on sale that's completely silent (the GMKTec very much is not, nor is the Z3 Flow). And for me, that's just a dealbreaker. I can't listen to roaring fans anymore. Here's the key benchmark for me: That's the only type of multi-core workload I really sit around waiting on these days, and the Framework Desktop absolutely crushes it. It's almost twice as fast as the Beelink SER8 and still a solid third faster than the Beelink SER9 too. Of course, it's also a lot more expensive, but you're clearly getting some multi-core bang for your buck here! It's even a more dramatic difference to the Macs. It's a solid 40% faster than the M4 Max and 50% faster than the M4 Pro! Now some will say "that's just because Docker is faster on Linux," and they're not entirely wrong. Docker runs natively on Linux, so for this test, where the MySQL/Redis/ElasticSearch data stores run in Docker while Ruby and the app code runs natively, that's part of the answer. Last I checked, it was about 25% of the difference. But so what? Docker is an integral part of the workflow for tons of developers. We use it to be able to run different versions of MySQL, Redis, and ElasticSearch for different applications on the same machine at the same time. You can't really do that without Docker. So this is what Real World benchmarks reveal. It's not just about having a Docker advantage, though. The AMD 395+ is also incredibly potent in RAW CPU performance. Those 16 Zen5 cores are running at 5.1GHz, and in Geekbench 6 multicore, this is how they stack up: Basically matching the M4 Max! And a good chunk faster than the M4 Pro (as well as other AMDs and Intel's 14900K!). No wonder that it's crazy quick with a full-core stress test like running 30,000 assertions for our HEY test suite. To be fair, the M4s are faster in single-core performance. Apple holds the crown there. It's about 20%. And you'll see that in benchmarks like Speedometer, which mostly measures JavaScript single-core performance. The Framework Desktop puts out 670 vs 744 on the M4 Pro on Speedometer 2.1. On SP 3.1, it's an even bigger difference with 35 vs 50. But I've found that all these computers feel fast enough in single-core performance these days. I can't actually feel the difference browsing on a machine that does 670 vs 744 on SP2.1. Hell, I can barely feel the difference between the SER8, which does 506, and the M4 Pro! The only time I actually feel like I'm waiting on anything is in multi-core workloads like the HEY test suite, and here the AMD 395+ is very near the fastest you can get for a consumer desktop machine today at any price. It gets even better when you bring price into the equation, though. The Framework Desktop with 64GB RAM + 2TB NVMe is $1,876. To get a Mac Studio with similar specs — M4 Max, 64GB RAM, 2TB NVMe — you'll literally spend nearly twice as much at $3,299! If you go for 128GB RAM, you'll spend $2,276 on the Framework, but $4,099 on the Mac. And it'll still be way slower for development work using Docker! The Framework Desktop is simply a great deal. Speaking of 64GB vs 128GB, I've been running the 64GB version, and I almost never get anywhere close to the limits. I think the highest I've seen in regular use is about 20GB of RAM in action. Linux is really efficient. Especially when you're using a window manager like Hyprland, as we do in Omarchy. The only reason you really want to go for the full 128GB RAM is to run local LLM models. The AMD 395+ uses unified memory, like Apple, so nearly all of it is addressable to be used by the GPU. That means you can run monster models, like the new 120b gpt-oss from OpenAI. Framework has a video showing them pushing out 40 tokens/second doing just that. That seems about in range of the numbers I've seen from the M4 Max, which also seem in the 40-50 token/second range, but I'll defer to folks who benchmark local LLMs for the exact details on that. I tried running the new gpt-oss-20b on my 64GB machine, though, and I wasn't exactly blown away by the accuracy. In fact, I'd say it was pretty bad. I mean, exceptionally cool that it's doable, but very far off the frontier models we have access to as SaaS. So personally, this isn't yet something I actually use all that much in day-to-day development. I want the best models running at full speed, and right now that means SaaS. So if you just want the best, small computer that runs Linux superbly well out of the box, you should buy the Framework Desktop. It's completely quiet, fantastically fast, and super fun to look at. But I think it's also fair to mention that you can get something like a Beelink SER9 for half the price! Yes, it's also only 2/3 the performance in multi-core, but it's just as fast in single-core. Most developers could totally get away with the SER9, and barely notice what they were missing. But there are just as many people for whom the extra $1,000 is worth the price to run the test suite 40 seconds quicker! You know who you are. Oh, before I close, I also need to mention that this thing is a gaming powerhouse. It basically punches about as hard as an RTX 4060! With an iGPU! That's kinda crazy. Totally new territory on the PC side for integrated graphics. ETA Prime has a video showing the same chip in the GMK Tech running premier games at 1440p High Settings at great frame rates. You can run most games under Linux these days too (thanks Valve and Steam Deck!), but if you need to dual boot with Windows, the dual NVMe slots in the Framework Desktop come very handy. Framework did good with this one. AMD really blew it out of the water with the 395+. We're spoiled to have such incredible hardware available for Linux at such appealing discounts over similar stuff from Cupertino. What a great time to love open source software and tinker-friendly hardware!

2 hours ago 2 votes
Doing versus Delegating

A staff+ skill

yesterday 6 votes
p-fast trie, but smaller

Previously, I wrote some sketchy ideas for what I call a p-fast trie, which is basically a wide fan-out variant of an x-fast trie. It allows you to find the longest matching prefix or nearest predecessor or successor of a query string in a set of names in O(log k) time, where k is the key length. My initial sketch was more complicated and greedy for space than necessary, so here’s a simplified revision. (“p” now stands for prefix.) layout A p-fast trie stores a lexicographically ordered set of names. A name is a sequence of characters from some small-ish character set. For example, DNS names can be represented as a set of about 50 letters, digits, punctuation and escape characters, usually one per byte of name. Names that are arbitrary bit strings can be split into chunks of 6 bits to make a set of 64 characters. Every unique prefix of every name is added to a hash table. An entry in the hash table contains: A shared reference to the closest name lexicographically greater than or equal to the prefix. Multiple hash table entries will refer to the same name. A reference to a name might instead be a reference to a leaf object containing the name. The length of the prefix. To save space, each prefix is not stored separately, but implied by the combination of the closest name and prefix length. A bitmap with one bit per possible character, corresponding to the next character after this prefix. For every other prefix that matches this prefix and is one character longer than this prefix, a bit is set in the bitmap corresponding to the last character of the longer prefix. search The basic algorithm is a longest-prefix match. Look up the query string in the hash table. If there’s a match, great, done. Otherwise proceed by binary chop on the length of the query string. If the prefix isn’t in the hash table, reduce the prefix length and search again. (If the empty prefix isn’t in the hash table then there are no names to find.) If the prefix is in the hash table, check the next character of the query string in the bitmap. If its bit is set, increase the prefix length and search again. Otherwise, this prefix is the answer. predecessor Instead of putting leaf objects in a linked list, we can use a more complicated search algorithm to find names lexicographically closest to the query string. It’s tricky because a longest-prefix match can land in the wrong branch of the implicit trie. Here’s an outline of a predecessor search; successor requires more thought. During the binary chop, when we find a prefix in the hash table, compare the complete query string against the complete name that the hash table entry refers to (the closest name greater than or equal to the common prefix). If the name is greater than the query string we’re in the wrong branch of the trie, so reduce the length of the prefix and search again. Otherwise search the set bits in the bitmap for one corresponding to the greatest character less than the query string’s next character; if there is one remember it and the prefix length. This will be the top of the sub-trie containing the predecessor, unless we find a longer match. If the next character’s bit is set in the bitmap, continue searching with a longer prefix, else stop. When the binary chop has finished, we need to walk down the predecessor sub-trie to find its greatest leaf. This must be done one character at a time – there’s no shortcut. thoughts In my previous note I wondered how the number of search steps in a p-fast trie compares to a qp-trie. I have some old numbers measuring the average depth of binary, 4-bit, 5-bit, 6-bit and 4-bit, 5-bit, dns qp-trie variants. A DNS-trie varies between 7 and 15 deep on average, depending on the data set. The number of steps for a search matches the depth for exact-match lookups, and is up to twice the depth for predecessor searches. A p-fast trie is at most 9 hash table probes for DNS names, and unlikely to be more than 7. I didn’t record the average length of names in my benchmark data sets, but I guess they would be 8–32 characters, meaning 3–5 probes. Which is far fewer than a qp-trie, though I suspect a hash table probe takes more time than chasing a qp-trie pointer. (But this kind of guesstimate is notoriously likely to be wrong!) However, a predecessor search might need 30 probes to walk down the p-fast trie, which I think suggests a linked list of leaf objects is a better option.

yesterday 3 votes