I have a fish tank now

I set up a fish tank.

A fish tank with duck weed and airline tube rings so you can see the plants below

It’s a low tech planted tank, using the Walstad method. Kinda like the one described here.

The TLDR is that it’s chock full of plants and critters that maintain the water quality without needing to do lots of cleaning, water changes, or filtering. And it’s cute!


Sometime last year I was admiring the betta fish at Mappins and Ben encouraged me to get one. But I wanted to read up on them first, and it turns out bettas prefer larger tanks, lots of space, things to do. Don’t we all?

So instead of getting a betta I decided to set up a planted aquarium because it seemed like a cool hobby.

Given it’s full of soil and some people haven’t had luck setting up this style of tank, I was cautious about the potential for the water quality go weird and gross. But it was perfect from the moment I set it up. There was a tiny nitrite spike at the start, but it settled down and I haven’t even registered any nitrates. It’s been a pretty steady PH around 7.6 which is about what the tap water is. So all the numbers have been super stable, presumably because of the plant load!

I did a 50% water change maybe a month in. Not because I needed to, but because I wanted to give it a try. It did improve the colour of the water, but it’s been fine since.


One of the coolest things were the critters that hitched a ride along with the plants.

I had a population of bladder snails (they can float!!!) and seed shrimp (so cute!!!) explode in the tank. This was a great way to start off the ecosystem, because the snails and shrimp were a great cleanup crew. I can’t stress enough how much detritus used to be on the bottom of the tank, but now it’s perfectly clean.

After I was sure the water quality was stable, I picked up some shrimp. They’re yellow cherry shrimp (neocaridinas), and I also accidentally picked up a transparent one which I didn’t realise until it was in the bag.


The shrimp are great. They scrounge around the looking for tasty treats, and clean up lots of dead stuff. They swim and crawl around the tank, and sometimes make me think they’re dead when they stop moving in weird positions. They’ve got a lot of personality.

Maybe a month after the shrimp were settled in I went back to Mappins because I saw they had ember tetras – a teeny tiny fish that I was hoping wouldn’t eat my seed shrimp.

I was wrong, they ate the seed shrimp. But they’re the perfect size for such a small tank at up to 2cm long. And once they’re comfortable they’re quite an outgoing and social fish.

They also never sit still so they’re impossible to get a photo with my phone camera.


Anyway, it’s been three months and the tank has gotten overgrown. So I gave the tank a bit of a trim. I didn’t do all of the plants but took a lot of them down and replanted them to make a thicker forest.

I think I freaked the fish out, because they’re all schooling again, rather than out exploring by themselves.

I also found a bunch of the shrimp that were in hiding. I’ve definitely got at least five in there. A bunch of them were chilling under the pump, I guess they like cleaning out the gunk in there and it’s a good hiding spot 😌


I also threw together a quick Walstad style jar. Partly because I thought it was cute, and partly because I know Ben wants to start a tank and it’s a good way to keep some cuttings alive. I’m very curious if the jar will work out or not, but from everything I’ve read it should be fine.

So that’s my three month tank.

A jar with soil, sand, and aquatic plants

Why I no longer recommend GoPro (cw swears)

Cos they sucked me into a free trial and charged my card a year later without my realising. I didn’t even know I was subscribed.

Further, as an Australian there’s no way to contact them because they work on US business hours. So I was up at 2AM the other night hanging off a chat box where Vincent was apologetic, but kept pasting prefabricated blocks of text telling me to get fucked.

It’s enshittification at its best. They’re beholden to investors, and have to invent junk services to get that sweet recurring revenue. And their policy is no refunds, ever. So it’s obviously deliberate.

So basically, fuck em.

Insta360 and DJI both have excellent products in this space that may have their own ideological foibles, but at least won’t brazenly steal money from your card.

My script to auto-delete Google Maps reviews

Google Maps like much of the web has devolved into an AI generated slurry.

Nowadays every review I leave gets a reply “from” the business which is clearly generated by a machine. All the photos are inevitably going in to train Gemini. And the level-up gamification was fun at first but led to nothing more than endless grinding to reach the next level.

Anyway, I’m out. Have been for a little while. But I’ve left a trail of photos and digital detritus I’d rather clean up.

Google doesn’t have a way to bulk-delete your stuff (for obvious if annoying reasons) so I thought I’d write a little script to do it for me.

We're in Google Maps and there's an automation running to delete photos from the Local Guide section.

The scripts

There’s two scripts, one for photos and one for reviews. They’re pretty naive and need to be restarted from time to time but they should be safe enough.

Huge disclaimer: these will probably get out of date at some point and may not work. You should read and understand the code before you do anything with it.

For photos, make sure you’re on the “Photos” tab of the My Contribution section (see above), then I ran the following in the console:

(async () => {
  const sleep = (time) => new Promise(resolve => setTimeout(resolve,time));
  const go = async () => {
    // click the kebab
    document.querySelector('button[jsaction*="pane.photo.actionMenu"]:not([aria-hidden="true"])').click();
    await sleep(200);
    
    // click the delete menu item
    document.querySelector('#action-menu div[role="menuitemradio"]').click();
    await sleep(200);
    
    // click the "yes I'm sure" button
    document.querySelector('div[aria-label="Delete this photo?"] button+button,div[aria-label="Delete this video?"] button+button').click();
    await sleep(1500);
    
    // check if there's any left, and do it all again
    if(document.querySelector('button[jsaction*="pane.photo.actionMenu"]:not([aria-hidden="true"])')) go();
  }
  go();
})()

And for reviews on the reviews tab:

// delete reviews from Google Maps
(async () => {
  const sleep = (time) => new Promise(resolve => setTimeout(resolve,time));
  const go = async () => {
    // click the kebab
    document.querySelector('button[jsaction*="review.actionMenu"]:not([aria-hidden="true"])').click();
    await sleep(300);
    
    // find the delete review menu item
    const deleteMenuItem = document.querySelector('#action-menu div[role="menuitemradio"]+div');
    if(deleteMenuItem.textContent.trim() !== 'Delete review') return console.error('wrong menu item', deleteMenuItem.textContent);
    deleteMenuItem.click();
    await sleep(300);
    
    // click the "yes I'm sure" button
    document.querySelector('div[aria-label="Delete this review?"] button+button').click();
    await sleep(2000);
    
    // check if there's any left, and do it all again
    if(document.querySelector('button[jsaction*="review.actionMenu"]:not([aria-hidden="true"])')) go();
  }
  go();
})();

These are also on Github because my blog code formatting isn’t great. I should fix that up sometime.


What I learned hacking Google Maps (lol)

This code is pretty naive, and breaks a lot. I had to go back in a few times to restart the script, or adjust the timings when something broke. But it got there in the end.

I do appreciate the simplicity of the sleep() function/promiseified setTimeout. This isn’t in the language because it’s generally better to attach some kind of event or observer or do some polling to make sure the app is in the correct state. But in this case I thought it was a fairly elegant way to hack together a script in 5 minutes.

I could make this faster and more stable by implementing some kind of waitUntil(selector) function to await the presence of the menu/dialog in the DOM. But I would also need a waitUntilNot(selector) to wait for the deletion to finish. In any case that’s more complex, and we don’t need to overengineer this.

Anyway, the second script is done now and all my photos and reviews are gone. I’m still somehow a level 6 local guide (down from a level 7) so good for me.

Screenshots from Google Maps: you haven't written any reviews yet, add your photos to Google Maps + Ash is a Local Guide level 6

Human made web button

I’ve been thinking a lot about the problem of garbage AI generated content on the web. It’s a problem that erodes trust and makes everyone look bad. So how do you combat that?

Web buttons, of course!

A bunch of old school 88x31 web buttons (advertising netscape, IE, Acrobat reader, etc), with my new one with rainbow text "Human Made" and a cute pixel art android with a red strikethrough
I took a minute to make a colourful web button, I hope you like it.

These were popular in the early 2000s as a way of adding pieces of flair to your website, to link to friends, and to show off your W3 validated XHTML and CSS.

A web badge that says Valid XHTML 1.0 A web badge that says Valid CSS

There’s a few archives around showing these off, including The 88×31 GIF Collection and this collection of old buttons. (edit: also Pixel Sea by Daniel aka Melon is a delightful way to explore buttons)

And given my web site is of a certain vintage I thought I’d do something up in the style.

I think it worked out really well. The little robot is very cute, and the colours were kind of inspired by the old Amiga button. And as was common at the time I exported it and closed before remembering to save my sources, so this is it now.


If you want to download and use the button on your own website, go for it. It’s all yours. Use it as you wish:

A cute android crossed out, and rainbow text reads 'human made'.   A cute android crossed out, and rainbow text reads 'human made'.

I’ve decided to upscale it here for effect, but the original version is indeed 88×31 pixels.

If you want to upscale it, you can add the css image-rendering:pixelated; to make it render pixel perfect, rather than using a more blurry modern scaling designed for photos.

Dev log: Debugging Safari, an ogre with layers

We’re releasing a WebGL feature soon, and let me tell you Safari has lived up to its reputation as the new Internet Explorer.

We’ve had two big issues:

  1. Safari completely crashes the tab with “a problem repeatedly occurred”
  2. Safari WebGL rendering flickers when scrolling the page

Safari completely crashes the tab with “a problem repeatedly occurred”

The most concerning issue was the Safari crash, which only happened on iOS, not the simulator. I don’t have any iOS devices to test with, so I made the decision to get myself an iPad mini. It’s a write off!

Anyway I’m now the proud owner of a cute lil purple iPad and it still crashes so that’s a good thing, now I can work out how to fix it.

After debugging for way too long, I worked out this was a memory usage issue. Even though Safari runs on some of the most powerful mobile hardware around, it has a hard limit on how much RAM a web page can consume. Even when that page is open. So you can’t make full use of the device in Safari.

My problem had several causes:

  1. I was loading multiple WebGL interactives on page load. Deferring these until the user scrolls them into view helped fix the issue.
  2. I was caching WebGL textures to make the interactive feel snappier. In the end I had to remove all the caching optimisations to get under the Safari memory limit, and now each texture is rendered in realtime when it’s needed.
  3. On top of this, I was hitting an issue with too many layers in Safari causing excessive memory usage.

Compositing layers are created when animation happens in your page. Much like old cel animated films, the browser keeps content that isn’t likely to change in separate layers so it can sandwich animated layers in between, without having to draw everything all over again. For instance a parallax effect will have a background layer and a foreground layer, moving at different speeds.

A pink panther cel animation, showing the panther being lifted off the background.
An example of animation layers drawn on old school cel sheets. The pink panther can be added and removed from the scene without redrawing the layers underneath. Via The Art Professor.

Layers in Safari are created by a number of things, including:

  1. 3d transforms – e.g. -webkit-transform: translateZ(0);
  2. the will-change property – (intended to be used as a last resort. It should not be used to anticipate performance problems)
  3. Canvas layers – canvas is designed to be drawn and redrawn at arbitrary times, so the browser keeps it on its own layer.
  4. position:sticky/position:fixed – similar to parallax effects, these are just a part of doing business and we can’t optimise them any further

In our case, we had a number of 3d transforms and unnecessary will-change properties creating extra layers. These were contributing to Safari crashing. Cutting down on these layers stopped our page from crashing.


Safari WebGL rendering flickers when scrolling the page

This one was killing me because I couldn’t reproduce it on the iPad, and I don’t have an iPhone to test on.

This mostly seemed to happen in in-app browsers (like Slack), but this morning a colleague was able to reliably reproduce it in Safari proper, and I was able to reproduce it in the simulator.

It only seemed to happen while scrolling text boxes over the WebGL canvas So my assumption was that something was clearing the canvas without drawing the scene back in. The app is creating a separate offscreen WebGL instance for performing calculations and I assumed it might be some sort of weird race condition.

I tried a number of fixes that didn’t help:

  1. Render every frame, regardless of whether render is needed (i.e. don’t pause rendering when the scene hasn’t changed. Based on this similar bug).
  2. I tried enabling preserveDrawingBuffer in the renderer, because it seemed related. No dice.
  3. Disable anti-aliasing (per this bug, where Mapbox GL and Three.js fight it out in the same WebGL context)
  4. Downgrade to WebGL 1 (instead of 2). I can’t find the original post suggesting this, but it didn’t do anything.

The actual bug in my case was completely unrelated to Three.js.

When the screen resizes, I update the canvas size, the camera aspect, and projection matrix so that the canvas scales to fit its new dimensions:

  resizeToRoot() {
    const rect = this.root.getBoundingClientRect();
    this.renderer.setSize(rect.width, rect.height);
    this.camera.aspect = rect.width / rect.height;
    this.camera.updateProjectionMatrix();
  }

The problem is that this code doesn’t rerender the scene. So after it’s run the frame is left blank until the next requestAnimationFrame runs.

This wasn’t a huge problem, except when the Safari chrome disappears off the page the browser triggers a whole bunch of resizes in rapid succession. These were resizing the scene, and resulted in black frames until the scene rerendered, multiple times per second. And it didn’t happen on the iPad because the chrome never disappears offscreen.

Adding a this.renderer.render() to the resize function was a somewhat inefficient but effective fix.

Changelog 2023-26 – a summer christmas

It's a trifle, heavy on the jelly, full of sponge and custard and christmas colours. It's messy but it's 100% delicious.

I have two weeks off so I’m spending the giftmas period with my parents. It’s hot, and I’m sitting on the veranda while Charlie pesters me to throw the ball. There’s a decent breeze, but it looks like we’ve missed the storms.

  • Dark mode fix: I fixed a bug on the site where it was always defaulting to dark mode. It’s been that way since I first implemented it because I was checking the truthiness of window.matchMedia("(prefers-color-scheme:dark)") instead of the boolean window.matchMedia("(prefers-color-scheme:dark)").matches. It should now respect your system preferences, and you can use the little icon in the bottom right to toggle between them.
  • Missing images: The backend of this site is running on WordPress, and there was a period of time where I was using it to photoblog. Anyway, some of those posts only contain a featured image and nothing else, and the featured image wasn’t showing up on this frontend site. So now feature images will show unless they’re already elsewhere in the post.
  • Dessert upgrade: I have acquired my mum’s family trifle recipe. It’s not beautiful, but it is my favourite. We made it together on xmas eve, and I’m so happy with it.
  • Hardware acquisition: I placed a late night ebay bid on an old Thinkpad and put Arch Linux on it, inspired by Josh‘s recent blogging. It’s a lot of fun and even though it’s a few years old, it’s way faster for day to day tasks than my M1 mac. Obvs not for video work, but I want to use it to get back into vector art. Though I see the Arch community has AURs for DaVinci Resolve which is shockingly difficult to get running on Linux, so I might dust off my desktop and put it on that as well.

Beyond that I made rum balls in my parents Thermomix and they came out alright. I blitzed up some almonds for a bit of health, and I think I’m gonna start making protein balls at home as a healthy snack. Do you have any good recipes?

I’ve also booked accommodation for my second week of holiday so I’m gonna take my new bike for a spin out to Bribie Island for a few days. No vlog cos it’s gonna be public holiday and kids everywhere. But I’ll post some bits on Mastodon.

Test run across the bridge, it’s _so_ nice to ride. I missed having gears 😆

#breadposting: the story so far

A loaf of delicious golden milk bread, with a freshly made ham cheese and tomato sandwich in the foreground. It looks succulent.

Lately I’ve been making bread.

I know people have been on this bandwagon since the start of the panini, but by bread making adventures are more out of necessity.

I like bread. But I’m not a big sourdough man. I want something light and fluffy to put cheese and cold cuts and salad on. Maybe a little peanut butter. But living in a gentrified neighbourhood, the options are stark.

The Aldi down the road is abominable. Bread from Aldi is made off-site before being shipped in, and in my experience it’s stale before it’s even put on the shelves.

The cheap Woolies bread is decent, but the store has staffing issues. Recently the sole baker went on leave. I don’t begrudge them that, but it meant there was no fresh bread on the shelves, and it had a flow-on effect to other supermarkets nearby. Fresh bread became impossible to get!

Then there’s the French patisserie around the corner. I’ve tried their baguette a few times and it’s alright, but it’s crusty enough to cut your teeth on. I don’t like bread that leaves sore bits in your mouth for the rest of the day so it’s really only an occasional thing.

So at my wit’s end I’m making fresh bread.


Japanese Milk Bread

It started with a message from Ben (as it often does):

Milk bread is not the easiest bread, especially to start with, so I’m told. But I was sitting around at my parents’ place with an idle Thermomix and little else to do so I figured I’d give it a shot.

The tricky part of milk bread is the tangzhong, which is a paste of cooked flour and milk. Wikipedia says it improves the texture of the bread, and also “stabilizes the wheat starches in the bread, to prevent recrystallization” which stops it going stale.

The recipe had Thermomix instructions for the first steps. I have a bit of a love-hate relationship with the Thermie because the recipe paywall stuff is a nuisance when trying to find a recipe, and it’s kinda hard to deviate from a set plan. But this one was a custom, non-wizard affair so it was fairly easy to follow. The trickiest part was finding the “dough” setting — apparently it just spins the blades backwards rather than having a specialised dough hook.

As it was my first time making bread, and it seemed to take forever, because I was hanging on every step of the recipe. Peering at the Thermie waiting for the temperature to get just so. Waiting around for the bread to prove. I didn’t want to leave it, in case it grew legs and ran off.

But the results were great. The bread was true to its tangzhong: soft and fluffy and kind of set my bar high for the rest of my bread making career.

A loaf of perfect bread. It's got three domes, and is perfectly golden brown.

Making bread with a Ninja

At home I don’t have a Thermomix, but I do have a Ninja Foodi Power Blender and Processor System which comes with a dough hook. So I got myself a loaf tin, and tried to recreate the magic.

The Ninja was a bit fiddly on the dough setting. The program runs for exactly one minute and it rattled all over the bench so I needed to hold it down. It seemed super quick for a bread loaf so I ran it again and it went into some kind of shutdown protection mode, presumably because the motor was working too hard.

I later found out you’re supposed to manually break the dough up before running it again, which helps the food processor process it. But even with one and a half runs, the bread came out super impressive for a second attempt.

A very thick cut sandwich, with chicken, tomato, cheese and greens.
I don’t know about you, but a thick-cut milk bread grilled chicken burger is exactly the thing I want.

Soft white bread is too hard

Having mastered the milk bread, and truth be told gotten a little sick of it, I turned my attention toward white bread.

And so I set out confidently on my next bake. Little did I know it would be my undoing.

I don’t know exactly what went wrong, but it was probably a combo of:

  1. I got scared of the Ninja dough setting burning the motor out, so I only did one run of the kneading. That’s 60 seconds, which is probably much too short.
  2. It’s summer, and we’ve been in a heatwave so the temperature has been pretty high and they may have proved too fast.
  3. I think I probably let them prove too long, because:

They immediately deflated in the oven. The first attempt came out like a long flat bread brick. It was edible, but highly questionable.

The bread is maybe 5cm high, if that. It looks dense, and unappealing. Looks a bit like it could knock you out.

The second time I tried to prove in the fridge to try to account for the heat variable. It didn’t seem to do much.

It didn’t really rise much in the fridge overnight, I thought it was supposed to? But it’s currently warming up again on the bench. I’m determined to make this work.

And to make matters worse, I took it out of the oven too soon so it was still raw in some parts. I’m not even going to show that one because it was a disaster.

Thankfully Shawn was here to help eat the edible bits. And it was tasty! But a fair bit of it went in the bin.

So even though that was only yesterday I thought I’d give it one more try. This time I:

  1. Ran the dough through the food processor several times. It came out a much better, springy consistency.
  2. Didn’t fuck around while proofing. I let it go for 30 minutes the first time, and another 40 minutes the second time.
  3. Gave it a bit of a bash to make sure it wasn’t going to collapse before putting it in the oven. It stayed solid.
  4. Got overly excited and underbaked the damn thing again lol.

So I sit here staring at my still slightly-flat loaf cooling on the rack. Apparently allowing the bread to come to room temperature rather than tucking right in “optimizes texture and flavour, and there are several theories as to why“. So I’m gonna leave it go.


Several hours later

I cut the bread and to my surprise it actually looks like bread.

It’s still a touch heavy, but the texture is about right. One jam and peanut butter sandwich later I’m content.

Two slices of bread are cut off. It's a fairly good crumb, with an even texture. It's about 70 cm high, and there's peanut butter and jam in the background.

I still don’t entirely know what went wrong with the others, but the extra kneading and possibly the quicker proving time made a difference. Next time I’ll just have to keep it in long enough for a bit more colour. And I think if I want it to be any taller I might need to multiply the recipe.

I’m not sure how to get it fluffier but apparently there are various types of tangzhong that can help get more moisture in, so I might try that next.

Anyway if you have any ideas, hit me up. I’m keen to hear your tips and tricks. You can reply to this thread on Mastodon or just shoot me an email.

Gonna try Vodafone this year & I got a ticket

I now have the trifecta of wifi at home, work, and my parents place. So I just ordered the cheapest $100/year Kogan/Vodafone SIM and I’ll activate it when my Telstra prepaid runs out next month. You can’t beat those kinds of dollars.

I’ve been at my parents all week for assorted reasons. Just finished the drive home.

I got pulled over by a police man on a motorcycle on the way back and given a fine because my bike was obscuring the number plate. He didn’t want me to fix it or anything, just a fine. So that’s cool. That’s $125 bucks going towards… I don’t know, what does that even contribute to?

I’m exhausted. It feels like perpetual Sundays afternoon.

Some of my plants are dead, some of them are double the size.

I’ve collapsed on the couch, I fear I may never get up again.

Things are alright.

First weekend of spring

On Saturday I woke up with a vague sense of unease and decided to fix it by cleaning the house. Kitchen, laundry, bedroom, floors. I didn’t have anywhere to be so as I noticed tasks I did them, and it left my place feeling like a great space to be.

It’s been feeling like spring for most of winter, it’s been a very mild one. But nevertheless the sun has swung around and I’m getting more light in my place now. I can leave my doors open (screens closed) to let air circulate, and the plants are having a great time.

It is a time of optimism and, well, occasionally having to put that climate anxiety back on the shelf. But largely optimism.

Riverfire 2023

I didn’t get up to much during the day but in the evening I decided to head out to watch the Riverfire fireworks.

It’s usually packed in South Bank and everywhere really, so I though I’d roll around on my bike and find a spot. Turns out the Kurilpa Bridge was open and you could just hang out and watch from there. I didn’t have an amazing view, but it was a nice $0 activity that took almost no effort on my part.

On the way home I stopped at the William Jolly Bridge because there’s a light installation underneath now. That night it was lit in rainbow colours and looked fetching in the eerie firework smoke.

Sunday funday with these idiots

Ben came over and we went to Banette, the little French bakery.

They revamped their menu and they’re providing table service now so it’s a proper place to dine out. I was really happy to see their sad and crusty Croque Monsieur from the hot box is now freshly made, along with a number of other breakfasty options.

So we had brunch for lunch. It was pretty great. I don’t imagine I’ll get this too often, but it was a decadent but not too heavy option and the greenery really rounded it out. I’m very pleased.

Mrs Crunchy with a fried egg and salad on a plate

After that we went on a nursery crawl and looked at all the plants.

I’m trying to be a bit frugal since I changed jobs and accidentally went 6 weeks between proper pay cheques, so I only picked up a couple of little friends and a colourful pot because I need to re-pot one of my plants.

But Mappins also has fish and they were very cute and I really wanted to take one home with me. That might be a separate project needing a bit more research, but I reckon it could be a good hobby.

A brightly coloured pot with a couple of grean leafies inside.