Latest Posts (20 found)
iDiallo Today

AI didn't delete your database, you did

Last week, a tweet went viral showing a guy claiming that a Cursor/Claude agent deleted his company's production database . We watched from the sidelines as he tried to get a confession from the agent: "Why did you delete it when you were told never to perform this action?" Then he tried to parse the answer to either learn from his mistake or warn us about the dangers of AI agents. I have a question too: why do you have an API endpoint that deletes your entire production database? His post rambled on about false marketing in AI, bad customer support, and so on. What was missing was accountability. I'm not one to blindly defend AI, I always err on the side of caution. But I also know you can't blame a tool for your own mistakes. In 2010, I worked with a company that had a very manual deployment process. We used SVN for version control. To deploy, we had to copy trunk, the equivalent of the master branch, into a release folder labeled with a release date. Then we made a second copy of that release and called it "current." That way, pulling the current folder always gave you the latest release. One day, while deploying, I accidentally copied trunk twice. To fix it via the CLI, I edited my previous command to delete the duplicate. Then I continued the deployment without any issues... or so I thought. Turns out, I hadn't deleted the duplicate copy at all. I had edited the wrong command and deleted trunk instead. Later that day, another developer was confused when he couldn't find it. All hell broke loose. Managers scrambled, meetings were called. By the time the news reached my team, the lead developer had already run a command to revert the deletion. He checked the logs, saw that I was responsible, and my next task was to write a script to automate our deployment process so this kind of mistake couldn't happen again. Before the day was over, we had a more robust system in place. One that eventually grew into a full CI/CD pipeline. Automation helps eliminate the silly mistakes that come with manual, repetitive work. We could have easily gone around asking "Why didn't SVN prevent us from deleting trunk?" But the real problem was our manual process. Unlike machines, we can't repeat a task exactly the same way every single day. We are bound to slip up eventually. With AI generating large swaths of code, we get the illusion of that same security. But automation means doing the same thing the same way every time. AI is more like me copying and pasting branches, it's bound to make mistakes, and it's not equipped to explain why it did what it did. The terms we use, like "thinking" and "reasoning," may look like reflection from an intelligent agent. But these are marketing terms slapped on top of AI. In reality, the models are still just generating tokens. Now, back to the main problem this guy faced. Why does a public-facing API that can delete all your production databases even exist? If the AI hadn't called that endpoint, someone else eventually would have. It's like putting a self-destruct button on your car's dashboard. You have every reason not to press it, because you like your car and it takes you from point A to point B. But a motivated toddler who wiggles out of his car seat will hit that big red button the moment he sees it. You can't then interrogate the child about his reasoning. Mine would have answered simply: "I did it because I did it." I suspect a large part of this company's application was vibe-coded. The software architects used AI to spec the product from AI-generated descriptions provided by the product team. The developers used AI to write the code. The reviewers used AI to approve it. Now, when a bug appears, the only option is to interrogate yet another AI for answers, probably not even running on the same GPU that generated the original code. You can't blame the GPU! The simple solution is know what you're deploying to production. The more realistic one is, if you're going to use AI extensively, build a process where competent developers use it as a tool to augment their work, not a way to avoid accountability. And please, don't let your CEO or CTO write the code.

0 views
Unsung Today

The land where time stood still

It’s hard to be in charge of continuity on a movie set. It would already be difficult under the best of circumstances: after all, you can’t freeze the sun in the sky, prevent hot drinks from going cold, cigarettes from extinguishing themselves, or entropy in general for doing all the stuff it loves doing. But on top of that, scenes are shot out of sequence, and movies are shot out of sequence. There are pick-ups if you’re lucky, and reshoots when you’re not. About the only time your job will be noticed is if you mess up: cue Super-man’s reverse CGI moustache, Josh Trank’s Fantastic Four wig situation, Commando’s damaged-then-pristine Porsche, and so on and so on. ( This 7-minute YouTube video is a great walkthrough from an expert .) = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-land-where-time-stood-still/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-land-where-time-stood-still/1.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-land-where-time-stood-still/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-land-where-time-stood-still/2.1600w.avif" type="image/avif"> Apple famously freezes time on their phones in all the promotional materials to be 9:41am. The specific moment they chose is a celebration of the first iPhone unveiling to be at around that time, but it also makes production easy – while people won’t mind that the time on the screen doesn’t match the current time, or even that it doesn’t seem to advance at a normal rate, they will definitely notice if you happened to splice two screenshots with different time side by side, just because you didn’t anticipate that splice as you were preparing them. So it’s easiest just to avoid this situation altogether. But what I didn’t realize until today as I was recording the previous post’s screengrab is that 9:41am is also enforced whenever you record your phone’s screen via QuickTime. It’s a peculiar feeling: Start recording, and the time on your phone jumps to 9:41. Yank the USB cord out, and it’s back to where it was: Oh yeah, the date changes too, for the same reason – to January 9, 2007. In a time-honored Apple tradition, I can’t decide whether I’m annoyed at it (there seems to be no option to turn it off), or admire it.

0 views
Unsung Today

The vision of persistence

I want to show you something glorious. This is Bear , the note taking app: There are desktop apps that get flustered if you ⌘+Tab away and back, misplacing focus or closing a dialog box inside. There are iOS apps that fully reset themselves whenever they get swapped out of memory and have to be reloaded. But Bear, right here, remembers which note you were on, and exactly where you were in that note, even between phone reboots . Software is transient and malleable, and one of the hard parts is knowing when that’s beneficial and when detrimental. In real life, you can leave a notebook on your desk, open on a certain page, leave a pen pointing to a specific word – and then depart for a two-month trip to Europe. You will find your notebook exactly how you left it. Why shouldn’t software behave this way? Also, another thought: This is very likely not something users will complain about when broken, or suggest when absent, even if you go out of your way to open yourself for feedback. Just swapping an app out of memory is hard to understand and “repro” (in engineering parlance). There’s a certain design mindset and taste necessary to notice and care, and a certain vision to carry it through. The lack of direct user feedback doesn’t mean it’s not worth doing. It just means that there are some things that designers and only designers will know how to properly weigh, describe, and prioritize. If you have a few design-minded users that actually send you feedback like this – treasure them. But most likely this will have to come from “within the house.” To me, it’s clear that within Shiny Frog (the makers of Bear), there are people who care about this kind of stuff, and leadership that trusts them. Kudos. #above and beyond #culture #flow #research

0 views

📌 married for a year

Celebrated our first wedding anniversary by hanging out with some friends who were here for my wife's birthday the previous day, then visited a botanical garden, walked through beautiful parts of a city, and had a sushi dinner date. Also drank some flower tea we got as a gift when we got married, and soon, we will attend the jewelry making workshop we also got :) Reply via email Published 04 May, 2026

0 views

The whole joy of it all

Four days ago, on Thursday 30 April 2026, my wife Olga gave birth to a beautiful baby named Marius , and the three of us seem to be glowing with love ever since. Mother and child are both healthy, and I am as happy as I am in admiration of the work from every one involved at the hospital; midwives in particular have been truly incredible. I cannot wait for us to be back home as a family.

0 views
Stratechery Yesterday

Google Earnings, Meta Earnings

Wall Street loved Google's earnings, and hated Meta's, even though the latter's core business was more impressive. The difference is that Google is monetizing its investments now (and it might be all Anthropic).

0 views

Adding a feature to a closed-source app

I use Audiobookshelf (abbreviated ABS) for all my legal audiobooks that I bought legally, and I really like it. I also use the Smart Audiobook Player (abbreviated SABP) Android app, which I also bought (legally this time) to listen to books, because it has the strongest featureset out of all the apps I’ve tried, particularly when it comes to navigating around books. Unfortunately, there’s one problem: SABP can’t synchronize my reading progress with the ABS server, which is inconvenient for me. I use SABP when cycling or walking, but use other apps that integrate deeply with ABS (mostly Lissen and ABS’s own app) on my car’s Android console, and the lack of syncing between the two is a major pain. The ABS-compatible apps are mostly open source, and what better way to contribute to open source than to submit some patches that add the features I like? “However”, I thought, “why not not do that, and instead see if I can add Audiobookshelf syncing to the app?” “Yes”, I decided, “this sounds reasonable, despite SABP being a closed-source Android app, a platform with which I have zero familiarity”. What I do have familiarity with, though, is telling Claude what to do and steering it along. Therefore, I decided I would do the impossible , and use LLMs to add ABS syncing to SABP ! The first step was to see whether this is possible at all. Android apps come as APKs, which are just zip files containing bytecode. The first thing I did was to ask Claude to decompile the app (even though I didn’t really know if that was possible, or how it was done). Luckily, all this required was to run and on the files in the APK. is a utility that turns bytecode into a textual representation (called smali) so that it can be edited. This is a lossless, reversible process (which means you can edit the resulting code and recompile it back into the app), but the textual representation is basically assembly, and pretty hard to work with. , on the other hand, decompiles to (hopefully) readable Java, but is useful only for illustration; you can’t recompile it back into an app, and you can’t really edit it in any way. Some developers use obfuscation tools (like ProGuard) to make their decompiled code much more opaque and hard to read. So, the question at this stage was whether the app could be decompiled, and how readable the resulting output would be. Running the tools gave some promising results: The app was fairly readable, with even human-readable class names having been partially preserved! A lot of the code was obfuscated, with names like , , , but I lucked out and enough relevant code was readable that I didn’t have to spend hours piecing things together. This was encouraging, but I still didn’t know whether I could easily inject syncing code into the app. To begin my due diligence, I asked Claude to trace whether there was a point where we could add a hook to send our position to the server. After a bit of digging around, it discovered that one function, , was being called by every code path that saved progress to disk: regular ticks, pauses, file changes, backgrounding, they all saved progress using it. The existence of this code path was a stroke of luck, as it meant that I had found a natural point to hook my progress updating into, but Claude did a lot of work to verify that the code paths actually converged. This was great, we found a single spot where we could hook things, but how could we do the hooking itself ? We can’t edit or recompile the decompiled Java, and smali, which we can edit and recompile, is a real pain to write anything significant in. Still, though, the impossible was slowly drifting within my reach. The second part of due diligence was to see for myself how the ABS API worked, so I knew what to send in the payload if I ended up being able to hook into the syncing. I sent a few requests by hand, but kept getting some weirdness. The times I was submitting didn’t match what I was getting back, and the progress indicator was out of sync with the submitted position in seconds. This was surprising to me, because I know ABS progress syncing works fine with other apps. After some trial and error, I realized that during my testing I had accidentally set to on the book I was testing with, and ABS was resetting the progress when the book transitioned from “finished” to “not finished”. This is a surprising thing to happen, since I’d expect the server to reset when I’m going the other way (i.e. when I finish the book), but I guess the rationale is that I’m starting the book fresh if I mark as on an already-finished book. When I used a non-finished book as the target, the API started responding reasonably, and I had all the info on the endpoints I needed, with their payload shapes, which I gave to Claude. It’s important for me to do this sort of experimentation myself, as often edge cases will be hiding in these API contract boundaries, and I want to build a good mental model of how the change will work before I ask the LLM to implement it. Having the API calls was good, but writing smali code to perform an HTTP request and send/receive JSON would still be taxing work, even for an LLM, and I couldn’t really help here. Luckily, Claude knew that Android makes modding significantly easier than other platforms: We didn’t have to write smali at all! We could write all the syncing code in bog-standard Java, compile it with into bytecode, create the necessary file with (which ships with the regular Android SDK!), and put that into the tree. Then, we just needed a tiny bit of smali code in to jump to our compiled Java code, and everything should work: This works because Android itself natively supports multiple files in one APK, so you don’t have to hack around anything. The investigation was finished, but now we also needed to actually build the thing (an affair whose success was still not guaranteed). Writing the code for this and compiling it into an APK was all Claude, with steering from me. You can read about my exact LLM workflow in my recent post , but it roughly consists of planning (using ticket to write… tickets), implementation, and review steps. Claude discovered that apktool 2.7.0 doesn’t like $-prefixed filenames in the resource table, and decided to use the original manifest, which was fine because we weren’t using custom resources. It also caught a timing bug in the smali patch, where it needed to call a function after another one was run, otherwise the BookData field would be stale. These issues did affect the final implementation, and I was relieved that Claude is smart enough to catch and fix them. Claude did a lot of heavy lifting here, and we ended up with ~550 lines of Java, and some smali magic with to jump to our Java code. The code review phase was all LLMs (Opus 4.6/GPT-5.5), and it’s a step I never skip, as I’ve found that it catches most of the bugs. In one case, Claude had written thirty lines of reflection code because it assumed a setter didn’t exist. The reviewer caught that the setter existed, and had Claude use it directly and remove the superfluous code. This is a pattern I see very frequently in LLM-assisted development, where one model will have big blind spots, leading to bugs or departures from the desired functionality. A second review pass with another model generally fixes this, though I’m not sure whether it’s because of different models spotting different things (like “you can’t spot your own typos” for LLMs) or because a second, focused review pass makes the model pay more attention. I suspect it’s a combination of the two. The reviewer also caught a mistaken compression of the resources file, which would have caused the APK to silently fail to install on my device, even though it looked fine. There was also a race condition that was flagged and fixed in this step, and an instruction to clamp the end timestamp to the book’s length, though I would hope that this check happens on the server too. The codey bits having been done, I had to decide how to handle book matching and server configuration. I needed to make a decision on two things: There were a few options, one of them being adding an “Audiobookshelf” section to the settings, and adding the server’s hostname and API key there, but this was too much work, especially trying to find call sites to patch into existing screens. For the book matching, Claude recommended that we do a lookup of the book by name every time we loaded progress, but that was brittle and would break with more than one book of the same name. I decided to use a config file in the book directory, which was a simple JSON file that looked like this: This way, the app could load everything it needed with minimal fuss (the Java code could simply read this file at startup). There was something that Claude didn’t catch, and actually recommended the opposite: Its advice was to only send the timestamp to the server if it was later than the server’s timestamp (ie if it was later in the book). I pointed out to Claude that this would create a significant problem where, if you seeked to a later position for some reason, you’d never be able to come back from it. The app would keep syncing your position to the later one when loaded, and never update the server’s timestamp, effectively not only invalidating the syncing, but also forcing you to remember your position manually, which is quite a big regression from current functionality. This bug would also cause other apps to get their position overwritten with the later one every time SABP loaded. Claude quickly agreed that this was an issue, and changed the code to sync all seeks. Testing it out, I realized that Claude never retrieved the book’s position from the server at all. I pointed out here that this was necessary to avoid clobbering the position in other apps, because I might use Lissen (and progress there), go back to SABP, and have my (true) progress overwritten by the old position. This was a serious data loss issue that the LLMs completely missed, both in planning/implementation and in review, and an issue that human involvement solved. The code was now in good enough shape to actually try out, which led to another problem. Android, like basically any modern platform, requires apps to be signed by the developer before they can run. Unfortunately, I’m not the developer of SABP, which means I didn’t have access to the key used to sign the app. This isn’t a big obstacle, since apps can be signed by any key (though Google is trying to force us to show them ID to run our apps on our devices), so I just created my own key and signed the recompiled APK with it using . Unfortunately, this does have one downside: The resigned app can’t be installed over the old one, you need to uninstall the old app (and probably lose data) and install the new one again. I opened it up, I started playing a book, and verified that the ABS server position got updated. I didn’t even lose any settings, because SABP keeps its settings in a file next to the audiobooks, which wasn’t deleted when uninstalling. Modifying the application to add the feature I wanted worked fine, and, with the increased skill the LLMs gave me, the lack of source access didn’t block me (it merely posed a sizable problem). However, there was still significant friction (what with the decompile dance, smali, figuring out call sites, etc), and I got very lucky that the code wasn’t more obfuscated. Even after the functionality has been implemented, though, I can’t share the output, both because of potential legal issues and because it’s just a hassle and will break every release. The journey was fun, and having an app that works how I want it is helpful, but there’s a wider point: Before LLMs, the code’s license didn’t matter much for end users wanting to modify their software. Whether the source was open or closed, the biggest reason people didn’t mod their software was just that they didn’t know how to . LLMs have expanded the candidate pool, and, now that many more people can write code that works, the availability of the source is the most important hurdle. The set of people who can now modify their software has increased by orders of magnitude, and includes people who always had good ideas, or good product sense, but didn’t have the skills to make them a reality. In this example, the feature I implemented will be used by me, and basically nobody else, because closed-source software has close to no mechanism for change ingestion. Open source software has always had concrete ways to accept contributions from others, you’d simply make the change you wanted and submit it to the maintainers for inclusion/rework/feedback. This contribution process is even more important now that code can be generated orders of magnitude more cheaply, and the fact that it exists is an important advantage that open-source software has over closed-source. When starting out, I thought this would be impossible, but each step turned out to be very doable. Where a few years ago only a handful of people could reverse engineer an app, now it’s within reach of the average developer with a free afternoon. I’m really happy about the way this feature turned out, but this adventure only made me realize that open source software just aligns with my interests so much more. I’m going to do what I joked I wouldn’t at the start of this article, and switch to Lissen as my audiobook player. I hadn’t used it in a while, but, while writing this post, I fired it up again, and it seems to have gained a few features, plus it’s always been very well-designed and looks great. I guess I’m not going to need SABP any more, but, well, the journey is the destination. The hostname and API key of the ABS server. The ID of each book on the server, so it can submit progress to the specific book without having to rely on name matching.

0 views

Superpowers 5.1.0

I'm pleased to announce Superpowers 5.1.0. The biggest changes in this release are: -A reworking of how we use git worktrees, now that Claude Code and Codex have first-class support for them. You can always find the latest version of Superpowers on GitHub Removing the deprecated slash commands that date to the earliest days of Superpowers, when skills triggering was still really brittle. Switching the 'code reviewer' subagent to be a regular subagent with a custom prompt, reducing our platform footprint, so that we no longer need any special subagent types. A more efficient OpenCode integraiton New support for Factory Droid Cleanups to Subagent Driven Development and Code Review Removal of some old boilerplate text in skills that didn't improve outcomes.

0 views

Content for Content’s Sake

Language is constantly evolving, particularly in some communities. Not everybody is ready for it at all times. I, for instance, cannot stand that my community is now constantly “cooking” or “cooked”, that people in it are “locked in” or “cracked.” I don’t like it, because the use of the words primarily signals membership of a group rather than one’s individuality. But some of the changes to that language might now be coming from … machines? Or maybe not. I don’t know. I, like many others, noticed that some words keep showing up more than before, and the obvious assumption is that LLMs are at fault. What I did was take 90 days’ worth of my local coding sessions and look for medium-frequency words where their use is inflated compared to what wordfreq would assume their frequency should be. Then I looked for the more common of these words and did a Google Trends search (filtered to the US). Note that some words like “capability” are more likely going to show up in coding sessions just because of the nature of the problem, so the actual increase is much more pronounced than you would expect. You can click through it; this is what the change over time looks like. Note that these are all words from agent output in my coding sessions that are inflated compared to historical norms: The interactive word trend chart requires JavaScript. Something is going on for sure. Google Trends, in theory, reflects words that people search for. In theory, maybe agents are doing some of the Googling, but it might just be humans Googling for stuff that is LLM-generated; I don’t know. This data set might be a complete fabrication, but for all the words I checked and selected, I also saw an increase on Google Trends. So how did I select the words to check in the first place? First, I looked for the highest-frequency words. They were, as you would expect, things like “add”, “commit”, “patch”, etc. Then I had an LLM generate a word list of words that it thought were engineering-related, and I excluded them entirely from the list. Then I also removed the most common words to begin with. In the end, I ended up with the list above, plus some other ones that are internal project names. For instance, habitat and absurd , as well as some other internal code names, were heavily over-represented, and I had to remove those. As you can see, not entirely scientific. But of the resulting list of words with a high divergence compared to wordfreq, they all also showed spikes on Google Trends. There might also be explanations other than LLM generation for what is going on, but I at least found it interesting that my coding session spikes also show up as spikes on Google Trends. The choice of words is one thing; the way in which LLMs form sentences is another. It’s not hard to spot LLM-generated text, but I’m increasingly worried that I’m starting to write like an LLM because I just read so much more LLM text. The first time I became aware of this was that I used the word “substrate” in a talk I gave earlier this year. I am not sure where I picked it up, but I really liked it for what I wanted to express and I did not want to use the word “foundation”. Since then, however, I am reading this word everywhere. This, in itself, might be a case of the Baader–Meinhof phenomenon , but you can also see from the selection above that my coding agent loves substrate more than it should, and that Google Trends shows an increase. We have all been exposed to LLM-generated text now, but I feel like this is getting worse recently. A lot of the tweet replies I get and some of the Hacker News comments I see read like they are LLM-generated, and that includes people I know are real humans. It’s really messing with my brain because, on the one hand, I really want to tell people off for talking and writing like LLMs; on the other hand, maybe we all are increasingly actually writing and speaking like LLMs? I was listening to a talk recording recently (which I intentionally will not link) where the speaker used the same sentence structure that is over-represented in LLM-generated text. Yes, the speaker might have used an LLM to help him generate the talk, but at the same time, the talk sounded natural. So either it was super well-rehearsed, or it was natural. At least on Twitter, LinkedIn, and elsewhere, there is a huge desire among people to write content and be read. Shutting up is no longer an option and, as a result, people try to get reach and build their profile by engaging with anything that is popular or trending. In the same way that everybody has gazillions of Open Source projects all of a sudden, everybody has takes on everything. My inbox is a disaster of companies sending me AI-generated nonsense and I now routinely see AI-generated blog posts (or at least ones that look like they are AI-generated) being discussed in earnest on Hacker News and elsewhere. Genuine human discourse had already been an issue because of social media algorithms before, but now it has become incredibly toxic. As more and more people discover that they can use LLMs to optimize their following, they are entering an arms race with the algorithms and real genuine human signal is losing out quickly. There are entire companies now that just exist to automate sending LLM-generated shit and people evidently pay money for it. If we take into account the idea that the highest-quality content should win out, then the speed element would not matter. If a human-generated comment comes in 15 minutes after a clanker-generated one, but outperforms it by being better, then this whole LLM nonsense would show up less. But I think that LLM-generated noise actually performs really well. We see this plenty with Open Source now. Someone builds an interesting project, puts it on GitHub and within hours, there are “remixes” and “reimplementations” of that codebase. Not only that, many of those forks come with sloppy marketing websites, paid-for domains, and a whole story on socials about why this is the path to take. I have complained before that Open Source is quickly deteriorating because people now see the opportunity to build products on top of useful Open Source projects, but the underlying mechanics are the same as why we see so much LLM slop. Someone has a formed opinion (hopefully) at lunch, and then has a clanker-made post 3 minutes later. It just does not take that much time to build it. For the tweets, I think it’s worse because I suspect that some people have scripts running to mostly automate the engagement. And surely, we should hate all of this. These low-effort posts, tweets, and Open Source projects should not make it anywhere. But they do! Whatever they play into, whether in the algorithms or with human engagement, they are not punished enough for how little effort goes into them. That increases in speed and ease of access can turn into problems is a long-understood issue. ID cards are a very unpopular thing in the UK because the British are suspicious of misuse of a central database after what happened in Nazi Germany. Likewise the US has the Firearm Owners Protection Act from 1986, which also bans the US from creating a central database of gun owners. The gun-tracing methodologies that result from not having such a database look like something out of a Wes Anderson movie . We have known for a long time that certain things should not be easy, because of the misuse that happens. We know it in engineering; we know it when it comes to governmental overreach. Now we are probably going to learn the same lesson in many more situations because LLMs make almost anything that involves human text much easier. This is hitting existing text-based systems quickly. Take, for instance, the EU complaints system, which is now buckling under the pressure of AI . Or take any AI-adjacent project’s issue tracker. Pi is routinely getting AI-generated issue requests, sometimes even without the knowledge of the author . I know that’s a lot of complaining for “I am getting too many emails, shitty Twitter mentions, and GitHub issues.” I really think, though, that now that we know that it’s happening, we have to change how we interact with people who are increasingly automating themselves. Not only do they produce a lot of shitty slop that we all have to sit through; they are also influencing the world in much more insidious ways, in that they are influencing our interactions with each other. The moment I start distrusting people I otherwise trust, because they have started picking up LLM phrasing, it erodes trust all over society. You also can’t completely ban people for bad behavior, because some of this increasingly happens accidentally. You sending Polsia spam to me? You’re dead to me. You sending me an AI-generated issue request and following up with an apology five minutes later? Well, I guess mistakes happen. Yet, in many ways, what is going on and will continue to go on is unsettling. I recently talked with my friend Ben who said he forced someone to call him to continue a conversation because he was no longer convinced he was talking to a human. Not all of us have been exposed to the extreme cases of this yet, but I had a handful of interactions in which I questioned reality due to the behavior of the person on the other side. I struggle with this, and I consider myself to be pretty open to new technologies and AI in particular. But how will my children react to stuff like this? My mother? I have strong doubts that technology is going to solve this for us. The reason I don’t think technology is going to solve this for us is that while it can hide some spam and label some generated text, it won’t fix us humans. What is being damaged here are social interactions across the board: the assumption that when someone writes to you, there is a person on the other side who has put some care into the interaction. I would rather have someone ghost me or reject me than send me back some AI-generated slop. Change has to start with awareness and an unfortunate developmend is that LLMs don’t just influence the text we rea and influence the text we write, even when we don’t use htem. Given the resulting ambiguity, we need to become more aware of how easily we can turn into energy vampires when we use agents to back us up in interactions with others. Consider that every time someone reads text coming from you, they will have to increasingly have to make a judgement call if it was you, or an LLM or you and an LLM that produced the interaction. Transparency in either direction, when there is ambiguity, can help great lengths. When someone sends us undeclared slop, we need to change how we engage with them. If we care about them, we should tell them. If we don’t care about them, we should not give them visibility and not engage. When it comes to creating platforms and interfaces where text can be submitted, we need to throw more wrenches in. The fact that it was cheap for you to produce does not make it cheap for someone else to receive, and we need to find more creative ways to increase the backpressure. GitHub or whatever wants to replace it, will have a lot to improve here and some of which might be going against it’s core KPIs. More engagement is increasingly the wrong thing to look at if you want a long term healthy platform. Whatever we can do to rate-limit social interactions is something we should try: more in-person meetings, more platforms where trust has to be earned, and maybe more acceptance that sometimes the right response is no response at all. And as for AI assistence on this blog, I have an AI transparency disclaimer for a while. In this particular blog post I used Pi as an agent to help me generate the dynamic visualization and I use the agent to write the code to analyze and scrape Google Trends.

0 views
Susam Pal Yesterday

From RSS to Atom

Yesterday, I switched my website from RSS feeds to Atom feeds. In case you are wondering whether you have somehow landed on an ancient post from 2010, no, you have not. Yes, this is the year 2026, and I have finally switched from RSS feeds to Atom feeds. Yes, I am fifteen, or perhaps twenty, years too late. I have always wanted to do this but could never make the time for it. Finally, it happened while I was giving my brain some rest from my ongoing algebraic graph theory studies. That's when I felt like spending a little time on my website and doing a little Lisp to change the feeds from RSS to Atom. I suppose this was impulse coding , a bit like impulse buying, except that I ended up with an Atom feed instead of a new book. I find it quite surprising that when I have plenty of time, it usually does not occur to me to do these things, but when I am too busy and really short of time, these little ideas possess me during the short breaks I take. My personal website is one of my passion projects. Common Lisp is one of my favourite programming languages. So any time spent on this passion project using my favourite programming language is a very relaxing experience for me. It serves as an ideal break between intense study sessions. It took about an hour to implement the changes needed to make the switch from RSS to Atom. In the end, I could go back to my studies reinvigorated. In case you are curious, here is the Git commit where I implemented the change from RSS to Atom: 596e1dd . As you might notice, a large portion of the change consists of replacing the attribute in each post with the attribute. The attribute value was used as the value of the element in the RSS feeds. While an arbitrary short string could serve as the element for the items in an RSS feed, the element of the entries in an Atom feed needs to be a URI. It turns out UUID URNs are a common choice for such a URI. I ran the following shell command to replace all occurrences of the attribute with : The rest of the changes went into the feed templates and the Common Lisp program that statically generates the feeds along with the website. For examples of the resulting feeds, see feed.xml and absurd.xml . The first is the main website feed and the second is an example of a tag-specific feed. Yes, the aforementioned Common Lisp program generates a feed for each tag . As of today, the main feed at feed.xml contains only two entries even though this website has over 200 pages . I explain the reason later in Temporary Workaround . Here is an example Atom entry from my feeds: The ellipsis ( ) denotes content I have omitted for the sake of brevity. I like how each entry in the feed now has its own UUIDv4. I also like that timestamps in an Atom feed are in the format specified in RFC 3339 , which also happens to be a profile of ISO 8601. Further, I like that I can explicitly declare the content type to be HTML. Commonly used values for the content type attribute are , and . If it is , the content should be escaped HTML. If it is , the content should be an XHTML element containing valid XHTML. Explicit content type support is likely the biggest advantage of Atom over RSS. In comparison, RSS 2.0 does not specify any way to declare the content type. So feed readers have to inspect the content and guess what the content type might be. As I mentioned before, as of today, the main feed contains only two entries. That's because only new posts published since the migration to Atom are now included in the feed. This was done to avoid spamming subscribers. The Atom specification's requirement that each entry's ID must be a URI has caused the IDs of every entry to change. If I were to include the older posts from before the change in the feed, then those posts would appear as new unread items. Subscribers can find this quite annoying. In fact, I have received a few complaints about this in the past. So I was careful this time. I have a little one-liner workaround in my site generator to exclude posts published before this change from the feed. That was the only workaround I had to implement. Fortunately, my feed file had a neutral name like , rather than a format-specific name like , so I could avoid a URL change and the subsequent overhead of setting up redirects. Does any of this matter today? I think it does. Contrary to the recurring claim that RSS and Atom are dead, most of the traffic to my personal website still comes from web feeds, even in 2026. Every time I publish a new post, I can see a good number of visitors arriving from feed readers. From the referrer data in my web server logs (which is not completely reliable but still offers some insight), the three largest sources of traffic to my website are web feeds, newsletters and search engines, in that order. On the topic of newsletters, I was surprised to discover just how many technology newsletters there are on the Web and how active their user bases are. Once in a while, a newsletter picks up one of my silly or quirky posts, which then brings a large number of visits from its followers. Back to the topic of web feeds, there is indeed a decent user base around RSS and Atom feeds. A good number of visitors to my website arrive by clicking a feed entry that shows up in their feed reader. I know this with some confidence by looking at the (sic) headers of visits to my HTML pages and the subsequent browsing of the website, as opposed to the isolated and automated fetches of the XML feeds. So there must be a reasonably active base of users around web feeds. It is a bit like being part of an invisible social network that we know exists and that we can measure through indirect evidence. I found these three resources useful while switching to Atom feeds: Read on website | #web | #technology Impulse Coding Atom Entries Temporary Workaround Does It Matter? W3C Introduction to Atom W3C Feed Validation Service RFC 4287 : The Atom Syndication Format

0 views

29th August 2026: a scenario

On 29 April 2026, a Korean security firm called Theori published 732 bytes of Python that breaks Linux container isolation. CopyFail (CVE-2026-31431) is a page-cache corruption bug in the kernel's crypto code. It's been sitting in production since 2017. A compromised pod on a shared Kubernetes node can corrupt binaries visible to every other container on that host, and to the host kernel itself. EKS, GKE, AKS, every shared-tenant node, every CI runner, every multi-tenant SaaS that took the cheap path on isolation - all exposed until patched. It took an AI tool four months to find it. Nine years of human eyes did not. Container escape is bad. Despite arguably a poorly coordinated disclosure/mitigation response [1] , it looks like a near miss rather than a catastrophe. But, this class of bug - old, subtle, in a corner of the kernel that everyone assumed someone else had read - is exactly the class of bug that lives in every hypervisor stack underneath every cloud. Those bugs are still there. They just haven't been found yet. Here's a (fictional) story about what happens four months from now, on 29th August 2026. As Europe basks in an extreme heatwave, many engineers are paged as with EC2 instances hard crashing. Hacker News reacts to the news as per normal - another us-east-1 outage, AWS status showing green, eyes roll. Some commenters post though that many other AZs are showing issues, though not all servers are affected. Over the next hour though, more and more machines go down. One Reddit user posts that they are having issues provisioning even fresh machines - as soon as they launch, they get moved into "unhealthy" and go down. A few minutes later, the entire AWS dashboard and API set goes down. Cloudflare Radar shows AWS network traffic dropping to a small percentage of what is normal. As many AWS hosted services start going down - Atlassian, Stripe, Slack, PagerDuty, some comments on Twitter report issues with Linux-based Azure instances. Indeed, Cloudflare Radar shows significant drops in Azure traffic. News channels across Europe start leading with vague breaking news headlines on outages across Amazon. They make sure to point out that this isn't an unusual occurrence, with normal service expecting to be resumed like it always has been, and mistakenly insist only US services are affected. As the East coast of the US starts their weekend, a very unusual step is taken. TV channels are briefed that POTUS will be doing an address to the nation at 8am EDT. Few connect the dots - with the emphasis being placed on a potential new strike in the Middle East, or an announcement on the Russia-Ukraine war. POTUS announces that there is a significant cybersecurity incident under way. The head of CISA (the Cybersecurity and Infrastructure Security Agency) gives a very vague but concerning warning. Americans are requested to charge their cell phones, and to await further news - reminded that there may be outages on IPTV based services. POTUS rounds it out by speculating that China is behind the attack, despite his much-heralded reset with Beijing earlier in the year. Other Western leaders do similar addresses - with European leaders speculating on background it is more likely to be Russia or North Korea than China behind the attack. The French president says "without doubt" this is a nation-state actor. While he doesn't publicly point to a specific country, he says those responsible will be brought to justice. While these addresses happen, engineers at various banks are battling various outages. Most concerningly, the 1st biggest and 3rd biggest card processors by volume in Europe have stopped accepting payments, returning cryptic error messages. While they have a multicloud strategy, they cannot move workloads off those two clouds successfully. Google Cloud Platform and smaller cloud providers - unaffected until now - start showing issues. While current workloads are unaffected, the huge spike in demand from enterprises activating their disaster recovery protocols simultaneously completely swamps available compute on alternate providers. One smaller cloud provider tweets they are seeing 10,000 VM creation requests a second, draining their entire spare allocation in less than a minute. CEOs of major banks bombard Google and Oracle leadership with calls, offering blank cheques to secure failover compute. The calls go unanswered. WhatsApp groups throughout Europe start lighting up with misinformation that money has been stolen, amplified by many mobile apps showing a "we are undertaking routine maintenance" fallback error simultaneously, causing huge lines at ATMs and banks with people trying to withdraw their savings. As the chaos continues to grow, a press release is distributed from the leadership of AWS and Azure: At approximately 4am EDT this morning a critical and novel vulnerability was exploited in the Linux operating system. This has caused widespread global outages of Linux based virtual machines. Our engineers are working with security services globally to mitigate the impact and engineers across both Microsoft and AWS are working collaboratively to release emergency patches for affected software. Equally we are working hard to understand the impact and will provide regular updates to the media. We sincerely apologize for the impact this is having to our customers and society at large. Behind the scenes, it is chaos. Engineers have isolated the root causes - a complex interplay of vulnerabilities, with the most critical being an undiscovered logic error in the eBPF Linux subsystem that allows a hypervisor takeover. Curiously no data has been stolen - a mistake in the exploit just leads to machines hard crashing exactly 255 seconds after receiving the malicious payload. A few engineers question the sloppiness here, but leadership doubles down in their private communications with government that it has to be nation state. The core issue though is that nearly all of Azure and AWS's control plane is down. Attempts to "black start" it results in perpetual failures as various subsystems collapse under the intense traffic from VMs stuck in bootloops. The first VM instances start up again. Restoration is painfully slow, with AWS struggling to get more than 2% of machines back online. Communication internally is severely degraded - with both Slack and Microsoft Teams down instant messaging is out of the question. Amazon's corporate email runs on AWS itself, and Microsoft's on Azure-hosted Exchange. Both are degraded, massively complicating internal communications. An enterprising AWS employee starts an IRC server locally which becomes the main source of communication - restoration efforts start to speed up once this system becomes known about. Restoration continues, with the worst of the panic dying down. Banks ended up getting priority compute - with POTUS publicly threatening "extreme actions" if major banks are not put to the front of the queue. Asian stock markets open, triggering multiple circuit breakers. After the 3rd one in a row, Tokyo forces markets to close for the day, other Asian markets follow in quick succession. One curious question remains though - what was the purpose of this attack? No ransomware was deployed, no data was stolen, and while various terrorist groups claimed responsibility, none of them were believed to be credible. Meanwhile AWS engineer finally isolates snapshots containing the first known failure. An EC2 instance, provisioned on August 13th. Curiously provisioned on an individual account in - Paris. The account matches an individual in Lyon, France. French security services are alerted. In an outer suburb of Lyon, France, French anti-terrorism police arrive at an apartment building. A 17 year old teenager is apprehended, along with his grandmother. Two days earlier, his own president had vowed those responsible would be brought to justice. The police chief on the scene passes the information up the chain that the lead was a total dud - there is no chance that the suggested foreign intelligence service was here. A search of the apartment confirms it - nothing found apart from a PS5 mid-FIFA tournament and a 6 year old gaming computer. Neighbours confirm that they've seen no one enter or exit the apartment apart from the two residents, who've lived there for "as long as anyone can remember". Media arrive on the scene, with a blustered and embarrassed police chief suggesting that it was a bad tip off and for local residents to stay calm. The decision is made to seize the electronics and release the two "suspects". A couple of digital forensics experts get the seized gaming PC, scanning it for malware. Nothing much of interest is found, and just as they start writing their report up one folder pops up. . They take a further look, noting it on the report - not thinking much of it, probably a kid trying to play pirated games. They've seen it before. The image of the machine is uploaded. When the code gets up the chain a few hours later, the whole set of dominoes fall into place. A specialist from the French Agence nationale de la sécurité des systèmes d'information - National Cybersecurity Agency of France - pulls the code from the image. He quickly realises what's happened. The teenager had been quietly mining crypto for months, using the proceeds to rent cheap GPUs on a small European cloud provider, where he ran an uncensored fine-tune of the new Qwen 4 open weights model. He'd been desperately trying to downgrade his PS5 firmware to bypass the latest piracy checks. Interestingly his coding agent, unbeknown to him, had found the most critical *nix kernel exploit in many decades. Attacking a little known about eBPF module on the PS5 (the PS5, like every PlayStation since the PS3, runs FreeBSD), it managed to a complete takeover of the device. Intrigued, he also asked his coding agent to run it on a Linux server on AWS he ran a gaming forum on - same thing, but curiously he noticed he could see other files on the machine. Annoyingly the VM he rented crashed after a few minutes. Excitedly, he set up an Azure account - same thing. He asked his coding agent what this meant, and with its usual sycophantic personality started explaining what he could do with this - mining crypto and making him rich beyond his wildest dreams. The agent came up with a final plan, to deploy the exploit on both Azure and AWS, install a cryptominer. His last known chat log was "is this definitely a great idea?". The agent responded "You're absolutely right!", and began deploying the code, first to AWS and next to Azure. The agent had built a complex piece of malware that spread across millions of physical servers. However, it hallucinated a key Linux API which resulted in the machines crashing after 255 seconds instead of deploying the cryptominer. This is fiction. The teenager doesn't exist. Qwen 4 doesn't exist yet either. When it does, an uncensored fine-tune will appear within days, like every prior open-weights release. Almost everything else in here is real, or close enough that it doesn't matter. CopyFail is real. A nine-year-old kernel bug, found by an AI tool in a few months that nine years of human eyes had missed. That class of bug - old, subtle, in a corner of the kernel everyone assumed someone else had read - sits in every hypervisor stack underneath every cloud. Those bugs are still in there. They just haven't been found yet, and the rate at which they get found from now on is bounded by GPU hours, not human ones. The centralisation is the bit that's hard to think clearly about. Most people I talk to about this, even technical people, underestimate how much of modern life is sitting on AWS and Azure. The DR plans I've seen at large enterprises mostly assume there's a cloud to fail over to. They don't really model what happens if the fallback is also down, or if every other org on earth is failing over at the same minute and draining GCP's spare capacity. Almost nobody keeps full cold standby compute. And even the ones that do are sitting on top of hundreds of services that don't: Stripe, Auth0, Twilio, Datadog, every queue and identity provider in the stack. They're all running somewhere, and that somewhere is mostly two companies. The attribution thing is the bit I'm least sure about, but worth saying anyway. Everyone is worried about nation states. Most of the big incidents that have actually happened turned out to be a kid, a misconfiguration, or someone who didn't really understand what they were doing. The Morris Worm. Mirai. The threat model in most boards' heads assumes a sophisticated adversary. The thing that's actually arriving is an unsophisticated adversary holding tools that are now sophisticated for them. I wrote this as fiction because I've spent the last few months talking to journalists and other non-technical people about what AI changes for cybersecurity, and the technical version of the argument doesn't land at all. Engineers get it instantly. Everyone else needs to feel what it looks like. So this is what it might look like, more or less. The only bit I'm reasonably confident about is that the date is wrong. The entire story here is still evolving at the time of writing, but there is a serious coordination problem on Linux security. The Linux kernel security team recommend that downstream distributions of Linux (such as Ubuntu, Fedora, Arch, etc) are not notified of security issues. This has lead to slow patches to the issue as many distributions were not informed and only found out when it was made public. People are pointing fingers in many directions. ↩︎ The entire story here is still evolving at the time of writing, but there is a serious coordination problem on Linux security. The Linux kernel security team recommend that downstream distributions of Linux (such as Ubuntu, Fedora, Arch, etc) are not notified of security issues. This has lead to slow patches to the issue as many distributions were not informed and only found out when it was made public. People are pointing fingers in many directions. ↩︎

0 views
Unsung Yesterday

The 1990s called and they want their dialog box back

This is perhaps my favourite feature in Lightroom. You press ⇧T, you draw a few lines, and presto – your photo is now even: This is doubly magical to me. The first part is that this is even possible – that you can straighten the photo in both dimensions after the fact , and save for some parallax nuances the viewer won’t know any better. For decades, this has been the domain of tilt-shift lenses , but if you ever tried to use one, you know how harrowing of an exercise this is. A tilt-shift lens looks more like a medical device and less like a piece of photography equipment: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/2.1600w.avif" type="image/avif"> The “obvious” way to emulate a tilt-shift lens in software is a bunch of sliders, and Lightroom has those also… …but that’s still pretty cumbersome in practice, abstracted in a strange ways, like piloting a plane by pulling the linkages connected the flying surfaces: you will admire someone who can do that, but won’t ever want to do it yourself. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/4.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/4.1600w.avif" type="image/avif"> Hence the second magical moment: The team created the new interface I showed at the beginning, where you point to things that should be straight directly , and the necessary tilt-shift calculations happen behind the scenes. Alas, Lightroom didn’t fully stick the landing. The interface is a bit jittery, and missing nice transitions that could help understand what’s going on. But what brought me here was this unpleasant interaction: What’s wrong with it? If you want to play along, stop here and ponder: How would you improve it? Because this is a classic UI exercise where there are symptoms, and there are problems, and there are principles under the hood of it all. The first possible improvement: Don’t do a dialog like this. These are ancient and so annoying. Every time I see a centered dialog covering everything, popping up in response to a delicate mouse operation, I want to shout “read the room!” It’s better to drop a little tooltip next to the cursor that automatically disappears: more modern, and more “compatible” with mousing. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/6.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/6.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/7.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/7.1600w.avif" type="image/avif"> Then: Why am I allowed to start and finish an action that the machine already knows won’t go anywhere? Disable the drawing option, put a little “verboten” icon on the mouse pointer, or do something else that will prevent me from drawing a line to begin with. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/8.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/8.1600w.avif" type="image/avif"> But that brings us to point three, and how I would approach this as a designer. Because I would – counterintuitively – go the other way and allow the user to draw as many lines as they wanted, and just didn’t permit to commit the entire operation if there were more than four lines on the screen. Why is that? It’s the same principle as you see in all the social media composing fields, and in well-trained forms: do not constrain the editing process . This field is limited to 300 characters, but it’s clever enough to only enforce its limits when you try to post. There is no downside to allowing you more room in the editing process. Maybe you write by constructing a few sentences first and only then combining them into one, maybe you want to see two riffs one below the other to choose the better one, or maybe – this is most likely – you’re not even paying attention and your motor memory is doing the editing for you, instinctively. Use any text editor for just a few months, and cut, copy, and paste, word swapping, and splitting sentences become second-nature gestures – that is, until the UI starts throwing in some arbitrary barriers. Above in Lightroom, it might actually be easier for me to draw a fifth line and then delete a previous one, instead of doing it in the precise order Lightroom desires, or by dragging an existing line to move it instead of creating a new one. Maybe an overarching principle would be this: If you are aiming to build something so delightfully direct manipulation as Lightroom did here, you have to fully commit to that stance, even deep in the weeds. Because every time I see a 1990s dialog appear when my fingers are flying fast, I feel like this: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/10.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/the-1990s-called-and-they-want-their-dialog-box-back/10.1600w.avif" type="image/avif"> And something tells me others will too. #flow #interface design #mouse #principles #text editing

0 views
Unsung Yesterday

“Have you ever been annoyed by your Mac’s media keys?”

In our Unsung yellow pages, in between people writing Chrome plugins to fix UI of other apps , and gamers creating mods to fix bugs that the developers leave behind , we need to make some room for another category of apps. Some time ago, Daniel Kennett created a little utility called Keyhole with a singular purpose: Have you ever been annoyed by your Mac’s media keys triggering a random video in your web browser, doing something else weird, or by them doing… nothing? Even though your music player is right there? Me too! And so Keyhole was born. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/have-you-ever-been-annoyed-by-your-macs-media-keys/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/have-you-ever-been-annoyed-by-your-macs-media-keys/1.1600w.avif" type="image/avif"> Keyhole intercepts media transport key presses before the operating system gets a hold of them, and promises to do a better job dispatching them to the right place. This week Kennett added another feature – the app will monitor the repeat setting that apparently occasionally gets out of whack, and fix it for the user. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/have-you-ever-been-annoyed-by-your-macs-media-keys/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/have-you-ever-been-annoyed-by-your-macs-media-keys/2.1600w.avif" type="image/avif"> We could call these kinds of apps “janitor apps.” I know of a concept called cron jobs , but I’m assuming these quiet workers do backend-y things like moving files around, cleaning up databases, pinging servers, and so on. I am less aware of work like Kennett’s that fixes stuff on the UI layer. Is it strange that I find this kind of an app pretty… noble? Of course, Apple should fix it; perhaps Bugs Apple Loves could even introduce a serious multiplier for “a bug bothers someone so much they fix it for Apple.” Of note in the last dialog box: “Keyhole has fixed Music’s repeat setting X times.” I think this kind of a counter is pretty brilliant. #bugs #keyboard

0 views
Jim Nielsen Yesterday

Reminder: You Can Stitch Together Lots of Little HTML Pages With Navigations For Interactions

I wrote about building websites with LLMs — (L)ots of (L)ittle ht(M)l page(s) — and I think it’s time for a post-mortem on that approach: I’ve tweaked a few things from that original post but the underlying idea is still the same, which I would describe as: Avoid in-page interactions that require JavaScript in favor of multi-page navigations that rely on HTML and are enhanced with CSS view transitions (and a dash of JS if/where prudent). As an example, on my blog I have a “Menu”. It doesn’t “expand” or “slide out” or “pop in” or whatever else you can do with JS. Instead, it navigates to an entirely-new page that is focused on just the menu options of my site. I say “navigates” because it’s just a link — — and it functions like a link, but the navigation interaction is enhanced by CSS view transitions. Have a newer device with a modern browser? Great, you get a nicer effect. Have an older device, or an older browser, or JS disabled, Et al.? It’ll still work. If you can follow a link — which is the most fundamental thing a browser can do — it will work. So how’s it all work under the hood? In essence, all the pages have a link to the menu (except the menu page). When you navigate to the menu, that link is changed to an “X” which “closes” the menu. The closing is still just a link (back to ) but it’s enhanced with JS to actually do a “back” in the browser history. This makes it so “opening/closing” the menu doesn’t add an entry to your browser history. As a simplified example, the code looks like this: The checks whether we came to this page as a navigation (mostly likely from within the blog itself) or via a direct visit (i.e. somebody typed it into the URL bar, unlikely but possible) which is how I suss out whether there’s a meaningful run or not. Here’s a video of how it all works, if that’s your thing: While this solution seems simplistic, it was not a simple thing to arrive at. It required me to spend time thinking about what was essential to navigation, how that interaction could work across multiple pages, and how I could ensure page size stayed small so the interaction was both fast and robust while remaining intuitive to use. In other words, the approach shaped the design. Turns out, if you have a website and you think of the browser as a way to navigate documents — rather than a runtime to execute arbitrary code and fetch, compile, and present them — things can be a lot simpler than our tools often prime us to make them. Reply via: Email · Mastodon · Bluesky

0 views
Ginger Bill 2 days ago

Signed By Default Camp

As with many discussions in the programming space, there are ;wars; between different ways of doing things. These are typically about minor aesthetic preferences, such as:Tabs vs Spaces for indentation vs vs for naming conventions vs for strings (if the language allows both)1TBS vs K;R vs Allman for brace stylesThese wars are largely pointless; what actually matters is coherency and consistency in your coding style. However, when it comes to designing a language, some binary choices have a massive impact. This article focuses on one such ...

0 views
Brain Baking 2 days ago

Favourites of March 2026

It’s May! What happened? This weekend was unusually hot! What happened? Everyone knows but no-one admits or cares… Anyway, welcome to another month of 2026. I like May. It’s got a lot of national holidays. It signals the start of lots of great local food: strawberries in abundance, a strong asparagus month that you should enjoy while it lasts as in June the season is usually over, and we already ate some fresh French artichokes. It’s getting warmer but not as scorching as some of the coming months (although given the start of this month, that remains to be seen). But most of all: the end of May usually indicates the beginning of the exam period, which for me as an examiner instead of student is always interesting. Let’s light a candle and pray for not too many LLM-only submissions. Previous month: March 2026 . A miracle happened: I made some time to get back into gaming—and writing about games. In May, we’re finally digging into UFO 50 , in chronological order. If we play one a week we might finish in May 2027… So far, the first entry already is a home run. Related topics: / metapost / By Wouter Groeneveld on 3 May 2026.  Reply via email . I finally got to the Kirby spin-offs on the Game Boy: Kirby’s Pinball Land , Kirby’s Block Ball , and Kirby’s Star Stacker . They’re all really good! But then Robert and a GOG discount pushed me to finally try out The Drifter . What a thrill. I loved every minute of it. If you like gritty pixelated adventure games, you can’t miss this. After being turned off by the bad technical performance of Ruffy and the Riverside on the Nintendo Switch, I switched gears to other games. I picked it up and finished it. It’s an OK N64-inspired collect-a-thon that should be enjoyed on PC instead. Speaking of Robert, his /concerts slash page is very cool: it contains scans of all concert tickets he ever went to. Kenneth Reitz tells us to separate our identity from our work/projects , otherwise bad things happen (via Roy Tang ). Stefano Marinelli explains why he loves FreeBSD . The Power To Serve conveys such as strong message, its almost convincing me to jump ship! Until I read about the laptop gap . This year huh. Zakhary Kaplan stole the GBC logo from a ROM and made a cool web logo from it. Cal Newport’s In Defense of Thinking hits yet another nail on the head. Forrest’s essay On Pulling The Master Sword links Link’s (ha!) N64 behaviour to our capitalistic world. It’s a very long essay but well worth your time if you can stomach a game rant, some swearing, and philosophical questions about life and society. Drakenvlieg manages to pull more students into literature using journaling (in Dutch). Juhis shares his favourite two-player board games . Hive (pocket) is on the list! Chris Smith rates the movies he watched . I’m always interested in the rating systems other people employ when they do something like this. I liked blinry’s Do It Yourself soft drinks experiment. Translucent coke looks weird! Night’s Ham Stock examines the ending story of SKALD the video game I played in 2024 . It was great but I couldn’t make sense of the ending. Now I still can’t… Kain Klarden’s Gex Trilogy review saved me from throwing money at Limited Run Games. Again. It Fits On A Floppy is a strong manifesto for small software that more developers should read and take heart. Eli (Oatmeal) re-iterates something very important: “choose to truly care about something.” But then he goes much further. I need to re-read this a couple of times and let it sink in. It was also Eli who pointed out the existence of picoSYNTH . Richard Moss, the author known for The Secret History of Mac Gaming , is writing a book on Age of Empires ! Ruben Schade’s enthusiasm for the Commodore 64 knows no boundaries. The newly released C64 Ultimate looks very enticing, but where to put all these things? Amelia’s little blog website/host got hammered by AI bots . It’s yet another infuriating story but the visualisation part is very cool. There’s an interesting upcoming documentary on Clojure the programming language that might be worth checking out. https://www.codingfont.com/ is a cool way to help pick a monospaced editing font. I’m using JetBrains Mono for now. Did you know Windows was released for the Game Boy: I didn’t know palm rejection was a thing on Linux/KDE . The Underkeep Steam demo looks very promising; something to keep close tabs on! I don’t know what this is, but Listography looks like a lot of fun. I happen to like lists so I should be liking this. Isowulf is a very cool isometric perspective Wolfenstein 3D mod . You can build retro games using WebAssembly with https://wasm4.org/ I love the GoodEnough guestbook that even used to print the drawn images on thermal paper! Thomas Lehmann, the designer of one of my favourite card games ever Race for the Galaxy , took the deck building genre for another spin. The result is Dark Pact . Needless to say, it’s on my list.

0 views
Unsung 2 days ago

Early names

The original 2004 Gmail iteration of the now-ubiquitous modern status bar (here presenting undo send ) was internally nicknamed a butter bar because… well, just look at it: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/early-names/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/early-names/1.1600w.avif" type="image/avif"> (I believe at least Google today calls this a snackbar .) The UI pop-up element hosting Google Talk inside Gmail – the very same thing that’s more commonly called a “toast” these days – was originally termed a mole : = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/early-names/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/early-names/2.1600w.avif" type="image/avif"> The column view in NeXTSTEP was called a browser , but a few years later someone put together a different kind of a browser on that very same machine, and the original term has been sunset – after NeXTSTEP became Mac OS, the view was renamed to “ column view ”: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/early-names/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/early-names/3.1600w.avif" type="image/avif"> These three are off the top of my head. Please send in more! #history #interface design

0 views
Rafael Camargo 2 days ago

Alternative solution for small React apps that need i18n

In most projects I've worked on, the default pick for internationalization has always been react-i18next. It works, and it covers a lot of ground. But when I kicked off a tiny personal project called Veedgee, I started wondering: do I really need all of that? It was a simple app. Not much copy, only two languages, and still I'd have to...

0 views

callgraph analysis

I recently wrote a post for work titled "Callgraph Analysis", about how to write a custom lint with access to the full call graph of a program. You can read it on my work's blog .

0 views