❀✿❀ SuperLaserNino ✿❀✿

New JavaScript features I didn’t know about

5 June 2025

571 words

Now that going bouldering once a week no longer counts as having a personality, maybe I should try to stand out by being that guy who says “ECMAScript” instead of “JavaScript”.

Anyway, here are some new(-ish) JavaScript features that I recently discovered.

groupBy (2024)

There are now built-in functions to group array elements. With Object.groupBy(items) the result will be a normal object, and with Map.groupBy(items) the result will be a Map.

Both of these have been widely available since March 2024, so they’re ready to use.

Iterator helpers (2025)

I’ve always been irrationally upset that JavaScript’s iterators aren’t as ergonomic as Rust’s. In Rust, you write something like

things.iter()
    .map(|thing| do_stuff(&thing))
    .filter(|thing| do_stuff2(&thing))
    .map(|thing| do_stuff3(&thing))
    .collect();

and that turns your original things into a lazy iterator, then does a bunch of operations, and then collect() turns it back into a Vec. The iterators just go through the items one by one instead of allocating a whole pointless intermediate array.

If you do the same thing in JavaScript, it looks like

things
  .map((thing) => do_stuff(thing))
  .filter((thing) => do_stuff2(thing))
  .map((thing) => do_stuff3(thing));

Semantically that does the same thing as the Rust code, but each map and filter allocates an intermediate array. If the initial things is large, this could theoretically be a performance issue. So far, the way to optimise the above code would be to just use for loops, but for loops are cringe, so the fine folks at TC39 have blessed us with the same helper methods found on Array, but for Iterators. So you can write

Iterator.from(things)
  .map((thing) => do_stuff(thing))
  .filter((thing) => do_stuff2(thing))
  .map((thing) => do_stuff3(thing))
  .toArray();

and bask in the glory of some beautiful premature optimisation.

These methods are available in all modern browsers as of 2025, so are also ready to use.

Error.isError (2025/2026?)

If you ever get annoyed at catch (error: unknown) in TypeScript, there is now a reliable way of checking whether something is an error, and that is Error.isError(error). It’s more readable than

error &&
  typeof error === "object" &&
  "message" in error &&
  typeof error.message === "string";

and it’s more reliable than error instanceof Error, because the instanceof check apparently fails when the error was constructed in a different realm.

Error.isError is available in all modern browsers, except that Safari will currently return false for DOMException objects, which it shouldn’t. So this one is almost ready to use.

TypedArray.prototype.subarray

This one’s actually been around for 10 years, but I only discovered it recently (partly because I’ve never really used TypedArrays before). Unlike TypedArray.prototype.slice, TypedArray.prototype.subarray creates a new TypedArray that points to the same block of memory used by the original, and the data isn’t copied. I found this useful when I was writing (part of) a disassembler, which looked kind of like this:

function decode_instruction(data: Uint8Array): [Instruction, Uint8Array] {
  // Some horrifying ungodly logic
  return [
    my_instruction,
    data.subarray(however_many_bytes_my_instruction_has_taken_up),
  ];
}

function* decode_instructions(data: Uint8Array): Generator<Instruction> {
  let rest = data;
  while (rest.length > 0) {
    const [instruction, new_rest] = decode_instruction(rest);
    yield instruction;
    rest = new_rest;
  }
}

With TypedArray.prototype.slice, this would have allocated a whole new copy of the input data after each instruction was decoded. Without subarray, I’d have had to just keep a reference to the original array and pass around an offset index, and that would have been so inconvenient.

Make Neovim respect your macOS appearance settings

7 January 2025

363 words

[It turns out they’re working on native support for this feature, but it looks like my approach might be more flexible, so I decided to post this anyway.]

When I discovered that Ghostty can change its colours based on the system light/dark mode settings, I was curious whether I could achieve the same effect in Neovim. It turns out, it’s possible! (You don’t need to use Ghostty for this to work, that was just my inspiration to figure this out.)

This code only works on macOS, but I’m sure you could get an AI to translate it to work on Windows or Linux:

function CheckAppearance()
  local theme = vim.fn.system('defaults read -g AppleInterfaceStyle'):gsub('\n', '')
  if theme == 'Dark' then
    vim.o.background = 'dark'
    vim.cmd('colorscheme modus_vivendi')
    vim.cmd [[ hi DiffText cterm=bold gui=bold ctermbg=225 guibg=DarkRed ]]
    -- For some reason the RenderMarkdown plugin doesn't pick up the changes
    -- when run through an autocmd
    vim.cmd [[ hi RenderMarkdown_bgtofg_RenderMarkdownCode guifg=#1e1e1e ]]
  else
    vim.o.background = 'light'
    -- These are my settings, but you can go wild and put whatever you want
    vim.cmd('colorscheme modus_operandi')
    vim.cmd [[ hi DiffText cterm=bold gui=bold ctermbg=225 guibg=LightRed ]]
    -- For some reason the RenderMarkdown plugin doesn't pick up the changes
    -- when run through an autocmd
    vim.cmd [[ hi RenderMarkdown_bgtofg_RenderMarkdownCode guifg=#f2f2f2 ]]
  end
end

-- Run on startup
CheckAppearance()

So far, it’ll just check the appearance setting when you start Neovim. There are a few ways to get it to dynamically detect changes to the setting:

Manually. Just create a user command and run it manually (ugh):

vim.api.nvim_create_user_command('CheckAppearance', function()
  CheckAppearance()
end, {})

On focus gained. This is what I’m using – Neovim will check the appearance whenever you activate it. This means it won’t switch immediately with the system, but it’ll always match when you’re actually using Neovim:

vim.api.nvim_create_autocmd("FocusGained", {
  callback = function()
    CheckAppearance()
  end,
})

Note: Some plugins might not pick up the changes properly, so you might have to add extra stuff in the CheckAppearance function, like that RenderMarkdown_bgtofg_RenderMarkdownCode highlight rule.

Here’s what it looks like in the end:

Don’t manually hunt around for yarn.lock or package-lock.json

27 August 2024

151 words

#!/usr/bin/env perl

# Node dependency manager. Never wonder whether to use `yarn` or `npm` again.

# Usage:
# np        --> runs `yarn` or `npm i`
# np <args> --> runs `yarn <args>` or `npm run <args>`

use strict;
use warnings;

if (!-e 'package.json') {
  my $did_run = 0;
  my @dirs = grep { -d $_ } glob('*');
  for my $dir (@dirs) {
    $did_run = 1;
    chdir($dir);
    if (-e 'package.json') {
      print "Running `np` in $dir\n";
      my $exit_code = system('np ' . join(' ', @ARGV));
      exit($exit_code >> 8) if $exit_code != 0;
    }
    chdir('..');
  }

  if ($did_run == 0) {
    print "No package.json found. Exiting.\n";
    exit(1);
  }
  exit(0);
}

my $exit_code;
if (-e 'package-lock.json') {
  if (@ARGV) {
    $exit_code = system('npm run ' . join(' ', @ARGV));
  } else {
    $exit_code = system('npm i');
  }
} else {
  $exit_code = system('yarn ' . join(' ', @ARGV));
}

exit($exit_code >> 8);

Catio Chronicles 1: Let’s make a plan

18 August 2024

743 words

Our cat Jinx keeps getting sick and vomiting, and the vets are saying this is most likely because young cats like to eat random stuff outside. We really don’t want to restrict Jinx to being an indoor cat, so we decided we would build him a little outside enclosure, a “catio”.

Since this will be a big undertaking, we need to figure out what we want to achieve with the catio, and make a plan. These are some hard requirements:

And we also have some things that would be nice to have:

The basic plan we started out with looked like this:

We would then add mesh to the top, the slanted area, the right side (against the brick wall), and the side facing the viewer. The walls of our flat should ideally not get any mesh, so Jinx can get in and out through the windows.

Buuuuut there are a bunch of pipes running along the walls, so the frame needs to somehow go around them.

My sister came up with the idea of putting the frame far enough away from the walls to avoid the pipes, and then fill the gaps with flat wooden panels (highlighted in red):

I discussed the plans with my dad (who had built us a tree-house when we were young, so I figured he might know stuff). His advice was:


If you know me in real life, you may be wondering: Didn’t I start this whole project like 8 million years ago? Why did it take me so long to write this post?

Well, I’ve been sitting on this unfinished draft since June, planning to have the following be the end of the article:

The final design looks like this:

%% Better design %%

In the next episode, we'll look at finding materials and starting to build stuff!

But it turned out, making a nice archviz-y render of the catio never felt quite worth it. We used Blender to solve many of the design problems – and it was really helpful for that purpose – but once we’d figured out the general idea, it felt really tedious to painstakingly attach every single bracket and make sure all the beams line up right, etc. Also, whenever I started finessing the scene, I’d hit some new design issue that we needed to solve, and that would mean more changes to the design, so, the more high-fidelity the model, the more stuff I’d need to change every time.

The main takeaway for this episode is:

  1. This is an exciting project.
  2. It was absolutely the right choice to plan this project in 3D. I tried a few sketches on paper and I kept getting hopelessly lost.
  3. I’d wished for a nice photoreal render at the end, but this would have required a lot more time investment. And if I’m gonna be spending time making “art” in Blender, I’d rather it be spaceships instead of wooden frames with mesh.

Anyway, here are some screenshots of the design as it currently exists:

I hesitate to call it “finished”, since a bunch of stuff might still change, and we’ve probably already deviated from this plan anyway in real life.

Next time, we’ll look at the materials we got, and the beginning of the construction process.

Finally you can remember whether the lift will ding once or twice

18 August 2024

78 words

Elevators Lifts have this cool accessibility feature where, depending on whether they’re going up or down, they’ll ding once or twice when they arrive. This is so you know the direction without having to look at the display above the doors.

I’ve finally come up with a mnemonic for remembering which is which:

🤯