Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
1
Over the past couple of years I’ve been building KidzFun.art, an art & education app for my young kids and hopefully yours. The first feature I ever added was simple colouring pages, hand drawn by my lovely and talented wife. However that was a slow and laborious process, and with the advances in AI since I began, I decided to add the ability for young children to generate a near infinite number of fun, age-appropriate, colouring pages using AI. I chose to use Open AI’s Dall-E for this, partly as an excuse to use their SDK in production, which is generated by a fantastic company I’m advising called Stainless. There were four main things required in order to ship this feature: Use the Open AI Node SDK to generate the image. This turned out to be by far the simplest, hat tip to Stainless and the Open AI team Build a UI suitable for young children that gave them the ability to easily create an infinite number of colouring pages, while ensuring that the generated content is age-appropriate. Allow parents (but not their kids) to pay for the Open AI costs, so I don’t go broke. Cache as much generated content as is feasible to minimize the costs Using the OpenAI Node SDK This part was trivial. I created a new NextJS API endpoint that accepted a query parameter, created the OpenAI object using either an API token provided by the user (more on that later), or the default one on my account. Then simply call the openai.images.generate function and after a few seconds (it’s not particularly fast) it returns you an array of URLs, in my case just one. A UI suitable and safe for young kids To make the UI simple, I created a tabbed UI that let kids selected up to ten things to put into the colouring page. When testing this with my 5 and 7 year olds, they found it intuitive – you can’t beat having your users living in your house! Some older kids may want more control, so I also allowed them to type in the image description manually, using the pencil icon you see in...
9th Apr 2024

Stay updated

Get a weekly newsletter with the top 5 articles worth reading every week.

More from SOS

A new home for all my apps: apps.chofter.com

Say hello to apps.chofter.com, the new home for everything I build. The speed at which I ship new things has massively increased with the advent of AI, and I’ve been encouraged by the fact that, as I approach my sixth decade, new ideas for useful & fun things to build still come at me apace. These no longer fit on my personal homepage, which is a combination of work experience, interests, things I made long ago that are no longer relevant (but still interesting), and of course, the many apps & tools I have out there in the wild right now. The site was 100% built using Claude Code, which did an amazing job of inspecting all the various websites, app stores and code bases and constructing a site in 30 minutes or so. I had to push it to make the site more SEO friendly, pre-rendered to HTML rather than over relying on client side rendering, but that was it. So there we go, enjoy the delightful and hopefully useful apps that I’ve already built and will continue to build in the future

2 weeks ago • 1 votes
Clean your Mac & Windows/Linux PC with Disk Space

TLDR: Download the best disk cleanup app in existence from diskspace.io. A fun experiment in writing the same app three times (well kind of 4 times) with AI, and being as optimised and OS native as possible. My Mac recently filled its 1TB drive and my quest to find out where the space had gone was very frustrating. The existing tools for tracking down disk space were slow, clunky and it was impossible to use them until their 30 minute crawl of my hard drive completed. I decided to fix this by building a new app called Disk Space, which takes the great features of my favourite old time app Disk Inventory X, makes it blazingly fast and adds much improved file/folder deletion aesthetics so that you can clean up safely and quickly, as well as highlighting recently created files so you can find what changed more quickly. Let’s get nerdy The three versions, Mac, Windows and Linux, are all mostly independent, with just a little shared C++ code. My goal was to make each app as small, fast, as native to its environment as possible. This meant not using any of the more common cross platform libraries, and leaning on Claude to do the work. MacOS The MacOS version of Disk Space is written fully in Swift, with no dependencies on other libraries. This was the first version I built. It detects how many CPU cores your machine has and optimises itself to maximise the throughput. The size of the installed app is just under 1MB, with about 300KB of that being multiple sizes of the icon, so the app itself is just over 600KB in size. Not bad! Releasing desktop apps for the Mac is actually not too bad an experience. You can choose to put it in the App Store, but then it can be a pain to release a new version. Apple offer a free notarisation service that signs the built app with your Apple Developer credentials, and it works well. For this reason, you can simply download the MacOS version of Disk Space from the site. Windows The Windows version is a direct port of the Swift code to C++. Similar to the MacOS version it optimises its operation based on the number of cores, but it also checks if the drive being scanned is a spinning disk or a solid state drive. If it’s a spinning disk there’s no point running many threads against it, it’s physically incapable of responding, so it caps the number of threads. Since it it just pure C++ with no dependencies pulled in, the installer is just about 600KB, pretty cool. Releasing apps on Windows these days generally means you are forced to either release through the Microsoft Store or pay for quite expensive yearly fees to have your app notarized. Without this, the user will be shown very scary warning dialogs, making the app very hostile to use. Since this is a small free app, I went the Microsoft Store route. Linux The Linux version shares some of the C++ with the Windows version, especially the code that draws the multi-coloured tree map on the right. Similar to the Windows version, it checks the hardware of your storage to best optimize itself and otherwise builds the UI using Linux native code with almost not dependencies. The first version Claude recommended depended on the GTK libraries for the UI, which was convenient, but it meant that using the app on any system that didn’t include those libraries would force the user to download hundreds of megabytes just to get a 500KB app running. Luckily, within an hour Claude had completely rewritten the app to be almost fully self contained. This means that the Linux app, which is packaged as an AppImage file, is just about 600KB all in, and you can simply download it from the site. Epilogue This was a fun experiment in building an identical app for all three operating systems while keeping it as native and optimised as possible. The hardest part was the hardware setup required for testing. I now have on my desk: My MacBook Pro (my primary machine). I do most of my work on this, run Claude and do all testing of the MacOS app. A small but powerful Windows Desktop. This is pretty great, as not only can Claude build and test Windows apps on it, it can also build and test (to some degree) Linux apps too. I use this as the main machine for those two operating systems. An ancient, 2009 MacBook Pro 17″ that I installed Linux on just for building this app. I use this for testing the Linux version on a real machine, not just on Windows WSL. It works relatively well, but with just 4GB of memory I won’t be doing any development on it any time soon. Still, it’s great to make use of the old hardware instead of throwing it away – I knew there was a reason I hung on to it! … a lot of messy crap I need to tidy up. Any day now…. I can’t believe you read this far, thanks! Now go get Disk Space from diskspace.io, your hard drive will thank you

3rd Sep 2026 • 1 votes
Huge under-the-hood upgrade to Animations in Kidz Fun Art

One of the absolutely coolest features of Kidz Fun Art is the ability to create Animations. This was initially inspired by watching my nieces creating an animation on another Android app, so I focused purely on the creation case. This worked well, where most animations were under 50 frames in length, as it takes time to create them. I later added the ability to import Gif images, since it was a relatively simple change – parse the Gif image into frames, save them and boom, you can edit and re-export it. However this exposed a problem: Gifs can be huge, and Kidz Fun Art didn’t work well with thousands of animation frames of data. I wasn’t sure where the bottlenecks were, but at some point the app would just crash if the Gif was big enough, of if the user created an animation over 100 frames or so. This is all now fixed, and the app comfortably imports multi-Megabyte Gif images and provides a better user experience when some operations (like deleting hundreds of frames) are not instant. Using AI to find performance issues & subtle bugs While I have always written the vast majority of the code for Kidz Fun Art by hand (using AI for more complex things like WebGL shaders and C++ based paint brush simulation), it’s been invaluable recently for reviewing and testing the code. I asked Claude Opus 4.8 (the current frontier model as of July 2026) to identify the bottlenecks and it did a great job. There were multiple places where I initially wrote a function to take an action on a single frame that would then save the full animation, but later reused this function in a loop over all the frames. This caused the full animation to be saved hundreds of times in a few seconds, crashing the app. The list of frame thumbnails at the bottom of the screen rendered all thumbnails up front. This is fine for 50 but not for 1000. When saving a Gif, the file was far too large. This is because it wrote each frame in its entirety to the Gif image. The Gif standard obviously supports just writing the pixels that changed from the previous frame, and I wasn’t doing that. Deleting a large animation would take multiple seconds, with not user feedback. This was fine for other media types as they are more or less instant, but in this case it let the user click around the app, then have unexpected things happen 5 seconds later. It made the app feel broken. Gifs that stored some frames with the option to simply restore the previous frame were not handled, making the import of some images be inaccurate. There were a number of places where race conditions between multiple asynchronous actions could cause bugs. The AI was very good at finding places where my code should have been waiting for one to complete before beginning the second. There were multiple places where memory leaks occurred, specifically with not cleaning up event listeners. It found them all. When leaving the app open for days or weeks at a time without a reload, this could have been a real problem. What is better now? Animations now scale up to very large sizes, with instant access to all frames. You should be able to import basically any reasonable Gif image, and it will be exported in a highly optimized manner, with perfect colour matching per pixel. When deleting a large animation, you are told it is being deleted immediately, so it’s obvious the app is doing what you asked it to do. When importing a large Gif, you are shown a dialog telling you that it is happening, and blocking you doing other work until that completes. Many subtle bugs fixed. The frame list is fully virtualized, and scales up to essentially any size of animation. We have tests now! Another great use of AI is for writing tests, in this case laboriously creating dozens of large integration tests. These were invaluable in both validating the deep changes being made and in finding more edge cases and race conditions. Kidz Fun Art now has full end-to-end tests covering animations, layers, comics, cards, drawing, colouring, handwriting, maths and puzzles. I hate writing these, but AI doesn’t get bored, and I look forward to adding more and more regression tests in the future to keep quality high for all the world’s young artists out there.

2nd Jul 2026 • 1 votes
Spirograph fun in Kidz Fun Art

For a long time I’ve wanted to add Spirographs to my (awesome ) drawing app for kids, Kidz Fun Art, and today it’s ready! There was quite a bit of fun mathematics in getting it to feel natural and work with all sizes of circles, but it seems to have worked out very well! You can move the Spirograph around, change the size of the outer and inner circles, and draw in any colours you like. Read more about it on the main blog post here, try it out on the web at https://kidzfun.art , get it for iPad here, or download for Microsoft Windows here.

12th Jun 2026 • 1 votes
Mazers – a WebOS app rises again on iOS & iPad

Way, waaayyy back in 2010, I built a fun little game for the Palm WebOS series of phones called Mazer. I was happy with it, loads of people downloaded and played it, and then WebOS died. I recently found the source code again, and with the help of Claude AI I rewrote it to run on iOS and iPad! Get it for free today from the iOS App Store. (Android version coming soon) There are four different game types You can find your way around a simple maze, or race a terrifying fiery ball to the finish. Over 120 hand crafted obstacle courses to get around with worm holes, force fields, evil fiery balls, and more. My personal favourite, a Pacman like maze where the four ghosts chase your little ball around as you try to open the portal and get outta there!

8th Apr 2026 • 1 votes

More in programming

BazelCon 2026 recap

My now-traditional “summary” of the conference along with my own commentary and thoughts.

17 hours ago • 1 votes
The Future of CSS ✨

Every year, browser vendors collaborate via the Interop project to synchronize their efforts on new HTML/CSS/JS features. This year, there’s a ton of super exciting features being considered! In this blog post, we’ll explore the CSS of the future, to get a glimpse of what we’ll soon be able to do.

2 days ago • 1 votes
Speeding up code review with `small-change`

Imprint’s average engineer is merging 4x more PRs than 2 years ago. Yay. Aggregated behind the average is something even more interesting: we have two teams that are merging 10x their PRs over that period. One engineer is merging 25x the PRs per week of our average engineer two years ago, and a number are merging 10x. That’s pretty neat, and the slope of the line suggests our average in a year is going to look much closer to today’s p90 than today’s p50. Even more importantly, we aren’t just shipping more, our quality has improved: incidents are down 42% half-over-half, and critical incidents are down 78% half-over-half. I think we’ve always known the quality/velocity tradeoff wasn’t a particularly useful construct, but that’s undeniable today. As we’ve walked this path, we–like everyone else progressing down it–have run into the problem of pull request review. The p50 pull request review took about 33 minutes to complete 2 years ago, but now takes us about 77 minutes. The slope here correlates tightly with the number of pull requests getting merged. Tightly enough that addressing it became one of my priorities for the week. We solved it! Just kidding, we’ve absolutely not solved it, but I think our current approach is an interesting one, which we call the small-change. The work discussed here was shaped and done by many folks at Imprint including Dan, Joy, Daniel, Lenny, Taylor, Ted, and Annie, among many others. Why review pull requests? There are four reasons we review pull requests: Knowledge sharing between author and reviewer – software engineering is generally learned via apprenticeship, and reviews are a primary mechanism of experienced engineers teaching others what good software looks like. We believe the current volume means that this knowledge sharing will have to move towards operating on clusters of pull requests, rather than individual pull requests Preventing errors – review has always been an expensive time to catch errors, and we believe that effective CICD along with harness verification techniques like poteto’s pstack are more effective mechanisms for preventing errors than review Preventing malicious behavior – my experience is that even careful code review will generally miss competent malicious behavior, although fortunately most malicious behavior is incompetent. We believe both sorts of behavior are best identified using a combination of fixed rules (modifying these specific objects/classes/files requires review), anti-abuse mechanisms that we’ve refined in our LLM-based member support experiences, auditing, and audit log inspection Compliance obligations in our documented controls – these can be changed, but today they require that an independent human reviews changes in a subset of our repositories In the modern development paradigm, we think there is a better solution for each of these problems. But, we also want to make these changes very cautiously. Small steps with small-change We believe we move fast by finding narrow, contained ways to validate new practices, so to iterate our way into an effective solution, we’ve developed the concept of a small-change. Here’s how it works: When a pull request is marked ready, CICD determines whether it is eligible to be a small-change. This is represented as a passing Github check. There is also a label for visibility, but it is the check itself, managed by the build and not human modifiable, that determines eligibility. (The label is used for initial filtering, but the check is still independently verified.) Something is a small-change if it meets all of these criteria: It is less than 1,000 lines of code There are no p1 or p2 findings from our ai-code-review automated review bot The author has “experience” in the service or application, defined as 10+ prior merged pull requests into that service or application’s directories. For changes working across multiple services or applications, the author must have experience in all of them to qualify Impacts fewer than N distinct services or applications. N is 1 on our frontend monorepo, and N is 3 in our backend monorepo, where typical changes will modify both a gRPC service and a common HTTP gateway Shared libraries and utilities files are not eligible for small-change Each repository has a .small_change file which allows per-path customization while we continue to tune the behaviors. For example, a service dealing with money movement can be excluded Our Slack automation tags every reference to a pull request with reacji labeling the state of that pull request, for example if it has failing tests, if it has been approved but tests are still running, or if it is ready to merge. We added a small-change reacji that indicates which of those pull requests meet the small-change criteria In our internal skills repository, we created two skills that have been distributed across harnesses: review-small-change which takes a list of repositories and reviews all open, non-draft, small-changes, including providing approval if they meet the review’s criteria (all checks pass including small-change check, and not touching on sensitive topics such as payments and security) review-pr-cluster which reviews batches of pull requests to identify common themes that came up, and suggest opportunities for discussion and adding verifications We set the social expectation that any error caused by a small-change is a failure in our system, and has to be solved at the system level: improvements to ai-code-review, to our verification mechanisms, to our CICD, to our harness, etc. This took about two days of infrequent attention to implement and rollout, although I imagine we will be tuning the knobs here for a long time. Maybe forever. fleet: centrally orchestrated harness I’ll briefly mention our other approach to reducing code review volume, which anchors on our centrally-orchestrated harness, fleet. (Which is similar to Stripe’s Minions although not following the one-shot approach in particular). We rely on fleet to implement and verify software in a highly controlled harness running in the cloud, with independent evaluation mechanisms to identify the correct number of independent reviewers for any given change. That number can be as low as one (e.g. fleet writes, one human reviews) for changes evaluated as safe. Results / next steps / reception I’ll be honest, it’s too early to give a useful result here. From early results, about 10% of changes qualify as a small-change. Those are now eligible for very fast approval, and generally get it unless they trip a flag, generally due to touching a security or money adjacent topic. While 10% is a small percentage, the core concept here is working, and ultimately will succeed or fail based on our ability to expand that percentage over the next few months in tandem with expanding our verification mechanisms to be increasingly comprehensive. This is ultimately going to fail or succeed depending on our ability to improve our verifications, which does require more difficult sorts of techniques to accomplish in low scale-complexity, high domain-complexity software than in the high scale-complexity, low domain-complexity products where many techniques like incremental rollouts are exceptionally effective. (Don’t get me wrong, they still work, they just provide less coverage when most users have different configuration and behavior.) Folks have not been uniformly excited about this, it’s a substantial change from how we’ve learned to be good engineers, and it’s uncomfortable to reevaluate whether a long-held best practice has become merely dogma. The most energizing part of the current cycle is that we get to reevaluate these practices we grew up on every week; the most painful part of the current cycle is that we’re likely to fall behind if we ever stop reevaluating them.

5 days ago • 1 votes
Building a Chestnut Launcher

The park near my house has lots of chestnuts and my kids have been collecting them like crazy (it’s actually their favorite post-school activity). We accumulated quite a hoard. I thought it would be fun to make a launcher so we had something to do with our new chestnut collection. I wanted to go with a very simple crossbow-style design (like the famous D2 item) using latex tubing and a leather pouch for holding the chestnuts. This is what I came up with. It launches chestnuts about 220 feet, and it’s simple enough to build in an afternoon. The frame is a 42 inch 2x2 and a spare IKEA bed slat for the crossbeam, glued and screwed together. A 3/8 inch eye bolt goes through each end of the crossbeam, and the latex tubing is tied to those and to the pouch from a leather shepherd’s sling. Tying the tubing was the fiddliest part. It has to be stretched while you wrap it with butcher’s twine, so I clamped a couple of scrap blocks to my workbench to hold it. I like this design because the tubing can be wrapped around the eye bolts to easily add more tension and power. The Trigger My favorite part of the design was that I decided to use an archery release like this one for the trigger. It provides a safe and reliable way to hold the tubing back, and it’s made to let go cleanly. I 3D printed a little housing that mounts the release securely to the back of the stock, and the release clips onto a loop of archery D-loop cord on the back of the pouch. Then I added a dowel for a rear grip and an old handle I had previously removed from my shop vac. How Well Does It Work? We tried it at the park and it works surprisingly well. Aimed at about 35 to 40 degrees, it sends our chestnuts (on the small side, about 10 to 15 grams) about 220 feet. Thanks to the archery release, we haven’t had a single misfire. I got curious and did some rough math. To go that far with air drag, the chestnuts are leaving at somewhere around 85 to 100 mph, which works out to about the same energy as a paintball. So safety glasses are important and you never point it at anyone. You can find the 3D print on Printables and a full build guide on Instructables. Make sure to always use it with adult supervision :)

5 days ago • 1 votes
Organising my cross-stitch patterns

I have a lot of digital cross-stitch patterns and until recently they were a mess. My “collection” was a series of several hundred PDFs, disorganised and spread across my disk. Recently, I was looking for a pattern and it took me several minutes to find it. I had a brief moment of panic, thinking that I’d lost it, and the website this pattern came from is long since gone. Fortunately it did turn up, but it was clear I had to organise my pattern collection. Some people would use apps like Pattern Keeper or Flosshaven, but I just created another static website on my media drive. This is what it looks like: The patterns are arranged in a masonry layout with a preview thumbnail for each pattern, which is normally the first page of the PDF. If a pattern has multiple files – for example, different files for colour and black-and-white printing – there’s a list of links below the thumbnail. I can also add a source link to where I bought the pattern, if I want to find more patterns by the same author. Internally, it’s structured like my other static websites. The metadata for the patterns is stored in a JavaScript file, and then I have an HTML page which renders that metadata in this visual design. My pattern collection doesn’t change very often, so I’m adding new files by hand. There are no sorting or filtering options, because my collection is small enough that they’re unnecessary. I can scroll through the entire list in less than a minute and find whatever I need. I’d like to start making my own clothes soon, and I expect to copy this design to store clothing patterns. This collection now sits alongside my saved bookmarks, podcasts, TV shows, and more. I can keep all my patterns organised, and I won’t lose them in the depths of my hard drive. Technical tricks There are a couple of technical pieces I’m quite pleased with in this process: Masonry layout. I love the cascading grid style of a masonry layout, and I think they’re great when you have variable-sized content – like my pattern cards. I’ve been using them for over a decade; screenshots of my early web apps show my experiments with David Desandro’s Masonry.js library. Masonry layouts are now a native feature of CSS called “Grid Lanes”, which is simpler and more performant than the JavaScript approach. I’m excited to use these layouts in even more places. Thumbnails of PDFs. Most digital postage patterns are shipped as PDFs, and I’m creating the thumbnail images with my create_thumbnail tool. I had to extend my tool to support PDFs, which turned out to be straightforward with the pdf_oxide crate. I’d not come across Oxide before, but it’s a cross-language library for working with PDFs, and I can imagine using it in future. A coloured stripe for the palette. In the metadata for each project, I have the complete list of DMC thread colours and the pattern dimensions. I have a lookup table that converts DMC thread colours to hex codes, and then I’m able to create a linear gradient in CSS to render a coloured stripe of the pattern colours. Here’s an example from a pattern with four colours: border-bottom: 12px solid transparent; border-image: linear-gradient( to right, #7a451f 0%, #7a451f 25%, #985e33 25%, #985e33 50%, #e4bb8e 50%, #e4bb8e 75%, #361f0e 75%, #361f0e 100% ) 1; Building the HTML, CSS, and JavaScript was very quick – I have notes on how I build my media sites, and I’ve done it plenty of times before. I threw together the initial prototype in less than 15 minutes. The longer part was finding all the PDFs scattered across my disk – but now that’s done, and I know exactly where to find my patterns. [If the formatting of this post looks odd in your feed reader, visit the original article]

a week ago • 1 votes
📚 BoredReading

You seem to be enjoying this.

Join free to unlock everything.

Create free account

Already have an account? Sign in