More from SOS
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
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
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.
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.
More in programming
TokyoDev has grown substantially over the past few years. We’ve expanded our community, dramatically increased the scope and frequency of our events, and have curated a library of articles for international developers. Despite all that, the way we run the business hasn’t changed much. I’ve continued to coordinate most of these activities myself, making the final decisions and keeping everything connected. In short, TokyoDev cannot run without my daily input. That approach worked well when TokyoDev was smaller, but it’s increasingly limiting how much we can accomplish. To help the business grow further, I wanted to find someone to take over some of my responsibilities and bring a fresh perspective. This is a significant step for me personally, as while I have worked with many contractors over the years, I have never actually hired a regular employee. Most importantly, I wanted to ensure that this person was someone I could trust to understand what TokyoDev stands for, and to make decisions in line with our philosophy. So I’m happy to announce that Scott Rothrock is joining TokyoDev full time as our Head of Operations. About Scott Scott has lived in Japan for nearly 20 years and has worked as a software engineer in multiple industries and environments, including small Japanese-owned businesses, overseas employers, and international companies based in Japan. Until the end of October 2026, he was the head of the Japan Markets team at Kraken Technologies, where he led an international team of software engineers in delivering complicated, regulated products on strict timelines for Japanese electricity and gas retailers, while also ensuring compliance with evolving organizational and global standards. He’s made a name for himself both inside and outside Japan as a kind, effective mentor who can cultivate great engineers and leaders. Scott’s previous role in TokyoDev Some of you may already know Scott in another capacity, as he has been involved with TokyoDev since 2022. He began as a community volunteer and later became our community manager, which also included responsibilities such as managing our article pipeline from submission to publishing. When I created TokyoDev’s Discord, I had no previous experience running an online community. Scott joined soon after I started and began helping other members, stepping in when people behaved inappropriately, and configuring our roles and permissions and safety features. I was impressed by his initiative and asked him to become an official moderator in January 2023. Outside of his role as a moderator, Scott has always pushed TokyoDev to grow; for example, he arranged internet events such as livestreamed AMAs and interviews. He organized TokyoDev’s first meetup as a volunteer in February 2023, which taught me that there was interest in TokyoDev events outside of the internet. By the end of 2023, I had brought him on as our part-time community manager. What I saw throughout his tenure was that Scott continued to take initiative in expanding TokyoDev. Scott was one of our first external contributors, writing about his experience living with a disability in Japan. He encouraged me to expand and refine both our editing and submission processes so that we could reach out to even more contributors and platform their perspectives. The editorial team now consists of him and Rebecca Callahan, and they’ve had a hand in every article that TokyoDev has published. In other words, since 2022, Scott has shown that he cares about TokyoDev as much as I do. He is invested in what we do and why we do it, and shares my vision for what TokyoDev can become. What changes now Scott’s experiences as an engineer, a mentor, and a leader make me confident that, as our Head of Operations, he will continue to help TokyoDev mature. A big part of what has made TokyoDev successful is that I see the software developers who use our site as my peers and genuinely want to help them. Scott shares that perspective, and will join me in ensuring that TokyoDev remains true to its principles while we grow. So what changes in the short term? Nothing dramatic. I will teach Scott about managing TokyoDev as a business, and he will document our processes and products as a part of that learning. He will also look for areas of improvement while ensuring that we remain compliant with regulations and labor law. Over time, I will hand over some other responsibilities. I will continue to run TokyoDev, but TokyoDev will become less dependent on me for its day-to-day operations. Long term, I’m excited about what this will enable us to do. TokyoDev has always been about helping international software developers build successful careers in Japan, and bringing Scott on full time puts us in a much better position to continue doing that. We look forward to writing the next chapter of TokyoDev together.
It's time for a dev blogger's favorite topic: blogging about my new blogging setup. After migrating my blog from Hexo to Eleventy, building a photography website and creating a personal one-pager, it was time to find a new digital home for all my projects. If you notice any issue after the migration, please let me know. I redirect the RSS feed to the new domain, but you might want to update the feed URL in your reader. Motivation I've been maintaining three different websites under two different names: a mostly developer-oriented blog, a photography portfolio and a one-pager gluing everything together. When I started blogging about my sabbatical journey, I was thinking about making yet another page to create a clear separation. I've decided to do the opposite and merge all my websites into one instead. As I further reduce my dependency on social media and third-party services, it has become even more important to have my own space and practice POSSE. Every decision I've made was driven by two main goals: simplicity and personality. Setup I've stopped chasing the latest and greatest. I want to keep the setup as clean as possible, which means pulling fewer dependencies. Every package adds complexity and maintainability costs, so I've only added what was necessary — no more Gulp, Sass or Tailwind. Instead of taking one of my existing websites as a starting point, I created this project from scratch. I reused some of my previous code, but I didn't want to take over any technical debt (especially with the advancements in CSS). The foundation of this website hasn't changed: Eleventy (Build Awesome). It covers the fundamentals of a static site and offers a great level of customization. I've made my project more and less dependent on Eleventy at the same time: I replaced Gulp as my build pipeline with pure Eleventy processing. I replaced most shortcodes with Markdown alternatives (and markdown-it plugins), which are better supported by editors like Obsidian. I've also replaced the Nunjucks template engine with Liquid, which comes with a few trade-offs. The main reason was to avoid creating a new collection for every set of posts I render. With Liquid, you can apply array functions like filtering or sorting in the template itself: {% assign drafts = collections.all | where: "data.isDraft" | reverse %} {% for draft in drafts %} … {% endfor %} Another advantage is having date formatting included, so I don't need another library or custom code. You can even set a default date format, so you don't have to provide it each time. What I don't like is the verbosity of the template language, although I've gotten used to it by now. Nunjucks: {{ "/tags/" + tag + "/" }} {{ modified or created or date }} {% set perspectives = ["dev", "photography", "life"] %} Liquid: {{ "/tags/" | append: tag | append: "/" }} {{ modified | default: created | default: date }} {% assign perspectives = "dev, photography, life" | split: ", " %} After migrating my existing content, I've noticed the build taking way too long, both in the dev server and the production build. I've found out that the Eleventy template cache is turned off for Liquid by default. This was useful during development, but when updating actual content, it caused every template and partial to be constantly recalculated. After turning the cache on, this issue was resolved: eleventyConfig.setLiquidOptions({ cache: true }); Check the colophon for the full tech stack overview. Perspectives The main challenge was finding a way to separate and present my main personas. The Venn diagram intersection of people interested in software development, photography and my personal life is probably small. I could use tags, but they don't offer the level of personalization I was looking for. Most personal blogs separate the content by post type (blog post, note, photo), but this doesn't fit my use case. Instead, I've decided to structure my website around three perspectives, a term I have borrowed from the Eclipse IDE. Each perspective comes with its own avatar, tagline and bio. If someone is interested in everything I share (hi Mom!), they can still view all my posts. Design Since building my photography website, I've made many changes, but I never got around to share an update. The biggest change was the design, which I have used as a blueprint for the new website. I've decided to mimic a social timeline/feed, which most people are familiar with. At the same time, it should be clear that it's a personal website and not yet another walled garden. Typography is an important part of every design. The first step is to choose between a system font stack and a custom font, which comes with the usual performance vs. personality trade-offs. I've decided to go with a uniform look across all devices. I've settled on Source Sans 3 for copy text and Hack for code. Another big change was introducing a sidebar. I always felt like the empty horizontal space was "wasted" on a desktop device, but getting the sidebar UX right is challenging. I'm happy with the result, especially with the last two panels adding a personal touch to the design. Images I've made several changes to the handling of image files. To make them compatible with Markdown editors, I had to replace my custom picture shortcode. The official Eleventy image plugin is a popular choice, but it violates my simplicity goal and doesn't support everything I needed. I've decided to go with a plain Markdown image markup and a single image file (no format fallback or 2x version for retina displays). Additionally, I wrote a markdown-it plugin to turn the optional title parameter into a caption. As for the file format, both WebP and AVIF have excellent browser support. For each image, I select the format based on the quality and file size. For my photos, I'm using WebP for the timeline thumbnails and JPG for the full-size version. Theme picker There have been some recent discussions about dark mode toggles, and I considered going for a light/dark solution based on the user's OS preference. Driven by the personality goal, I knew I had to add at least one more theme, so a proper theme picker was necessary. Similar to navigation, people like to hide the theme selection on mobile devices behind a burger menu or a dropdown. I prefer the solution by Sarah L. Fossheim, where all available themes are displayed above the page footer. A friend was scrolling through my new website, and their first question was "Why is the theme picker in the footer?". This tells me the solution was a good idea — I don't think he would have noticed a typical theme button in the header. The implementation follows standard practices: Use the light or dark theme as default based on the user's OS or browser preference. Store a selected theme in the local storage and use it as the single source of truth. Prevent a flash of inaccurate color theme. Make the theme play nicely with bfcache. I might have slightly gone overboard with my third theme, but the nostalgia kept me adding more and more features. Enjoy going back to the 90s: Feeds As browser vendors decided to remove XSLT support, I didn't bother to style my feed. Instead, I've created a new web feeds page, listing all available feeds and a short explanation. I provide one feed for every perspective and one with all my posts combined. I once offered tag-based RSS feeds. This was useful for my sabbatical, but even there I had only a single subscriber. Now, all tag feeds redirect to their main perspective feed. I've also dropped the JSON feed, despite having at least 16 subscribers. Given that every web feed reader supports Atom feeds, I don't see the value of providing a JSON alternative. A JSON feed doesn't add much complexity, but handling different media types (post, photo) and properly escaping the content is one more thing to implement and maintain. Quality assurance I've put a lot of effort into my new digital home, and I want to make sure it stays in good shape. I've included several tools, most running automatically as part of the CI pipeline: eleventyDataSchema — lint Eleventy data values like titles or dates markdownlint — format/lint Markdown files Vale — lint content (grammar, writing style, formatting) Lychee — find broken links (internal and external) Evaluatory — check accessibility and validate HTML Oxlint — lint JavaScript files Eleventy linters are another option to analyze a template's output, but I haven't found a use case for it yet. Not everything can be automated, so I also did the following manually: Test with Chrome, Firefox and Safari Test with Polypane Test with Google Lighthouse Test with a screen reader Test with JavaScript disabled Test with CSS disabled Test at 200% zoom Test with a bigger browser font size Test with custom fonts disabled Test with delayed assets Performance It's difficult to compare the performance of two separate websites with that of a combined one. Some parts got worse, e.g. bigger font files and more CSS. At the same time, I've noticed that gzip compression was turned off for anything but HTML for a few years now (I must have missed it when upgrading nginx) 🙈. One improvement was to implement on-demand assets. For example, the lite-youtube-embed CSS and JavaScript is only loaded if a <lite-youtube> element is present on the page. As before, JavaScript is fully optional (except for interactive demos in individual posts). I've been using a mix of <noscript> and CSS to hide elements that require JS (like the theme picker). I don't like the user experience of pagination, so I avoided it for years. But now that I have almost 300 posts, I've decided to finally add it. I display 30 posts per page, with images further down still being lazy-loaded for better performance. I haven't applied pagination to tag pages, as that's more complex with Eleventy. Bits and pieces Here are some other noteworthy changes: I've removed post-specific social share images and use a single WebP Open Graph image instead. I've dropped JSON-LD. Open Graph and microformats are sufficient. "Naming is hard" is a common expression in software development, but it also applies to photograph titles. I've decided to use a counter as the title, e.g. #0100. Sometimes I do come up with a title that I love, like the Castle in the Sky, so I might mention it in the description. On a photo page, you can now open the previous/next photo using the left/right arrow keys (in addition to the existing navigation buttons). It's now possible to filter photos by date, gear, or location. Check the explore page for an overview. I've learned about and used some experimental CSS features (with progressive enhancement), like scroll-triggered animations or squircle corners. I've added an AI statement on my LLM usage. I finally have a proper about page. Cool URIs don't change, but it's inevitable when moving to a new domain. All my previous URLs are redirected, except for some assets that are no longer used. I will keep the old domain, so the redirects will continue to work for a foreseeable future. I've kept my previous Easter egg and added a few more. Have a happy hunt! 🕵️♂️ Inspiration I visited hundreds of personal websites looking for inspiration. Here are a few that I kept coming back to: Emma Goto. I love Emma's approach to presenting the variety of her interests, even though I went a different route. Brennan Kenneth Brown. This website has so much to offer. It's a great example of what the open web looks like. Rex Barrett. The sentence "Collecting hobbies is my hobby." hit home — so much that I had to steal borrow it for my short life bio. Conclusion This project was mostly about merging the content of two websites into one. I knew this would take a considerable amount of time, but I still greatly underestimated the effort. In the end, it was all about the journey, not the destination. I loved fine-tuning the design and tweaking little details that probably no one will ever notice. I still have many more ideas left in my backlog. How do you like my new digital home? If you have an opinion, let me know! Reply via email
New electronica album by Warrington-Runcorn New Town Development Plan