Latest Posts (20 found)

Behold the perfect algorithm!

1984, Minority Report, Black Mirror — bedtime stories compared to the horrors the UK Government publish, am I right? I’m led to believe “Watch this space” is the latest propaganda piece from His Majesty’s Nanny State . I haven’t read past the title but according to gaming site Dexerto, YouTube lawyers read it and YouTube ain’t happy. Poor little YouTube. The government is consulting on options, considering whether to make public service news easier to discover on sites like YouTube and TikTok, with greater prominence and with more visibility during periods of major public importance. It also seeks to discuss misinformation and online viewing habits. YouTube urges creators to fight proposed UK algorithm changes - Matthew Benson, Dexerto I glossed over the Dexerto article too. This whole thing is something about kids being hooked on Skibidi and not paying their racketeering license fee . Minecraft Let’s Plays will be spliced with a BBC impartiality report on what some fascist gammon thinks. Should the proposal become law, of course. This is somewhat of a dilemma for a guy like me. If there’s one thing I hate more than a meddling GOV.UK, that might just be Big Tech . The thought of Google et al being ruffled warms my heart like a hot cup of tea on the summer solstice. That was too many words on something I never read so I’ll get to the lede. I’m about to reveal the secret sauce that Big Tech has tried to suppress. The one true algorithm, which ironically might be their saviour. Only one parameter is required in the perfect algorithm: who I choose to follow. I’m literally providing the exact data needed to curate my feed. I know what defenders of the deceptive arts are thinking: but algorithms are proven to increase engagement! — I know, Sherlock. Do you enjoy your doomscrolling misery? Not every metric needs to be min-maxed at the expense of human health. Modern apps sucks. Modern media sucks. Stick your “algorithm”. † It’s been decades since I studied SQL and database normalisation so please have mercy. Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

0 views

Inkdrop Roadmap vol.6: Completed 🎉 — Now preparing for the official v6 release

Hi folks, it's Takuya here, the solo developer of Inkdrop . I'd like to report a status update on the Inkdrop project here. About a year and a half ago, I published the roadmap of Inkdrop vol.6 . And I'm happy to announce that every planned feature and improvement on that roadmap is now done! 🥳 They all shipped as part of the v6 canary series — 21 canary releases so far, built and tested together with the community. When I wrote the roadmap, I honestly wasn't sure how long it would take. I would have been surprised if the me of that time had seen this result. Thank you so much for all your feedback along the way — I couldn't have done it without you. Even beyond the roadmap, I've added so many new features and improvements. So, I'm confident you'll enjoy it if you're coming from v5. Let's dive into what I accomplished along the roadmap, what came out of it beyond the plan, and what's next. What made the development slow down was the huge technical debt, as I mentioned in the past post . Inkdrop was originally built on the Atom editor's framework, and when Atom was sunsetted in 2022, many of the modules it depended on were no longer maintained. I had to replace them one by one while keeping the app stable — the hardest and least visible part of this journey. With v6, that debt is finally paid off. Here's a quick before & after: None of these are shiny features on their own. But they're exactly what allowed me to ship everything you'll see below, and they make Inkdrop much faster to develop going forward. The codebase is now modern, healthy — and honestly, fun to work on again. I'm an indie developer, and Inkdrop is a one-person project — so manpower has always been the bottleneck. Paying off the tech debt was a particularly big headache: some of the inherited modules were so large that it originally took the whole Atom team to maintain them. But thanks to the recent advancements in coding agents, that burden finally feels manageable — and even enjoyable to tackle. AI didn't just speed up the coding; it changed how I work: These new workflows have opened up possibilities that simply didn't exist for solo developers before. A refactoring of this scale used to be unthinkable for one person — now I can maintain a codebase that once took a team, and spend the saved energy on what matters most: the product itself and my users. Here's the roadmap vol.6, item by item, with what actually shipped: The roadmap was only half the story. While working through it, I ended up rebuilding a huge part of the app and shipping a lot of features that weren't planned. Here are the highlights, grouped by area: And on top of all that, hundreds of bug fixes reported by canary testers. The community has also been building amazing plugins on the new APIs — note-tabs (browser-like note tabs), code-runner (run JS/Python code blocks in notes), constellation (an interactive note graph), copy-as-jira , kanso-ink (theme), and more. Existing plugins are getting v6 support too, like hitahint , link-compact , thumbnail-list , and editor-utils . My goal remains the same as I wrote in the roadmap: keep improving the core user experience without bloating the app, so you can stay focused on taking notes. I believe v6 embodies exactly that. You can download the binary here: Please create a topic on the “ Issues > Canary ” category. This is the most preferred way for me because I can manage which issue has been resolved or not. We have our Discord server , where you can casually discuss and talk with other users. With the roadmap completed, I've shifted gears to preparing for the official release of v6 . That means polishing the details, stabilizing the canary builds, updating the documentation and the website, and helping plugin and theme authors migrate. Especially, building a new landing page is gonna be fun! I'm also going to work on the mobile app as well. The official v6 release is getting close. Stay tuned! 💪 I manage implementation plans as Inkdrop notes and let the agents work through them. Watch: Note-driven agentic coding workflow using Claude Code and Inkdrop I built and published a tool to manage multiple Claude Code sessions on tmux . While building the AI features, I had an agent explore Zed's source code and save the report to Inkdrop , to learn how it implements similar functionality. ✅ Share target & share extension — You can quickly stock web pages into Inkdrop from other apps on mobile. ( v5.5.0 ) ✅ Command palette — It became Telescope , a versatile Spotlight-like search bar (the name is borrowed from telescope.nvim, haha). It fuzzy-searches commands, notebooks, tags, and the table of contents of the current note, with scope prefixes like for commands and for notebooks. It's extensible, so plugins can add custom sources. ( canary.1 ) ✅ Migrate to CodeMirror 6 — The biggest one. The whole editor was rebuilt on CodeMirror 6, and it enabled a bunch of new editing features: a floating toolbar, slash commands, GitHub Alerts syntax support, emoji autocompletion, autocompletion inside code blocks, and quick note-link insertion with . ( canary.1 ) ✅ Outline view — Powered by Telescope. Click the button in the editor header (or run ) to jump between sections. It highlights the current section based on your cursor or scroll position, and even lists task items. It's provided as a plugin ( telescope-toc ), which doubles as a reference implementation for custom Telescope sources. (Thanks Basyura-san for the original sidetoc plugin!) ( canary.6 ) ✅ Preview pane improvements — Copy buttons for code blocks landed in both the preview and the editor, and double-clicking an image opens it in an image viewer. As a bonus, find-in-preview finally works — it highlights matches even across DOM elements, which is essential for finding text in code blocks. (Thanks q1701 and Basyura for the original plugins!) ( canary.2 , canary.4 ) ✅ Two-factor authentication — OTP-based 2FA is available for your account. ( v5.11.0 ) ✅ Prepare for ARM64 & other platforms — This required repaying a lot of technical debt. I replaced the deprecated LevelDB backing store with SQLite , stopped bundling (which used to bundle all of Node.js and npm!), and rebuilt it as a lightweight standalone CLI ( @inkdropapp/ipm-cli ). As a result, Inkdrop now supports ARM64 on Windows and Linux , plus Flatpak and AppImage packages for modern Linux distros. ( canary.1 , canary.4 , canary.5 ) ✅ Improve image upload speed — Attachments are now uploaded in parallel via signed URLs, so syncing image-heavy notes is significantly faster. ( canary.12 ) ✅ Diff view for revision history on desktop — The diff view I loved on mobile is now on desktop, too. ✅ Notebook icons — You can assign custom icons to notebooks from a picker with 1,500+ icons from the Lucide icon set, with category tabs and search. Icons show up everywhere — the sidebar, Telescope, and notebook selectors. ( canary.9 ) ✅ Visualize your progress and achievements — The activity stats view shows how many notes you created and tasks you worked on over the past 52 weeks, along with your current and longest streaks. Note-taking is a contribution to your work, after all! ( canary.14 ) ✅ AI integrations — Shipped as an opt-in, bring-your-own-API-key design, so you stay in control of your data. The inline AI assistant transforms selected text in place with built-in prompt presets (proofread, summarize, Mermaid diagrams, Markdown tables, and your own custom prompts). Next Edit Suggestions predicts your next edit like GitHub Copilot — set to manual trigger by default so it doesn't distract you — and it can even draw context from your linked notes and backlinks. ( canary.16 , canary.18 , canary.20 ) Reading highlights — Select text and hit the highlight button to wrap it in a tag, rendered beautifully in the preview. Perfect for emphasizing what resonates in your reading notes. ( canary.3 ) Native spellcheck support — The editor now uses the OS-native spellchecker. ( canary.10 ) Smarter link pasting — Pasting a URL now suggests link formats inline through the autocompletion menu instead of a dialog, and the page title is fetched in the background so nothing interrupts your flow. ( canary.15 ) Create a note from autocomplete — Start typing a title after , choose "Create new note," and it's created, linked, and opened in one step. ( canary.16 ) Little things that add up — ToDo item strikethrough, link-open tooltips, commands (Thanks Lukas and TheRabidOstrich !), View menu toggles for line numbers / line wrapping / readable line length, and a refurbished editor header with navigation back/forward, view mode buttons, and a native action menu (Cmd/Ctrl+J). ( canary.2 , canary.3 , canary.12 , canary.18 ) Embed GitHub code snippets by pasting a link — Paste a GitHub source URL and the code is fetched and inserted as a syntax-highlighted snippet with line numbers and a link back to the source. Connect your GitHub account via OAuth and it works with private repos too, including rich link titles for repos, issues, and PRs. ( canary.6 , canary.11 ) Advanced code blocks — Language icons, line numbers, and meta info rendering, plus GFM highlighting inside fenced code blocks — nested code blocks and YAML frontmatter included. ( canary.6 , canary.9 , canary.20 ) Mermaid got a serious upgrade — A pan & zoom toolbar with a full-screen viewer, and diagrams are now themed entirely through CSS variables, so they automatically match your theme in light and dark mode. (Thanks @inkwadra for the original pan/zoom PR!) ( canary.21 ) Manual notebook ordering — Drag and drop notebooks in the sidebar into your preferred order; it syncs across devices. ( canary.9 ) Fuzzy matching everywhere — Telescope, the notebook and tag list menus, and the tag input all use the same fuzzy-matching algorithm, so you find things fast without spelling them right. ( canary.15 ) Quicker navigation — Filter buttons for notebooks and tags in the sidebar, a search bar in the notebook picker, context menus on the workspace and note-list headers, and a sort-order button that shows the current order as a label. ( canary.6 , canary.15 , canary.16 ) Keep running in the system tray (Windows & Linux) — Handy if you use the local HTTP API, and it makes reopening the app instant. (Thanks Kyoichiro-san and Micha for the request!) ( canary.21 ) Plus a custom-built tooltip UI, a macOS "Look Up Selection" context menu, and an account usage stats tab. ( canary.14 , canary.16 ) A new CSS-variable-based theming system — Themes are now a thin layer of variables over the base styles instead of a full Semantic UI stylesheet, which makes them far easier to build and maintain. ( canary.18 ) One theme package instead of three — The UI / syntax / preview theme types inherited from Atom have been merged into a single unified package that styles the whole app. ( canary.21 ) Live theme previews — The Themes preferences show preview cards rendered live from each theme's color palette, and is uploaded to the plugin registry to power previews before you install. ( canary.20 , canary.21 ) New official themes — Kanagawa ( Wave / Dragon / Lotus ), Solarized ( Light / Dark ), and Nord ( Dark / Light ), plus a default syntax theme overhaul built on modern CSS like . ( canary.18 , canary.20 , canary.21 ) Dropped Electron's module — I replaced it with type-safe IPC bridges in a massive architectural overhaul. Database access from plugins became roughly 13x faster , and the app is more secure because only intended methods are exposed. ( canary.11 ) SQLite as the backing store — Replacing the long-deprecated LevelDB unblocked ARM64 support and repaid one of the oldest debts from the Atom era. ( canary.4 ) Modern build pipeline — Migrated from Webpack + Grunt to electron-vite (Vite + Rolldown), which made production builds 10x faster and the dev build launch almost instant. I also converted all Less stylesheets to plain CSS, moved drag & drop from the unmaintained to , and kept Electron riding the latest releases throughout the canary series. ( canary.14 , canary.18 ) Security hardening — Access keys moved to the system keyring, and the login flow is protected with Cloudflare Turnstile against credential-stuffing bots. ( canary.16 , Security Update ) A brand-new CLI — No more bundled Node.js and npm. It publishes tarballs directly like npm (no more committing compiled files to GitHub), and scaffolds a new plugin or theme in seconds with TypeScript all wired up. ( canary.5 , canary.18 ) Official TypeScript definitions — @inkdropapp/types gives plugin authors full type safety without exposing the app's internals. ( canary.14 ) Auto-installed essential plugins — mermaid, math, and markdown-emoji are installed and kept up to date automatically, and you can disable them anytime. ( canary.14 ) Vim plugin improvements — Relative line numbers (Thanks @p1n9_d3v !) and an option to keep Vim registers separate from the system clipboard (Thanks @birtles !). ( canary.11 ) Updated docs — The plugin migration guide and theme development guide are refreshed for v6, along with new component and module references. https://my.inkdrop.app/download/canary Inkdrop Website: https://www.inkdrop.app/ Send feedback: https://forum.inkdrop.app/ Join the Discord server: https://docs.inkdrop.app/start-guide/join-discord-server 𝕏: https://x.com/inkdrop_app 🦋: https://bsky.app/profile/devaslife.bsky.social

0 views
Unsung Today

About Unsung: Recent improvements

(This is one of the meta posts about this very blog . If that’s not interesting to you, skip to the next one!) Here are some improvements I’ve made to Unsung in recent months. Always curious of your feedback or pointers to places that do these things better! Weekly emails. I made it so clicking on every (non-YouTube) video or image takes you to the equivalent of the weekly email you’re looking at, but on the web, where you can watch the videos in their natural habitat. It’s scrolled to the right position, so you can just continue reading there. I’m sorry, I know it isn’t great to shove people outside of their mailbox, but I don’t think there is any way for videos to work well inside emails, and a lot of Unsung is about precise videos. (The only thing allowed is GIFs, and they are really not up to the task.) Video playback. On that note, I improved the handling and controls of video playback. On mobile, you can tap to play/​pause and swipe left and right to move. On desktop, you can drag the handle, or also swipe left/​right. You can also use ← → keys to advance frame by frame. My goals are to have video controls that are both minimalistic (for example, never covering the contents) and precise, to match how videos are used here. (But if you tab to the video, it still shows “classic” controls for accessibility.) Blink comparators. You might have noticed that I added some blink comparators in a few posts where they seemed to be useful ( one , two , three , four ). Is that fun? Does it work for you? Because I have more ideas for light interactivity on Unsung. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/about-unsung-recent-improvements/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/about-unsung-recent-improvements/2.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/about-unsung-recent-improvements/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/about-unsung-recent-improvements/3.1600w.avif" type="image/avif"> Technical details. Some people asked technical details about specific things on this blog, so I added a technical details page with answers. Dashboard. If you are interested in that kind of stuff, I added some more charts and stats to Unsung’s internal dashboard (and deprecated sentiment, which wasn’t really working). #about unsung

0 views

Week 2 of daily driving the iPad

I’ve been thoroughly enjoying my new iPad , it’s pretty much replaced my personal laptop in my day-to-day. Surprisingly it’s also replaced my need for a desktop computer. Through the use of my USB-C dock and Apple wireless keyboard/trackpad, this little iPad works perfectly as a desktop machine. It drives my 32” Ultrawide perfectly, and the windowed multi-tasking is excellent. It can even use my webcam! The only downside I’ve really run into is my monitor is pretty old, so the refresh rate is much lower than the iPad making my mouse feel laggy when moving between the two.

0 views
Unsung Yesterday

“You are a printer we are all printers”

From Nilay Patel, a recommendation for the best printer of 2023 : Here’s the best printer in 2023: the Brother laser printer that everyone has. Stop thinking about it and just buy one. It will be fine! The Brother whatever-it-is will print return labels for online shopping, never run out of toner, and generally be a printer instead of the physical instantiation of a business model. […] I am telling you to just buy whatever Brother laser printer is on sale and never think about printers again. Patel did the same in 2024 and 2025 – you should check them all out if you want to smile, because they’re genuinely funny, as are some of the comments: I’ve been using one of these for 6 years. The low toner indicator came on about 7 months ago. I bought new toner. Reader, I haven’t replaced anything. It still prints fine, the new toner is still sitting on a shelf somewhere. Least frustrating printer I’ve ever owned. Would buy again. I’m sharing these on this ostensibly software-related blog not only because printer enshittification happens primarily via software . I wanted to share it also because this feels very similar to me to the post about TextEdit – a simple and deserved desire to own technology that works without any strange machinations, forced updates, and stress. #enshittification #hardware

0 views
Unsung Yesterday

Balls (practically) to the wall

The last post about the Nothing Phone not buffering its button presses reminded me of something. Here’s IBM Selectric, a 1961 typewriter: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/1.1600w.avif" type="image/avif"> Past decades get compressed into a singular point in time, so we might all think of Selectric as “yet another old typewriter,” and I definitely did before learning about it. But the Selectric came 80 years after the first typewriters, and it packed so much user-benefitting innovation it really was an iPhone of its time. (Alas, I don’t believe there was a matching “are you getting it?!” keynote.) = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/2.1600w.avif" type="image/avif"> Selectric was, honestly, a triumph of engineering. It popularized swappable typewriter fonts , showcased good industrial design, enabled jam-free typing, and even invented – although that came a decade after its introduction – an actual destructive Backspace . Crucially, on day one, its typing experience was so fantastic that many of the keys on keyboards we’re using 60 years later are still in the same place Selectric put them. What’s even more impressive? Selectric was purely electromechanical . It had no software, no chips, and no electronics. Everything it has accomplished was expressed in the mechanical language of steel, grease, links, and levers. Here’s one problem that’s trivial in software, but hard in hardware: How do you prevent people from pressing two keys at the same time? This is a thing that plagued typewriters since day one, and IBM’s engineers came up with a smart solution: each key was connected to a bar (interposer), each bar had a little protruding notch (lug), and that notch would smoothly dip into a little horizontal row of steel balls (selector compensator tube). = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/3.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/4.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/4.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/5.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/balls-practically-to-the-wall/5.1600w.avif" type="image/avif"> The balls had just enough wiggle room for one notch, so if you tried to press a second key at the same time, the balls would now be packed tight, there would be no room to accommodate the second notch, and the key press would be blocked. I thought that was really clever, but it was even more clever than that. If you read my essay , you know it starts with the very notion that fingers overlap: as one is going up, often another one is already pressing down. If you were to block any second press before the first press was completely done, you wouldn’t be able to type very fast – and Selectric was meant to be a professional typing tool. Here’s where the choice of the carefully sized and arranged steel balls came into play. In practice, the second press was not completely blocked. The lug was able to slide just a little bit in between the adjacent steel balls. It was a half press – or, effectively, a half-character buffer . It was all fine-tuned just enough to not impede overlapping typing, while still offering protection from two keys at the same time. Now, if Selectric did this, in a universe where creating even a half-character buffer meant a little row of carefully machined steel balls, and added weight, and anticipating future wear and tear, and multiple pages in the maintenance manuals… what’s your excuse? #hardware #history #keyboard

0 views

sqlite-utils 4.0rc2, mostly written by Claude Fable (for about $149.25)

I wrote about the sqlite-utils 4.0rc1 release a couple of weeks ago. Since we only have Claude Fable on our Max subscriptions for a few more days, I decided to see if it could help me get to a 4.0 stable release that I felt truly comfortable about, since I try to keep to SemVer and like my incompatible major versions to be as rare as possible. I started with this prompt, in Claude Code for web on my iPhone: Here's that initial report it created for me. There were some significant problems that I hadn't myself encountered yet - 5 that Fable categorized as "release blockers". Here's the worst of the bunch: 1. never commits and poisons the connection (data loss) ( ) runs its DELETE via a bare with no wrapper — compare at , which wraps correctly. The connection is left , so every subsequent call takes the savepoint branch ( ) and never commits either. Reproduced end-to-end: That's a really bad bug! Very glad I didn't ship that, although at least it would have been a bug I could fix in a 4.0.1 point release, not a design flaw that would force a 5.0. Over the course of 37 prompts, 34 commits and +1,321 -190 code changes over 30 separate files, we worked through the entire set of feedback in turn, making several other design improvements along the way. A weird thing about coding agents is that harder tasks like this one actually provide more opportunity to do other things at the same time, since the agent sometimes needs 10-15 minutes to churn away on a new task. I went out to enjoy the Half Moon Bay 4th of July parade, occasionally checking in and prompting the next step for Fable from my phone. Full details in the PR and this shared transcript . I switched to my laptop for the final review, which I conducted through GitHub's PR interface. The most significant changes relate to transaction handling, which was the signature new feature in the earlier RC . The new RC now includes comprehensive documentation on the new transaction model, the intro to which I'll quote here in full: Every method in this library that writes to the database - , , , , , , , , and the rest - runs inside its own transaction and commits it before returning. Your changes are saved to disk as soon as the method call finishes: The same applies to raw SQL executed with db.execute() - a write statement is committed as soon as it has run. You never need to call , and you do not need to close the database to persist your changes. There are exactly two situations where you need to think about transactions: You want to group several write operations together, so they either all succeed or all fail - use db.atomic() . You are managing a transaction yourself with , in which case nothing is committed until you commit - the library will never commit a transaction you opened. In reviewing Fable's documentation - I find that reviewing the documentation edits first is an excellent way to build an initial understanding of what has changed - I spotted this detail : and the automatic per-method transactions are designed for connections in Python's default transaction handling mode. Connections created with the Python 3.12+ or options are not supported, because and behave differently on those connections. I admit I hadn't thought about how would react to the more recent autocommit setting , added in Python 3.12. It turns out "behave differently on those connections" equated to almost the entire test suite failing, so I worked with the model to ensure that this difference would not break how the library works. I used to think that the idea of having one model review the work of another was somewhat absurd - it felt weirdly superstitious. The problem is it really does work - I've started habitually having Anthropic's best model review OpenAI's work and vice versa, because I've had that turn up interesting results often enough to be valuable. I prompted Codex Desktop and GPT-5.5 xhigh with the following: Which was enough to turn up two issues worth investigating: I pasted that into a fresh Fable session, which ran some experiments to confirm the problem: Both findings were confirmed. called first, which auto-commits writes, and only then checked — so committed the update before raising . And the commit lived at the end of the returned generator, so it never fired unless you exhausted the iterator — or an un-iterated call left the transaction open, contradicting what the changelog and docs promise. Here's the PR with the fix, and the full Claude Code transcript . Reviewing this code helped me build a better mental model of the edge cases of SQLite transaction semantics! I upgraded to the Claude Max $200/month plan (I was previously on $100/month) to increase my Fable allowance for the remaining time until the July 7th Fablepocalypse , when even Claude Max subscribers will have to pay full API cost for the model. I was curious as to how much this would have cost me if I had been paying those costs directly. At first I thought those numbers weren't available to me since I had run the work remotely using Claude Code for web, and then I realized I could run AgentsView inside that existing session to get that cost estimate! Claude figured out how to use the command and came out with the following: I'm very glad I'm on that subscription! I really should have followed my own advice and leaned more heavily into subagents with cheaper models. Here's what claude.ai/settings/usage is showing me right now: I have several other major Fable-driven projects on the go right now as well, with the goal of hitting 100% on that Fable bar just in time for the price increase. Here are the full release notes for the RC. I had Fable add these to an "Unreleased" section of the changelog as each change landed, reviewing them as it went. This has the neat side effect that the commit history of the changelog acts as a concise summary of each of the changes that went into the release. In the past I've had a policy of writing release notes by hand, but honestly these are better than I would have created myself. Release notes are a great example of writing that I'm OK to outsource to agents because they need to be boring, predictable and accurate. Breaking changes: Everything else: 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 . You want to group several write operations together, so they either all succeed or all fail - use db.atomic() . You are managing a transaction yourself with , in which case nothing is committed until you commit - the library will never commit a transaction you opened. [P1] sqlite_utils/db.py:663 now rejects non-row statements only after calling , and sqlite_utils/db.py:705 auto-commits those writes first. So raises but the update is already committed. That is a surprising side effect for a method documented as “can only be used with SQL that returns rows.” [P1] sqlite_utils/db.py:672 through only commits after the returned generator is fully exhausted. without iteration, or common usage, leaves the transaction open and the write can be rolled back on close. This contradicts docs/changelog.rst:15 and docs/python-api.rst:232 , which say it takes effect without iteration. Write statements executed with are now committed automatically, unless a transaction is already open in which case they join it. Previously they opened an implicit transaction that stayed open until something committed it - writes appeared to work when read on the same connection but were silently rolled back when the connection closed. Code that relied on rolling back uncommitted writes should use the new method to open an explicit transaction first. The transaction model is documented in full at Transactions and saving your changes . now executes its SQL as soon as it is called, rather than waiting until the returned generator is first iterated. Rows are still fetched lazily during iteration. SQL errors are now raised at the call site, statements such as are executed and committed immediately without needing to iterate over their results, and passing a statement that returns no rows - previously a silent no-op - now raises a recommending instead. A statement rejected this way is rolled back before the error is raised, so it has no effect on the database. Python API validation errors now raise instead of . Previously invalid arguments - such as with no columns, on a table that does not exist, or passing both and - were rejected using bare statements, which are silently skipped when Python runs with the flag. Code that caught for these cases should catch instead. and now raise if a record is missing a value for any primary key column, or has a value of for one. Previously such records - which can never match an existing row - were quietly inserted as brand new rows, or triggered a confusing after the insert had already taken place. and now raise a if called while a transaction is open. Previously they would silently commit the open transaction as a side effect of changing the journal mode, breaking the rollback guarantee of and of user-managed transactions. The class no longer has an method. It existed only to raise , since full-text search is not supported for views - calling it now raises instead, and the method no longer appears in the API reference. The command shows a clean error when pointed at a view. The no-op flag has been removed from the and commands. Type detection has been the default for CSV/TSV data since 4.0a1, so the flag did nothing - invocations using it should simply drop it. remains available to disable detection. now raises a if passed a connection created with the Python 3.12+ or options. and behave differently on those connections, which previously caused every write made by the library to be silently discarded when the connection closed. Fixed a bug where , and did not commit their changes, leaving the connection inside an open transaction. Their work - and any subsequent writes - could then be silently rolled back when the connection was closed. All three now use , consistent with the other write methods. The command now refuses to drop a view, and refuses to drop a table. Previously each would silently drop the wrong type of object if the name matched. Both now exit with an error suggesting the correct command to use. Migrations applied by the new migrations system now run inside a transaction, together with the record of the migration having been applied. If a migration raises an exception its changes are rolled back and it stays pending, so it can be safely re-applied after the error is fixed. Migrations that cannot run inside a transaction, such as those executing , can opt out using - see Migrations and transactions . and now detect the primary key or compound primary key of an existing table, so the argument is no longer required when upserting into a table that already has a primary key. can now be used to insert a row consisting entirely of default values into an existing table, using . ( #759 ) Improvements to the command: values that do not match any known migration are now an error instead of being silently ignored, now works correctly with migration files that still use the older class, and is now a read-only operation that no longer creates the database file or the migrations tracking table. now returns migrations in the order they were applied. New , and methods for taking manual control of transactions, as an alternative to the context manager. New documentation: Transactions and saving your changes describes how transactions work and when changes are committed, and a new Upgrading page details the changes needed to move between major versions.

0 views
Unsung Yesterday

If you’re a button, you have one job

One thing I was (and still am) worried about when it comes to my recent big interactive essay is that by showing all these classic desktop examples, the whole thing might appear old-fashioned, relevant only to a bygone era. Yet, the challenges it shows are universal. Here’s something I just spotted. This is how you rotate an image on an iPhone and on a Nothing Phone: It’s a pretty standard control – tap once to rotate counterclockwise, tap a second time to do it again, etc. – with a helpful transition of the photo’s orientation so that you don’t lose yours. Now, I’m going to exaggerate the problem a bit and tap 90-degree rotation quickly eight times . Eight times should result in what engineers call a “no op” – the image rotating twice in full, and ending up where it started. That indeed happens on the iPhone: But it’s a different story on the Nothing Phone/​Android: iPhone will remember and buffer the taps, so that the second, pending rotation will happen as soon as the first is done. The Nothing Phone button gives you a tap confirmation via both haptics and sound, and then ignores the tap if a previous rotation is still animating. Why does it matter? I often keep thinking about the framework of situational disability , stating that disability is not just something that happens to a few people and no one else. No, pretty much everyone will occasionally encounter a situation that will make them effectively disabled, and this is why accessibility matters much more than many of us assume: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-youre-a-button-you-have-one-job/5.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-youre-a-button-you-have-one-job/5.1600w.avif" type="image/avif"> I think similarly about casual and non-casual use. Photo-taking on phones is typically casual. Phone cameras are typically very good at detecting the photo orientation – but get confused when you’re pointing down. Now, as an example, if you had to take photos of a bunch of landscape documents, you might end up having to rotate dozens of photos, one by one. And it would be so much more predictable and pleasant if you could just tap the button three times at any pace you wanted without thinking, without paying attention, without getting your UI blocked by an animation that no longer helps you. This is, I suppose, “situational power user-ness.” Given a long enough timeframe – or, in this case, a large enough population – even a casual interface like phone photo editing (or, GarageBand ) will meet someone who will have no choice but to treat it more seriously and expect more from it. By the way, buffering the taps is not the only answer. You can also just stop/​accelerate the animation after an interrupting tap. But the rule is: never force the user to wait for the animation to finish. #android #flow #ios #touch

0 views
Unsung Yesterday

“The root of all margin-collapsing evil”

I liked this page I just learned of called Incomplete List of Mistakes in the Design of CSS . It might not mean much to you if you don’t write CSS, but could be fun to check out if you do. Here are some choice quotes: It reminded me of a similar list called Known Anomalies in Unicode Character Names . Here’s one example: U+02C7 CARON U+030C COMBINING CARON The “caron” should have been called hacek and combining hacek . The term “caron” is suspected by some to be an invention of some early standards body, but it has also been claimed by others to have been in use at Linotype before the days of digital typography. Its true origin may be lost in the mists of time. These are great because they simply say “this is how we messed up.” They are succinct and candid about problems. More work needs to be done at this point, of course – the CSS list only really contains the “simple,” low-level observations, and I think for both CSS and Unicode fixes cannot simply be made because people and systems rely on the existing behaviour – but the first step is admitting you have a problem, right? If you’re on the outside, it can be comforting to realize “oh, it wasn’t just me, other people don’t like this, too.” (Scanning bug reports from other users can help in a similar way.) If you’re on the inside, consider making a list like this for a long-standing project. It might do you or your team good! If you are aware of more documents like these, I’d love if you could send them over. #bugs #change management #process should have been . It shouldn’t be — that reads to engineers as “not important”. We should have picked another way to write this. should be .

0 views
Unsung Yesterday

“I know you think I’ve lost my mind, but trust me.”

A fun (and funny!) 9-minute video from Linternet User about designing a perfect onscreen lever with the right amount of juice : = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/i-know-you-think-ive-lost-my-mind-but-trust-me/yt1-play.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/i-know-you-think-ive-lost-my-mind-but-trust-me/yt1-play.1600w.avif" type="image/avif"> Love seeing real work in progress like that, plus it ends up in a place I didn’t expect. It was also great to see “delay and snap” action elucidated so clearly. It feels like a variant of rubberbanding (or, elastic scrolling) where you intentionally disconnect an object from the cursor or finger dragging it. #above and beyond #hardware #interface design #youtube

0 views
neilzone Yesterday

Chatting to a friend who has bought a pair of Meta AI glasses

I’ll be honest, I was a bit surprised that I knew anyone who would buy a pair of Meta AI glasses. Sure, they are readily available, and presumably heavily advertised, but most of the people with whom I spend time - online or offline - are not the kind of people who would buy them. I’ve got friends who use Facebook. I’ve got friends who use WhatsApp. But the glasses just - to me, anyway - feel rather different. Nevertheless, a friend did buy a pair, and was happy to chat about them. My friend - like me - is a married, middle-aged, white man. Unlike me, he saw the glasses in our local optician’s, and wanted them. We didn’t chat for long, and, to be clear, I was kind , not accusatory. And yes, he wore the glasses throughout our chat. It was an interesting conversation, because it was so unexpected. His choices would not have been mine, for sure. No, it did not worry him that using the glasses entailed sharing lots of data with Meta, or that this would entail sharing the personal data of other people Yes, he thought that they were rather cool, and that, it seems, was the main selling point. He didn’t need them for anything, he just fancied them No, he hadn’t really thought about whether other people might want to be filmed, or whether people might be worried about being filmed Yes, he had seen other people talking about them online, and they were talking positively about them. He hadn’t seen any negative commentary / criticism Yes, he uses “AI”, and rather likes it

0 views
Kev Quirk 2 days ago

This blog is written in en-GB

by Terence Eden Terrence talks about some of the wonderful idiosyncrasies of the British language and that, no, he won't be making his writing more global. Read post ➡ I really enjoyed this post and like that Terrance could have said "year sure, I'll try and be more inclusive for you non-Brits" , but he didn't. Instead he said: Here's the thing. No. [...] There's a reason for that. It is more than the language I speak; it is the culture I live in, the way that I think, and the accent I use. Love this, and I appreciate Terrance holding firm on our wonderful British culture - just like everyone should do on their blog. That's part of the fun - to learn about the idiosyncrasies of difference languages and cultures. It still surprises me that someone had the gall to leave a comment effectively saying "can you change the way you write to be more inclusive, because I don't understand some of the references, and I can't be bothered to learn." Some people... Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views
James Stanley 2 days ago

ProofToken16: My Proposal for Private Decentralised Age Verification

So there's this whole thing now about how under-16s aren't meant to be allowed on social media, and this is supposed to be enforced through technical means using Zero-Knowledge Proofs, or something. Here is my proposal. Let's disregard the declaration of the independence of cyberspace for a moment and assume that actually we changed our mind and we do want the government to be mandating age verification on the web, and let's say we don't mind the fact that enforcing "no social media for under-16s" actually places the burden on everyone over 16 to prove it. Let's assume the requirements are: websites have a way to test whether the user is over 16 websites can't learn any private information other than whether or not the user is over 16 multiple decentralised issuers can provide proofs-of-age new issuers can be created without websites having to be updated to accept them the website can't tell which issuer you used this whole thing isn't secretly a tool to expand the surveillance state, it is in fact narrowly implementing only age verification Where everyone else is going wrong with private age verification is that they're trying to encode extra information about the user in the proof-of-age (like their actual date-of-birth, or the actual issuer), and then construct a zero-knowledge proof that hides this information from the website. What I propose is that the proof-of-age that is issued to a user over the age of 16 only contains the fact that they are over 16 . We purposely don't put any more information in the proof, and that way we can be sure that no vulnerability can leak this extra information, and we save a load of complexity. When the proof only contains that single fact, that the user is over the age of 16, it turns into just containing a single boolean. And since we will never bother to issue a proof where that boolean is false , the proof doesn't even need to contain the value! All proofs-of-age contain equivalent content (asserting that the user is over 16) so we can take the value of the boolean to be true , as long as the proof is valid. So all that remains is to issue an empty-string proof to everyone who is over 16, and let websites check that they are genuine. So let's pick a 4096-bit private key, keep it secret from under-16s, and say that knowledge of that key is the proof of being over 16. This is the ProofToken16 . Use whatever kind of message-signing scheme you want to let the user prove that they know the ProofToken16 . Since we only provide the ProofToken16 to people who are over 16, the ability to sign a message using it is proof of age. QED. Any new issuer can start up a service where they check your age however they want and reveal the ProofToken16 if you are over 16, with no centralised control. And, crucially, the decentralised nature will not be a backdoor allowing under-16s to create false proofs. To become an issuer you need to know the ProofToken16 yourself ! Since under-16s won't know it, they won't be able to issue it. Since all of the issuers are providing the same ProofToken16 , the website doesn't have any way to tell the issuers apart so the scheme does not even leak that information. There are a couple of minor drawbacks to this scheme, I admit: I'm actually not completely sold on the idea that age verification is even a good idea. What happened to a cyberspace without borders? What happened to the free and unencumbered flow of information? Humanity has spent tens of thousands of years building up our technological capabilities, to the point that we now have a global communications network that lets any two people on the planet communicate with each other practically instantaneously and practically for free. But it seems like in the last few decades we have been putting more effort into limiting our technological capabilities than expanding them, this can not end well, this is how we architect the downfall of civilisation, please be careful. And beyond that, requiring grownups to submit themselves to age verification before they can communicate with each other is disrespectful, undignified, and humiliating. Someone who knows the ProofToken16 might give it to someone who is under 16. Note this is not a weakness unique to my proposal. What stops someone from handing their proof to a child under any other scheme? If the proof reveals nothing other than whether the user is over-16, then a website has no way to check whether all of its users are using the same proof anyway. It is a fundamental technical impossibility to verify a user's age with cryptography, the closest we can come is to issue proofs-of-age and teach people not to share. I think that my ProofToken16 scheme is as good as you can do under the proposed requirements. Any alternative has at least the same flaws and possibly others besides. So I have already implemented it. I have created a ProofToken16 number, if you want to find out what it is then email me with proof of age and I will provide it. This will then entitle you to participate in my MatureChat social media site for over-16s only. You will have to do message signing at the command line every time you login, for now, but I am working on a browser extension to automate it. (The key will also entitle you to start your own ProofToken16 issuance service, which could one day be very lucrative!) See you on the other side. Go to MatureChat » websites have a way to test whether the user is over 16 websites can't learn any private information other than whether or not the user is over 16 multiple decentralised issuers can provide proofs-of-age new issuers can be created without websites having to be updated to accept them the website can't tell which issuer you used this whole thing isn't secretly a tool to expand the surveillance state, it is in fact narrowly implementing only age verification I'm actually not completely sold on the idea that age verification is even a good idea. What happened to a cyberspace without borders? What happened to the free and unencumbered flow of information? Humanity has spent tens of thousands of years building up our technological capabilities, to the point that we now have a global communications network that lets any two people on the planet communicate with each other practically instantaneously and practically for free. But it seems like in the last few decades we have been putting more effort into limiting our technological capabilities than expanding them, this can not end well, this is how we architect the downfall of civilisation, please be careful. And beyond that, requiring grownups to submit themselves to age verification before they can communicate with each other is disrespectful, undignified, and humiliating. Someone who knows the ProofToken16 might give it to someone who is under 16. Note this is not a weakness unique to my proposal. What stops someone from handing their proof to a child under any other scheme? If the proof reveals nothing other than whether the user is over-16, then a website has no way to check whether all of its users are using the same proof anyway. It is a fundamental technical impossibility to verify a user's age with cryptography, the closest we can come is to issue proofs-of-age and teach people not to share.

0 views

A peculiar bug in Safari

On weekend mornings, I have the inescapable habit of looking at my website and seeing what I can change, what I can remove, what I can improve in terms of HTML, CSS, layout, links, etc. This Saturday, as I wanted to look closer at the way the period at the end of a sentence rendered when appearing just after a word in italic (I know), I noticed something curious. When I zoomed in the page, using “Command – Plus Sign” (⌘+), I could see that the line length was changing with the size of the text. The bigger the text, the longer the line. You see, I’m very protective of the I use on this site —  — especially for Mac users, who see it in the Charter font. *1 This value sets an ideal number of characters for each line making it, when paired with the right line height, easier to read (supposedly). Zooming in on text shouldn’t change the line length, so I looked around and realised that I was a bit clueless when it comes to identifying bugs, and even checking if they were already reported. I found a few bug reports related to zooming in, but none of them described my issue. Not only that, but I didn’t really know if this was a Webkit problem, or a Safari problem. So instead of working my way to either confirming an existing bug or filing a new one , I did what I usually do when facing a problem: I avoided it altogether rather than trying to solve it. Therefore I changed to in my CSS, resulting in a similar line length for Charter. *2 With as the unit, zooming doesn’t modify the line length, so I’m pretty happy with this easy fix. Bonus point: takes up the same number of bytes as in my default CSS, still capped at 132 bytes. Imagine the extra-byte horror if I had to use something like or ? It would have ruined my sunny Saturday morning. This little website update made me realise something: my site design is pretty much done, and I hadn’t changed anything for a few weeks or even months. I actually miss the satisfaction of changing something at the end of my little routine. Checking every detail on every page, revisiting every line of code just to see what can be improved, even if it’s just removing extra quotation marks in an attribute or an optional closing tag, is not as fun when there is nothing to do at the end. I really like my site’s current design, and even if there might be a few tiny tweaks like this one in the future, I feel that the overall look and feel is pretty much final. It’s a weird feeling, but now I have no excuse for not writing more, and publishing more posts, even if they are unfinished , or shorter than usual . For others, falling back to the default serif, usually Times New Roman, is indeed a bit narrow; or would be better, but it’s too wide for Charter.  ^ For the serif/Times New Roman fallback, creates a slightly longer line, which is atually better than what it was with .  ^ For others, falling back to the default serif, usually Times New Roman, is indeed a bit narrow; or would be better, but it’s too wide for Charter.  ^ For the serif/Times New Roman fallback, creates a slightly longer line, which is atually better than what it was with .  ^

0 views
マリウス 2 days ago

Making My Content More Easily Digestible

Over the past few months a recurring theme has emerged in my inbox, as well as within the community channel , and it is one that I have been chewing on for a while now. Several of you have, kindly and very politely, told me more or less the same thing, which is that even though the topics I write about are interesting enough, the posts themselves have grown so long and so dense that actually getting through one of them has turned into something of a commitment rather than the casual read it probably ought to be. I cannot really argue with that, because it is true. Whenever I sit down to write about something like Bureaucracy is Eating the World , or A Word on Omarchy , or Doubting Your Favorite Web Search Engine , I find myself pulled in two directions at once. On the one hand I want to be accurate and diligent, which in practice means citing my sources, anticipating the counter-arguments, and walking through the reasoning (and in many cases evidence) step by step instead of asking the reader to simply take my word for it. On the other hand I am painfully aware that the result of all that diligence is, more often than not, a wall of text that runs to several thousand words. In those posts in particular I clearly landed on the wrong side of that trade-off, and the feedback was entirely fair. The information is, I think, worth having, but the packaging asks a lot of the reader, and not everyone who might benefit from the content has the hour or so of uninterrupted attention that getting through it properly demands. Note: Yes, I am perfectly aware of the irony of writing a not-exactly-short post about how my posts have become too long, but bear with me here for a moment. What the feedback really did, though, was hand me an idea. Rather than butchering the original write-ups down to a length at which they would lose the very nuance that justified writing them in the first place, I figured I could instead try to produce a second, more compact version of my most detailed pieces. One that lives alongside the original rather than replacing it. I decided to start with Bureaucracy is Eating the World simply because it is one of the longest, and densest, and newest write-ups. And when you ask yourself what tends to be more digestible than a multi-thousand-word essay, the answer that most people arrive at almost immediately is video and audio , both of which you can consume while doing the dishes, commuting, or otherwise not staring at a screen full of paragraphs. So I started fiddling around with a whole handful of different programs and apps, trying to work out a reasonable pipeline for turning the written text into something more compact and considerably easier to consume, and it turned into a much deeper rabbit hole than I had naively assumed it would be when I started. The first piece of the puzzle was the narration, and here I worked my way through a zoo of “AI” text-to-speech services before ultimately settling on a service called ElevenLabs to generate the spoken version of the existing post, mostly because the quality of the output was, to my ears at least, the least robotic and the easiest to listen to for any extended stretch of time. Now, the obvious question, is why I would hand my own words over to a machine to read out loud rather than simply recording myself, which would arguably be more authentic and would certainly have involved less fiddling. The answer, predictably for anyone who has read more than a post or two on here, is privacy . Your voice is not merely a sound, it is a biometric identifier, just as much as your fingerprint or the geometry of your face, and the moment you put a sufficiently long, clean recording of it onto the public internet you have effectively handed anyone who cares to grab it the raw material they need to clone it. Voice cloning has, over the past couple of years, gone from an expensive novelty to something that runs on consumer hardware off a few seconds of reference audio, and it is already being used in the wild to defraud people, whether that takes the shape of the classic “grandchild in trouble, please wire money” phone call, or the more targeted corporate variety in which an employee approves a transfer because the “CEO” apparently rang and asked them to. On top of the outright fraud there is the machinery of surveillance capitalism, which will happily fold a voiceprint into the (shadow-)profile it is already busy assembling on every single one of us, cross-reference it against the recordings collected by smart speakers, call centres, telecommunication companies, and who knows what else, and then use it as yet another durable identifier that follows you around regardless of which account you happen to be logged into or not. I am simply not willing to surrender my right to my own voice, along with a measurable chunk of my privacy, in exchange for the modest convenience of having a blog post read aloud, especially not when a machine can today do that very job equally well and at a quality that is, for this particular purpose, entirely sufficient. With the audio sorted, I needed something for the viewer to actually look at, and this is where the project spiralled into something far more involved than I had anticipated. My initial plan was to do everything in Blender , which is the obvious, powerful, free and open-source choice, but the learning curve on Blender is famously steep, and after a few evenings of mostly fumbling around I had to be honest with myself about the fact that I was spending far more time fighting the software than producing anything watchable. I therefore ended up reaching instead for Source Filmmaker , or SFM , the slightly ancient animation tool that Valve built on top of the Source engine, purely because its learning curve is so much gentler than Blender ’s and because it let me get the job done without first having to become a 3D animation expert. Where things became tedious, however, was the animation itself. My first instinct was to take the lazy route and let motion capture ( “mocap” ) do the heavy lifting, so I gave Rokoko ’s video-to-mocap tool a try, hoping that I could simply feed it some footage and get usable animation data back out, but it failed pretty miserably, probably because I didn’t have the space nor the equipment (multiple cameras) to set it up properly. I then went looking for alternatives, and discovered that you can, for instance, pair an old Xbox Kinect with various bits of software (the likes of Brekel ) that are able to spit out FBX files, which in turn can be used to drive the characters. The catch is that the pipeline of exporting the SFM animation, importing it into Blender , and then using Rokoko ’s retargeting plugin to map the captured motion onto the SFM model is a fiddly, multi-step affair, and the end result, no matter how patiently you tweak it, will never come close to what you would get out of Rokoko ’s actual motion-capture suit and gloves, which I do not own and was not about to buy for a single experimental video. So I abandoned the shortcuts altogether and animated every sequence by hand instead, and even though the individual sequences are fairly simple and relatively short, doing it this way still took a considerable amount of time and not a small amount of patience. SFM is, after all, a fairly old piece of software that carries a noticeable amount of quirks, and the StarBook that I happened to be running this entire experiment on was, to put it generously, never the right tool for 3D animation work in the first place. To make matters slightly worse, I was unable to coax SFM into exporting anything above 720p, no matter how I adjusted its startup parameters, because anything beyond that resolution would come out glitchy and unusable, so 720p is, for this first attempt at least, simply what we are working with. All of this rather long-winded preamble is simply to say that what follows below is a first experimental attempt at presenting one of my denser posts in a format that some might find easier to digest than the original wall of text, in the hope that it piques the interest to dive deeper into the topic. The whole point of this is to find out whether the slice of my readership that feels buried under several thousand words actually prefers something like this, or whether the effort is better spent elsewhere. Keep in mind that the video is nevertheless a compressed version of the original post, that does not include every little detail, as it would have otherwise, too, grow out of proportion. You can find the result here , or, if you happen to have JavaScript enabled despite my warnings , below: If the response is positive, then I might well turn these into a more regular thing. If it is not, then at the very least I will have learned a fair bit about text-to-speech, Source Filmmaker , and the dark art of motion capture along the way, which is hardly the worst outcome. Either way, I would very much appreciate your honest feedback on this, so please do let me know what you think, whether this format could work, whether the pacing and the visuals help or hinder, and whether this is something you would like to see more of going forward. As always, you know where to find me .

0 views
David Dodda 2 days ago

Why Don’t Websites Put All Their Images Into One Giant JPEG? (Nerd-Sniped by My Brain)

I had a simple question: Why do websites load lots of individual images instead of stitching them into one giant image and cropping out the pieces they need? At first glance, an image atlas sounds great. Instead of this: You create this: Then each UI tile crops a specific region from the atlas. That would mean: fewer network requests images arrive together no staggered popping maybe better perceived loading maybe less request overhead Not a new idea by any means. Games and UI libraries have used sprite sheets and texture atlases forever. The question is: why isn’t this the default for websites? I compared three approaches: Individual optimized images 14 separate optimized JPG files rendered as normal elements Canvas atlas one stitched atlas JPG each tile rendered by cropping from the atlas into CSS background atlas one stitched atlas JPG each tile rendered with , , and The atlas was regenerated from the same optimized images, so the comparison was more fair. NOTE: I ran the experiment by hosting it locally. so all the number you see are when you have the application served using a python server running locally. If you want to poke at it yourself, the experiment is live here: https://daviddodda.com/experiments/img-atlas/ note: make sure you disable cache. try each version a couple of times. I focused on three headline metrics. How many bytes were downloaded? When did the last required image resource finish downloading? When was the image grid actually ready to see? This last one matters because network completion is not the full story. The browser still has to decode images, rasterize, paint, composite, and show pixels. On a remote machine running Chromium, all files hosted locally, 10 runs each: The surprising result: The CSS background atlas was the fastest to visible. The atlas had a clear network advantage: Well, one larger request has less overhead than many smaller requests. This effect is especially visible when the server/browser are using less optimal connection behavior. In my test, Chromium reported for the local server, so request overhead was more obvious than it would be under HTTP/2 or HTTP/3. With modern HTTP/2 and HTTP/3, many individual image requests are less painful because requests can be multiplexed over one connection. But request overhead still exists. The individual images transferred: The regenerated atlas transferred: Because an atlas is a rectangle. Real images have different aspect ratios. When you pack them into one big rectangular sheet, you often create empty space. In my case: That is about 31% extra pixel area. So even though the atlas used one request, it transferred more data and required the browser to decode a bigger image surface. The canvas atlas looked like it should be fast (thought modern hardware was fast enough). It loaded one atlas image, then cropped each tile into a canvas. But the results were poor: The breakdown showed: The actual JavaScript canvas drawing was not expensive. The expensive part was making all those canvas results visible. That means the bottleneck was not: It was the browser’s later paint/composite work. The CSS background atlas used normal DOM elements: This was much faster: The breakdown: The decode cost was still there. But paint/composite was dramatically better than the canvas version. So if you are going to do image atlasing in normal web UI, CSS backgrounds may be much better than drawing many cropped canvases. They are great for: emoji sheets game textures small repeated UI assets known fixed-size tile sets maps or tile-like interfaces cases where all assets are needed immediately They are less great for: photo galleries blog images user-generated content responsive images content-heavy websites long scrolling pages frequently changing assets now, don't go getting any ideas about rewriting your website's image pipeline to use image atlas. here are some reason why it's a really bad idea. With individual images, the browser can load only what is needed: With a giant atlas, loading one image means loading everything in that atlas. That is great if you need everything immediately. It is terrible if the user only sees 5% of the images. The web has powerful responsive image tools: The browser can choose the right image for the device, viewport, DPR, and network. With a giant atlas, this becomes much harder. You may need multiple atlases: The combinatorial complexity gets ugly quickly. Atlases require packing. Packing creates waste. If the images have different shapes, the atlas may contain a lot of empty or unused area. Even a good packing algorithm cannot always avoid this. In my test, the atlas had about 31% more pixel area than the individual images. With individual images: Only that image needs a new URL/cache entry. With an atlas: The whole atlas cache is invalidated. That is bad for websites where content changes often. Browsers are good at prioritizing resources. The hero image can be high priority. Below-the-fold images can be lazy. Tiny thumbnails can wait. With a giant atlas, everything has one priority. You cannot easily say: The atlas is all-or-nothing. A compressed JPG might be 2 MB on the network, but decoded pixels are much larger. Decoded RGBA memory is roughly: A large atlas can become a huge decoded surface. In my first broken atlas attempt, the atlas was: That is around: Even if the file downloads quickly, that is a lot for the browser to decode, rasterize, and paint. An has natural semantics: A CSS background image is decorative by default. If the image is meaningful content, you need to rebuild semantics with ARIA or hidden text. That is doable, but it is extra work and easier to get wrong. Browsers have spent decades optimizing: If you use an atlas, you bypass some of that machinery and take on more responsibility yourself. Sometimes that is worth it. Often it is not. Every approach has its niche use case (shocker). My brain nerd-sniped me into exploring and writing about this. It was fun seeing the cute animals load in though. fewer network requests images arrive together no staggered popping maybe better perceived loading maybe less request overhead Individual optimized images 14 separate optimized JPG files rendered as normal elements Canvas atlas one stitched atlas JPG each tile rendered by cropping from the atlas into CSS background atlas one stitched atlas JPG each tile rendered with , , and emoji sheets game textures small repeated UI assets known fixed-size tile sets maps or tile-like interfaces cases where all assets are needed immediately photo galleries blog images user-generated content responsive images content-heavy websites long scrolling pages frequently changing assets

0 views
Unsung 2 days ago

A simple notification preview in Retro

A nice and I think effective notification preview in Retro , with a verbatim sample text of a notification right below its name: Not only you can see exactly what you’re going to get and make a much better-informed decision, but the app even uses actual names of your in-app contacts, so you can relate to the notifications more. #attention #preview

0 views
Brain Baking 2 days ago

Favourites of June 2026

The beginning of this month marks the official end of my own company. After just two years of establishing and owning Brain Baking BV , the notary ended it. There have been no professional activities related to the company since I switched back to education in December so for me it made little sense to keep that door open only for the monthly administrative costs to pile up. I hope to build a bit more stability this time around, both on personal and professional level. My statute as lecturer has been extended for a year: so far, so good! Previous month: May 2026 . A few very short ones and one quite big one that I ended up enjoying very much. DreadXP, the developers behind Dread Delusion , also recorded dev diaries on YouTube: Related topics: / metapost / By Wouter Groeneveld on 3 July 2026.  Reply via email . The Aching —a Sierra On-Line-like adventure game weighing less than that runs on any 8086 machine. It also happens to be good, even though it feels more like an introduction of this horrified world. Serious Sam: The First Encounter —I started replaying this two years ago and finally pushed forward a bit more. After endless complete freezes of my Win98 machine I gave up. AAAAAHHHHHH boom . I remember liking this a lot more: it’s…. bland? Dread Delusion —A weird looking game that I was drawn to the first time I laid eyes on screenshots a few years ago. I remembered it and felt the time was right to crack this one open. It’s one of the best games I’ve played in the last years. I recorded a playthrough log to convince you to drop everything and go play it as well! Lucy Dreaming —A lovely classic nineties adventure game that’s perhaps playing it too safe to try to be an homage to Monkey et al. ? I still enjoyed myself but the abrupt ending was a bit of a letdown. Speaking of The Aching , the developer explains their philosophy behind the Gorgon Engine . Interestingly, Gorgon is designed to be small and able to run on older original hardware, while new adventure games that look and feel old like The Telwynium are made with PowerQuest for Unity and require hundreds of megabytes. Nobody really cares, but I do. This ACM paper on a conceptual model for ownership types in Rust sheds new light on how the borrow checker works from an educational point of view. More Rust-y stuff—even though I have yet to touch the language—Michael Neumann investigated how long it takes to compile Rust from source compared to other languages. Hint: looooooonnnnggg. As in lonngggggggggg. James also printed his blog in book form years before I did! He selected all coffee-related articles to create a lovely personal caffeinated hardcover. Games That I Missed documents progress on their pinball machine projects . That old electronic stuff inside the machines is mesmerising. Phil Gyford laboriously kept track of how much money he spent each year on music for the past 30 years (via ) In a timely manner, Miss Booleana wrote about Claire Dederer’s Monsters: What Do We Do With Great Art By Bad People? . I asked myself the same question recently and added the book to my toread list. Andrew Webster publishes a Great Truth on The Verge: The Nintendo DS is still the best gaming handheld for travel . Yup. Another paper that confirms LLM-driven gender bias in citations in academic work . James Pennebaker confirms what I’ve been thinking and feeling: expressive writing can influence thoughts, feelings, and behaviours . The link is a past event but a good starting point to find publications by Pennebaker. Chris Kirk-Nielsen begs us to start playing indie games . Stop that Assassin’s Creed nonsense: scroll up and watch the Dread Delusion dev diary instead! Nic tringali sometimes feels the creative drudgery . A surprise ending is in it for you if you decide to read it. Jeff Gerstmann finally decided to apply Rigorous Science (TM) to compile an exhaustive (!!) list of the best NES games ever released in USA . Number one is NOT Mario nor Zelda! I particularly enjoyed Erik Hane’s piece in Typebar Magazine on fandom strain and the IP illness killing Magic: the Gathering . The magazine really is “An interesting thing to read on the internet”, as their footer claims. In a post called Cultures of making and relating , Konrad Hinsen brings the recent Cultures of Programming book our attention. It’s been an open browser tab ever since. Memray looks like an interesting memory profiler for Python, if I ever would need one. GentleOS is a friendly hobby OS for 32-bit PCs. The Corporate EU Observatory revealed that Big Tech invested almost 50% more in lobbying throwaway money ( !) compared to 2020. Diablo II has a new class: the Warlock . I really wish it was playable without the remaster though. Warp Point is a curated list of indie video game websites and Jefklak’s Codex is in it.

0 views
David Bushell 3 days ago

Fixing full-bleed CSS

I’m a front-end developer not a medical practitioner. If you’re bleeding IRL visit the hospital and stop googling medical issues! The full-bleed layout — as described there by Josh Comeau — can be done with CSS grid ( and subgrid ). Sometimes you can’t grid the entire page. That’s where Andy Bell’s utility class is useful. But it ain’t perfect. The issue is that viewport units don’t solve the classic scrollbar problem . If you’re on macOS or a fancy OS that has fancy scrollbars, test on Windows! can be wider than the viewport. Because why would browsers do anything sensible? It’s hard to see in Andy’s CodePen but a few pixels can be cropped either side. Add something like a border or shadow and it’s easier to see. This is not always a problem but it can lead to subtle alignment issues. By the way, macOS has a scrollbar setting “Show scroll bars > Always” that’ll let you test the issue. Andy solves this partially by hiding horizontal overflow on the element. An alternative fix is to always reserve space for the classic scrollbar. That can look weird if there is no vertical scroll necessary. The “modern” approach is to use CSS containment . Turn the element (or any 100% width child) into a container. Then replace the viewport units with container units. Now hiding overflow is not strictly necessary. I prefer — see Overflow Clip guide by Ahmad Shadeed. I clip out of caution because I make dumb things. I also use logical properties and values to support right-to-left (RTL) text direction. Ahmad has an excellent RTL Styling 101 too. Using units assumes is the parent container of the element. What if we have nested containers? Check this out. I’ve forked Andy’s CodePen to add another container that is not the full viewport width. This alone would usually break the new class and ruin the fun. But we can fix that! What is that magic? To be honest I struggle to wrap my smooth brain around this! Let me try to explain it to myself. Without the at-rule the value of is calculated at the time of use, i.e. within and therefore relative to the container. By explicitly defining a the value is now calculated when it’s set within . There refers to the parent container and inherits that value. But what if you have more containers? (╯°□°)╯︵ ┻━┻ Gosh! Stop being so difficult! I would have a direct child of like set the value. † I’m multiplying by 0.5 because division is for chumps. What CSS needs is a way to reference a container when using container units. Ideas have been proposed for example: Cancel Interop 2026 and make this happen! Thanks for reading! Follow me on Mastodon and Bluesky . Subscribe to my Blog and Notes or Combined feeds.

0 views