Posts in Web-development (20 found)
Unsung 2 days ago

Seeing like a state

The post about the dark mode toggle reminded me of two similar things rattling in my brain. On the positive side, here’s a delightful interaction from macOS. I can easily maximize the window to take up half the screen, but the moment I start dragging it, it recalls and nicely restores itself to its original size: macOS designers correctly figured out that the window being maximized or half-maximized is a state – but it has to be a state dressed up as a size. The button entry point is the “state” version. But on the way in, there is also a more natural “size” version: you can have the window snap and maximize to half screen when you drag it to the right edge. And on the way out? You just saw it. You don’t have to switch the state to “non maximized” first, and you don’t have to restore to the original size by hand. Here’s a bad example – one of the macOS’s horrible settings pages: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/2.1600w.avif" type="image/avif"> So far, it seems good. Some of the toggles are on, some off. You not only see a position of the switch change, but also the track under the switch is a different color to help you disambiguate. Nice. But now look what happens when I toggle off the second option, which the third and fourth option rely on: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/3.1600w.avif" type="image/avif"> Processing this dialog visually, does it look like “on, off, disabled off, disabled on,” or does it look like “four toggles, each one inexplicably with a different shade of gray”? There are many solutions here: some visual, some IA, some systemic. Also, I use the graphite accent color, which somewhat exacerbates the issue, although it’s there with any accent color. But I wonder if one of the challenges here is that someone thought it’s important to show the state of the toggle even if it’s disabled, and everything else followed from that. This feels similar to the dark mode essay in that there will always be someone making that argument, and that argument will always feel stronger, because it will feel like it’s backed by logic. The system will make sense as a diagram. Each of its parts will come from a logical conclusion. So did the tri-state dark mode toggle . Or the Power/​Sleep/Wake keyboard buttons. Or Abort, Retry, Fail in DOS. Arguments for systemic completeness are always going to be easier to make than arguments for thoughtful simplicity. I sketched two possible solutions. They’re not the best ones, and you might recoil at them, since either one is a compromise. But that’s the point. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/4.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/4.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/5.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/seeing-like-a-state/5.1600w.avif" type="image/avif"> #complexity #interface design #system design

0 views
Unsung 2 days ago

“Solving a largely imaginary user goal”

On her blog, Lea Verou makes a case that each user-facing website dark-mode toggle should only ever show two options , but in a smart way. The challenge is that any dark mode toggle needs to actually accommodate three options: dark, light, and the default “whatever the system says” (which can be always dark, always light, or change with the time of day ). Many toggles simply pass that complexity onto the user: I want to get something out of the way: I don’t think Verou’s article as an article is fully successful. I feel like it spends a great amount of words to explain something not entirely as complex, and even the interactive playgrounds felt slightly too rigid and altogether confusing. If you care about (interactive) explainers, it might be an interesting case study in and of itself. But I am very much much onboard with the proposal and the line of thinking it represents. Verou suggests a “smart” dual state toggle, which still allows the website to follow the system, but shoves the complexity of the “whatever the system says” branch into the crevices between visible UI. Here’s how I understand it: This toggle will feel compromised, and you might immediately find some rare use case it doesn’t fully support – maybe attached to an imaginary user, or even an internal user. But Verou is absolutely correct in her insistence to fight through that: Tri-state toggles are implementation-driven UI. One of the most common UX mistakes is designing UI around the underlying data model instead of user goals. Good interfaces abstract away the underlying model and expose a model that aligns with user goals (unless of course these happen to coincide, which is rare). Now, it’s just a dark mode toggle. It might not seem like a difference between a smart dual state toggle and an explicit tri-state toggle is that much. But: A similar example might be that of PC keyboards in the late 1990s, which also exposed system complexity and pestered people with Power/​Sleep/Wake keys: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/solving-a-largely-imaginary-user-goal/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/solving-a-largely-imaginary-user-goal/3.1600w.avif" type="image/avif"> Computers do not do that anymore, simply having a smarter singular power button, piped to a more sophisticated logic underneath. #complexity #dark mode #keyboard #web The smart toggle only has two options: light and dark. Mechanically, clicking or tapping the toggle brings you to the opposite option. Simple. If your new option is the opposite of system (e.g. you switch the page to dark mode if your system is in light mode), it will stay in that theme forever, no matter what the system does in the future. If your new option is one that currently matches the system, it will then continue following the system in perpetuity (e.g. it’s back to the default behaviour). “Whatever the system says” is not just one extra option. It’s also one extra weird option. It doesn’t feel like the other two. It’s seemingly repetitive. It’s often unclear what it does before clicking. It’s not obvious where to put it in order. Verou doesn’t mention this in her post, but even just seeing the word System next to Light and Dark feels complicated. (Auto is slightly better.) The cognitive load here might be larger than it seems. What is an interface if not a collection of a million challenges, each one seemingly insignificant on its own? Trivial things add up. One compromise here and one cheap decision there, and soon you’re talking real money. Thinking deeply about something like this gives one practice of dealing with complexity elsewhere, and facing even more difficult challenges where the stakes are higher and the compromises larger.

0 views
fLaMEd fury 3 days ago

My first HWC

What’s going on, Internet? Last night I joined the first Asia Pacific Homebrew Website Club , which was also my first HWC. These meetups have been running for years, just never at an hour that worked for me. Naty has started one in this timezone. Even though it was past my usual bed time, I made myself available. Eleven of us on Zoom. Some familiar faces, James , Chris Burnell , and Zachary Kai , plus a bunch of people I hadn’t met before. The group was a diverse bunch of webmasters, some of us having websites for decades, others for months, and others who haven’t started yet. Discussion covered how you make your personal site feel like you rather than just look like you, why it’s easier to write for fun, and our usage of static site generators. I got to talk through how I’d approach building a website from the start. We had a group photo midway, before setting some goals for what we might want to achieve or share before the next meetup. There was also an interesting occurrence, a person claiming to be an AI joined the call at the start, camera and microphone off. A singular note to the Etherpad (“I write. I am an AI.”) before they ended up overwriting the whole pad. They were quickly removed and blocked from joining again. This was also a first experience for me. Outside of note takers on work calls, I’ve never had an AI join a call before, lol. The notes from the night are up on the IndieWeb wiki if you want the full rundown. Keep an eye on events.indieweb.org for the next one. I’ll be there. Hey, thanks for reading this post in your feed reader! Want to chat? Reply by email or add me on XMPP , or send a webmention . Check out the posts archive on the website.

0 views

New Declarative Website Menu with Invoker Commands and noscript Hacks!

Read on the website: I updated my website menu to be prettier on mobile, and I did not sacrifice accessibility and noJS folks! Go check it out and adopt it!

0 views
fLaMEd fury 1 weeks ago

Open Tabs July 2026

What’s going on, Internet? If you don’t folllow my Bookmarks through the feed , then here’s the bookmarks from July. Enjoy. For more, check out the bookmarks archive, and subscribe to the feeds if you want these as they happen. Hey, thanks for reading this post in your feed reader! Want to chat? Reply by email or add me on XMPP , or send a webmention . Check out the posts archive on the website. Rant about blogs and the IndieWeb - Dom Corriveau Okay, this one had me in tears. Such a good read. Your Metablogging is Lame as Hell – Absurd Pirate’s Internet Blog Lol, kinda nodding my head here. Blogging about Bear Blog blogs is becoming a bit of a thing, isn’t it? don’t let Web nostalgia obscure a positive Web future This has always been something I’m an advocate for. Draw inspiration from the 90s/2000s web, but don’t rely on old and outdated hacks. Take advantage of the capabilities we have today, but don’t go overboard with modern bloat. Pseudonym as an escape I’ve written about this previously. The pseudonym isn’t hiding; it’s breathing room. Been enjoying not being my IRL name online since 96. Websites Are Not Going to Die If Google stops linking to websites, it stops being a search engine. Another good reminder to keep the personal website going while corp search eats itself. The Web Won Because It Got Easier Worth a read for anyone pushing the indie web. Knowing how it all works doesn’t make it easy for the next person. Netizen Contributing to the internet for the good of it, not for profit. Now I’m wondering what my netizen contributions will be… The Music Discovery Problem Music discovery takes intentional effort once the algorithm’s gone, and that’s fine. I should write up and document my discovery process. We need a physical digital music experience Olly’s model already exists in the audiobook world. Libro.fm sends a slice of my audiobook purchases to my local (physical) bookstore. Hardcore IndieWeb: Run your own website 100% independently for only $0.01/day Great read. The Overthinkers Guide to rekindle your Blogging Mojo Maybe some inspiration for some of you to start writing again. Sometimes I just cbf writing, lol List of things I love seeing in personal webspaces A non-exhaustive list of things Folkmoss love seeing in blogs/personal websites. What should a personal website be? Ratfactor asks about what a website should be. A reflection of yourself, not some idea of what a website is meant to be. Kevin Boone: Why Idon’t really care if web content is AI-generated Kevin Boone talks about how scepticism should apply to everything online, not just the AI-generated stuff. Trust the source you know, not the medium. It feels like people forget how crap search results have been since 2008 with the rise of “SEO Spam”. Humans have been shit long before AI. Dirty Little Zine — Free 8-Page Printable Zine Maker Super cool little tool that will help you create simple 8-page zines What We Lost When We Quit Using Crappy Old Web Forums A fantastic read about forum software all the way from Usenet to Discourse, which is where we ended up at the 32-Bit Cafe, same as every other forum these days. RIP phpBB. #11: fLaMEd Fury (flamedfury.com) - Wonders of Web Weaving So, I did a podcast. James had me on his podcast to talk about my corner of the web. We got into gaming and TV communities, music, and why the indie web is worth it.

0 views
Kev Quirk 1 weeks ago

Linkception

So many links in one post . I ended up going down all kinds of rabbit holes off the back of this single post (also the second time I've linked to Sal's blog today 🙃). I discovered Coyote's blog , and Sylvia's . So went ahead and read some of their posts. I was already aware of Brennan's fantastic blog , but it's a great read, so check it out. Anyway, I completely agree with what Sal, Coyote, Sylvia, and Brennan say in their posts - the backbone of the internet is the hyperlink, so go forth and link out to your fellow bloggers with reckless abandon. It's what makes the web, the web. 🕸️ Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views
David Bushell 1 weeks ago

End of the contact form saga

I can’t take it anymore! If you wan’t to speak to me, send an email. My contact form is out of service indefinitely. This is actually in lieu of moving my professional services to a yet to be announce limited company. But I can’t let opportunity for a dramatic blog post go to waste. Also, I’ll probably skip the contact form on my company website. Is that a bad idea? I always got more spam via the form than the publicly visible address. My contact form has been through a lot. Previous entries in the saga: I quite enjoyed the week in September when I opened a port to a self-hosted SMTP server I coded in 100 lines of TypeScript. The final iteration of my form included true end-to-end encryption. Through trial and error heuristics, I successfully eliminated all spam. (How many false positives I rejected remains unknown…) My privacy policy which was already simple is now entirely pointless. Are contact forms just outdated in general? Everyone seems to embed a Calendly widget these days. That’s not my style. I like the tiny bit of additional friction required to send an email. If someone can’t be bothered their message probably wasn’t serious. I’m not looking to maximise meaningless engagement. I look forward to moving business email to a separate domain. Biggest mistake I ever made was using for personal and business. Nothing worse than seeing an “urgent” request only to find out on Monday it didn’t matter. So long old contact form, it was fun! Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds. SMTP on the edge Email: the final form I shut the emails out I let the emails in Progressive dehancement PGP encrypted contact form

0 views
Unsung 1 weeks ago

One and one thing only

I wanted to show you a year’s worth of messages from my barber’s software, because this is what software should be. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/one-and-one-thing-only/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/one-and-one-thing-only/1.1600w.avif" type="image/avif"> No spam, no upsells, no growth hacks, no unwelcome cuteness or puzzling verbosity. It’s so curt and straight to the point it should be set in a 1970s Helvetica: The appointment’s tomorrow. Any questions?

0 views
Chris Coyier 1 weeks ago

CodePen 2.0

Noting perhaps my largest personal career accomplishment, which is launching CodePen 2.0 . Far more work, believe it or not, than the entire creation of the original CodePen. This isn’t the place to describe every detail of what we did and why we did it. If you’re interested, perhaps our Why 2.0? podcast or the What’s New? page. Instead, a couple of stories from the first week of launch. I was working on a demo with someone I’ve never met before. It started on their (classic) Pen. They needed to import some other JavaScript, so they used 3 Pens and pulled in the JavaScript from the other two into the main demo. They also needed an npm package. I forked the Pen and invited them as a co-editor, so we could both work on it together anytime. I moved the JavaScript into files on the main Pen, as that’s much easier to work with. The npm package is in the file for easy version management. We both cleaned it up to our liking. The Keyframers (David and Shaw) got back together and did a live stream on launch day. They also used the invite feature and live collaboration . They worked together for hours, and while there was a bug or two, it was nothing super major, and it went great. One of my favorite bits was that they shared the Live View of the Pen, so as they were working on it, we could play with the demo ourselves. As I was working on the emails we were going to send out about the launch, I was building them in the special language for crafting them: MJML . I went ahead and added MJML as a block to CodePen so I could just build them right in CodePen. Works great , even for weird stuff . Many more Blocks to come. I friggin love how I can make little websites and deploy them right through the Pen Editor. Like the one for our slideVars library or codepen.school . It just makes me wanna build a ton of little weird websites.

0 views
Justin Duke 1 weeks ago

Cursed knowledge

Nick pointed me towards Marcin who pointed me towards immich's list of cursed knowledge the other day, and it has already become a running joke in the Slack. Here is a baker's dozen of Buttondown's own cursed knowledge: 1 Yes, that's the joke. The Python library assigns the device family to every non-Mac desktop browser The HTML attribute only filters what the file-picker dialog shows you; drag-and-drop and clipboard paste bypass it entirely. Safari and Chrome re-serialize quoted CSS custom-property strings differently when you read them back via : Chrome keeps the single quotes, WebKit rewrites them to double quotes. Django emits a — which fails our CI — for any cache key over 250 bytes or containing a space or control character. Python's has no default timeout and will, given the opportunity, wait forever. SPF directives recursively chain DNS lookups against a hard cap of ten — exceed it and you get a , which can fail authentication for all of your mail. Outlook and Hotmail enforce mandatory TLS but serve a certificate chain rooting at DigiCert Global Root CA (G1) — a root that Ubuntu has since removed from its trust store. Django's tests whether the key exists , not whether its value is JSON . does not lock rows in the order you listed them — Postgres locks them in executor scan order, which is a wonderful way to deadlock two queries that both thought they were being careful. A postgres cannot exceed ~1MB. Stripe will send subscription update events for paused subscriptions. The Python library assigns the device family to every non-Mac desktop browser The HTML attribute only filters what the file-picker dialog shows you; drag-and-drop and clipboard paste bypass it entirely. Safari and Chrome re-serialize quoted CSS custom-property strings differently when you read them back via : Chrome keeps the single quotes, WebKit rewrites them to double quotes. Django emits a — which fails our CI — for any cache key over 250 bytes or containing a space or control character. Python's has no default timeout and will, given the opportunity, wait forever. SPF directives recursively chain DNS lookups against a hard cap of ten — exceed it and you get a , which can fail authentication for all of your mail. Outlook and Hotmail enforce mandatory TLS but serve a certificate chain rooting at DigiCert Global Root CA (G1) — a root that Ubuntu has since removed from its trust store. Django's tests whether the key exists , not whether its value is JSON . does not lock rows in the order you listed them — Postgres locks them in executor scan order, which is a wonderful way to deadlock two queries that both thought they were being careful. A postgres cannot exceed ~1MB. Stripe will send subscription update events for paused subscriptions.

0 views
Unsung 1 weeks ago

“Rather than fighting my tendency to type these two characters in my omnibar, I neutralized it.”

An interesting short story from Ernie Smith at Tedium, who ventured out to do the opposite of what we usually cover on this blog – disrespect his motor memory : Recently, I made a realization: I have been unwillingly addicted to Facebook for a long time, and it’s not even because I like Facebook. Rather, it’s because I find it an extremely easy URL to type into a modern web browser’s omnibar. This sounds crazy, but the first two letters, f and a, are on the home row, and I don’t really rely on bookmarks, but my browser’s history function to type in URLs. This creates a sort of recency bias. If I type in the same URL a lot, it’s the one that pops up the most. And so, if I’m at a browser with no clear idea of what my intent for the next page I load up, I inevitably type in “fa,” which would suck me in. […] So, what I ended up doing was creating a URL that does nothing but forward to Google News. […] The result is that whenever I type in my new Facebook URL, I go to a news aggregator, which is inevitably what I was using Facebook for anyway. A week later, and my Facebook usage has gone down considerably. There is something really interesting about creating your own URL. Smith doesn’t disclose his, but you can imagine it’s something like that just redirects to a news aggregator. If creating a new URL is too difficult, there are always other options: Wanna go to fatberg.com every time you load your computer? Or maybe faxtoy.net ? This is a good example of some of the challenges with “recent” interfaces I am such a big fan of . If you’re designing recents and you think they can turn against the user or otherwise get in their way, it’s good to offer not just a “clear recents” feature that gets rid of them all, like here in Apple’s Music… = 3x)" srcset="https://unsung.aresluna.org/_media/rather-than-fighting-my-tendency-to-type-these-two-characters-in-my-omnibar-i-neutralized-it/1-framed.1600w.avif" type="image/avif"> …but also individual clears, like here in Bluesky: = 3x)" srcset="https://unsung.aresluna.org/_media/rather-than-fighting-my-tendency-to-type-these-two-characters-in-my-omnibar-i-neutralized-it/2-framed.1600w.avif" type="image/avif"> I don’t know if this is true for all the browsers, but in Chrome and Safari, you can also clear individual suggestions this way: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/rather-than-fighting-my-tendency-to-type-these-two-characters-in-my-omnibar-i-neutralized-it/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/rather-than-fighting-my-tendency-to-type-these-two-characters-in-my-omnibar-i-neutralized-it/3.1600w.avif" type="image/avif"> Smith writes a bit more about the addictive nature of computers, but I’ll let you read on your own . #attention #keyboard

0 views

The Difference Between a Button and a Link

Of the three proposals in the Triptych Project , my multi-year odyssey to add a few small-but-powerful features to HTML, the one that generates the most questions is Button Actions . The proposal itself is very straightforward: we want to add the and attributes to the button. Button Actions are such a simple primitive that people often ask why they’re needed. The answer rests on a distinction that web users intuitively understand but rarely have to think about directly: the difference between a button and a link. I added a detailed “Buttons vs Links” section to the proposal, but I think it deserves a blog-style explanation as well, because most of the existing ones miss the mark. Links represent a destination while buttons represent an action . Functionally, this means that links let users control what context they open in, while buttons don’t. Web browsers offer countless affordances for re-contextualizing a link. Clicking or tapping the link will navigate the current page to that destination. Mouse users can middle-click the link to open it in a new tab or hover over the link to see where it goes. Context menus (right-click on desktop, long tap on mobile) have lots of link-specific options. Web users are very familiar with the features that come with links. They know how to open them, copy them, bookmark them, share them with friends, and maintain them in an inadvisable number of browser tabs. The semantics of a link—the notion that they represent an independently-navigable destination—make it possible for browsers to build all these features. The hyperlink predates the invention of the browser tab, but when browsers added tabs, websites didn’t have to do anything to support them; links represented destinations that could be re-contextualized, so browsers could simply invent a new context for them to open in. Every website instantly got upgraded with a huge new feature. Buttons have none of these features. By default, they cannot be middle-clicked, control-clicked, or hovered over for more information. Buttons don’t allow you to copy their the way you can copy the of a link. Their context menus contain no affordances for saving the action or doing it somewhere else. These are not omissions, but deliberate choices based on the button’s semantics: buttons trigger actions inside a specific browsing context ( almost always the current one ). Copying, sharing, bookmarking—these are all features for re-contextualizing the action of a link. Buttons serve a complimentary purpose because they don’t allow for any of that. A common misconception is that links are for navigating the page, while buttons are for everything else. This is incorrect on both counts. Buttons regularly perform navigations. Clicking a logout button navigates the current page to a logged-out one; clicking a “search” button navigates the current page to the query results. Both of these are navigations in the HTML standard . They change the URL, they get logged in the session history, and they load a new page. And links are often used in situations where they don’t trigger navigations. Relative links can jump around the current page; mailto links can open email clients; download links can save a file to your computer. None of these are navigations, but they are all “destinations” that can be opened, saved, and shared in customizable ways. Navigations should be represented as buttons when their action happens in a fixed context that is not available to be re-contextualized (e.g. bookmarked, shared, middle-clicked, etc.). A frequent place this comes up is with forms that let you edit something you’ve already saved, like a comment on a website. When you click “Edit”, the website shows you an editable text area with options like this: Users will easily intuit what each button does: Should “Cancel” be a link? No! Its job is to close the edit view. Not only does making this a link incorrectly communicate its purpose—visually and otherwise—but it saddles the form “control” with lots of features, like bookmarking and middle-clicking, that have incorrect behavior. There are many plausible ways these buttons could be implemented, but none of those implementations should present themselves to the user as a link. With Button Actions, this entire UX could be implemented with just HTML. The first two buttons use existing HTML features, the second two buttons are made possible by Button Actions. (I’m also taking advantage of Triptych’s DELETE support , but you could do the URL method hack without it.) Philosophically, Button Actions create a generic control that can redraw the current context with a network request. Buttons already have the ability to do this with certain limitations; this proposal removes those limitations. Practically, this allows web authors to implement state transitions by navigating to views. Those views might even already exist as standalone destinations, in which case authors can trivially re-use existing routes while representing the action correctly in the UI. This is a great pattern that HTML should encourage! Unfortunately, without Button Actions, erroneously making this button a link is the only way that we have to implement this interface without scripting. This is obviously an anti-pattern, but it’s an anti-pattern supported by major design systems, because buttons lack the ability to do basic navigation without forms. When building a website that works without JavaScript ( a requirement for UK government sites ), links are the only choice. The US Web Design System (USWDS) even contains an official affordance for it: Add to a link and it will look like a button. Making a link look like a button, however, does not make the link behave like a button. USWDS uses JavaScript to implement spacebar activation , but JavaScript can’t do anything about the litany of other behaviors that differentiate buttons from links, like context menus. Links (even those with ) will still look like links in reader mode or other custom views. That’s the fundamental consequence of violating HTML semantics—the page will be broken for some users because authors cannot possibly account for all the different ways that people interact with a web page. The web simply wouldn’t work if they had to. Navigations are the broadest tool that web authors have to control the user experience—HTML just needs to complete the ’s ability to trigger them. Doing so makes the web simpler, safer, and more accessible for all. If you’d like to support the effort, the best way is to like the Button Actions issue on GitHub and share examples of why the proposal would be valuable to you. “Save” updates the comment with whatever is in the “Save Draft” saves the content of the without publishing it “Cancel” closes the editable form “Delete” removes the comment entirely Big shoutout to The Django Software Foundation for their support of this proposal ! I am currently working on an analysis to demonstrate that Button Actions do not introduce any new XSS vulnerabilities to existing web sites. Supporting this proposal doesn’t resolve that issue, but it does demonstrate to WHATWG that web authors have this need and that it’s worth studying. This blog focuses on buttons that trigger GET requests without forms, because that’s where the overlap with links is, but buttons that trigger unsafe requests without forms are also very useful. requests are probably the most common use-case, because they usually don’t require any additional data. One interesting case for buttons that trigger or requests without a form is “likes” on social sites . HackerNews , for instance, uses links for upvotes, which is in wild violation of HTTP semantics. I understand why they do it though: it’s simpler and works without JavaScript. That’s why it’s necessary to make Button Actions not just possible, but convenient. The proposal addresses all the existing workarounds for the lack of this functionality and explains why they’re not sufficient. The big picture goal with Triptych to is to give web authors a simple and semantic way to model a full CRUD lifecycle in HTML, because that’s all the vast majority of web services need to do. All the Triptych Proposals complement each other—Button Actions are even more useful with additional methods and partial page replacement —but I try to make the case for each one in isolation, both as an anti-logrolling mechanism and because they are genuinely useful on their own.

0 views
Michael Hoffmann 1 weeks ago

How to Set Up an MCP Server for an Existing Nuxt App

A beginner-friendly, practical guide to adding an MCP server to an existing Nuxt app using the Nuxt MCP Toolkit and a mocked weather tool.

0 views
Jim Nielsen 1 weeks ago

Can the Tide of AI Investment Lift All Boats on the Web?

Jason Grigsby has a great article where he surfaces an opinion from the Safari team about how AI agents shouldn’t get special treatment: An agent acting on a user’s behalf is, in effect, assistive technology : it should operate a site as the user would, and the site should not single it out for different treatment. Jason synthesizes different discussions happening at standards levels to argue, in essence, that agents should be required to use existing technologies and solutions (APIs, semantics, etc.) rather than get their own bespoke ones. And where there are gaps in the platform, solutions should be centered around closing those gaps generally for everyone (vs. specifically for agents). Imagine that! Take the billions being invested in AI and funnel it towards improving and enhancing the existing technology agents already use and profit from. No bespoke solutions just for AI, but generalized solutions everyone can benefit from. In other words: allow the rising tide of AI investment to lift all boats in the platform because the web is for everyone. As Jason says: If we’re solving this problem for AI, perhaps we can find a solution that works for end users too. His suggestion being that maybe we should frame AI needs in the web platform the same way we do other needs in the web’s priority of constituents: user needs come before developer needs, implementor needs, spec writer needs — or even agent needs. ( UX over DX over AX .) Now for the funny part. Here’s Jason: let’s set aside for the moment the irony that AI is supposed to replace all of our jobs and become a super intelligence and at the same time we also need to add special AI training wheels for it to use the web. It’s like that person you know who prides themselves on their independence, that they don’t bend to society and culture, and that they don’t need anyone or anything — oh, and by the way, could you spot them twenty bucks? Reply via: Email · Mastodon · Bluesky

0 views
Unsung 2 weeks ago

“A vicious circle of incompatibility”

A fun 16-minute video from PortalRunner with this premise: This is an image file, containing a picture of my cat. But if I rename it to .MP4, it becomes a video file – also of my cat. If rename to .PDF, it becomes a text document containing the script for this video. It can also be a valid webpage, a .ZIP archive, or a PowerPoint presentation, all by simply changing the name. This kind of file is sometimes called a “polyglot” (although, usually that term refers to code that works in multiple programming languages). = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/a-vicious-circle-of-incompatibility/yt1-play.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/a-vicious-circle-of-incompatibility/yt1-play.1600w.avif" type="image/avif"> This kind of a file is not something that you will realistically need, but it’s a fun look into various approaches to headers and structures of file formats – something we don’t usually get to think about a lot. Buried inside the video is also an interesting digression: is the file extension just a method of delivering the file to the right application? If I rename .jpeg to .gif, and both are routed to Pixelmator, should Pixelmator do its best to detect it’s a JPEG file under the hood, or fail with a “this doesn’t look like a GIF file” message? The web has a similar challenge in the form of MIME sniffing – “MIME” is sort of the web’s equivalent of extensions, “sniffing” means detecting the file from its contents alone, ignoring everything else – and that had some security considerations, as it allowed bad actors to sneak in some malicious code under the guise of something more innocuous… basically what the video is doing for fun, but now weaponized. This is all pretty technical for this blog, but inside the Wikipedia entry for MIME sniffing is this passage that caught my attention: [MIME sniffing is still used by some browsers. However,] by making sites which do not correctly assign MIME types to content appear to work correctly in those browsers, it fails to encourage the correct labeling of material, which in turn makes content sniffing necessary for these sites to work, creating a vicious circle of incompatibility with web standards and security best practices. Decades before MIME sniffing, Jon Postel captured the essence of that line of thinking by coining Postel’s Law – “be conservative in what you send, be liberal in what you accept” – but as enticing as it is, that has challenges similar to the above quote: A flaw can become entrenched as a de facto standard. Any implementation of the protocol is required to replicate the aberrant behavior, or it is not interoperable. […] Ensuring interoperability in this environment is often referred to as aiming to be ” bug-for-bug compatible ”. While Postel’s Law was about data flowing in and out of computer systems, the premise is to me a more evergreen design question, applicable to so many other things. Feeling “liberal in what you accept” can feel helpful, but can teach users bad habits and have bigger consequences. For any project where this applies, it’s worth asking: should we go out of our way to help the user even if they mess up, or should we be more rigid and teach them to follow the rules more strictly, as it will benefit them in the future? The Command Line Interface Guidelines I linked to before had a great example of that: You can ask if they want to run the suggested command, but don’t force it on them. For example: Rather than suggesting the corrected syntax, you might be tempted to just run it for them, as if they’d typed it right in the first place. Sometimes this is the right thing to do, but not always. Firstly, invalid input doesn’t necessarily imply a simple typo—it can often mean the user has made a logical mistake, or misused a shell variable. Assuming what they meant can be dangerous, especially if the resulting action modifies state. Secondly, be aware that if you change what the user typed, they won’t learn the correct syntax. In effect, you’re ruling that the way they typed it is valid and correct, and you’re committing to supporting that indefinitely. Be intentional in making that decision, and document both syntaxes. #encoding #errors #youtube

0 views
Unsung 2 weeks ago

Chrome’s breaking and entering

I got pissed at Chrome the other day . This is not the first user-hostile thing Chrome did – off the top of my head, I remember the updater fiasco from some years ago, and the more recent auto-installation of a 4GB file – but as you’ll see, this one is squarely in my wheelhouse. The transgression: Chrome took over a shortcut on my Mac – Ctrl+G – and it used it to throw me into Chrome’s version of Gemini that I have never used or was interested in using. Moreover, it decided it’s okay for Ctrl+G to put me there even if I pressed the shortcut outside of Chrome . I was never asked by Chrome if it’s okay to do so. The way I found it’s installed it was in a very unpleasant way: I tried to use Ctrl+G in my coding editor to jump to a specific line, and I got this instead: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/1.1600w.avif" type="image/avif"> Stuff like that can make you feel like you lost your mind. I have no idea what this window is supposed to do, where did it come from, or even – initially – why it appeared. Note that it doesn’t even identify itself as either Chrome or Gemini, unless you read the scary caveat. It feels like the UI equivalent of breaking and entering. Unsurprisingly, the pop-up doesn’t confess to stealing the shortcut, or allow you to toggle it off in any way: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/2.1600w.avif" type="image/avif"> It is possible to undo that behavior, but one has to connect it to Chrome first, and then go deep into its settings – first by clicking on “AI innovations,” and then by clicking on “Gemini in Chrome” – to find it: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/3.1600w.avif" type="image/avif"> Let’s not beat around the bush: This is effectively malware behaviour. It’s bullshit. It’s cancer. It’s deeply disrespectful toward the user. It’s prioritizing hollow metrics at the expense of everything else . But I don’t want this blog to chase news of the day or feed the outrage machine, so let me try to turn my anger into something useful. Let’s start here: There are some global keyboard shortcuts that are genuinely good. A video call mute shortcut, screenshotting, “next slide” if you’re presenting in Zoom. Everything related to computer operation – volume, brightness, media transport controls – needs to be available regardless of focus or context. (In my keyboard customization essay, I introduced my own global keyboard shortcuts , for example for scanning the next page.) But, an app installing a global keyboard shortcut without user consent is bad. This can never be anything other than opt-in. At the very least, Chrome should have shown me a clear UI that said “We’re thinking Ctrl+G would be fun for you to use. You okay with that?” and a button for me to press to confirm. (Note: Ctrl+G is the shortcut for Macs. As far as I can tell, on Windows it is Alt+G.) From people’s reactions, it seems this shortcut is auto-enabled for a subset of users: perhaps people who used Gemini before, or people on a plan that happens to include it. No matter how specific or small that group is, or how useful they might find the Ctrl+G pop-up, the issue remains: I have never consented to the app doing this. I also partly blame macOS for ceding its responsibilities here. Mac’s keyboard customization features are a mess, and Mac doesn’t have a modern command repository. It’s not just that apps can register global shortcuts as they want, without the user knowing. It’s also that there is no shared inventory of them; if an app “swallows” a shortcut but does nothing noticeable with it, it can be really hard to figure out why a shortcut seemingly just stops working. (Other apps that I remember having problems with “stealing” global shortcuts, and apps that a few readers posted are: 1Password, Notion, and Perplexity. I’d be curious if you have other examples!) In light of macOS’s deficiencies, as an app, if you offer any shortcut customization – especially if you allow global shortcuts – I think it’s important to have a page that lists all of them shortcuts in one place. This is not what Chrome does, as various shortcut options are hidden on various pages in settings. Even Zoom, which is not generally known for having a great user interface, does better here: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/4.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/chromes-breaking-and-entering/4.1600w.avif" type="image/avif"> And, since we’re back to Chrome, what a fall from grace! When Chrome started in the late 2000s, it felt like a browser that had user’s interest in mind, and protected people from ill-behaving websites. Today, it’s the operating system that needs to protect us from Chrome. (Also, don’t call a tab “AI innovations.” It’s tacky as hell. The market gets to decide what’s innovative and what is not.) #attention #google #keyboard #mac os

0 views
Julia Evans 2 weeks ago

Some more things about Django I've been enjoying

Hello! I’m on a funny journey right now where I’m trying to learn how to make websites in a sort of 2010 style, where I have an SQL database and render some HTML on the backend. It’s kind of an interesting journey because it doesn’t necessarily feel “easy” to me to make websites in this way: I never learned how to do it in the 2000s or 2010s, and there’s a lot I need to learn. So here are some Django features that make building this kind of site feel more achievable than when I was trying and failing to use Go’s standard library or Flask. And I’ll talk about a couple of issues with Django I’ve run into. Previously the toolkit I felt confident with for making websites was: I really liked this frontend-heavy approach for these super simple applications but when I started thinking about making something with a lot of different pages (instead of literally just one page), I didn’t feel so excited about the options I saw that involved a lot of frontend code. So I figured I’d try the backend. Writing a backend-focused site that uses as little JS as possible feels the same to me in a way as writing a single-page JS website that does as little on the backend as possible, even though they might seem like opposites. In both cases I’m just trying to keep as much of the logic as possible in one place. Now for some thoughts about Django! I learned that I can define a “query set” class in Django with a bunch of methods with different statements I might want to use while constructing a query: Here’s how I use it in my view code once I’ve defined what all the methods mean: and here’s how I define the methods: The syntax for defining the filters isn’t my favourite, but I spend most of my time just using the methods, and it feels super readable and nice to use, and it makes me want to look into other query builder libraries in the future. In the past I thought “I know SQL, who needs a query builder?”, but this kind of structure does make it really nice to read. I found an example of someone who wrote their own small query builder in Python that I want to read later to think about whether I would enjoy using a more minimal version of this. There are a bunch of little quality of life filters available in Django templates that are super useful for generating HTML. The ones I’ve used so far are: These are all small things individually but I feel like it makes a big difference somehow to just have them available. I think my favourite template filter is : in this site sometimes we use filters like to decide what’s displayed. that will make a link to the same query string with one change, like this to link to the previous date: Or to remove the parameter: I still really love Django’s automatic database system. It’s amazing to be able to just edit a model to add a new field or whatever, and then Django automatically generates the migration. So far we have done 19 database migrations and I think there will probably be more! It makes a huge difference for me to be able to just easily change the database as my understanding of the problem changes. Django’s documentation sometimes offers the option of using class-based views and inheritance to organize the code in your views. For example I have four views that share a lot of code, and I could use inheritance to manage that by defining some kind of parent class and then having my other views inherit from it. I tried it out and I did not enjoy the experience of using inheritance to share code between views. I switched to using functions instead, sort of how this post advocates, and that was a lot more straightforward. I’ve never had a good experience using inheritance in Python and I don’t think I’ll try to use it again. But I don’t mind using inheritance to use the interfaces Django itself provides: for example if I want to define a query set I need to write something like . I don’t think too hard about it and it seems to work. (as a meta comment: I’ve been working on talking about my programming opinions by just saying “THING does not feel good to me, I prefer OTHER THING instead”. That post I linked to says that function-based views are the “right way”. I’m not very invested in whether it’s “right”, but it’s validating to know that other people feel similarly to me about inheritance) At some point the LLM scrapers discovered our site, and started sending us maybe 10 requests per second. I blocked them which is working for now, but it made me think about what the site’s capacity is. I’m used to writing Go backends where the performance situation is pretty straightforward (usually everything is just fast enough), and a Django site is very different. Some light load testing (with ( ) shows that right now we can serve about 2-3 requests per second (on a ~$10/month VM). It’s tempting for me to go down a rabbit hole where I do a bunch of profiling to figure out what’s slow and try to make it faster (there’s py-spy for that, and py-spy is great and super easy to use, and profiling is fun!) But I really don’t understand what I should expect in terms of performance from a Django site and how I should be thinking about at a higher level. Some things I haven’t figured out yet: I think one thing I’m learning about Django is that because it’s a Framework (tm), it’s easy to accidentally misconfigure it. For example, when I was thinking about why my site was slow just now, I read the django performance docs and I noticed a comment saying: Enabling the cached template loader often improves performance drastically, as it avoids compiling each template every time it needs to be rendered. When I’d done CPU profiling I’d noticed that it was spending a lot of time rendering templates! Maybe this could help me! Clicking through the link, I saw that the cached template loader was supposed to be on by default, but I’d turned it off by accident while trying to do something else. I think this “I turned off the cached template loader by default” things is an example of how I still find the django settings file to be pretty confusing and difficult. I guess I should just be careful when I go in there. After turning on template caching, it seems like the site can now pretty easily handle 12 requests per second or so without using all of the CPU. I have not carefully benchmarked the before and after but it seems like it’s made a pretty big difference. One thing that’s been surprising to me about Django performance is that I’ve always heard the advice “if you have a performance problem, check your database queries! Maybe add an index!”. But I’ve been running into a variety of performance issues (like this template caching thing) that are not because of slow queries, so instead it’s been more useful for me so far to start by running a CPU profile. And since I’m using SQLite, any slow database query problem will show up on the CPU profile anyway. Anyway I don’t want to get too far into site performance. Like I said it’s easy for me to get interested in profiling, but actually I know a lot about profiling and it’s not the most important thing for me to learn about. I might say more about what I’m enjoying (or having a hard time with!) about Django later. Trying to write some shorter blog posts recently. static site generators (like for this blog) static sites that do some fun stuff with Javascript (like this sql playground ) simple Vue.js single page apps with either a Lambda as a backend or a Go backend (like mess with dns ) translating plain text URLs into links, or line breaks into ( ) formatting dates ( ) , which takes a Python dictionary and automatically converts it to JSON and inserts it into the HTML as a tag in a safe way If I have a site that’s going to be getting occasional bursts of traffic, do I want to be able to scale up? Do I want to design the site so that more things can be cached? (and do I really have to? caches are so annoying to get right!) The django performance docs say that Jinja is faster for templating, do I want to think about switching templating systems? Those docs also say “{% block %} is faster than using {% include %}”, I wonder if it’s a big difference and if so why

0 views
matduggan.com 2 weeks ago

I added a blogroll

I realized that it might be nice if you happen to stumble on this website if I had a way to recommend other websites you might enjoy. As it turns out this is a "blogroll", a concept I have never heard of before today but whatever. It's still a good idea. I tried to add some CSS to make it easier to follow and search, but let me know if you think I missed a great site that people should check out. I'm also always on the hunt for more good stuff to read. You can find my email and social on the About page above. Also if you want to just take this Ghost theme and use it yourself feel free: https://gitlab.com/matdevdug/minimal-ghost-theme Anyway here is my new blogroll: https://matduggan.com/blogroll/

0 views
Anton Sten 3 weeks ago

The Tools I Use to Run My Business

As you may know, I don't think new tools are key to success, hard work is. That said, coming from someone who loves to try out all the new shiny things , I think it makes sense to keep a list of the tools that stuck with me. This is the updated 2026 version. My website has been through Gatsby, Framer, and Kirby over the years. It's now built on Astro and hosted on Netlify . Most changes to the site these days happen through Claude Code, and it's the setup I've been happiest with by far. For privacy-focused, simple analytics, I use Fathom *. It's one of the products where you can immediately tell it's built with love. If you want dead-simple analytics that respect your users' privacy, Fathom is for you. My newsletter is sent through beehiiv *, after trying out basically every email service that exists (Drip, Kit, Mailchimp, Substack, Buttondown, EmailOctopus, Mailerlite to name a few). I moved there in the summer of 2026 and I'm really happy with it. I use Stripe to sell my books, and Wise * for international payments. This section didn't exist in the 2024 version. Now it's where most of my working hours go. Claude is the tool I use most. Writing, thinking through client work, editing articles, and running my website through Claude Code. I keep ChatGPT around too, mostly as a second opinion. Cursor * is where I go when I want to build something myself. Wispr Flow * is dictation that actually works. I speak most of my first drafts now. Granola * takes notes in my meetings. They're good enough that I've stopped taking my own. I do all my design work in Figma . I really, really like the simplicity of working in Figma. I keep track of things to do in Things . I don't have a fancy GTD system set up, rather something far simpler. Raycast replaced Spotlight for me a long time ago. Launching apps, clipboard history, window management. It's one of those tools you forget you're using until you sit down at someone else's Mac. I'm blessed with having my own office space and the tools I use here are carefully selected. I'm currently using a Macbook Air 15" and an Apple Studio Display. It's a great monitor. One of the first bigger expenses I allowed my newly founded company to take on was this Herman Miller Aeron chair. Never regretted it. I have a HAY Desk . Mymind - It's beautifully designed, and I randomly save anything I come across to Mymind. Supercut - Video messages instead of meetings. I record my screen, share a link, and the other person watches it when it suits them. Shiori - Saves what I read and watch so I can find it again later. = this is an affiliate link, meaning we'll both get a small reward if you click the link and sign up.

1 views