Latest Posts (20 found)
Justin Duke -2 days ago

Maybe use Plain

When I wrote about Help Scout , much of my praise was appositional. They were the one tool I saw that did not aggressively shoehorn you into using them as a CRM to the detriment of the core product itself. This is still true. They launched a redesign that I personally don't love, but purely on subjective grounds. And there's still a fairly reasonable option for — and I mean this in a non-derogatory way — baby's first support system. I will call out also: if you want something even simpler, Jelly , which is an app that leans fully into the shared inbox side of things. It is less featureful than Help Scout, but with a better design and lower price point. If I was starting a new app today, this is what I would reach for first. But nowadays I use Plain . Plain will not solve all of your problems overnight. It's only a marginally more expensive product — $35 per user per month compared to Help Scout's $25 per user per month. The built-in Linear integration is worth its weight in gold if you're already using Linear, and its customer cards (the equivalent of Help Scout's sidebar widgets) are marginally more ergonomic to work with. The biggest downside that we've had thus far is reliability — less in a cosmic or existential sense and more that Plain has had a disquieting number of small-potatoes incidents over the past three to six months. My personal flowchart for what service to use in this genre is something like: But the biggest thing to do is take the tooling and gravity of support seriously as early as you can. Start with Jelly. If I need something more than that, see if anyone else on the team has specific experience that they care a lot about, because half the game here is in muscle memory rather than functionality. If not, use Plain.

0 views

Leading Without a Map

No one can deny that our industry is in a period of great change. This industry never stops, and the rate goes up and down but change is a constant. Like it or not " change calls the tune we dance to ." One of the biggest reasons people resist change, even people who joined the software business to "change the world" is when they feel it threatens their self-perception and identity. In the west our job is often the primary piece of our identity. One sees it everywhere. Your LinkedIn profile has your name first, and some sort of job title or role description second. Heck even contestants on Jeopardy are introduced as "A marketing consultant from Eyebrow, Saskatchewan ." When completing the sentence "I am a..." most people pick their job. When change is high, that self-conception can quickly feel under threat. Even in the small it can happen. Your company decides they'd be better served writing new code in Java rather than Python or Ruby, you can expect a few "Pythonistas" or "Rubyists" to push back. In their heart of hearts they may agree with the decision on its merits but they nevertheless feel that their very identity is under threat. This can also include their social group/community/tribe membership, something that humans are genetically programmed to value and protect. So it's no doubt understandable that change can bring out strange and unpredictable behaviour in people when they feel like there's risk to their identity, self concept, or tribal membership. Well, first of all, acknowledge to ourselves that we are not immune from these phenomena either. Presumably most of us started out as software developers ourselves and when we started managing the people who did the job, it was the job we used to do so we got it. Over time, that's drifted. New frameworks and paradigms have emerged, new 'best' practices replaced the old 'best' practices and we became less intimately familiar with the day-to-day things our people were doing. This is uncomfortable at times, but we adapt. We learn what we can to stay involved at the right level and to coach and guide the people we're responsible for. Now, the game is changing in a much more fundamental and profound way. And it's happening fast. I don't know what the job of software developer is going to look like in a year from now (or even 6 months for that matter) and, frankly, neither does anyone else. This makes the job of manager much much harder. Your people are used to you having at least some concept of a map and sharing it with them and you don't have one. Everyone's figuring it out together. A good friend and former colleague once described an aspect of leadership as "smiling while the sky is falling." I'm not sure if he came up with it or if I should attribute it to someone else but I heard it from him first. My point here isn't that the sky is falling but rather, when your people are worried, you need to appear steadfast or you make the problem worse. You don't owe them certainty , because that would be dishonest and they'll clock your dishonesty whether they admit it or not. But just like in incident response, panic serves no one . You owe them calm reassurance that you're going to navigate this new world together and that you've got their best-interests at heart. You do this even though you might be feeling the same threat to your identity. You manage engineers but they're becoming some kind of new thing; bot-wranglers. Some of your other responsibilities are being offloaded to LLMs and everyone's role is going to keep changing until things inevitably settle down again (relatively speaking). With no playbook, we need some kind of framework for decision making. This is where we can fall back to 'first principles'. For me these are the things I hold important. Really, the basics: It sounds simple, and really, it is. Taking care of the people right now means recognizing that they're feeling that identity risk. The worst thing you can do is try to talk them out of it or convince them they're not feeling what they're feeling. Acknowledge that things are changing. Maintain ' esprit de corps ' as best you can. Draw on your experience navigating big changes before. If you've been around this industry for any amount of time, you've been through some big paradigm shifts and come out the other side. Tell some stories, but don't make it all about you. The business and customer angles come down to maintaining consistent principles around what software gets shipped to customers. I personally have the pleasing-to-nobody opinion that LLM coding tools are useful but not risk-free. Surely you have some skeptics in your midst who feel the same. Don't dismiss them either. Security, quality, maintainability, incident response, and the work-life balance of your people are still the responsibility of the humans running the company. That's the job right now, however the machinery of it changes. Keep taking care of your people and customers, like you always have. You already know how. " Statue of Captain George Vancouver, anchors and the Custom House, King's Lynn " by ell brown is licensed under CC BY 2.0 . Like this? Please feel free to share it on your favourite social media or link site! Share it with friends! Hit subscribe to get new posts delivered to your inbox automatically. Feedback? Get in touch ! Doing my best to take care of the people. Doing what the business needs most at the given moment. Providing value to customers.

0 views

Web font choice and loading strategy

When I rebuilt my website I took great care to optimise fonts for both performance and aesthetics. Fonts account for around 50% of my website (bytes downloaded on an empty cache). I designed and set a performance budget around my font usage. I use three distinct font families and three different methods to load them. Web fonts are usually defined by the CSS rule. The property allows us some control over how fonts are loaded. The value has become somewhat of a best practice — at least the most common default. The CSS spec says: Gives the font face an extremely small block period (100ms or less is recommended in most cases) and an infinite swap period . In other words, the browser draws the text immediately with a fallback if the font face isn’t loaded, but swaps the font face in as soon as it loads. CSS Fonts Module Level 4 - W3C That small “block period”, if implemented by the browser, renders an invisible font temporarily to minimise FOUC . Personally I default to and don’t change unless there are noticeable or measurable issues. Most of the time you’ll use swap. If you don’t know which option to use, go with swap. It allows you to use custom fonts and tip your hand to accessibility. font-display for the Masses - Jeremy Wagner Google Fonts’ default to which has performance gains. In effect, this makes the font files themselves asynchronous—the browser immediately displays our fallback text before swapping to the web font whenever it arrives. This means we’re not going to leave users looking at any invisible text (FOIT), which makes for both a faster and more pleasant experience. Speed Up Google Fonts - Harry Roberts Harry further notes that a suitable fallback is important, as I’ll discover below. My three fonts in order of importance are: Ahkio for headings. Its soft brush stroke style has a unique hand-drawn quality that remains open and legible. As of writing, I load three Ahkio weights at a combined 150 KB. That is outright greed! Ahkio is core to my brand so it takes priority in my performance budget (and financial budget, for that matter!) Testing revealed the 100ms † block period was not enough to avoid FOUC, despite optimisation techniques like preload . Ahkio’s design is more condensed so any fallback can wrap headings over additional lines. This adds significant layout shift. † Chrome blog mention a zero second block period . Firefox has a config preference default of 100ms. My solution was to use instead of which extends the block period from a recommended 0–100ms up to a much longer 3000ms. Gives the font face a short block period (3s is recommended in most cases) and an infinite swap period . In other words, the browser draws “invisible” text at first if it’s not loaded, but swaps the font face in as soon as it loads. CSS Fonts Module Level 4 - W3C This change was enough to avoid ugly FOUC under most conditions. Worst case scenario is three seconds of invisible headings. With my website’s core web vitals a “slow 4G” network can beat that by half. For my audience an extended block period is an acceptable trade-off. Hosting on an edge CDN with good cache headers helps minimised the cost. Update: Richard Rutter suggested which gives more fallback control than I knew. I shall experiment and report back! Atkinson Hyperlegible Next for body copy. It’s classed as a grotesque sans-serif with interesting quirks such as a serif on the lowercase ‘i’. I chose this font for both its accessible design and technical implementation as a variable font . One file at 78 KB provides both weight and italic variable axes. This allows me to give links a subtle weight boost. For italics I just go full-lean. I currently load Atkinson Hyperlegible with out of habit but I’m strongly considering why I don’t use . Gives the font face an extremely small block period (100ms or less is recommended in most cases) and a short swap period (3s is recommended in most cases). In other words, the font face is rendered with a fallback at first if it’s not loaded, but it’s swapped in as soon as it loads. However, if too much time passes, the fallback will be used for the rest of the page’s lifetime instead. CSS Fonts Module Level 4 - W3C The browser can give up and presumably stop downloading the font. The spec actually says that and “[must/should] only be used for small pieces of text.” Although it notes that most browsers implement the default with similar strategies to . 0xProto for code snippets. If my use of Ahkio was greedy, this is gluttonous! A default would be acceptable. My justification is that controlling presentation of code on a web development site is reasonable. 0xProto is designed for legibility with a personality that compliments my design. I don’t specify 0xProto with the CSS rule. Instead I use the JavaScript font loading API to conditionally load when a element is present. Note the name change because some browsers aren’t happy with a numeric first character. Not shown is the event wrapper around this code. I also load the script with both and attributes. This tells the browser the script is non-critical and avoids render blocking. I could probably defer loading even later without readers noticing the font pop in. Update: for clarity, browsers will conditionally load but JavaScript can purposefully delay the loading further to avoid fighting for bandwidth. When JavaScript is not available the system default is fine. There we have it, three fonts, three strategies, and a few open questions and decisions to make. Those may be answered when CrUX data catches up. My new website is a little chunkier than before but its well within reasonable limits. I’ll monitor performance and keep turning the dials. Web performance is about priorities . In isolation it’s impossible to say exactly how an individual asset should be loaded. There are upper limits, of course. How do you load a one megabyte font? You don’t. Unless you’re a font studio providing a complete type specimen. But even then you could split the font and progressive load different unicode ranges. I wonder if anyone does that? Anyway I’m rambling now, bye. Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

0 views

A chat with Byron Cook on automated reasoning and trust in AI systems

Over the past decade, Byron's team has proven the correctness of our authorization engine, our cryptographic implementations, and our virtualization layer. Now they're taking those same techniques and applying them to agentic systems.

0 views
(think) Today

Supercharging Claude Code with the Right (CLI) Tools

I’ve been using Claude Code quite a bit lately, and I got curious – what if I asked it directly which tools would make it more productive? Not the usual suspects like , or , but tools it wishes it had access to, tools that would genuinely extend its capabilities. So I did exactly that. I asked Claude Code: “What are the most valuable CLI tools I could install for you, outside of the ones you already have?” The answer was surprisingly thoughtful and insightful, so I figured I’d share it here along with my own commentary. Here are 10 tools, ranked by how useful they’d be for an AI coding assistant. Note: I write all my blog posts old-school, but this time around I took the liberty to just extend with my comments the output generated by Claude Code. Note also that the post includes some installation instructions that are macOS-specific. That’s what I got from Claude on my local machine (a Mac mini), and I felt it didn’t make much sense to tweak them given how many combinations of operating systems and package managers exist. This was Claude’s number one pick, and I can see why. ast-grep does structural code search and refactoring using AST patterns. Instead of fumbling with regex to find “all calls to function X with 3 arguments”, you write patterns that look like actual code: This is the kind of thing where regex is fragile and error-prone, but AST matching just works. Supports 20+ languages via tree-sitter . A structural diff tool that understands syntax. difftastic compares files by AST nodes rather than lines, so it won’t flag whitespace changes or reformatting as meaningful diffs. This makes reviewing AI-generated changes much clearer – and let’s be honest, reviewing changes is half the job when working with an AI assistant. AI assistants generate a lot of shell commands, and shell scripting is notoriously full of pitfalls (unquoted variables, vs. , POSIX compatibility…). ShellCheck catches these before they blow up. Given that shell bugs can be destructive (e.g., expanding to ), having a safety net here is valuable. A modern replacement with sane regex syntax – no more escaping nightmares. Uses standard PCRE-style regex and has a string-literal mode ( ) for replacing code strings full of metacharacters. Simple, but it eliminates a whole class of errors when generating substitution commands. Sloc Cloc and Code – a fast code counter that gives you an instant overview of a codebase: languages, lines of code, complexity estimates. Understanding the shape of a project before diving in is genuinely useful context for an AI assistant, and this is hard to replicate by manually scanning files. Note: I was under the impression that cloc is a better tool, but perhaps I was mistaken. 1 for YAML (and JSON, TOML, XML). Modern projects are drowning in YAML – GitHub Actions workflows, Kubernetes manifests, Docker Compose files. yq can programmatically query and update YAML while preserving comments and formatting, which is much more reliable than text-based editing that can break indentation. Structural search and replace that works across languages without needing a full parser. Complements ast-grep for simpler pattern matching – it understands delimiters (braces, parens, quotes) but doesn’t need tree-sitter grammar support. Great for quick refactoring across less common languages or config files. Note: I was happy to see that was written in OCaml, but when I installed it I got a warning that the project was deprecated and doesn’t support OCaml 5, so I’m not sure about its future. A command-line benchmarking tool that runs commands multiple times and gives you proper statistical analysis. When you ask an AI to optimize something, it’s nice to have real numbers. The flag produces results ready for a PR description. A file watcher that executes commands when files change. Useful for setting up persistent feedback loops – rerun tests on save, rebuild docs when markdown changes, restart a dev server after config edits. One command instead of cobbling together something with and shell scripts. A syntax-highlighting pager for and friends. Provides word-level diff highlighting, so when only a variable name changes in a long line, you see exactly that. Mostly benefits the human reviewing the AI’s work, but that’s arguably where it matters most. If you only install one tool from this list, make it . It’s the biggest capability gap – an AI assistant limited to regex-based search and replace is like a carpenter limited to a hand saw. Everything else is nice to have, but structural code understanding is a genuine superpower. You can install everything at once if you’re feeling adventurous: I’m not ashamed to admit that I had never heard of some of the tools (e.g. , and ), and I had only one of them installed ( ). 2 It’s never too late to learn something new! By the way, keep in mind that depending on the programming languages that you’re using there are other language specific tools that you can benefit from, so make sure to ask your favorite AI coding tool about those. That’s all I have for you today. Keep hacking! I asked Claude about this as well and it told me that it prefers because it’s written in Go (as opposed to Perl) and therefore it’s much faster than .  ↩ Of course, I didn’t really have it installed - I only thought I did, otherwise Claude wouldn’t have suggested it. (I switch between computers and my setup on all of them is not exactly the same)  ↩ I asked Claude about this as well and it told me that it prefers because it’s written in Go (as opposed to Perl) and therefore it’s much faster than .  ↩ Of course, I didn’t really have it installed - I only thought I did, otherwise Claude wouldn’t have suggested it. (I switch between computers and my setup on all of them is not exactly the same)  ↩

0 views

Two new Showboat tools: Chartroom and datasette-showboat

I introduced Showboat a week ago - my CLI tool that helps coding agents create Markdown documents that demonstrate the code that they have created. I've been finding new ways to use it on a daily basis, and I've just released two new tools to help get the best out of the Showboat pattern. Chartroom is a CLI charting tool that works well with Showboat, and datasette-showboat lets Showboat's new remote publishing feature incrementally push documents to a Datasette instance. I normally use Showboat in Claude Code for web (see note from this morning ). I've used it in several different projects in the past few days, each of them with a prompt that looks something like this: Here's the resulting document . Just telling Claude Code to run is enough for it to learn how to use the tool - the help text is designed to work as a sort of ad-hoc Skill document. The one catch with this approach is that I can't see the new Showboat document until it's finished. I have to wait for Claude to commit the document plus embedded screenshots and push that to a branch in my GitHub repo - then I can view it through the GitHub interface. For a while I've been thinking it would be neat to have a remote web server of my own which Claude instances can submit updates to while they are working. Then this morning I realized Showboat might be the ideal mechanism to set that up... Showboat v0.6.0 adds a new "remote" feature. It's almost invisible to users of the tool itself, instead being configured by an environment variable. Set a variable like this: And every time you run a or or or command the resulting document fragments will be POSTed to that API endpoint, in addition to the Showboat Markdown file itself being updated. There are full details in the Showboat README - it's a very simple API format, using regular POST form variables or a multipart form upload for the image attached to . It's simple enough to build a webapp to receive these updates from Showboat, but I needed one that I could easily deploy and would work well with the rest of my personal ecosystem. So I had Claude Code write me a Datasette plugin that could act as a Showboat remote endpoint. I actually had this building at the same time as the Showboat remote feature, a neat example of running parallel agents . datasette-showboat is a Datasette plugin that adds a endpoint to Datasette for viewing documents and a endpoint for receiving updates from Showboat. Here's a very quick way to try it out: Click on the sign in as root link that shows up in the console, then navigate to http://127.0.0.1:8001/-/showboat to see the interface. Now set your environment variable to point to this instance: And run Showboat like this: Refresh that page and you should see this: Click through to the document, then start Claude Code or Codex or your agent of choice and prompt: The command assigns a UUID and title and sends those up to Datasette. The best part of this is that it works in Claude Code for web. Run the plugin on a server somewhere (an exercise left up to the reader - I use Fly.io to host mine) and set that environment variable in your Claude environment, then any time you tell it to use Showboat the document it creates will be transmitted to your server and viewable in real time. I built Rodney , a CLI browser automation tool, specifically to work with Showboat. It makes it easy to have a Showboat document load up web pages, interact with them via clicks or injected JavaScript and captures screenshots to embed in the Showboat document and show the effects. This is wildly useful for hacking on web interfaces using Claude Code for web, especially when coupled with the new remote publishing feature. I only got this stuff working this morning and I've already had several sessions where Claude Code has published screenshots of its work in progress, which I've then been able to provide feedback on directly in the Claude session while it's still working. A few days ago I had another idea for a way to extend the Showboat ecosystem: what if Showboat documents could easily include charts? I sometimes fire up Claude Code for data analysis tasks, often telling it to download a SQLite database and then run queries against it to figure out interesting things from the data. With a simple CLI tool that produced PNG images I could have Claude use Showboat to build a document with embedded charts to help illustrate its findings. Chartroom is exactly that. It's effectively a thin wrapper around the excellent matplotlib Python library, designed to be used by coding agents to create charts that can be embedded in Showboat documents. Here's how to render a simple bar chart: It can also do line charts, bar charts, scatter charts, and histograms - as seen in this demo document that was built using Showboat. Chartroom can also generate alt text. If you add to the above it will output the alt text for the chart instead of the image: Or you can use or to get the image tag with alt text directly: I added support for Markdown images with alt text to Showboat in v0.5.0 , to complement this feature of Chartroom. Finally, Chartroom has support for different matplotlib styles . I had Claude build a Showboat document to demonstrate these all in one place - you can see that at demo/styles.md . I started the Chartroom repository with my click-app cookiecutter template, then told a fresh Claude Code for web session: We are building a Python CLI tool which uses matplotlib to generate a PNG image containing a chart. It will have multiple sub commands for different chart types, controlled by command line options. Everything you need to know to use it will be available in the single "chartroom --help" output. It will accept data from files or standard input as CSV or TSV or JSON, similar to how sqlite-utils accepts data - clone simonw/sqlite-utils to /tmp for reference there. Clone matplotlib/matplotlib for reference as well It will also accept data from --sql path/to/sqlite.db "select ..." which runs in read-only mode Start by asking clarifying questions - do not use the ask user tool though it is broken - and generate a spec for me to approve Once approved proceed using red/green TDD running tests with "uv run pytest" Also while building maintain a demo/README.md document using the "uvx showboat --help" tool - each time you get a new chart type working commit the tests, implementation, root level README update and a new version of that demo/README.md document with an inline image demo of the new chart type (which should be a UUID image filename managed by the showboat image command and should be stored in the demo/ folder Make sure "uv build" runs cleanly without complaining about extra directories but also ensure dist/ and uv.lock are in gitignore This got most of the work done. You can see the rest in the PRs that followed. The Showboat family of tools now consists of Showboat itself, Rodney for browser automation, Chartroom for charting and datasette-showboat for streaming remote Showboat documents to Datasette. I'm enjoying how these tools can operate together based on a very loose set of conventions. If a tool can output a path to an image Showboat can include that image in a document. Any tool that can output text can be used with Showboat. I'll almost certainly be building more tools that fit this pattern. They're very quick to knock out! The environment variable mechanism for Showboat's remote streaming is a fun hack too - so far I'm just using it to stream documents somewhere else, but it's effectively a webhook extension mechanism that could likely be used for all sorts of things I haven't thought of yet. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options . Showboat remote publishing datasette-showboat How I built Chartroom The burgeoning Showboat ecosystem

0 views

LLM-generated skills work, if you generate them afterwards

LLM “skills” are a short explanatory prompt for a particular task, typically bundled with helper scripts. A recent paper showed that while skills are useful to LLMs, LLM-authored skills are not. From the abstract: Self-generated skills provide no benefit on average, showing that models cannot reliably author the procedural knowledge they benefit from consuming For the moment, I don’t really want to dive into the paper. I just want to note that the way the paper uses LLMs to generate skills is bad, and you shouldn’t do this. Here’s how the paper prompts a LLM to produce skills: Before attempting to solve this task, please follow these steps: 1. Analyze the task requirements and identify what domain knowledge, APIs, or techniques are needed. 2. Write 1–5 modular skill documents that would help solve this task. Each skill should: focus on a specific tool, library, API, or technique; include installation/setup instructions if applicable; provide code examples and usage patterns; be reusable for similar tasks. 3. Save each skill as a markdown file in the environment/skills/ directory with a descriptive name. 4. Then solve the task using the skills you created as reference The key idea here is that they’re asking the LLM to produce a skill before it starts on the task. It’s essentially a strange version of the “make a plan first” or “think step by step” prompting strategy. I’m not at all surprised that this doesn’t help, because current reasoning models already think carefully about the task before they begin. What should you do instead? You should ask the LLM to write up a skill after it’s completed the task . Obviously this isn’t useful for truly one-off tasks. But few tasks are truly one-off. For instance, I’ve recently been playing around with SAEs and trying to clamp features in open-source models, a la Golden Gate Claude . It took a while for Codex to get this right. Here are some things it had to figure out: Once I was able (with Codex’s help) to clamp an 8B model and force it to obsess about a subject 1 , I then asked Codex to summarize the process into an agent skill 2 . That worked great! I was able to spin up a brand-new Codex instance with that skill and immediately get clamping working on a different 8B model. But if I’d asked Codex to write the skill at the start, it would have baked in all of its incorrect assumptions (like extracting from the final layernorm), and the skill wouldn’t have helped at all. In other words, the purpose of LLM-generated skills is to get it to distil the knowledge it’s gained by iterating on the problem for millions of tokens, not to distil the knowledge it already has from its training data. You can get a LLM to generate skills for you, so long as you do it after the LLM has already solved the problem the hard way . If you’re interested, it was “going to the movies”. I’ve pushed it up here . I’m sure you could do much better for a feature-extraction skill, this was just my zero-effort Codex-only attempt. Extracting features from the final layernorm is too late - you may as well just boost individual logits during sampling You have to extract from about halfway through the model layers to get features that can be usefully clamped Training a SAE on ~10k activations is two OOMs too few to get useful features. You need to train until features account for >50% of variance If you’re interested, it was “going to the movies”. ↩ I’ve pushed it up here . I’m sure you could do much better for a feature-extraction skill, this was just my zero-effort Codex-only attempt. ↩

0 views

Outgrowing Django admin

For a bit of dessert work this week, I'm working on a full-fledged attempt at replacing the majority of our stock Django admin usage with something purposeful. I say majority and not totality because even though I am an unreasonable person, I am not that unreasonable. We have over a hundred Django models, and the idea of trying to rip and replace each and every one of them — or worse yet, to design some sort of DSL by which we do that — is too quixotic even for me. The vast majority of our admin usage coalesces around three main models, and they're the ones you might guess: the user/newsletter model, the email model, and the subscriber model. My hope is that building out a markedly superior interface for interacting with these three things and sacrificing the long tail still nets out for a much happier time for myself and the support staff. Django admin is a source of both much convenience as much frustration: the abstractions make it powerful and cheap when you're first scaling, but the bill for those abstractions come due in difficult and intractable ways. When I talk with other Django developers, they divide cleanly into one of two camps: either "what are you talking about, Django admin is perfect as-is" or "oh my God, I can't believe we didn't migrate off of it sooner." Ever the annoying centrist, I find myself agreeing with both camps: Let's set aside the visual design of the admin for a second, because arguing about visual design is not compelling prose. To me, the core issue with Django's admin interface, once you get more mature, is the fact that it's a very simple request-response lifecycle. Django pulls all the data, state, and information you might need and throws it up to a massive behemoth view for you to digest and interact with. It is by definition atomic: you are looking at a specific model, and the only way to bring in other models to the detail view is by futzing around with inlines and formsets. The classic thing that almost any Django developer at scale has run into is the N+1 problem — but not even necessarily the one you're thinking about. Take a fairly standard admin class: If you've got an email admin object and one of the fields on the is a — because you want to be able to change and see which user wrote a given email — Django by default will serialize every single possible user into a nice tag for you. Even if this doesn't incur a literal N+1, you're asking the backend to generate a select with thousands (or more) options; the serialization overhead alone will timeout your request. And so the answer is, nowadays, to use or , which pulls in a jQuery 1.9 package 1 Yes, in 2026. No, I don't want to talk about it. to call an Ajax endpoint instead: This is the kind of patch that feels like a microcosm of the whole problem: technically correct, ergonomically awkward, and aesthetically offensive. But the deeper issue is composability rather than performance. A well-defined data model has relationships that spread in every direction. A subscriber has Stripe subscriptions and Stripe charges. It has foreign keys onto email events and external events. When you're debugging an issue reported by a subscriber, you want to see all of these things in one place, interleaved and sorted chronologically. Django admin's answer to this is inlines: This works — until it doesn't. You start to run into pagination issues; you can't interleave those components with one another because they're rendered as separate, agnostic blocks; you can't easily filter or search within a single inline. You could create a helper method on the subscriber class to sort all related events and present them as a single list, but you once again run into the non-trivial problem of this being part of a fixed request-response lifecycle. And that kind of serialized lookup can get really expensive: You can do more bits of cleverness — parallelizing lookups, caching aggressively, using and everywhere — but now you're fighting the framework rather than using it. The whole point of Django admin was to not build this stuff from scratch, and yet here you are, building bespoke rendering logic inside callbacks. I still love Django admin. On the next Django project I start, I will not create a bespoke thing from day one but instead rely on my trusty, outdated friend until it's no longer bearable. But what grinds my gears is the fact that, as far as I can tell, every serious Django company has this problem and has had to solve it from scratch. There's no blessed graduation path, whether in the framework itself or the broader ecosystem. I think that's one of the big drawbacks of Django relative to its peer frameworks. As strong and amazing as its community is, it's missing a part of the flywheel from more mature deployments upstreaming their findings and discoveries back into the zeitgeist. Django admin is an amazing asset; I am excited to be, if not rid of it, to be seeing much less of it in the future.

0 views

A new interview

Build Products for Yourself Low Costs, Small Company, Enough Customers Your Only Competition Is Your Costs How 37signals Stays Lean Rewriting Basecamp & Fighting Software Bloat Why "Enough" Beats Growth Product People vs. Business Shells The "So What?" Mindset Staying Close to Customers The Reward for Good Work Is More Work Six-Week Horizons & Compounding Decisions Anti-Fragile Business With Tiny Units Galápagos Product Design Radical Authenticity Over Marketing Tricks Rick Rubin & Intuition-Driven Building Lightning in a Bottle & Knowing When to Stop Defining Success: Pride in the Work Independence Through Profitability When Tech Adds Friction Instead of Value Ruthless Editing & What Never Changes Longevity as the Moat Building by Intuition

0 views

AI is destroying Open Source, and it's not even good yet

Over the weekend Ars Technica retracted an article because the AI a writer used hallucinated quotes from an open source library maintainer. The irony here is the maintainer in question, Scott Shambaugh, was harassed by someone's AI agent over not merging it's AI slop code. It's likely the bot was running through someone's local 'agentic AI' instance (likely using OpenClaw). The guy who built OpenClaw was just hired by OpenAI to "work on bringing agents to everyone." You'll have to forgive me if I'm not enthusastic about that.

0 views
ava's blog Yesterday

the tech-enabled surveillance of children

Every now and then, I'll be exposed to a world I have otherwise nothing to do with: Child surveillance. What I see is infuriating. Not only are children nowadays pressured by their parents to turn location services on their devices on, but the parents also set up notifications for when the child arrives and leaves a place and alerts for when they stray from the path. They also get weekly, if not daily updates about what their child did at school via an app or a message by the teacher directly. This is nuts! This is not normal. This is not how I grew up and this is not how those parents have grown up either. They know it is absolutely possible to do without, just like it has always been pre-2015, but they choose this. Parents' paranoia is allowed to completely overrule the child's own right to privacy, completely unchecked. Emotions run high with anything child-related, so anything goes that could potentially even help the safety of a child a little . The trade-offs are ignored. A newsletter I subscribe to (Dense Discovery) has a section advertising apps and services, and in a recent one, I was shocked to see that they would advertise what's probably the worst child surveillance tech I have seen in a while: "Bark is a parental control system that uses AI to scan texts, social media, images and videos across 30+ apps. It offers an app for existing devices (iPhone & Android) but also, it seems, custom hardware. The goal is to alert parents of potential dangers like bullying, self-harm content or predatory behaviour. It outsources parental vigilance to an algorithm, which is either reassuring or deeply unsettling depending on your stance on digital surveillance and trust. (Looks like it’s currently only available in the US, South Africa and Australia.) " This isn't quirky or an issue to be neutral about; this is completely dystopian, and I'd expect more people to be deeply uncomfortable with this shit and resisting it, child or not. What exactly is "reassuring" about any of this? You are way too comfortable making money off of advertising the complete dehumanization of children. You are treating them worse than prisoners , in ways you would never ever accept, in ways that wasn't even possible yet when you were a child! You know what also counts as "child protection"? Protecting their human rights . "Everyone has the right to respect for his or her private and family life, home and communications." "1. Everyone has the right to the protection of personal data concerning him or her." 2. Such data must be processed fairly for specified purposes and on the basis of the consent of the person concerned or some other legitimate basis laid down by law. Everyone has the right of access to data which has been collected concerning him or her, and the right to have it rectified. in the Charter of Fundamental Rights of the European Union, Article 7 and 8. " 1. Everyone has the right to respect for his private and family life, his home and his correspondence. in the European Convention on Human Rights, Article 8. " No one shall be subjected to arbitrary or unlawful interference with his privacy, family, home or correspondence, nor to unlawful attacks on his honour and reputation. Everyone has the right to the protection of the law against such interference or attacks. " in the International Covenant on Civil and Political Rights, Article 17. And very similarly: " No one shall be subjected to arbitrary interference with their privacy, family, home or correspondence, nor to attacks upon his honor and reputation. Everyone has the right to the protection of the law against such interference or attacks. " in the Universal Declaration of Human Rights, Article 12. These are not exclusively about protecting people from the state, but having privacy in general. There are also the constitutional rights, whose wording depends on where you live. It is likely not mentioned explicitly in there, but inferred. In Germany, for example, the right to informational self-determination (control over your data + privacy) is inferred from the general right of personality and privacy from Article 2(1) in connection to Article 1(1) Grundgesetz (GG). "(1) Human dignity shall be inviolable. To respect and protect it shall be the duty of all state authority." "(1) Every person shall have the right to free development of his personality insofar as he does not violate the rights of others or offend against the constitutional order or the moral law." People do not just begin to be people with rights when they reach adulthood. We should act accordingly. Reply via email Published 16 Feb, 2026 You don't show your child you trust them, so why should they trust you? You model complete distrust and that they are suspicious by default. They have no space where they can just explore how to be and make mistakes or act out without being seen and immediately reported on. It's not safe to test boundaries or make mistakes, because instead of getting to make that mistake and dealing with the fallout later (or it never coming out), their transgressions are immediately recorded, noticed, and punished. Abusive parents have even more pathways to abuse, control, and isolate. Instead of trying to make abusers happy trying to live your life and jumping through hoops, it's easier to just give in and stay home and do what you're told. You're completely normalizing state surveillance and companies snooping on us and present it as a good thing. The fear of recordings and repression makes them obedient in advance, altering normal development. They are much more likely to just act in ways that their parents want them to instead of finding their own selves and path. This is especially bad for queer children. You are raising a terrific liar, and forcing your child to download scummy circumvention methods onto their devices.

0 views
matduggan.com Yesterday

I Sold Out for $20 a Month and All I Got Was This Perfectly Generated Terraform

Until recently the LLM tools I’ve tried have been, to be frank, worthless. Copilot was best at writing extremely verbose comments. Gemini would turn a 200 line script into a 700 line collection of gibberish. It was easy for me to, more or less, ignore LLMs for being the same over-hyped nonsense as the Metaverse and NFTs. This is great for me because I understand that LLMs represent a massive shift in power from an already weakened worker class to an increasingly monarch-level wealthy class. By stealing all human knowledge and paying nothing for it, then selling the output of that knowledge, LLMs are an impossibly unethical tool. So if the energy wasting tool of the tech executive class is also a terrible tool, easy choice. Like boycotting Tesla for being owned by an evil person and also being crappy overpriced cars, or not shopping at Hobby Lobby and just buying directly from their Chinese suppliers, the best boycotts are ones where you aren’t really losing much. Google can continue to choke out independent websites with their AI results that aren’t very good and I get to feel superior doing what I was going to do anyway by not using Google search. This logic was all super straight forward right up until I tried Claude Code. Then it all got much more complicated. Let’s just get this out of the way right off the bat. I didn't want to like Claude Code. I got a subscription with the purpose of writing a review on it where I would find that it was just as terrible as Gemini and Copilot. Except that's not what happened. Instead it was like discovering the 2AM kebab place might actually make the best pizza in town. I kept asking Claude to do annoying tasks where it was easy for me to tell if it had made a mistake and it kept doing them correctly. It felt impossible but the proof was right in front of me. I’ve written tens of thousands of lines of Terraform in my life. It is a miserable chore to endlessly flip back and forth between the provider documentation and Vim, adding all the required parameters. I don’t learn anything by doing it, it’s just a grind I have to push through to get back to the meaningful work. The amount of time I have wasted on this precious time on Earth importing all of a companies DNS records into Terraform, then taking the autogenerated names and organizing them so that they make sense for the business is difficult to express. It's like if the only way I knew how to make a hamburger bun was to carefully put every sesame seed by hand on the top only to stumble upon an 8 pack of buns for $4 at the grocery store after years of using tiny tweezers to put the seeds in exactly the right spot. I feel the same way about writing robust READMEs, k8s YAML and reorganizing the file structure of projects. Setting up more GitHub Actions is as much fun as doing my taxes. If I never had to write another regex for the rest of my life, that would be a better life by every conceivable measure. These are tasks that sap my enthusiasm for this type of work, not feed it. I’m not sad to offload them and switch to mostly reviewing its PRs. But the tool being useful doesn’t remove what’s bad about it. This is where a lot of pro-LLM people start to delude themselves. In no particular order are the arguments I keep seeing about LLMs from people who want to keep using them for why their use is fine. This is the most common one I see and the worst. It can be condensed down to “because most things on the internet originally existed to find pornography and/or pirate movies, stealing all content on the internet is actually fine because programmers don’t care about copyright”. You also can’t have it both ways. OpenAI can’t decide to enforce NDAs and trademarks and then also declare law is meaningless. If I don’t get to launch a webmail service named Gmail+ then Google doesn’t get to steal all the books in human existence. The argument basically boils down to: because we all pirated music in 2004, intellectual property is a fiction when it stands in the way of technology. By this logic I shoplifted a Snickers bar when I was 12 so property rights don't exist and I should be allowed to live in your house. I have an internet friend I met years ago playing EVE Online that is a brutally pragmatic person. To someone like him, code craftsmanship is a joke. For those of you who are unaware, EVE Online is the spaceship videogame where sociopaths spend months plotting against each other. His approach to development is 80% refining requirements and getting feedback. He doesn’t care at all about DRY, he uses Node because then he can focus on just JavaScript, he doesn’t invest a second into optimization until the application hits a hard wall that absolutely requires it. His biggest source of clients? Creating fast full stacks because internal teams are missing deadlines. And he is booked up for at least 12 months out all the time because he hits deadlines. When he started freelancing I thought he was crazy. Who was going to hire this band of Eastern European programmers who chain smoke during calls and whose motto is basically "we never miss a deadline". As it turns out, a lot of people. Why doesn't he care? Why doesn't he care about these things? He believes that programmers fundamentally don't understand the business they are in. "Code is perishable" is something he says a lot and he means it. Most of the things we all associate with quality (full test coverage, dependency management, etc) are programmers not understanding the rate of churn a project undergoes over its lifespan. The job of a programmer, according to him, is delivering features that people will use. How pleasant and well-organized that code is to work with is not really a thing that matters in the long term. He doesn't see LLM-generated code as a problem because he's not building software with a vision that it will still be used in 10 years. Most of the stuff typically associated with quality he, more or less, throws in the trash. He built a pretty large stack for a automotive company and my jaw must have hit the table when he revealed they're deploying m6g.4xlarge for a NodeJS full-stack application. "That seems large to me for that type of application" was my response. He was like "yeah but all I care about are whether the user metrics show high success rate and high performance for the clients". It's $7000 a year for the servers, with two behind a load balancer. That's absolutely nothing when compared with the costs of what having a team of engineers tune it would cost and it means he can run laps around the internal teams who are, basically, his greatest competition. To be clear, he is very technically competent. He simply rejects a lot of the conventional wisdom out there about what one has to do in order to make stuff. He focuses on features, then securing endpoints and more or less gives up on the rest of it. For someone like this, LLMs are a logical choice for him. The annoying thing about my friend is that his bank account suggests he's right. But I can't get there. If I'm writing a simple script or something as a one-off, it can sometimes feel like we're all wasting the companies time when we have a long back and forth on the PR discussing comments or the linting or whatever. So it's not that this idea is entirely wrong . But the problem with programming is you never know what is going to be "the core" of your work life for the next 5 years. Sometimes I write a feature, we push it out, it explodes in popularity and then I'm a little bit in trouble because I built a MVP and now it's a load-bearing revenue generating thing that has to be retooled. I also just have trouble with the idea that this is my career and the thing I spend my limited time on earth doing and the quality of it doesn't matter. I delight in craftsmanship when I encounter it in almost any discipline. I love it when you walk into an old house and see all the hand crafted details everywhere that don't make economic sense but still look beautiful. I adore when someone has carefully selected the perfect font to match something. Every programmer has that library or tool that they aspire to. That code base where you delight at looking at it because it proves perfection is possible even if you have never come close to reaching that level. For me its always been looking through the source code of SQLite that restores my confidence. I might not know what I'm doing but it's good to be reminded that someone out there does. Not everything I make is that great, but the concept of "well great doesn't matter at all" effectively boils down to "don't take pride in your work" which is probably the better economic argument but feels super bad to me. In a world full of cheap crap, it feels bad to make more of it and then stick my name on it. The best argument for why programmers should be using LLMs is because it's going to be increasingly difficult to compete for jobs and promotions against people who are using them. In my experience Claude Code allows me to do two tasks at once. That's a pretty hard advantage to overcome. Last Tuesday I had Claude Code write a GitHub Action for me while I worked on something else. When it was done, I reviewed it, approved it, and merged it. It was fine. It was better than fine, actually — it was exactly what I would have written, minus the forty-five minutes of resentment. I sat there for a moment, staring at the merged PR, feeling the way I imagine people feel when they hire a cleaning service for the first time: relieved, and then immediately guilty about the relief, and then annoyed at myself for feeling guilty about something that is, by any rational measure, a completely reasonable thing to do. Except it isn't reasonable. Or maybe it is. I genuinely don't know anymore, and that's the part that bothers me the most — not that the tool works, but that I've lost the clean certainty that it shouldn't. So now I'm paying $20 a month to a company that scraped the collective knowledge of humanity without asking so that I can avoid writing Kubernetes YAML. I know what that makes me. I just haven't figured out a word for it yet that I can live with. When I asked my EVE friend about it on a recent TeamSpeak session, he was quiet for awhile. I thought that maybe my moral dilemma had shocked him into silence. Then he said, "You know what the difference is between you and me? I know I'm a mercenary. You thought you were an artist. We're both guys who type for money." I couldn't think of a clever response to that. I still can't.

0 views
iDiallo Yesterday

Programming is free

A college student on his spring break contacted me for a meeting. At the time, I had my own startup and was navigating the world of startup school with Y Combinator and the publicity from TechCrunch. This student wanted to meet with me to gain insight on the project he was working on. We met in a cafe, and he went straight to business. He opened his MacBook Pro, and I glimpsed at the website he and his partner had created. It was a marketplace for college students. You could sell your items to other students in your dorm. I figured this was a real problem he'd experienced and wanted to solve. But after his presentation, I only had one question in mind, about something he had casually dropped into his pitch without missing a beat. He was paying $200 a month for a website with little to no functionality. To add to it, the website was slow. In fact, it was so slow that he reassured me the performance problems should disappear once they upgraded to the next tier. Let's back up for a minute. When I was getting started, I bought a laptop for $60. A defective PowerBook G4 that was destined for the landfill. I downloaded BBEdit, installed MAMP, and in little to no time I had clients on Craigslist. That laptop paid for itself at least 500 times over. Then a friend gave me her old laptop, a Dell Inspiron e1505. That one paved the way to a professional career that landed me jobs in Fortune 10 companies. I owe it all not only to the cheap devices I used to propel my career and make a living, but also to the free tools that were available. My IDE was Vim. My language was PHP, a language that ran on almost every server for the price of a shared hosting plan that cost less than a pizza. My cloud was a folder on that server. My AI pair programmer was a search engine and a hope that someone, somewhere, had the same problem I did and had posted the solution on a forum. The only barrier to entry was the desire to learn. Fast forward to today, every beginner is buying equipment that can simulate the universe. Before they start their first line of code, they have subscriptions to multiple paid services. It's not because the free tools have vanished, but because the entire narrative around how to get started is now dominated by paid tools and a new kind of gatekeeper: the influencer. When you get started with programming today, the question is "which tool do I need to buy?" The simple LAMP stack (Linux, Apache, MySQL, PHP) that launched my career and that of thousands of developers is now considered quaint. Now, beginners start with AWS. Some get the certification before they write a single line of code. Every class and bootcamp sells them on the cloud. It's AWS, it's Vercel, it's a dozen other platforms with complex pricing models designed for scale, not for someone building their first "Hello, World!" app. Want to build something modern? You'll need an API key for this service, a paid tier for that database, and a hosting plan that charges by the request. Even the code editor, once a simple download, is now often a SaaS product with a subscription. Are you going to use an IDE without an AI assistant? Are you a dinosaur? To be a productive programmer, you need a subscription to an AI. It may be a fruitless attempt, but I'll say it anyway. You don't need any paid tools to start learning programming and building your first side project. You never did. The free tools are still there. Git, VS Code (which is still free and excellent!), Python, JavaScript, Node.js, a million static site generators. They are all still completely, utterly free. New developers are not gravitating towards paid tools by accident. Other than code bootcamps selling them on the idea, the main culprit is their medium of learning. The attention economy. As a beginner, you're probably lost. When I was lost, I read documentation until my eyes bled. It was slow, frustrating, and boring. But it was active. I was engaging with the code, wrestling with it line by line. Today, when a learner is lost, they go to YouTube. A question I am often asked is: Do you know [YouTuber Name]? He makes some pretty good videos. And they're right. The YouTuber is great. They're charismatic, they break down complex topics, and they make it look easy. In between, they promote Hostinger or whichever paid tool is sponsoring them today. But the medium is the message, and the message of YouTube is passive consumption . You watch, you nod along, you feel like you're learning. And then the video ends. An algorithm, designed to keep you watching, instantly serves you the next shiny tutorial . You click. You watch. You never actually practice. Now instead of just paying money for the recommended tool, you are also paying an invisible cost. You are paying with your time and your focus. You're trading the deep, frustrating, but essential work of building for the shallow, easy dopamine hit of watching someone else build. The influencer's goal is to keep you watching. The platform's goal is to keep you scrolling. Your goal should be to stop watching and start typing. These goals are at odds. I told that student he was paying a high cost for his hobby project. A website with a dozen products and images shouldn't cost more than a $30 Shopify subscription. If you feel more daring and want to do the work yourself, a $5 VPS is a good start. You can install MySQL, Rails, Postgres, PHP, Python, Node, or whatever you want on your server. If your project gains popularity, scaling it wouldn't be too bad. If it fails, the financial cost is a drop in a bucket. His story stuck with me because it wasn't unique. It's the default path now: spend first, learn second. But it doesn't have to be. You don't need an AI subscription. You don't need a YouTuber. You need a text editor (free), a language runtime (free), and a problem you want to solve. You need to get bored enough to open a terminal and start tinkering. The greatest gift you can give yourself as a new programmer isn't a $20/month AI tool or a library of tutorial playlists. It's the willingness to stare at a blinking cursor and a cryptic error message until you figure it out yourself. Remember, my $60 defective laptop launched a career. That student's $200/month website taught him to wait for someone else to fix his problems. The only difference between us was our approach. The tools for learning are, and have always been, free. Don't let anyone convince you otherwise.

0 views
ava's blog Yesterday

[trade] what surprises me most studying law

It's been a while, but I finally have a blog title trade again! James gave me the blog title " What surprises me most studying law ". You can read what title I gave him here . Starting off with some small surprises: Looking back on the attitude I had before I started studying law, I thought it would be a lot harder. Or rather, difficult in a different way than it actually ended up being so far. This is explicitly not meant as a humble brag of " Look how good I am at this, so easy! ", it's just that I didn't know just how much is explicitly mentioned in the law (because I did not care about reading it much before), and that you are allowed to take it with you into the exams to look stuff up in. I was surprised that law is not actually about knowing all of these by heart (almost none of that is needed), but that you just need to know where the information is and that it exists. This works in my favor. I can be forgetful about details, but I still know where exactly I found some piece of information. I think if more people knew about this aspect of studying law, maybe more people would consider it and not shy away from it! What also surprised me was the intense focus on practice cases (at least here, In Germany). This is heavily criticized and I also have my gripes with it, but it also means that in the exams, you are writing a report judging a given case based on a specific rigid structure. The cases usually belong to previously defined and covered case groups. It's likely you have already solved practice cases with that exact problem or heard about it in the news or existing case law, so you know what kind of laws you need to apply, and you also don't need to worry about how to structure the report as you need to follow a specific format. At least at my university, you do not actually end up writing a sort of free-form essay until the Bachelor thesis. As long as you follow the formalities, know some case law that was covered in the materials, solve some practice cases and can detect hints in the text about what the problem in the case is, you have a good chance of passing. All you have to do is follow the structure that's the same for all exams, open the book to the specific paragraphs you need, and read what's in it. You still need to know the numbers yourself and when each is applied, and some definitions of terms and different interpretations; but that is far less than other degrees have to learn by heart. But what surprised me most is that there is not "the one correct interpretation" of almost anything! There are controversies about most things in German law, with at least 2-5 different interpretations and views on how a specific paragraph is applied or worded. Laypeople often feel confident to just quote any paragraph at others and insist that it means xyz, but not even the experts and professionals are agreeing on it. The world of law may look black and white from the outside, but there's a good reason why most people you meet in law will answer anything with " It depends... ". On one hand: Yes, parts of law are written in a way that it is supposed to cover a lot of different cases under one umbrella; but on the other hand, law also needs to allow for some flexibility for outliers and new developments. That means it's usually not as clear-cut as it seems from the outside. That's also why we have courts - they continue to develop case law that adds to the interpretation of paragraphs and articles, and they stick to one interpretation or develop a new one, while making a decision in the discretion the law gives them. If law was actually such a straightforward thing that perfectly and clearly covers every situation, we wouldn't need courts deciding (aside from the right of parties to defend themselves, of course). That also means that two people that did the exact same thing could walk out of court with different results. That may feel unjust, but so is life. I can see this directly in action in the data protection law space: Law groups focused on digital rights and informational self-determination of users argue for different interpretations of GDPR articles than lawyers employed by large social media companies. Law is further developed and changing every day, and an on-going conversation between many different parties and circumstances; totally different from the rigid set of rules I expected. :) Reply via email Published 16 Feb, 2026

0 views
Rik Huijzer Yesterday

Raspberry Pi as Forgejo Runner

In my instructions on how to setup [Forgejo with a runner](/posts/55), I used a Hetzner server for the runner. This costs roughly 5 euros per month, so 60 euro annually. A full Hetzner server might be a bit overkill for a simple runner. Especially if you are just running Shell scripts or static site generation. The Hetzner server supports things like high bandwidth, low latency, unique IPv4 address, high uptime guarantees. Most of these are not necessary for your own runner. Therefore, in many cases it's probably a good idea to run the Runner on your own hardware. What I have tested and work...

0 views
JSLegendDev Yesterday

I Tried 3 Web Game Frameworks (So You Don’t Have To)

I went on a quest to test the 3 most popular 2D web game frameworks according to their NPM download numbers. To achieve this, I set out to build a Sonic infinite runner game in each framework. Today, I’d like to share my findings. Below are links to playable demos along with the source code for each version of the game. I recommend browsing the code of each version to get a better feel of the differences between them. KAPLAY version - ( live demo ) - ( source code ) Phaser version - ( live demo ) - ( source code ) Excalibur version - ( live demo ) - ( source code ) Note that even though KAPLAY markets itself as a library and Excalibur as a game engine they still feel like frameworks and occupy the same space as Phaser. That’s why I took the liberty to refer to them as frameworks. I will not beat around the bush. If you want stability and performance, you should pick Phaser. That said, overall, KAPLAY is the easiest one to learn but the least scalable of the 3. This makes it a good choice to get into game development as a beginner and to prototype ideas quickly but you’ll end up facing performance issues eventually. I’d like to note that there have been recent performance improvements. However, KAPLAY’s performance still doesn’t come anywhere close to the one offered by Phaser. Additionally, Phaser has an AI advantage. For example, if you ask ChatGPT a question related to Phaser, more likely than not, the answer provided will be right. As for KAPLAY and Excalibur, you’ll get a lot more hallucinations. This AI advantage lowers the barrier of learning for Phaser which, in contrast, makes the easiness of KAPLAY less compelling. While Phaser’s API is verbose, you get over it especially when you get performance and stability in exchange. Finally, Phaser is significantly more popular than the other 2 which means it will be easier to find help when needed. If you’re looking to target mobile, Phaser is also your best bet between the 3 as the performance on Safari is also quite good and the best. This is relevant because Safari uses the Webkit web engine which is what’s forced on every browser and webview on iOS. Finally, for multiplayer games, Phaser offers the ability to run it on the server in headless mode. This makes it much easier to handle state in a multiplayer game. To my knowledge, neither KAPLAY nor Excalibur offers something like this. As mentioned previously, KAPLAY might still be worth using in the context of prototyping or for game jams, as its component based approach allows you to truly iterate quickly on game design. Its simplicity also makes it an ideal candidate for teaching game development to beginners. Some have used it to teach children. Phaser and Excalibur felt very similar when using them as they both relied on the object oriented paradigm. Yet, I felt that Excalibur sometimes required more work. A notable example of this is when coming back to a scene after having switched away from it. The state of that scene would be left as is before transitioning over. This meant you had to write extra logic to reset the scene’s state, something you didn’t have to do in Phaser. That said, I still felt that Excalibur’s API was on average a bit more concise than Phaser’s. In conclusion, the issue with Excalibur is that it didn’t feel different enough from Phaser to warrant switching over to it, considering Phaser is more performant, more featureful, and more popular. At the same time, I was impressed with how Excalibur handled its debug mode. Instead of having it within the framework, it was instead available as a browser extension. Debug mode would therefore appear in the browser devtools and could be toggled and configured from there. It was a nice experience. Regardless of the web framework you end up using, if you want to bring your web game to Steam, the easiest tool for achieving this is GemShell. It allows you to create executables for Windows, Mac and Linux in what amounts to a single click. It also offers an integration with Steam so you can easily have access to features like Steam achievements and now also the Steam overlay. (The latter might not be available yet at the time of publishing this post.) (Video by the developer showcasing the use of Steam overlay on top of a game packaged with GemShell) This tool will save you hours that would have been wasted configuring tools like Electron and Steamworks.js. Time which could have been spent working on your game instead. To have a consistent experience across operating systems, GemShell now makes each executable Chromium based so that your game renders the same way on all platforms. Additionally, The developer is also considering implementing the ability to export for mobile platforms as well. Due to how useful this tool is, I have decided to partner with its developer to offer you a 15% discount if you use the following link : https://l0om.itch.io/jslegend To be completely transparent, if you end up purchasing GemShell using the link above, I’ll get a cut of that sale. That said, a free lite version is available, if you want to get a feel for the tool. I hope you enjoyed this post, I recommend subscribing to not miss out on future releases. Subscribe now In the meantime, you can read the following : I went on a quest to test the 3 most popular 2D web game frameworks according to their NPM download numbers. To achieve this, I set out to build a Sonic infinite runner game in each framework. Today, I’d like to share my findings. Below are links to playable demos along with the source code for each version of the game. I recommend browsing the code of each version to get a better feel of the differences between them. KAPLAY version - ( live demo ) - ( source code ) Phaser version - ( live demo ) - ( source code ) Excalibur version - ( live demo ) - ( source code )

0 views

Taste Is Not a Moat

I used to write the design while AI coded the software. Architecture decisions, API contracts, data models on my side; implementation on its side. Fast forward a few months and more and more the model now handles both, because it’s more right than wrong. The “outer loop” of system design, the thing that engineers would do while AI wrote the the code, turned out to be just another task it would absorb. Over the past year, Tech Twitter has mostly converged on the same answer to this problem 1 . As AI eats execution, taste is the moat. And while they’re right about taste mattering. I’m less sure about the moat part. A moat is something you build once and defend. Taste feels more like alpha 2 : a decaying edge, only valuable relative to a rising baseline. And that baseline is rising faster than many realize. Cartoon via Nano Banana. In this post, I wanted to focus on taste; how it changes the human role and questions around who owns it. Every domain has a threshold where AI crosses from “clearly worse” to “good enough to fool experts.” Those thresholds are falling fast as the models get better and organizations get better at providing the right context. 2024: AI could autocomplete but not architect. Copilot suggestions had a higher churn rate than human code. AI-generated marketing copy was formulaic and easy to spot. AI-generated playlists were novelties, not chart contenders. AI hiring meant keyword-matching resumes with well-documented bias. The consensus across every domain was the same: AI can handle the grunt work, but taste, judgment, and design are ours. 2025: “Vibe coding” became mainstream. Consumers rated AI marketing copy better than professional copywriters. Music listeners couldn’t distinguish AI music from human music. Frontier models are matching human experts on nearly half of tasks across occupations, completing them 100x faster and 100x cheaper. In twelve months, “AI can’t do X” became “AI does X better than most people” in domain after domain. 2026: The majority of US developers use AI coding tools daily. AI-generated music accounts for over a third of daily uploads on major platforms. The question is no longer whether AI can match human taste in a given domain, but how long until it does in yours. This is why I see taste as alpha, not a moat. My judgment is only valuable relative to what AI can do by default, and that default resets every few months. If taste decays, the question stops being “do you have it?” and starts being “how fast can you get it into a system before the baseline catches up?” Thanks for reading Shrivu’s Substack! Subscribe for free to receive new posts and support my work. Even as taste decays as a durable advantage, it’s becoming the primary thing organizations pay humans to do (on top of just having the agency to use AI to do their job) 3 . Having taste matters just as much as being able to communicate it. Out of the box, many of us can’t fully articulate why we prefer one design over another, why this copy lands and that one doesn’t, why this architecture will scale and that one won’t. The knowledge is tacit, embodied in instinct rather than rules. However, jobs are shifting from executor to taste extractor: someone whose primary skill is getting their judgment into the system. The best hire for an AI-native marketing team isn’t the person with the most original campaign ideas (despite their ‘taste moat’). It also isn’t the person with mediocre instincts who happens to be good at prompting ChatGPT. It’s the person with N years of pattern recognition about what converts, what falls flat, and why, who can turn that into something a system can use. Their day-to-day looks nothing like a traditional marketer’s. They spend the morning reviewing AI-generated campaign variants, not writing them. They run A/B interviews against their own preferences to build an Essence document for the brand’s voice. They flag the three outputs out of twenty that feel subtly wrong and articulate what’s off: too eager, wrong register for the audience, buries the value prop. That articulation becomes a constraint the system applies to the next hundred outputs. By afternoon they’re not writing copy; they’re tuning the machine that writes copy. It’s not “creativity” in the traditional sense and it’s not “prompt engineering” in the shallow sense. Taste extraction builds a persistent model of your judgment that compounds across every output after it. It’s the ability to encode experienced judgment into a system that scales it. The pattern applies to plenty of taste-heavy roles: Designers stop pushing pixels and start curating. Recruiters stop screening and start calibrating for “high potential”. Product managers stop writing specs and start steering batches of features. Your taste can be a black box. You know what good looks like when you see it. You probably can’t explain why. Extraction is about surfacing that tacit judgment so a system can act on it and so you can leverage up on your productivity. Without extraction, your outcomes with AI drift. Prompt the same task across sessions without a persistent reference document and the AI reconstructs from training data priors each time; the output flattens toward the sloppy median 4 . There are a couple ways I’ve been experimenting with getting better at this. They apply across domains, not just code. Have AI present you with paired options and ask which you prefer and why. Your explanations, especially the ones you struggle to articulate, are the signal. After 10-15 rounds, the model synthesizes your preferences into a document it references for future work. Given this [style guide / doc / brief], interview me using A/B examples to help refine the [tone / style / design] guidance. Present me with two options for a [headline / paragraph / layout / architecture]. First, critique both options. Then ask me which I prefer and why. Use my answers to write a 1-page “Essence” document summarizing my taste in this domain. The Essence document becomes a reusable asset. I’ve done this for writing style, UI design preferences, and code architecture patterns. I now run this process for every new domain before generating anything significant. Increasingly every remaining task I do by hand, I also run AI on the same task in the background with minimal context. If I’m writing a blog post, I have AI write one too before I start. If I’m designing an architecture, I have AI propose one in parallel. Then I compare. Here is the [requirements doc / ticket / brief]. Propose a full [architecture / system design / data model]. Include your reasoning for each major decision. The value isn’t in which draft is better. It’s in where I flinch. The moments where the AI output feels off, where the slop is obvious to me but hard to name, those reactions are taste made visible. And the places where I’m contributing something the model didn’t, those are my actual alpha. Over time, the flinch points become encodable. I note them down, turn them into constraints, and feed them back into the system. This does two things at once: it helps me identify where my taste actually lives, and it gives me the language to express preferences I previously couldn’t articulate. The gap between my draft and the AI’s draft shrinks, but my ability to direct the output sharpens because I’m forced to make the tacit explicit. Your own taste has blind spots. One way I augment mine is by extracting the approximate taste of other people and running it through AI. In practice, this means using tasks ( aka dynamic subagents ) in Claude Code that represent specific perspectives 5 : An audience segment I’m writing for. A manager or coworker whose judgment I trust. A writer or brand whose voice I admire. I feed them real context: Zoom transcripts from calls, written feedback I’ve received, published work I want to learn from. Then I ask each agent to independently review whatever I’m building or writing. Use a task to read the attached [transcript / feedback thread / writing samples] from [person or role]. Extract their values, preferences, and recurring critiques. Then review this [draft / design / architecture] as if you were that person. Flag what they’d push back on, what they’d approve of, and what they’d want to see more of. A screenshot of Claude Code tasks reviewing this post. In a very meta way, it actually suggested adding this image to illustrate. It’s not a replacement for real feedback, but it catches the obvious misses before I ask for it. And it compounds: every round of external review surfaces blind spots in my own taste that I can then fold into my Essence documents and constraints. This also means the real feedback I get back is higher-signal too. Everything above is about extracting your taste to stay ahead. But there’s a catch: you’re not the only one extracting. TikTok doesn’t need any individual user to have great taste. It collects millions of low-signal interactions (swipes, watch time, replays, skips) and synthesizes something that functions like taste at industrial scale. No single swipe is valuable. In aggregate, those micro-signals train a recommendation system that N billion people spend hours inside daily. YouTube, Spotify, Instagram, Netflix: every app with an algorithmic feed is essentially a taste factory. The factory doesn’t just curate what humans make; it increasingly curates what AI makes, selecting for whatever the aggregate says “good” looks like. The extraction workflow that empowers you at the role level simultaneously trains these platforms. Your prompts, preferences, and clicks all teach systems that then compete with your own judgment. And this doesn’t require anyone to train directly on your data. It happens indirectly: your taste-informed outputs perform well, get clicked, shared, imitated, and that performance signal feeds back into the next generation of models and recommendation systems. The platform learns what “good” looks like from millions of people, then serves it back at scale without needing any of them individually. In the extreme, platforms could become the primary owners of taste, not individuals. Our role shifts from having taste to feeding it. The system doesn’t need to match your judgment on any specific decision; it just needs enough signal from enough people to converge on something the market accepts. The “platform” here might not even be the feeds, but could be the labs and token producers. Can taste be taught? If it develops through some opaque function of experience and exposure, what happens to people who haven’t had the right experiences? Dario warned about AI “slicing by cognitive ability,” creating stratification based on traits harder to change than specific skills 6 . Taste is a version of that divide. Do taste roles mean more hiring or less? Right now, the pattern is fewer people with more leverage: marketing teams shrink, one designer steers what five used to produce. But if the job is encoding judgment, don’t you actually want more people sourcing taste from more angles? A single extractor’s blind spots become the system’s blind spots. Who wins the taste race: individuals or platforms? Every extraction technique in this post works in both directions. You encode your judgment to scale your leverage; the platform collects that same signal to scale without you. If the platform can interview me better than I can articulate myself, farm preferences from millions of users simultaneously, and apply the aggregate at near-zero cost, does individual taste become a contribution to someone else’s moat? If so, will people accept taste farming as work? If platforms need human micro-signals to train their systems, does “pay per swipe” become a job category? If AI results in far fewer jobs, is this the remaining option? Who reaps the majority value of individual taste at that point? The people calling taste a moat are right that it matters. They’re wrong that it’s yours to keep. The more I practice articulating my own taste, the less sure I become that it’s durable. Thanks for reading Shrivu’s Substack! Subscribe for free to receive new posts and support my work. A moat, in business strategy, is a structural advantage that’s hard to replicate: a patent, a network effect, a regulatory lock-in. You build it once and competitors can’t easily cross it. Alpha, in finance, is the return you earn above what the market gives you for free, the gap between a hedge fund’s performance and a simple index fund. The key difference: a moat persists by design, but alpha decays. The more people discover the same strategy, the more the market absorbs it, and the edge shrinks. Taste behaves like alpha, not a moat. Your judgment is only valuable relative to what AI produces by default, and that default gets better on its own. I say this from the perspective of someone who works for a fairly AI-native organization and spends time with a lot of people who are in the SF AI bubble. I recognize that there’s still quite a few jobs out there that are now trivially done by AI but are still being done by humans as AI transformation takes time to diffuse to the rest of the world. Two things happen at once. First, without externalized standards, AI outputs regress to the distribution it was trained on. Each new session starts from that distribution’s center, not from where you left off, so quality flattens toward the median. Second, that median itself keeps rising. The taste you encode today, your Essence documents, your constraints, your feedback, eventually gets absorbed into training data for the next generation of models. What was your alpha becomes the new default. This doesn’t even require the model to train on your prompts or data directly; it happens indirectly through how your taste-informed outputs perform in the world, what gets clicked, shared, purchased, and imitated, which all feed back into the next training distribution. This is why taste behaves like alpha: your edge above the median is real but temporary, because the median is a moving target that absorbs the signal you feed it. It’s key that you use sub-agents or tasks for this workflow because you want a fresh, unbiased session/context-window to review the work. See The Adolescence of Technology . Cartoon via Nano Banana. In this post, I wanted to focus on taste; how it changes the human role and questions around who owns it. Where the baseline is now Every domain has a threshold where AI crosses from “clearly worse” to “good enough to fool experts.” Those thresholds are falling fast as the models get better and organizations get better at providing the right context. 2024: AI could autocomplete but not architect. Copilot suggestions had a higher churn rate than human code. AI-generated marketing copy was formulaic and easy to spot. AI-generated playlists were novelties, not chart contenders. AI hiring meant keyword-matching resumes with well-documented bias. The consensus across every domain was the same: AI can handle the grunt work, but taste, judgment, and design are ours. 2025: “Vibe coding” became mainstream. Consumers rated AI marketing copy better than professional copywriters. Music listeners couldn’t distinguish AI music from human music. Frontier models are matching human experts on nearly half of tasks across occupations, completing them 100x faster and 100x cheaper. In twelve months, “AI can’t do X” became “AI does X better than most people” in domain after domain. 2026: The majority of US developers use AI coding tools daily. AI-generated music accounts for over a third of daily uploads on major platforms. The question is no longer whether AI can match human taste in a given domain, but how long until it does in yours. Designers stop pushing pixels and start curating. Recruiters stop screening and start calibrating for “high potential”. Product managers stop writing specs and start steering batches of features. An audience segment I’m writing for. A manager or coworker whose judgment I trust. A writer or brand whose voice I admire. Can taste be taught? If it develops through some opaque function of experience and exposure, what happens to people who haven’t had the right experiences? Dario warned about AI “slicing by cognitive ability,” creating stratification based on traits harder to change than specific skills 6 . Taste is a version of that divide. Do taste roles mean more hiring or less? Right now, the pattern is fewer people with more leverage: marketing teams shrink, one designer steers what five used to produce. But if the job is encoding judgment, don’t you actually want more people sourcing taste from more angles? A single extractor’s blind spots become the system’s blind spots. Who wins the taste race: individuals or platforms? Every extraction technique in this post works in both directions. You encode your judgment to scale your leverage; the platform collects that same signal to scale without you. If the platform can interview me better than I can articulate myself, farm preferences from millions of users simultaneously, and apply the aggregate at near-zero cost, does individual taste become a contribution to someone else’s moat? If so, will people accept taste farming as work? If platforms need human micro-signals to train their systems, does “pay per swipe” become a job category? If AI results in far fewer jobs, is this the remaining option? Who reaps the majority value of individual taste at that point?

0 views

Running My Own XMPP Server

About a year ago I moved my personal messaging to Signal as part of a broader push to take ownership of my digital life. That went well. Most of my contacts made the switch, and I’m now at roughly 95% Signal for day-to-day conversations. But Signal is still one company running one service. If they shut down tomorrow or change direction, I’m back to square one. XMPP fixes that. It’s federated, meaning your server talks to other XMPP servers automatically and you’re never locked into a single provider. Your messages live on your hardware. The protocol has been around since 1999 and it’s not going anywhere. I’d tried XMPP years ago and bounced off it, but the clients have come a long way since then. Monal and Conversations are genuinely nice to use now. This post covers everything I did to get a fully working XMPP server running with Prosody in Docker, from DNS records through to voice calls. XMPP uses SRV records to let clients and other servers find yours. You’ll need these in your DNS: Port 5222 is for client connections, 5269 is for server-to-server federation. You’ll also want an A record pointing to your server’s IP. If you want HTTP file uploads (I’d recommend it), add a CNAME or A record for pointing to the same server. Same for if you want group chats with a clean subdomain, though Prosody handles this internally either way. Prosody won’t start without certificates. I use Let’s Encrypt with the Cloudflare DNS challenge so I don’t need to expose port 80: The file contains your API token: After certbot runs, fix the permissions so Prosody can read the certs: Set up a cron to renew monthly: Two ports exposed: 5222 for clients, 5269 for federation. The data volume holds user accounts and message archives. Config and certs are mounted read-only. This is the core of it. I’ll walk through the key sections rather than dumping the whole file. Prosody is modular. My module list: The ones I found matter most for a good mobile experience: syncs messages across all your devices instead of delivering to whichever one happened to be online. (Stream Management) handles flaky connections gracefully, so messages aren’t lost when your phone briefly drops signal. enables push notifications so mobile clients don’t need a persistent connection, which is essential for battery life. And (Message Archive Management) stores history server-side for search and cross-device sync. All connections are encrypted and registration is disabled since I create accounts manually with . I’ve enabled , which means Prosody will reject connections from servers with self-signed or misconfigured certificates. You’ll lose federation with some poorly configured servers, but if you’re self-hosting for privacy reasons it doesn’t make much sense to relax authentication for other people’s mistakes. TLS encrypts connections in transit, but the server itself can still read your messages. If you’re self-hosting, that means you’re trusting yourself, which is fine. But if other people use your server, or if you just want the belt-and-braces approach, OMEMO adds end-to-end encryption so that not even the server operator can read message content. OMEMO is built on the same encryption that Signal uses, so I’m comfortable trusting it. There’s nothing to configure on the server side either. OMEMO is handled entirely by the clients. Monal, Conversations, and Gajim all support it, and in most cases it’s enabled by default for new conversations. I’d recommend turning it on for everything and leaving it on. Messages are kept for a year and archiving is on by default. Clients can opt out per-conversation if they want. Prosody serves HTTP on port 5280 internally. I leave HTTPS to my reverse proxy (Caddy), which handles TLS termination. The tells Prosody what URL to hand clients when they upload files. The MUC (Multi-User Chat) component gives you group chats with message history via . I restrict room creation to local users so random federated accounts can’t spin up rooms on my server. The file share component handles image and file uploads. A 10 MB limit and 30-day expiry keeps disk usage under control. Prosody’s HTTP port needs to be reachable from the internet for file uploads to work. I use Caddy: When a client sends an image, Prosody hands it a URL like and the receiving client fetches it over HTTPS. With registration disabled, accounts are created from the command line: It prompts for a password. Done. Log in from any XMPP client. Open the XMPP ports: Port 80/443 for the reverse proxy if you haven’t already. If your server is behind a router, forward 5222 and 5269. Text and file sharing work at this point. Voice and video calls need one more piece: a TURN/STUN server. Without it, clients behind NAT can’t establish direct media connections. I run coturn alongside Prosody. The two share a secret, and Prosody generates temporary credentials for clients automatically. Generate a shared secret: The coturn : It runs with because TURN needs real network interfaces to handle NAT traversal. Docker’s port mapping breaks this. If your server is behind NAT, add: Then tell Prosody about it. Add to your modules, and inside the block: Open the firewall ports: Verify with . On iOS I went with Monal , which is open source and supports all the modern XEPs. Push notifications work well. On Android, Conversations seems to be the go-to. On desktop, Gajim covers Linux and Windows, and Monal has a macOS build. All of them support OMEMO encryption, file sharing, group chats, and voice/video calls. Prosody has solid built-in diagnostics: This checks DNS records, TLS certificates, connectivity, and module configuration. Fix anything it flags. The error messages are genuinely helpful. The XMPP Compliance Tester is worth running too. Mine scored above 90% after getting the config right. The whole setup runs in two small Docker containers and a reverse proxy entry. Prosody, file uploads, message archive, push notifications, group chats, voice calls. I still use Signal for most day-to-day conversations and I’m not planning to stop. But having my own XMPP server means I’m not entirely dependent on any single service. I can message anyone on any XMPP server, not just people who signed up to the same one. It’s a nice fallback to have. If you’re already running Docker on a server somewhere, it’s a good weekend project. A server with Docker and Docker Compose A domain you control TLS certificates (Let’s Encrypt works well)

0 views