Sign in

Aaron Christiansen

@aaronc.cc
98 followers 260 following 83 posts

Software Engineer in Leeds, UK. Web, Ruby, Rust, and electronics. he/him Cross-posting to Mastodon: @aaronc81@ruby.social

PostsRepliesMedia
Aaron Christiansen @aaronc.cc · 16/09/2026
Great example SVGs over on MDN
Screenshot of MDN, showing a documentation example for the SVG "text" tag. It reads "My cat is grumpy!", with "cat" in large bold text, and "Grumpy" in very large red text.
000
Aaron Christiansen @aaronc.cc · 15/09/2026
Working on this modelling tool again - now you can define *recursive* operators, which lets you construct complex layouts of children based on each of their geometries. Here's a `stack` operator which arranges all children next to each other, even of different sizes, with a gap between each.
A code editor showing this code:

```
operator stack(gap = 0) {
    if ($children == 1) {
        children();
    }

    if ($children > 1) {
        child = children(0);

        translate([child.max_point.x + gap, 0])
        stack(gap = gap)
        ...children([ 1 : $children - 1 ]);
    }
}

stack(gap = 0.5) {
    cube(1);
    cube(2);
    cube(3);
}
```

Besides that, a model viewer shows three cubes of increasing size, with a small gap between each.
000
Aaron Christiansen @aaronc.cc · 23/06/2026
It's day #100 of Loconundrum! Thanks so much to everybody who's played - it's been lovely to hear from people enjoying the game. To mark the milestone, you can now play all 100 past puzzles from the new Archive! Fill out a missed day, or catch up on puzzles from before you started playing.
Screenshot of the new Archive page, showing a colour-coded calendar of different puzzle days
241
Aaron Christiansen @aaronc.cc · 14/06/2026
Here's a peek behind the curtain at Conductor, the thrown-together dashboard I use to schedule Loconundrum puzzles. It used to automatically pick at random from a pool, but once I ran out of the major stations, I started scheduling puzzles manually instead so I could test things out a bit more.
Conductor UI. There are filters "Show all previous days, reveal today/tomorrow/future days" and a tools section with "All CRSes to date", "Quick check", and a "re-run routing" button. Underneath is a list of all stations queued up, with buttons to reorder or edit them.
130
Aaron Christiansen @aaronc.cc · 09/06/2026
Loconundrum now has dark mode 🌙
A screenshot of Loconundrum with dark mode activated
020
Aaron Christiansen @aaronc.cc · 20/03/2026
Arc's "File" menu is probably the worst example of macOS Tahoe's automatic menu icons that I've seen yet
Screenshot of the macOS menu bar for Arc, with the "File" menu expanded. There are about 20 options, but only one of them has an icon, "Save Page As", which causes a bizarre-looking intendation for the icons around it.
010
Aaron Christiansen @aaronc.cc · 19/02/2026
Falling in love with inline snapshot testing My usual problem with snapshot testing is that you need to find the snapshot files to learn the expected output of a test - but with inline snapshots, the expectation is in the test code, which can be updated automatically by the snapshot tool. Neat!
#[test]
fn test_no_subcommand() {
    let test_env = TestEnvironment::default();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    // Outside of a repo.
    let output = test_env.run_jj_in(".", [""; 0]);
    insta::assert_snapshot!(output, @r#"
    ------- stderr -------
    Hint: Use `jj -h` for a list of available commands.
    Run `jj config set --user ui.default-command log` to disable this message.
    Error: There is no jj repo in "."
    [EOF]
    [exit status: 1]
    "#);
020
Aaron Christiansen @aaronc.cc · 09/02/2026
For the latest Gosu Game Jam, I wrote a crop-cultivating roguelike deckbuilder in Ruby, where you race the clock to score coins by combining your crops' effects. This is the most fun I've had creating a jam entry, I've wanted to try making a card game for a while! orangeflash81.itch.io/topsoil
Screenshot of the game. There's 01:24 left on the timer. From my hand, I'm playing a Runner Bean, which reads "On Harvest: Enhance this tile's growth rate by 20%." I'm hovering the game grid, which has crops at various stages of growth already, to decide where to plant my Runner Bean.
010
Aaron Christiansen @aaronc.cc · 26/01/2026
TIL that: 1) you can have SVG favicons nowadays, and 2) they can be styled to respond to light/dark mode automatically evilmartians.com/chronicles/h...
Your HTML page should have a <link> tag in its <head> with rel="icon", type="image/svg+xml" and with the href containing a link to the SVG file as attributes.

SVG is an XML format and can contain a <style> tag to describes CSS. As with any CSS, it can contain media queries like @media (prefers-color-scheme: dark). This will allow you to toggle the same icon between light and dark system themes.
010
Aaron Christiansen @aaronc.cc · 05/01/2026
The inconsistency of icon usage between apps is really interesting, and I wonder if it's consequence of how SF Symbols (Apple's icon font) is organised. Icons are named by SHAPE, not PURPOSE. That could make it more likely for different apps to assign different meanings to the same icon.
A side-by-side comparison of SF Symbols and Material Symbols. The SF Symbol icons are named "arrow.clockwise" whereas the material icon is named "Refresh". This is repeated for fullscreen and reply icons.
100
Aaron Christiansen @aaronc.cc · 04/01/2026
Even better, this ancient version still supports WebSocket integration, so there are some fun browser toys you can play around with. I enjoyed flailing my hand to interact with this particle simulation (which also works with a mouse): molefrog.com/etc/knife/
A particle simulation in a Chrome browser window. Large blobby red and orange particles surround a yellow point, similar to a flame.
000
Aaron Christiansen @aaronc.cc · 04/01/2026
Windows is more promising, because there are older (so more compatible) driver versions available, still branded as "Leap Motion". After downgrading from v6 to v2 drivers, with a manual patch for Windows 10+ (web.archive.org/web/20250309...), this device works with some of the stuff on itch.io!
A screenshot of the "Leap Motion Control Panel" for Windows
110
Aaron Christiansen @aaronc.cc · 04/01/2026
I suspect the development scene was more active when this gadget first released 😅 In an itch.io search for "Leap Motion", every macOS game is either too old to run, or can't see my hands. The Windows driver doesn't support ARM, so no Parallels. I'll have another go later on an x86 Windows PC!
A Windows dialog with this text:

This program can only be installed on versions of Windows designed for the following processor architectures: x64
110
Aaron Christiansen @aaronc.cc · 04/01/2026
The infrared camera actually shows up to macOS as a camera, but sadly selecting it doesn't seem to work - the video freezes on the old camera. I won't be giving my colleagues Ghost Hunters-style Zoom calls any time soon
A macOS camera selector menu listing "Leap Motion Controller" as an option
100
Aaron Christiansen @aaronc.cc · 04/01/2026
The software is now distributed as "Ultraleap" - somewhat amazingly, It Just Works on my Apple Silicon Mac. The hand tracking seems pretty good! It feels more responsive than my Quest 2 or 3 in this demo window.
A screenshot of the "Ultraleap Control Panel" showing my hands with some tracking skeletons overlaid.
110
Aaron Christiansen @aaronc.cc · 04/01/2026
The crucial requirements for "startup gadget from 2013" are ticked: * Dead links in all of the box paperwork * Micro USB 3.0
A Leap-branded Micro USB 3.0 cable
100
Aaron Christiansen @aaronc.cc · 04/01/2026
Do you remember this gadget? It was sold as letting you control your apps/games with your hands. Young-me saw this in Maplin back in the day, thinking how cool it would be. I saw one in a second-hand shop, for a price where I'm not too bothered if it's rubbish. Let's satisfy young-me's curiosity!
The box for a "Leap Motion" controller. It shows the device on the front - a light-grey metal cuboid with a piano-black top. The copy on the front of the box reads "The remarkably accurate, incredibly natural way to interact with your computer."
100
Aaron Christiansen @aaronc.cc · 02/01/2026
I've done some mad science with Ruby 4.0's new Ruby::Box namespacing feature - what if Ruby had JavaScript-style `import`/`export`, rather than requiring entire files? You only get what you import, and imported items don't "leak" between files. Try it, if you dare: github.com/AaronC81/rixby
A VS Code window with three editors:

shapes.rb:
class Rectangle export
end

class Circle export
end

assertion.rb:
export def assert_equal(expected, actual)
end

canvas_test.rb:
import { from 'canvas', :Canvas }
import { from 'shapes', :Rectangle, :Circle }
import { from 'assertion', :assert_equal }
010
Aaron Christiansen @aaronc.cc · 13/12/2025
For a bit of polish and ease-of-experimentation, there's now a basic editor! Here's one possible implementation of a rounded rectangle. Being able to define a new operation that inspects the geometry, like this `rotate_around_centre`, feels rather useful to me.
YASCAD code editor, with dark theme. On the left, a Monaco code editor, with file and rendering controls. On the right, an orange cuboid with rounded corners on a grid.

The code in the editor is:

operator rotate_around_centre(angle) {
    c = buffer() children();

    // Move so that centre is on (0,0), rotate, then move back
    dx = c.origin.x + c.size.x / 2;
    dy = c.origin.y + c.size.y / 2;
    translate([dx, dy])
    rotate(angle)
    translate([-dx, -dy])
    copy(c);
}

// Subtract this from a rect to round its closest corner to the origin
module rounded_corner(r) {
    translate([r, r]) rotate(180)
    difference() {
        square(r);
        circle(r=r);
    }
}

module rounded_rect(w, h, r) {
    difference() {
        square([w, h]);

                                                        rounded_corner(r);
        rotate_around_centre(90)  translate([w-r, 0])   rounded_corner(r);
        rotate_around_centre(180) translate([w-r, h-r]) rounded_corner(r);
        rotate_around_centre(270) translate([0, h-r])   rounded_corner(r);
    }
}

linear_extrude(1)
rounded_rect(5, 6, r=1);
130
Aaron Christiansen @aaronc.cc · 30/11/2025
Starting to get interesting - now you can write operator modules which behave based on some properties of their children - here repeating the children in a grid. You can't do something like this in OpenSCAD unless you also pass a separately-calculated size for the children.
Code editor showing cylinders repeated on a 3x4 grid.

Code:

```
// Repeat the children a certain number of times in the
// X and Y directions to form a grid
operator repeat(xs, ys) {
    // Draw the children to a virtual buffer once
    t = buffer() children();

    for (x = [0:xs-1]) {
        for (y = [0:ys-1]) {
            // Based on the size of that virtual buffer,
            // stamp out copies of the children
            translate([t.size.x * x, t.size.y * y]) {
                copy(t);
            }
        }
    }
}

repeat(3, 4)
cylinder(5, 5);
```
020
Aaron Christiansen @aaronc.cc · 28/11/2025
Having a crack at a project I've been thinking about for a while. An OpenSCAD-alike where you can introspect objects you've already drawn - which means less thinking about how to structure your variables for quick thrown-together 3D prints. First prototype works well!
A code editor showing an STL preview with a small cube on top of another big cube. The code beside is:

```
// Create an object and make a binding for it
big_cube = cube([20, 20, 20]);

// Reference that binding to create another object
translate([0, 0, big_cube.size.y])
cube([5, 5, 5]);
```
030
Aaron Christiansen @aaronc.cc · 02/11/2025
With the 3D-printed base added, and some spare "retro" keycaps, it's good enough for me to call it done! On the software side, it only needed some little tweaks to support the extra three keys, and swap to active-low because it made the wiring easier.
The 3D-printed macro pad, now with a thick base plate with a cutout for a USB port, and grey keycaps fitted.
010
Aaron Christiansen @aaronc.cc · 02/11/2025
Next day update: I tried to route wires between the switches and Pico, but couldn't figure out anything I was happy with. So I remade the plate to have the Pico integrated, which made the wiring much easier! The blank space doesn't look too great, but I can always vinyl-cut something to go there
A 3x3 grid of mechanical keyswitches on a black 3D-printed plastic plate, with a blank space above themThe other side of the plate, with wires connected from the switches to a Raspberry Pi Pico microcontroller screwed into the plate
100
Aaron Christiansen @aaronc.cc · 01/11/2025
…and a little Rust program for the Pi Pico, using Embassy to send keypresses for F13-F18 when pins change. Embassy makes USB device implementation pretty straightforward 🦀
    let keys_future = async {
        loop {
            let selected = select6(
                f13_pin.wait_for_high(),
                f14_pin.wait_for_high(),
                f15_pin.wait_for_high(),
                f16_pin.wait_for_high(),
                f17_pin.wait_for_high(),
                f18_pin.wait_for_high(),
            ).await;

            let code = 
                if      selected.is_first()  { KEY_F13 }
                else if selected.is_second() { KEY_F14 }
                else if selected.is_third()  { KEY_F15 }
                else if selected.is_fourth() { KEY_F16 }
                else if selected.is_fifth()  { KEY_F17 }
                else if selected.is_sixth()  { KEY_F18 }
                else { panic!() };

            info!("Pressing key: {}", code);

            writer.write_serialize(&KeyboardReport {
                keycodes: [code, 0, 0, 0, 0, 0],
                ..KeyboardReport::default()
            }).await.unwrap();

            Timer::after_millis(50).await;

            writer.write_serialize(&KeyboardReport {
                keycodes: [0, 0, 0, 0, 0, 0],
                ..KeyboardReport::default()
            }).await.unwrap();
        }
    };
100
Aaron Christiansen @aaronc.cc · 01/11/2025
Seeing if I can put together a tiny macro pad to live in my work’s office - I miss my macros when I’m not working from home! First steps - a 3D-printed plate with hardwired Gateron Browns (with jumpers I now realise I’ve made far too long)…
Six Gateron Brown key switches inserted into a black plastic plate, with jumper wires coming out the back
100
Aaron Christiansen @aaronc.cc · 20/10/2025
I’ve landed a little warning improvement in this release! Gleam was a lovely codebase and community to contribute to - high hopes for where this language goes in the future
A helpful error for Rustaceans, C#ers, and friends

Gleam has a consistent syntax for constructors at the type and value level, using () for both. It does not use () for value constructors and <> for type constructors, as is common in some other languages.

To help folks coming from other languages a set of helpful errors have been added for when they try to use this non-Gleam syntax in their Gleam code.

error: Syntax error
  ┌─ /src/parse/error.gleam:2:12
  │
2 │ type Either<a, b> {
  │            ^ I was expecting `(` here.

Type parameters use lowercase names and are surrounded by parentheses.

    type Either(a, b) {

See: https://tour.gleam.run/data-types/generic-custom-types/
Notice how the error message includes the correct syntax for the specific code that the programmer has written. The programmer could copy/paste the correct version into their code, if they so desired. A link to the documentation is also provided, linking to whichever feature the syntax error is for.

Thank you Aaron Christiansen!
170
Aaron Christiansen @aaronc.cc · 17/10/2025
Of course, 3D printing caught on quickly, so Microsoft added native support for connecting a 3D printer to your computer
Windows 95 UI mockup.

"Add 3D Printer Wizard" 
Windows can print physical objects by sending G-code
to a 3D printer.

This wizard will help you to install your 3D printer.

To begin, select how the printer is connected to your
computer.
The printer is connected using a serial cable.
The printer is connected using a parallel cable.
The printer is connected to another computer on my network.
010
Aaron Christiansen @aaronc.cc · 15/10/2025
What if we were 3D printing decades ago? Downloading STLs over dial-up? I mocked up Cura for Windows 95
A UI mockup of Ultimater Cura running on Windows 95. In the centre is a greyscale render of a Benchy on a 3D print bed. The left-hand panel includes dropdowns to select printer, nozzle, and filament settings. Underneath are print properties like a layer height profile, infill slider, support checkbox and adhesion checkbox. Under the render is an object selection list box, with toggle buttons for render controls.
220
Aaron Christiansen @aaronc.cc · 22/09/2025
I don’t know how long this has existed, but I’ve never spotted it before… Apple Photos smooshing together geotagging and event listings to give concert photos an artist tag - which you can search by! - is such a damn clever feature
A screenshot of Apple Photos on iOS showing “Magdalena Bay Concert” as a tag
020
Aaron Christiansen @aaronc.cc · 23/08/2025
Echo Point Nova is one of my favourite recent indie games, a super fast movement-shooter, and the first game in a while I’ve 100% completed …and it just got an entire second map as a free update. Time to 100% complete this too 😁
A screenshot of Echo Point Nova. Looking from a height over a dense forest, surrounded by water, with snowy mountains in the distance. Checkpoint markers are visible as tall blue and orange streaks of light are visible, sprinkled across the world.
020
Aaron Christiansen @aaronc.cc · 22/08/2025
I enjoy using keyword-argument-heavy languages, and IMO an underappreciated feature is letting arguments have different external vs internal names. An example in Swift - variables named “of” and “with” would be unintuitive inside the function body, but read naturally when calling
Apple Developer documentation for the method “replacingOccurrences(of:with:)”.

Returns a new string in which all occurrences of a target string in the receiver are replaced by another given string.

func replacingOccurrences(
    of target: String,
    with replacement: String
) -> String
140
Aaron Christiansen @aaronc.cc · 05/08/2025
And of course, like any good language feature, you can use it for Funky-Looking Shenanigans™
import gleam/io

fn twice(f: fn() -> Nil) {
  f()
  f()
}

pub fn main() -> Nil {
  use <- twice()
  io.println("Hello!")
}

// Prints:
//   Hello!
//   Hello!
000
Aaron Christiansen @aaronc.cc · 05/08/2025
You could also use this for resource management, like deferred finalisation. Gleam doesn't have early returns, so you don't need to worry about that skipping your clean-up.
import gleam/io

pub fn defer(deferred: fn() -> Nil, body: fn() -> t) -> t {
  let return_value = body()
  deferred()
  return_value
}

pub fn main() -> Nil {
  use <- defer(fn() { io.println("All done!") })
  io.println("Main function body")
}

// Prints:
//     Main function body
//     All done!
100
Aaron Christiansen @aaronc.cc · 05/08/2025
When made this flexible, it can take place of what most languages would implement as disjoint syntactic sugar. For example, error propagation through the `Result` type, which replaces a dedicated "try" operator:
import gleam/int
import gleam/result

fn div(left: Int, right: Int) -> Result(Int, String) {
  case right {
    0 -> Error("divide by zero")
    _ -> Ok(left / right)
  }
}

fn other() -> Result(String, String) {
  let left = 100
  let right = 5
  use result <- result.try(div(left, right))
  Ok("Your result is: " <> int.to_string(result))
}


pub fn main() -> Nil {
  let _ = other()
  Nil
}
110
Aaron Christiansen @aaronc.cc · 05/08/2025
I've been playing with Gleam, a little compiled language targeting BEAM (Erlang's runtime) and JS. I like a lot of its ideas, it's the closest I've felt to writing "garbage-collected Rust". My favourite trick is the `use` keyword, which is like a generalisation of JS' async/await transform:
import gleam/io

pub fn invoker(f: fn(String) -> Nil) {
  f("Hello")
}

pub fn main() -> Nil {
  invoker(fn(i) {
    io.println(i)
  })

  // is equivalent to

  use i <- invoker()
  io.println(i)
}
120