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 … Continue reading Mazers – a WebOS app rises again on iOS & iPad →
Show full content
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!
I’ve been using the Irish energy provider Energia for 5 years or so (as of writing, 2026) and they used to have a useful insights dashboard that let me analyse my power usage. Well, they seem to have removed it so I built a handy dashboard that anyone can use. It’s at https://energy.chofter.com/ , try … Continue reading Analyse and run simulations on your energy usage →
Show full content
I’ve been using the Irish energy provider Energia for 5 years or so (as of writing, 2026) and they used to have a useful insights dashboard that let me analyse my power usage. Well, they seem to have removed it so I built a handy dashboard that anyone can use.
As 2026 begins, thinking back on my 2025 is a bit nuts. I can’t believe how much new product I shipped. I think AI had a lot to do with it, freeing me up to take on projects that I still wrote probably 60% of the code I shipped this year, but when I used … Continue reading My 2025: The most productive year yet? →
Show full content
As 2026 begins, thinking back on my 2025 is a bit nuts. I can’t believe how much new product I shipped. I think AI had a lot to do with it, freeing me up to take on projects that
Had a technical component that would have discouraged me from starting
Are purely personal but fairly complex, meaning I wouldn’t otherwise have taken the time.
I still wrote probably 60% of the code I shipped this year, but when I used AI I mostly used Claude Code to either write an entire app from scratch or to implement code in a language I don’t know, like Swift or 3d shaders.
What did I work on in 2025?
Stainless SDKs
My main job, which took up most of my time, was managing the core SDKs team at Stainless. This is a wonderful company full of great people, and our team shipped some great new products in the year, like the Java, Kotlin, C#, PHP, Ruby, Terraform and CLI SDKs. The code was written by my amazing colleagues, but I like to think my management helped smooth the path for them to be as great as they can be.
My main side project for the last 4 years has been Kidz Fun Art, a pretty awesome art app I built for my two young daughters. I added so many new features to it in 2025 it warranted its own post, so go read it here. I still wrote most of the code myself, but Claude helped me tackle really complex problems I likely couldn’t have on my own, like writing a WebGL based greeting card feature, a realistic paint brush written from scratch in C and compiled to WASM and a bunch of Swift code in the iPad app.
Years ago I built a Chrome extension for Github called GitMeme that makes it simple to add funny GIFs to your commit comments. Well, Github rewrote their site in a very silly way and broke it, so I spent a bit of time fixing it up, and now it works well again.
I wanted to teach my two young kids to code in JavaScript last summer and couldn’t find a good entry point for them, so I built JavaScript Adventure Courses (code is open source here). It is an interactive app that takes the student through many parts of the web and JavaScript ecosystem, with an AI tutor to work alongside you when you get stuck.
Many years ago, when my now 7 and 9 year olds were much younger, they liked a bubble popping game on my phone. I started to write one in React Native, but gave up when I realized I had no way to create the graphics for it. Well, AI is now a thing, so I polished it off, used Googles amazing Nano Banana to generate all the game graphics and shipped it as Super Bubbly on the iPhone and iPad. I wrote a post about it here.
About 4 years ago I had the idea to create a simple app that allowed me to quickly write down a film, book or piece of music I’d like to share with my kids when they were older. I built a very early version, enough for me to use and gave up. Well, in 2025 I decided to actually finish it so that it looks good, runs quickly, has all the functionality I need and actually does the thing it’s supposed to do and send you a reminder email when your kids are old enough to watch something you saved. It’s live at showmykids.com and free to use.
My kids were struggling a bit with maths at school, so I built an app to help them get better at memorising some of the simpler calculations. It’s called Memory Maths and it’s free for everyone to use. The code is open source here. It supports tracking the progress of multiple people, gives little rewards when you hit some milestones, and is highly optimized to work well on an iPad.
When my kids create animations in Kidz Fun Art and export them as Gifs, I need to turn them into a video file to upload to Instagram. The app I was using for (that I paid good money for a few years ago!!) decided to shut down and take my money with it. All of the replacement apps were an absolute disaster with terrible ads everywhere. So rather than subject myself to that nonsense I built Gif2Vid.
Gif2Vid is first and foremost an NPM module that anyone can use to enable converting a Gif into an MP4 video on a website. For my own use I also created a mobile optimized site at gif2vid.com that is super fast and easy to use.
I used Claude extensively in writing Gif2Vid, as I needed to write a video encoder in C, which is then compiled to Web Assembly. I wrote a post about it here.
As part of shipping Super Bubbly on Android (I really need to get back to that…. It’s still not shipped, so much to do so little time), I ran into Google’s annoying new policy that for any new Android app to be published, you need a certain number of testers to install it. There seemed to be no simple and organised way to do this, other than a bunch of people on Reddit asking each other to install their apps. Another complication is that Super Bubbly is a paid app, and there is no good way to distribute free Promotional Codes to testers.
So I built an app almost 100% with Claude to do this for you.
The site at https://androidtest.chofter.com/ lets you register your Android app, which provides you with a single link to share with testers. When they go to that link they are assigned a Promotional Code that lets them install your Beta app for free, and that code is never shared with anyone else. The app shows you how many codes have been redeemed and track your progress.
I wanted an excuse to mess around with mapping data, inspired a bit by the creator of pastmaps.com on Threads. I thought the idea of creating a site to check my current elevation would be an interesting challenge. I did a lot of research on good places to get elevation data around the world, which was the main challenge. I then used Claude to find efficient ways to process this data to be easily consumed on a lightweight, mostly frontend web app.
The result is my-elevation.com . Try it out, it really works! The site was almost 100% coded with Claude, as were all the scripts for processing Gigabytes of mapping files into many, many tiny JSON files.
I’ve been a fan the Kermode & Mayo movie review podcast for 20 years or more. There used to be a fan app called iWittr on the Apple App Store that let their fans say where in the world they lived. I decided to recreate it as a web app, and it’s live at iwittr.com .
I wrote almost all of it using Claude in a few days, and I’m very happy to say that the global community of Wittitainment fans have been very thankful for the app, as have my movie-review-gods, who have given it many shout outs on their podcast during the year.
Back in the early 2010s I wrote a fun little app for the Palm Pre called Flickr Addict. It would change your phone background regularly using images from the Flickr Explore page, and very high quality images, as well as your own images.
Cut to 2025, and I wanted to see if I could ship an app written entirely in Swift, a language I don’t know, to the App Store by using AI. So I used Claude to recreate most of Flickr Addict as Explore Wallpapers, a fully native Mac app. You can get it here.
At Stainless I sometimes had to try to understand the content inside huge JSON files, many GBs in size. However these can be very difficult to work with – many IDEs try to format them and freeze, needing a restart, and all the web based tools I tried also failed to scale and froze.
So I used Claude Code to write Huge JSON, a web based tool specifically built to search massive JSON files, and tested to make sure it has no performance hot spots that prevent it from scaling. It lets you search by text, JSON Path and the JQ query syntax (that one was fun to get working!)
A final tiny project that took about an hour with Claude, I wanted to have an app that you give a website and it returns any RSS or Atom feeds in that site. You can use it at https://feedfinder.chofter.com/
What will 2026 bring? Well, lots more shipping with Stainless, that’s for damn sure :-). Other than that, who knows, we’ll see where inspiration takes me. I might just circle back to some of these projects and see if I can find some more users for them…..
Kidz Fun Art grew a lot in 2025! Many more people use it regularly, many more choose the paid version & I added far more cool features. Loads of Work… First off, here’s a graph of the number of changes (“commits” in technical terms) that I made. 612 changes in total with a few quiet … Continue reading 2025: A big year for Kidz Fun Art →
Show full content
Kidz Fun Art grew a lot in 2025! Many more people use it regularly, many more choose the paid version & I added far more cool features.
Loads of Work…
First off, here’s a graph of the number of changes (“commits” in technical terms) that I made. 612 changes in total with a few quiet months and many very busy months. In total, 121,946 lines of code were added and 31,579 lines were deleted.
And here is a calendar of my code contributions in Github for the year. The vast majority of these are to Kidz Fun Art
New Features Added
All that work has to mean lots of great new features, and this year didn’t disappoint. Here are the bigger changes made this year.
Fountain Pen
The fountain pen uses the pressure sensor in some digital pens to give a more natural writing style.
Mirror
The mirror is a fun tool that lets you draw anything and then have it be recreated identically in reverse. This is great for drawing things like portraits, people or animals.
Custom Gradients
Everyone likes using the Rainbow in Kidz Fun Art, whether it be drawing with it, filling in with the Rainbow colours, or dropping rainbow coloured sand and watching it fall. But what if you want different colours (as my eldest daughter did)? Fear not! I added a Custom Gradient tool that you can use to design the gradient you want to use.
Smudge
My eldest daughter asked to be able to smudge her drawing, to mix up the colours, so I added a simple smudge tool.
Payment in USD
Less a fun tool, and more of a practical matter, a user asked to able to pay in US dollars as he didn’t understand what a Euro was. No trouble at all, I went to my Stripe account, set up the ability to accept dollars and shipped it in the app!
Greeting Cards
Well this was a huge, technically mountainous feature to build!! I wanted to make the creation of greeting/birthday cards really intuitive for kids, and the best way to do that is to have a real 3D card to work with. This meant working with WebGL for the first time, getting help from Claude Code to write the shaders, and ensuring that it seamlessly transitioned from the 3D card to the 2D editing experience. I even added cool 3D sparkle effects for the hell of it
A small design detail that I’m really proud of is that when going to export it for printing, I placed a tiny faint + symbol between the four panes so you can always fold it in the right place, but otherwise you’d never notice it on the page once folded. Compared to all the 3D fanciness it’s a tiny thing, but it’s important to sweat the details!
The Blog
To help potential users find the app better, I decided to invest some time in quality written content. This led to me hand coding my own highly optimized, visually attractive blog at https://kidzfun.art/blog . I put a lot of time into making it really useful and informative, and even added its content to the Help tab in the main app.
Sticker Library& Clone Brush
I asked an artist friend who draws the very popular Dark Legacy Comics series what tools he would need to make a great comic authoring app, and the one he said was a must have was a Clone Brush. This is where you can choose a shape, e.g. a leaf, and draw with it, varying it’s shape, shade and size to give the impression that you drew hundreds of individual items. So I built it!!
You can now select any part of the picture and add it to your Sticker Library. Come back any time later and paint with it.
Paint Brush
This was another hugely difficult feature to build, but very rewarding in the end. I read a number of different academic papers to get an understanding of how best to simulate a real paint brush in a digital format. I then implemented it in C code and compiled it to Web Assembly (WASM) for speed, as the rest of the app is written in TypeScript. It took a lot of tuning to get just the right feel, to make the paints mix and fade out in as close to a real world manner as possible, and I think it turned out great!
I’m not aware of another Web based implementation of realistic painting other than some very basic demos that look terrible, and I did look. I don’t want to say that this is the first, but … it might be? Below is my 9 year old daughter Hannah quickly painting a landscape using it.
Save to your iPad
As Kidz Fun Art is a web application first, Apple has never made it possible to save images directly to your Photos app – this is something what works just fine on any desktop computer. I finally got around this using the native iPad app that I wrap the web app with by writing some native Swift code. This means now that users of the iPad app can save directly to their iPad instead of having to email their exported photos to themselves.
Sign in with Apple
No one likes typing into an iPad, and the sign up flow for the subscription has too many friction points. So, on the iPad app I wrote some native Swift code to let the web app sign in with the user’s Apple account. This means that they no longer need to:
Type in their email address
Open a confirmation email
Tap a link in that email
Come back to the app
This should hopefully result in more iPad users getting through the sign up flow and enjoying the full set of features that subscribers have access to.
Magic Wand
The last cool new feature of the year is the Magic Wand for quickly selecting shapes. You’ve seen this is most “grown up” art programs like Photoshop, and now here it is for kids! I simplified it a bit by defaulting to selecting objects on a white background, rather than by colour. This means little artists can more intuitively move objects around without having to draw a rectangle. However, if you just want to select a single colour, just tap the “By Colour” toggle, and you can choose how closely related two pixels must be to each other to be included in the selection.
User Growth
The number of monthly users has increased about 5x in 2025 alone. The app has been live since 2022, and I’ve been consistently adding features to it, but this is the year that it really took off. As you see above I’ve been investing a lot of time into things like ease of onboarding, help articles, SEO (helping Google find it), and posting about it in various places online, and it seems to be working!
The graph below shows the total users in the app this year.
Subscriber Growth
Even better, this year has seen a meaningful increase in subscriber numbers, meaning that more people are seeing the real value the app provides, and are willing to pay for it. It’s just €5 a year, so it’s not much, but they’re making the effort to put in the card details and trusting that their kids will use it for years to come. It feels amazing that this app that I built for my own little kids is being used daily by so many others.
The numbers are still small, and I still wouldn’t even make minimum wage from this (if it grows 1000x then maybe it’d be a reasonable financial return ) but still, it feels great to see it grow. The graph below from Stripe shows it growing 297% in 2025 compared to 2024.
What’s next in 2026?
Who knows?? I’ve never had a roadmap, not really, I just build things as either my kids ask for them or I think of them. I have a pretty cool 3D printer on order though, and I’m kind of fascinated with designing for laser etching. There are also some cool AI models out there that take a 2D drawing and turn it into a 3D model from printing, which seems like wonderful magic. Perhaps I’ll play with that, I think my two girls (now 7 and 9) would really like it.
As ever, you can get Kidz Fun Art
On the web at https://kidzfun.art . Use this on your laptop, desktop or install it as web app on any Android device.
TLDR: I’ve shipped a new NPM module called gif2vid that enables video encoding of GIF files in the browser, in your terminal and in a web app backend. I’ve also created an uber simple website at https://gif2vid.com where you can use it. My kids enjoy building cute (and sometimes downright weird) animations using Kidz Fun … Continue reading Gif to MP4 encoding in the browser, the terminal & everywhere else →
Show full content
TLDR: I’ve shipped a new NPM module called gif2vid that enables video encoding of GIF files in the browser, in your terminal and in a web app backend. I’ve also created an uber simple website at https://gif2vid.com where you can use it.
My kids enjoy building cute (and sometimes downright weird) animations using Kidz Fun Art, and I upload the exported Gifs to Instagram as a way to keep them forever. However Instagram doesn’t allow the upload of Gifs, so in the past I bought an iOS app to convert each Gif to an MP4, then uploaded that. Well, that app stopped working, and all the replacement apps are horror shows of aggressive adverts and terrible user interfaces.
I figured I could fix that, so I did. I built gif2vid.com, then open sourced:
Well, it allows you to run a JavaScript only process (containing an inlined WASM file for ease of packaging) in:
The UI thread of a browser
A worker thread of a browser
A terminal on your computer with
npx gif2vid input.gif output.mp4
The backend of a web app, running on Node.
Check out the README.md file of the source code for instructions on how to do each of these, and the sample projects in the source repo for fully functional example projects of how to use it in an Express app, NextJS app on client and server, Web Worker, and with a simple <script> drop in to a page.
TLDR: I built a simple & fun game using React Native, try it out on iPhone and iPad! In the Beginning…. My kids are 7 & 9 as I write this in 2025, but 6 years ago in 2019 they loved a simple little bubble popping app on my phone, and I thought it’d be … Continue reading Super Bubbly: React Native kids game →
Show full content
TLDR: I built a simple & fun game using React Native, try it out on iPhone and iPad!
In the Beginning….
My kids are 7 & 9 as I write this in 2025, but 6 years ago in 2019 they loved a simple little bubble popping app on my phone, and I thought it’d be fun to make the best possible version of that type of game.
That means make it the most ridiculously cute game possible, but keep it utterly simple, relaxing and satisfying. I had the idea of making it based on cute animals, and when you pop a bubble, little kittens and puppies run back to their mommies and daddies.
I wrote basically all the code for it, then hit a wall. Where would I get all the cute character and background images I needed? It didn’t make sense to pay an artist thousands of dollars for such a tiny app, and I wasn’t going to be that guy and reuse other peoples assets.
So I gave up.
Present Day….
Cut to 2025, and my now 2 year old twin nephew and niece made me remember my old game. Since 2019 generative AI had become a thing, so creating great new visuals is now pretty trivial. So I dusted off my old app & asked Claude to upgrade it to the latest version of Expo. I did this by creating a brand new Expo project in a sub folder and told the AI agent to move the code from the old app into the new app. It seemed much more likely to succeed than asking it to apply 6 years of migration instructions. And it worked!
Fun and no profit with AI
I found a great site at OpenArt.ai which lets you generate all kinds of styles of images using many different AI models. I found the most success using Google’s Nano Banana model, which was great at creating both characters on a white background (other models often ignored that instruction) and creating large background images.
One area that all models struggled with was the instruction of where to put the “camera”. They all wanted to create images of a character facing you directly, whereas I wanted the camera to be looking down on the top of the characters, as the game is top down. Oh well, it’s probably cuter that you can see all the animals’ faces…
I generated sounds using Adobe Firefly, which is really quite fantastic. It followed instructions very well, and gives away a lot of functionality for free. Well done Adobe! (never thought I’d be saying that).
A game in React Native? Seriously?
The physics of the game are done using MatterJS, which is sufficiently performant in React Native. I tested it on my 6 year old iPad and it runs just fine!
The initial rendering was quite naive, and just moved standard <View> components around, but the app would just crash randomly complaining about memory issues. I rewrote it to draw the main game surface using React Native Skia from Shopify, resulting in the framerate rising and the memory issues disappearing.
Epilogue
It honestly took longer to get the game ready for submission to Apple and Google than it did to shake off the cobwebs, generate the assets and submit it for review. Maybe there’s an opening there for an AI solution that takes care of that process? Please, someone build it that and I’ll happily pay you to use it!
TLDR: Click here for code to implement a lightweight, resilient clickable YouTube preview. YouTube videos are expensive to load on a web page. This compounds if you want your page to display many videos at the same time for the user to choose from. Before the user even plays the video, it loads 4MB onto … Continue reading Fighting with YouTube to show a preview image →
Show full content
TLDR: Click here for code to implement a lightweight, resilient clickable YouTube preview.
YouTube videos are expensive to load on a web page. This compounds if you want your page to display many videos at the same time for the user to choose from. Before the user even plays the video, it loads 4MB onto your previously zippy page.
Of course, YouTube does a decent job of caching these resources, but your browser is still doing all this work, creating another complete page context per video, executing all it’s JavaScript etc.
The ideal state is that an iframe is only created when the user plays the video, so if you’re listing dozens of videos that can be played in place you only pay the performance penalty when the user actually requests the video to play.
A solution … that YouTube makes brittle
One solution is to first show a preview image using a thumbnail image from the video, and when that is clicked, swapping it out for the iframe. This works great! … until it doesn’t.
It should be easy to add an onerror handler though right? Then we can fall back to a lower res preview image. Haha, yeah, but no.
Instead of simply failing to return the image, sending a 404 response header and causing the <img /> element to trigger it’s onerror listener, YouTube both sends a 404 response and sends a small ugly placeholder image that makes your site look broken. This triggers the onload listener instead of the the expected onerror listener.
The Solution
Instead of an onerror , add an onload handler to the <img /> element, and check the size of the image. The ugly fallback image is 120px wide and 90px tall. Check the size of the image, and if the fallback image is detected, iterate through the available thumbnail images until one loads. The image below shows an implementation of this.
Full Example
There’s a full no-framework example at https://chofter.com/examples/youtubePreview.html , view the source for the full example. This can be easily adapted for any framework like React, Vue etc.
Prior Art
Paul Irish describes another solution to this problem that I found after I built this, check it out here. That also points to these three implementations that are also worth perusing.
I like mine as it uses the least code and is trivial to implement in whatever framework you like, but another of these might suit your needs or taste better.
I’ve been building Kidz Fun Art (web, iPad & Windows) since 2021, so 4 years at time of writing. It’s a tablet optimized application intended to be used by children of all ages – my daughters were 3 and 5 when I started, and are 7 and 9 now, so I’ve seen how they use … Continue reading On Designing For Children →
Show full content
I’ve been building Kidz Fun Art (web, iPad & Windows) since 2021, so 4 years at time of writing. It’s a tablet optimized application intended to be used by children of all ages – my daughters were 3 and 5 when I started, and are 7 and 9 now, so I’ve seen how they use it almost daily at various ages. I’ve learned a few things about designing for their usability and how it differs from some more common patterns found in adult focused applications.
If you want kids under 8 to use your app, find a way to communicate all its primary functions with as little text as possible.
Text does not actually provide guidance to a large percentage of your user base as they can’t read it
Text takes up valuable space that could be used for better graphics or aesthetically pleasing empty space
Text is visually unattractive and off-putting to most children.
In the image below, note that there is not a single piece of text. While secondary and tertiary features sometimes require text, do what you can to make all primary controls text-free.
Show, Co-locate and Hint tools
Adult focused apps often have menus where the user must know to hunt and peck for the features they need. This is not well suited to younger users. I’ve found that it works much better to design ways to co-locate tools with the objects on which they are to be used.
For example, if a user wants to select and rotate part of the image, place the control for rotating directly on or next to the selected area, not far away in a control bar or menu.
Place tools for manipulating objects directly on top of the objects, with large and obvious hit targets. Feedback from my in-house testers was that putting things like rotation controls on the border of a selected area (like most paint apps) was not a good affordance, whereas overlaying a circle on top of the object to be rotated was immediately obvious.
If the available controls cannot be shown due to space constraints, find a way to hint at their existence in a way that does not require reading or tool tips.
The image above shows my attempt at gently suggesting to the user that there is a menu hidden under each of these buttons if they click a second time. A key point for avoiding a messy UI that is overly noisy is to only show any given hint a small number of times, ideally just once.
Mistakes should be easy to fix
Kids make mistakes, they mess around and generally use the app in every way you never thought of. They are naturally inquisitive and will tap, drag, slap, spit on and high five the screen at any given moment. In all cases, but especially if they are creating content, you should try at all times to make any actions quick and easy to reverse (I need to be better here, and my next mini-project is aimed at improving this for Kidz Fun Art). Some ideas:
Have an undo button placed prominently, and a redo button of course. This means building to support a stack of states, which definitely complicates things, but your app will be far less frustrating with these.
Use soft deletes, so the user can undo an accidental deletion. Nothing is more likely to make a kid never come back to your app than accidentally hard deleting something they’d spent hours creating and were about to proudly show their parents. This happened a year ago to one of my daughters, full on tears ensued and if it wasn’t their dad building the app that would’ve been the end of their usage of it. Safe to say I was up until 2am working on the fix, feeling like an absolute piece of shit. Don’t be that person.
Clean up the soft deletes at some point. I tend to do this after a few days, by which time the child has likely forgotten all about it. No need to keep the data forever. It bloats local storage and is just better for privacy all round.
Know when to involve an adult
If you have features aimed at older kids, your app will likely have stronger retention and usage of more advanced features if you encourage the child to involve the parent at the right times.
Use graphics as much as possible for these prompts, like in the example below. Once the user confirms that they are able to handle what comes next, it’s OK to use a bit more text than you normally would.
To reduce frustration for the user, for any single feature, only request the help of an adult once. This will require you to store the fact that you did so on a per-feature basis either locally, remotely or both.
Reduce the need for fine motor control
Younger children’s motor control is not as developed as that of older children, meaning that if you want them to get as much value from your app as possible, you should try to find ways to provide that value without perfect finger and pen control.
Some ways I’ve found effective to achieve this are:
Large hit targets
This is a pretty obvious one, all buttons and links should be plenty big enough for a child to tap with their finger. If you’re working on the Web, you can easily make the hit target on a button (or anchor tag) larger using CSS, without visually increasing its size. See this code example for one method of achieving this. There are other methods of achieving the same on other platforms, go forth and Google/Claude/ChatGPT (is that a verb?) them.
Long press is your friend
Children naturally long press on touch devices. If your feature can work via long press rather than dragging from one precise point to another, do so, even if it means losing some accuracy. I’ve seen it spark delight in children and adults alike.
For example, if inserting an image, like above, the common way to do this is to insert it with a size chosen using some heuristic, with resize handles for modifying it later. I found that kids much preferred the long press approach above, especially with the added sound effects. I’ve even had a few of their parents email me calling out their delight at this approach.
Try to solve Palm Rejection
I personally never rest my hand on a tablet when using a pen, and so worked on solving this later than I should have. Palm Rejection is the effort by the app to identify which touches represent a command from the user, and which are spurious connections with the skin, like the heel of a hand.
It’s a notoriously difficult problem to solve well, but is critical when building apps for children, and they frequently rest their little hands on the tablet, just as they would on a piece of paper (or their bedroom walls with a crayon….).
Depending on the technology you’re building your app with, there are various approaches to solving this, which I encourage you to research. I found the best that I could do was to use the touchType attribute on touch Event on iOS Safari (read more here) or the pointerType attribute on Pointer Events , which tells you if the user is using a stylus or not. If they are, I remember that for all future interactions and ignore all future events that are not from the stylus. I tried to use a number of different heuristics for the size of the contact area, the pressure applied and more, but when tested with my kids they always got different, less reliable results than I did in my testing. So, the simpler approach seems to work better.
The user may sometimes want to switch back to using a finger, so how best to allow this? My solution is to float this icon near any non-stylus touch point for a short time, two seconds or so, then automatically hide it.
When resting a hand on the screen it usually covers it, but it makes it simple to toggle between modes, and follows the previous guidance of keeping controls near to the location where they are needed, in this case, a few pixels away from the finger or palm (not directly under it).
If you’re aware of solid palm rejection techniques that work in other browsers, please let me know! See chofter.com for my contact info.
Simplify, then add delight(ness)
(with apologies to Colin Chapman for butchering his wonderful line) Even more so than with older users, designing small moments of delight are critical to the retention, both short and long term, of users.
When testing some little touches intended to delight users, I’ve seen my kids laugh out loud, and gotten emails from parents stating the same. This is what you’re aiming for. So what kind of things are we talking about? Well, that’s specific to your app.
In the artistic sphere, I’ve found that kids love bright colours, and especially rainbows. The rainbow pen and gradient fill you see above easily get the most positive reaction, and are relatively simple to build.
Adding sound can be another great way to add delight. When adding an emoji, as shown earlier, there is a nice growing sound that plays.
Kids also love tactility, and the more your 2D app can feel like a real physical object the better. Adding sound to interactions with real-seeming objects is even better. For example, I added the ability to grow and pop bubbles, which is something I used to do as a kid with washing up liquid and a straw (try it, you’ll lose hours of your life, seriously). The bubbles make a satisfying popping sound, and splash out as if you’re making a paint related mess. Not much artistic merit in this, but all kids giggle when they use it.
Adding sparkle effects is another easy way to add delight. These can happen when tapping a button, dragging something around or anywhere you like – kids are just happy to see them. For example, when adding the greeting card feature, I thought my kids would like it if I made rainbow coloured particles fly off the corners of the card when it was rotated. They agreed with loud giggles and “whoa” noises. Find ways to make kids say that fairly regularly and your app will go down well.
Maintain visual context when changing state
For some adults and most kids, if they interact with a visual element and it immediately disappears to be replaced with something else, they can lose context of where they were and what they were doing.
You should always try to make it extremely obvious to the user how they got where they are, and how they can get back to where they were. There are a number of approaches that can help with this. Some that I’ve used include:
Dialogs with a semi-opaque background. Popping these up when the user must make a choice makes it very clear what is needed from them, and dismissing them to get back to where they were previously is really simple. A mistake that some people make is to force users to tap a button to close a dialog. Some people struggle with this, so always make the dialog dismiss when the background is tapped too.
Only use one dialog at a time. It’s ideal to design flows where no dialog opens another dialog, but if you find yourself in this situation, it can be very confusing for younger users that by dismissing the second dialog, they are still in another dialog. If you must open a second dialog, close the first immediately.
Use tasteful animated transitions. It is very possible to overuse animated transitions, so be careful here, but animating from one state to the next is a great way to bring a user along with you. This is particularly important if you’re introducing your user to more complex ideas or objects. For example, when building a comic in Kidz Fun Art, when the user clicked a comic panel to zoom in, I initially just immediately swapped out the view for the zoomed in view, but my kids told me it made no sense at all. This led to me spending a lot (and I mean a lot) of time figuring out the exact way to play with pixels and CSS transitions to enable the zoom in and out effects you see below. It also makes it more clear when moving up, down, left and right between panels. This ended up ballooning the time to build the Comics feature by 300% or so, but it just wasn’t shippable to younger users (the main demographic) without this level of effort. Building for kids is hard, you’ve got to own this fact before you start, or you’ll ship sub-par products.
Monetize without ads, or not at all
To ethically ship an app intended for young children, there is no safe strategy to show 3rd party ads. Children will either be shown inappropriate content, or be tricked into clicking out of the app, which is the beginning of a slippery slope to inappropriate content. Even if the ads are non-click and display only, they are designed to trick and manipulate minds that are not ready for it (are any of us, really?).
If you plan to monetize, find another way. With Kidz Fun Art, I chose freemium + optional yearly subscription, what you choose should best suit your product. But save yourself time and don’t even consider ads – take it from someone who at one point led a decent sized chunk of the Facebook Ads org, ads work too well and should never be shown to children.
A small caveat to this ultimatum can be email related advertising. If you get the parent to sign up with their email, you can potentially use that as a surface for advertising or pushing content not intended for children. I’ve never done this, but you’re probably a lot safer taking this approach than showing ads in the app itself.
Plan for app growth without social sharing
Most apps these days turbo charge their growth by sharing user information or user generated content with others online. For apps with young users, there is almost no safe way to do this. If a young person writes, draws, colours, and uses media on their phone in your app, this opens them up to abuse the moment it is shared outside the app.
This makes it much more difficult to achieve explosive growth with child focused apps, which it partly why so few companies focus on building them – Roblox being an obvious exception, and their approach is more than questionable, I certainly don’t allow my children anywhere near it.
In Kidz Fun Art, the only sharing I found to be safe is:
Direct email to the owning account email whenever a new drawing is shared. This is obviously OK, as the parent can see anything the child wants them to .
AI generated images created by choosing pre-populated tokens, like “unicorn” or “spaceship” are shared with all users, even non-subscribers. This is safe as the user cannot provide any personal information, and they cannot create any content that might be inappropriate for others.
Outside of these, I’ve avoided any and all sharing: the safety and privacy of the user must be the foremost design principle at all times. If you find yourself straying from this, it’s time to do a hard reset and think about either achieving your growth goals another way, or deciding to abandon the project as a money making concern.
Children should never spend money
This is obvious enough if you’re building ethically, but children should never directly spend their parent’s money, so design your app to enforce this. If you have in app purchases, require proof that it is the adult making the purchase decision.
The pin for the tablet is not sufficient, as the person using the app almost certainly knows it. In Kidz Fun Art I use Stripe for subscription payments, which makes the last 4 digits of the credit card knowable, so I use this as a pin for any in app purchases (only AI image generation credits as of 2025, nothing else planned). Of course this is validated server side and never sent to the client. I figure that if the child has the parent’s credit card literally in hand, spending a little in our app is the least damage they’re likely to be doing that day. If you have better solutions, I’d love to hear them!
Epilogue
Thanks for reading all this, I hope it was of use and didn’t scare you away from building delightful apps for children. I’m still very much fumbling my way through this, so if you have any good tips, feel free to comment, or reach out to me on Threads, X or any other contact method you see on my personal site chofter.com .
Finally, if you’d like to try out my attempt at putting these ideas into practice and maybe get some inspiration, check it out at https://kidzfun.art
Working with very large JSON files (20MB+) using online tools tends to be a crashy affair. Whether you’re looking to format or search them, all the tools I found just crash. I found myself having to work with huge JSON files recently, so I built a tool specifically optimized for huge JSON files, called Huge … Continue reading Search Huge JSON files on the Web →
Show full content
Working with very large JSON files (20MB+) using online tools tends to be a crashy affair. Whether you’re looking to format or search them, all the tools I found just crash. I found myself having to work with huge JSON files recently, so I built a tool specifically optimized for huge JSON files, called Huge JSON Viewer (https://hugejson.chofter.com/).
Why does it not break like other JSON viewers?
It scales to very large and deeply nested JSON files using a few optimizations
No attempt to do syntax highlighting
All large operations take place in web workers.
Search results are rendered using a virtualized list, so it can easily scale to thousands of search results.
Some fancy algorithms for stringifying deeply nested files that break JSON.stringify (slower, but at least they don’t crash!)
What can it do?
I’m glad you asked! It’s focused on useful and fast search operations. It has three search modes:
Simple text search
JSON Path queries, when you know the path to the data you’re looking for.
This does what it sounds like, you type in a text prompt and it finds all occurrences of that string in the file.
JSON Path Search
When you know the path to the data you want, Huge JSON Viewer will find it for you. A nice little touch is that when you click on any search result, it scrolls to the matching position in the left pane and highlights it so you can see the context of the data around the search result.
JQ Search
JQ is like sed but for JSON data. If you already know how to use JQ, great, you’re off to a good start! If not, Huge JSON Viewer makes it really easy to get started.
It provides:
A step by step UI builder that let’s you construct a JQ search string visually.
A list of commonly used queries which will most likely get you what you want
The ability to save your commonly used queries locally so you don’t have to remember them by heart.
Many years ago (2010 or so) I released an app called Flickr Addict for Palm WebOS phones, which automatically downloaded beautiful images from the Flickr photography website and changed the phone background image on a regular basis. I wanted to see if I could rebuild that app in the Apple language Swift, which I didn’t … Continue reading Explore Wallpapers for Mac →
Show full content
Many years ago (2010 or so) I released an app called Flickr Addict for Palm WebOS phones, which automatically downloaded beautiful images from the Flickr photography website and changed the phone background image on a regular basis.
I wanted to see if I could rebuild that app in the Apple language Swift, which I didn’t know. I used a lot of AI (Claude Desktop from Anthropic mostly) to build the app and learn Swift, and I’m happy to say it’s now available on the Mac app store!
Last month I decided to do a quick fun project as an excuse to try out AI coding tools, called iWittr.com. It’s a fan site for the Kermode & Mayo podcast, which I’ve been listening to for over 10 years. I might do another post about that experience, but this one is about reducing it’s … Continue reading Optimizing iWittr.com to reduce Google Cloud & Vercel costs →
Show full content
Last month I decided to do a quick fun project as an excuse to try out AI coding tools, called iWittr.com. It’s a fan site for the Kermode & Mayo podcast, which I’ve been listening to for over 10 years.
I might do another post about that experience, but this one is about reducing it’s costs. I found that when it became popular (it’s been mentioned twice so far on their podcast), I began to get alerts from Google that I’d used half of my monthly budget (€20) in two days.
TLDR
Firestore is too bloody opaque to properly understand your site’s usage
Check your NextJS build logs to ensure that pages you think are cached on the Edge are not accidentally prevented from being cached
Close the Firestore console when you’re not using it
If you’re generating a huge HTML page that causes thousands of reads, in development mode just fetch a few records.
If you have a small data set, move it to a client Worker and read from it repeatedly rather than getting the same few hundred / low thousands of records repeatedly from the server
If you have a page that is expensive to load, make sure that any <Link> tags to it have prefetch={false} set
For context, the tech stack is
Firebase Firestore for storing data
Google Cloud Storage for files
NextJS for development
Vercel for deployment (UI and functions)
Debugging the costs
Finding the reason for the costs was initially quite difficult. Google’s Billing page lists them under App Engine, which I didn’t think I was using – I had nothing deployed on Google infrastructure. However, it seems they bundle Firebase related costs under App Engine, good to know.
This is where it gets difficult – Firestore will just tell you the total number of reads you are doing, but not the collections you are reading most from, forcing me to try to guess where I was being wasteful.
Optimizing the Map
My first guess was the Map page. I knew that this was the primary page that all users, both logged in and casual browsers, would go to, and it was reading (in the backend) over a thousand records every time they moved the map. Of course I was grouping these together to send far less to the client, but the reads were happening.
I decided to instead do all the map based filtering and grouping on the client, in a Worker thread. It turns out that if I just store the entire set of map data (a list of towns and the number of people checked into them) in a JSON file on Google Cloud Storage, it’s under 200k in size. Every time a new user checks in and adds themselves to the map, I now
Download the previous JSON file from storage
Update this list with the new town information and upload it again
The client app previously used to hit the /api/marker endpoint every time the map moved, passing it the bounds of the visible map (in longitude and latitude). This resulted in thousands of reads and CPU usage in grouping the data into as few visible map markers as possible all in the cloud.
The client app now does a single read from the server, asking for the URL of the most recent JSON upload. It downloads that file in the Worker thread and stores it in local storage. The UI thread now sends the map bounds to the Worker thread, it reads from memory, does all the filtering and grouping and returns in just a few milliseconds.
There’s two really cool things about this.
The first is that it’s much faster, basically instant, and works offline.
The second is that the refactoring from running on the API to running in the Worker took less than 10 minutes, as I simply told Claude Code to do it for me. It
Created the Worker file
Instantiated it in the React component for the Map
Moved all the logic from the API route to the Worker
Changed all calls in the React component from going to the API to speaking to the Worker
Wrote the LocalStorage code in the React component and had the Worker tell it to do the data storage, as the Worker does not have access to LocalStorage
When the Map page loads it first populates the map from LocalStorage, then gets the latest JSON file from the internet, if available, and updates the map with the latest data.
This approach only works of course because the data set is small, and I expect it to not grow to a huge size.
Result: a small reduction in the number of reads! Somehow, I’d guessed incorrectly.
Optimizing the Wiki
There was an old defunct wiki that contained loads of great user created content about the podcast, now stored graciously on Archive.org. I had taken all that data and rebuilt a new wiki from scratch, and decided that I wanted one huge page that lists all of the 1300+ articles. I’d told Vercel to cache this on the edge using the revalidate configuration.
However, it turns out that at some point I’d added a check for the user cookie (to determine whether or not they have Editor permissions), and this was causing it never to be cached!
Even worse, the home page used the <Link> component to link to all the other pages, and by default it would be preloaded, so regardless of whether or not someone visited the wiki, it would do its 1300 reads.
Finally, in development there is no Edge caching of course, and when I was working on the code, every time I was saving a file it was reloading the data.
The Fix
I told Claude Code to refactor all user related code to a client component that checks in the browser whether or not they are logged in, allowing the page to be cached properly. In development, I added a simple check to the page and if in development mode, I just load 20 articles instead of 1300+. Finally, I changed the link to <Link prefetch={false} ... > as it’s a huge HTML page and most people won’t navigate to it.
Avoiding the Firestore Console
After making these changes, the read count for Firestore dropped a lot, but there were still hundreds of thousands of unexplained reads. It turns out that if you leave the Firestore Console (their web dashboard) open on an active Collection, it repeatedly reads from the collection over and over again.
Solution: I closed the Firestore console, and only open it when I need it. With this, the number of reads finally became reasonable.
Results
Total number of reads per day have dropped from 9 Million to under 0.5 million, and improvement of around 20x. The map is far faster, and the Wiki loads much more quickly. All in all a good mornings work.
Wishlist
Google’s Firestore should give a breakdown per Collection, or even by Index, for reads, to make debugging easier.
Vercel should let you know if a previously statically cached page has become uncached, as happened to me accidentally
For a long time in Kidz Fun Art, the tablet app I built, you could draw with a rainbow brush, which is the favourite feature of many people. I noticed my eldest daughter trying to use it in such a way so to only use a subset of the colors and it was really awkward, … Continue reading Custom Gradients in Kidz Fun Art →
Show full content
For a long time in Kidz Fun Art, the tablet app I built, you could draw with a rainbow brush, which is the favourite feature of many people. I noticed my eldest daughter trying to use it in such a way so to only use a subset of the colors and it was really awkward, so I added this fun new feature where a child can intuitively design their own gradient.
It was a fun challenge to make it both powerful but still intuitive enough for a 3 year old. I think I achieved it, what do you think?
TLDR: Easily keep your NodeJS projects translated in many languages with my JSON AI Translation NPM package. A few months ago I translated KidzFun.art to multiple other languages, using ChatGPT’s AI for the translations. This worked fine when dealing with a blank slate, but I found that every time I wanted to add a new … Continue reading Translate your UI Strings with AI →
A few months ago I translated KidzFun.art to multiple other languages, using ChatGPT’s AI for the translations. This worked fine when dealing with a blank slate, but I found that every time I wanted to add a new string, it was labourious to manually translate those new strings into every other language and then update the JSON files myself.
Like any good engineer, I solved this 5 minute problem by doing 10 hours of work to automate it! If you have a NodeJS project (e.g. NextJS or similar), try it out yourself at https://www.npmjs.com/package/json-ai-translation
TLDR: Demo is here, Code is here, App is here In Kidz Fun Art, the web app for tablets I’ve built for my kids and hopefully yours, I recently added a nice little feature where you can fill in any area with a linear gradient. It’s highly responsive to the user moving their pen/finger, and … Continue reading Fast Linear Gradient Fills with OffscreenCanvas →
In Kidz Fun Art, the web app for tablets I’ve built for my kids and hopefully yours, I recently added a nice little feature where you can fill in any area with a linear gradient. It’s highly responsive to the user moving their pen/finger, and can quickly let them change the direction and spacing of the gradient at something like 60 frames per second. This post describes the technical details of how it is achieved.
The user clicks inside some shape that they want to fill with a gradient, in the example below it’s the green diamond.
At this point, the app sends a few things to the Worker Thread:
An OffscreenCanvas. A Canvas is a 2D drawing element in a web browser. For performance reasons, you can transfer control of a Canvas to a Worker thread, so that as the user is moving their pen around in the single threaded user thread, any paint operations can happen simultaneously without blocking the user’s actions.
A copy of the pixel data from the user’s Canvas, containing the green diamond you see above
Some data about the point that the user clicked, and the colours to use in the gradient
Step 2
Now the Worker thread performs a simple flood fill with a solid colour, starting from the point that the user clicked. The resulting pixels are set to black in the OffscreenCanvas (the actual colour doesn’t matter, it just has to be opaque). While doing this we record the bounding box around the pixels for use later.
Step 3
Since we now know the bounding box for the filled in pixels, we can fill it with a linear gradient using the context.createLinearGradient function, as below
Normally the code above would fill the entire rectangle with the gradient colours. However we prevent this by using the very cool globalCompositeOperation property on the Canvas context. By setting it to the value “source-in”, any modified pixels are only actually changed if the existing pixel is already non-transparent. So in this case, only the black pixels we previously drew are now drawn with the gradient colours, achieving the goal of rapidly filling any shape at all with a linear gradient.
Step 4
Finally, when the user lifts their pen/finger, the main thread draws the OffscreenCanvas to the main user canvas using the code
and the operation is complete: the user canvas now has the selected shape filled with the gradient colours.
Note that you must use the drawImage function to get the image data out of the OffscreenCanvas. You cannot use other operations as you would in a user thread Canvas, such as getting the ImageData from the Canvas context object.
Try it out
I’ve put an example implementation of this online for you to try out.
If you’re still here, thanks for following along, and give Kidz Fun Art a try – it’s packed full of goodies, all built using the best that the web and my kids’ imaginations have to offer.
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 … Continue reading Using Dall-E/AI to create kids colouring pages in KidzFun.art →
Show full content
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 the image above.
To ensure the images are safe, I add a number of instructions on the server side to instruct OpenAI to only generate age appropriate content.
Simple payment for adults only
Using Dall-E costs money, and so it’s necessary for users to pay for this. I kept it simple, allowing parents to buy a pack of 100 image generations at a time, which is likely to last a long long time (see later for why).
You never want to be in a situation as an app developer where a child accidentally spends their parents money. To prevent this, the parent must provide the last four digits of their credit card. I use Stripe for payments, and users of the AI generation feature must already be subscribers, so I have a record of their credit card and I can simply match against that.
Of course, if you’re technically minded and want full control over the spending and budget, you can generate your own Open AI API key and provide that instead of purchasing a pack of pictures.
Caching to minimize costs
A large benefit of providing pre-determined items to place in the picture to kids, rather than free text, is that it is likely that there will be many similar requests. I use this to cut down on generation costs. I use Firebase and Google Storage on the back end, and every time that a child accepts a generated image, I cache both a large and a small version of the image in Google Storage, and make a record of it in Firebase, noting the “tokens” associated with it, e.g. “dog, cat, classroom”.
The next time a child selects “dog, cat, classroom”, they will be shown the cached image first, without subtracting from their count of purchased image generations. It’s only once the child rejects all the cached images that they cause a new image to be generated with Dall-E and subtracts from their pre-purchased allocation. In this way, as more children use the feature, it will take longer and longer for the purchased allocation to run out.
For the sake of safety, only images generated by selecting the provided tokens are cached. If a user writes in free form text that image is never cached nor shown to anyone else. They simply download it and colour it in.
That’s all folks!
Go try out KidzFun.art today on any tablet, laptop or desktop!
Thanks to ChatGPT for this recipe with no stupid SEO in it. Total time to make: 80 minutes if you’re a normal slow chopper of vegetables (like me), 50 minutes if you’re super fast with a knife. For a simple chicken soup with potatoes, you’ll need the following ingredients: 500g (1 lb) chicken breast or … Continue reading Quick chicken soup recipe for sick people →
Show full content
Thanks to ChatGPT for this recipe with no stupid SEO in it. Total time to make: 80 minutes if you’re a normal slow chopper of vegetables (like me), 50 minutes if you’re super fast with a knife.
For a simple chicken soup with potatoes, you’ll need the following ingredients:
500g (1 lb) chicken breast or thighs, cut into bite-sized pieces
4 medium potatoes, peeled and diced
1 large onion, chopped
2 cloves of garlic, minced
1.2L (6 cups) chicken broth or stock
1 teaspoon salt (adjust to taste)
1/2 teaspoon black pepper
2 tablespoons olive oil or butter
Optional: chopped fresh parsley or dill for garnish
Here’s how to make it:
In a large pot, heat the olive oil or butter over medium heat. Add the chopped onion and garlic, sautéing until they’re soft and fragrant, about 2-3 minutes.
Add the chicken pieces to the pot and cook until they’re no longer pink on the outside, about 5-7 minutes.
Add the diced potatoes to the pot along with the chicken broth. Bring the mixture to a boil.
Once boiling, reduce the heat to a simmer and cover the pot. Let it simmer for about 20-25 minutes, or until the potatoes are tender.
Season the soup with salt and pepper. Taste and adjust the seasoning as necessary.
Serve hot, garnished with chopped fresh parsley or dill if desired.
Here’s how it looked when I made it for my 7 year old. She got some of it down the first time and liked it, then had more as she felt better.
No need to read this bit, look after yourself/your person.
But yeah, I hate all the recipes online these days that bury what you really want under mountains of SEO crap. So, while my blog is almost exclusively tech, every now and then I have a sick kid and no bloody patience for that SEO nonsense so this is the recipe that worked for me thanks to ChatGPT, and now I can easily find it forever!
In case you’re working with the Origin Private File System on a browser whose dev tools don’t yet support browsing the files (all browsers as of Nov 2023, though Chrome does have an unofficial extension which is nice), then here’s a code snippet you can use to list all the contents of the file system. … Continue reading How to list all files in a browser’s Origin Private File System (OPFS) →
Show full content
In case you’re working with the Origin Private File System on a browser whose dev tools don’t yet support browsing the files (all browsers as of Nov 2023, though Chrome does have an unofficial extension which is nice), then here’s a code snippet you can use to list all the contents of the file system. Great for working on Safari, Firefox etc, though of course native support in the browsers would be much preferred.
listDirectoryContents = async (directoryHandle, depth) => {
depth = depth || 1;
directoryHandle = directoryHandle || await navigator.storage.getDirectory();
const entries = await directoryHandle.values();
for await (const entry of entries) {
// Add proper indentation based on the depth
const indentation = ' '.repeat(depth);
if (entry.kind === 'directory') {
// If it's a directory, log its name
// and recursively list its contents
console.log(`${indentation}${entry.name}/`);
await listDirectoryContents(entry, depth + 1);
} else {
// If it's a file, log its name
console.log(`${indentation}${entry.name}`);
}
}
}
How to run a clean up script when your NextJS dev server is shut down
Show full content
Sometimes you need to modify files when building a web application that must be reverted before committing. In my case I’m building a Chrome extension that reads from a NextJS based web service, and when I’m working on the browser extension it reads from http://localhost:3005, so I have to modify its manifest.json file to allow this. Of course, I cannot leave that change in the file as it would be a privacy issue and Google would rightly reject it.
Rather than leaving this up to me remembering to manually revert the manifest.json change, here’s how you can do it in bash. The idea is that, when starting up the NextJS process, you run your setup script, and then you listen to the termination signal for the server and execute the cleanup script
Modify package.json
We’re going to use the standard npm run dev command to do all the setup and cleanup work, so make a new script command in the package.json file that runs the standard `next dev` command, e.g.
Now create the dev.sh script mentioned above, assuming it is the scripts folder and your setup and cleanup scripts are in the same folder and named run_setup_script.sh and run_cleanup_script.sh respectively
# Get the directory of the script
script_dir="$(dirname "$0")"
"$script_dir/run_setup_script.sh"
on_termination() {
# Add your cleanup script or command here
echo "cleaning up dev environment"
"$script_dir/run_cleanup_script.sh"
}
# Set up the trap to call on_termination()
# when a signal is received that shuts it down
# SIGINT is sent when you kill it with Ctrl+C
trap on_termination SIGINT
trap on_termination SIGTERM
# EXIT is sent when the node process calls process.exit()
trap on_termination EXIT
# Now run your NextJS server
npm run nextdev
Many years ago, back in 2018, I wrote a tiny NPM package called gcloud-storage-json-upload, which lets you authenticate with Google Cloud Storage and upload a file without needing to install any huge Google SDKs. I recently needed to use it with NextJS to upload Gifs created in my iPad/tablet/browser app Kidz Fun Art (you can … Continue reading Lightweight NextJS example of uploading files to Google Cloud Storage →
Show full content
Many years ago, back in 2018, I wrote a tiny NPM package called gcloud-storage-json-upload, which lets you authenticate with Google Cloud Storage and upload a file without needing to install any huge Google SDKs. I recently needed to use it with NextJS to upload Gifs created in my iPad/tablet/browser app Kidz Fun Art (you can make animations now!), so I wrote a simple example of how you can do this too.
It shows how you create an API endpoint that uses the gcloud-storage-json-upload package to authenticate with Google and returns a token to the client. The client then uses this token to upload a file to a Google Cloud Storage bucket.
All the code is available on GitHub, I hope it’s helpful.
TLDR: Demo is at https://shaneosullivan.github.io/example-canvas-fill/ , code is at https://github.com/shaneosullivan/example-canvas-fill . The Problem When building a website or app using HTML Canvas, it’s often a requirement to support a flood fill. That is, when the user chooses a colour and clicks on a pixel, fill all the surrounding pixels that match the colour of the … Continue reading Instant colour fill with HTML Canvas →
When building a website or app using HTML Canvas, it’s often a requirement to support a flood fill. That is, when the user chooses a colour and clicks on a pixel, fill all the surrounding pixels that match the colour of the clicked pixel with the user’s chosen colour.
To do so you can write a fairly simple algorithm to step through the pixels one at a time, compare them to the clicked pixel and either change their colour or not. If you redraw the canvas while doing this, so as to provide the user with visual feedback, it can look like this.
This works but is slow and ugly. It’s possible to greatly speed this up, so that it is essentially instant, and looks like this
To achieve this we pre-process the source image and use the output to instantly apply a coloured mask to the HTML Canvas.
Why did I work on this?
I’ve built a web based app called Kidz Fun Art for my two young daughters, optimised for use on a tablet. The idea was to build something fun that never shows adverts to them or tricks them into sneaky purchases by “accident”. I saw them get irritated by the slow fill algorithm I first wrote, so my personal pride forced me to go solve this problem! Here’s what the final implementation of the solution to this problem looks like on the app.
The Solution
[Edit: After initially publishing, a large speed up was achieved by using OffscreenCanvas in this commit]
Start with an image that has a number of enclosed areas, each with a uniform colour inside those areas. In this example, we’ll use an image with four enclosed areas, numbered 1 through 4.
Now create a web worker, which is JavaScript that runs on a separate thread to the browser thread, so it does not lock up the user interface when processing a lot of data.
let worker = new Worker("./src/worker.js");
The worker.js file contains the code to execute the fill algorithm. In the browser UI code, send the image pixels to the worker by drawing the image to a Canvas element and calling the getImageData function. Note that you send an ImageBuffer object to the worker, not the ImageData itself
The worker script then asynchronously inspects every pixel in the image. It starts by setting the alpha (transparency) value of each pixel to zero, which marks the pixel as unprocessed. When it finds a pixel with a zero alpha value, it executes a FILL operation from that pixel, where every surrounding pixel is given an incremental alpha value. That is, the first time a fill is executed, all surrounding pixels are given an alpha version of 1, the second time an alpha value of 2 is assigned, and so on.
Each time a FILL completes, the worker stores an standalone image of just the area used by the FILL (stored as an array of numbers). When it has inspected all pixels in the source image, it will send back to the UI thread all the individual image ‘masks’ it has calculated, as well as a single image with all of the alpha values set numbers between 1 and 255. This means that using this methodology, we can support a maximum of 255 distinct areas to instant-fill, which should be fine, as we can fall back to a slow fill if a given pixel has not been pre-processed.
You see in the fully processed image above that all pixels in the source image are assigned an alpha value. The numeric value corresponds to one of the masks, as shown below.
For this image, it would generate four masks as in the image above. The red areas are the pixels with non-zero alpha values, and the white are the pixels with alpha values of zero.
When the user clicks on a pixel of the HTML Canvas node, the UI code checks the alpha value in the image returned from the worker. If the value is 2, it selects the second item in the array of masks it received.
Now it is time to use some HTML Canvas magic, by way of the globalCompositeOperation property. This property enables all sorts of fun and interesting operations to be performed with Canvas, but for our purposes we are interested in the source-in value. This makes it so that calling fillRect() on the Canvas context will only fill the non-transparent pixels, and leave the others unchanged.
const pixelMaskContext = pixelMaskCanvasNode.getContext('2d');
const pixelMaskImageData = new ImageData(
pixelMaskInfo.width,
pixelMaskInfo.height
);
pixelMaskImageData.data.set(
new Uint8ClampedArray(pixelMaskInfo.pixels)
);
pixelMaskContext.putImageData(pixelMaskImageData, 0, 0);
// Here's the canvas magic that makes it just draw the non
// transparent pixels onto our main canvas
pixelMaskContext.globalCompositeOperation = "source-in";
pixelMaskContext.fillStyle = colour;
pixelMaskContext.fillRect(
0, 0, pixelMaskInfo.width, pixelMaskInfo.height
);
Now you’ve filled the mask with a colour, in this example purple, then you just have to draw that onto the canvas visible to the user at the top left location of the mask, and you’re done!
One caveat is that if you try this code on your local computer by just opening the index.html file, it will not work as browser security will not let the Worker be registered. You need run a localhost server and run it from there.
P.S.
Thanks to the Excalidraw team for making it so easy to create these diagrams, what a fantastic app!