4 September 2026
117 words
It’s almost exactly 60 years since the initial release of Star Trek (the
original series), and I thought it would be interesting to watch it in real time, just
60 years later. I made a little page that
shows you when new episodes are coming out. There’s even a calendar you can
subscribe to.
I was originally thinking I’d just do all Star Trek shows 60 years delayed,
but that would leave a huge gap between TOS and TNG, so I made everything after
TOS 45 years delayed.
The first episode will be this week, on Tue 8 Sept 2026.
Now I just need to make sure that page doesn’t go offline for the next 25 years.
20 July 2026
Modified: 30 July 2026
424 words
Bookmarks in browsers are nice. You can store a place you frequently go, so you
don’t have to constantly re-google things or memorise the URL of your employer’s
internal Elasticsearch instance. This is all great, but it’s not what
bookmarks are used for in the real world.
In the real world, you have one bookmark per book/magazine/article you’re reading, and you move that bookmark to update how far along you are in
the document. Browsers should have that too, so you can store how far you’ve
gotten in long blog posts or online books (like this one or
that or
Worm).
The existing solutions for those cases are kind of bad:
- You can just leave the tab open and get back to it later. But this is prone
to all sorts of accidents. If you resize your browser window, that might mess
up your scroll position; if you close the tab or restart your browser, that
might mess up your scroll position. And even if your browser syncs your open
tabs across devices, it probably won’t sync your scroll position.
- If you’re reading an online book where each (short) chapter is one page, you
could use regular browser bookmarks to save your reading position. But that
means you either need to delete your old reading-position bookmark every time
you create a new one, or add a date/time to the bookmark name, like “Reading
position 2025-03-29”.1 But this still doesn’t help if you want to read a
20,000-word LessWrong post in more than one sitting.
- You can use a more sophisticated bookmark manager (I use
Raindrop), and place a highlight on the last bit of
text that you read, maybe with a special colour to indicate that this is a
reading-progress marker rather than an actual highlight of the content. This
syncs between devices and works for long documents, but you still need to
tidy up after yourself.
What I’d like is to have a special type of bookmark (call it “Place marker” or
something) that I can put in a specific place on a page, and the next time I
place a marker with the same name, it deletes the old marker. (Basically, what
we have now is the equivalent of tags in Git, and I want the equivalent of
branches in Git (which, incidentally, is what Mercurial calls
“bookmarks”, I think?)). This would
be a perfect feature to build into something like
Raindrop so it can sync between devices.
20 July 2026
234 words
Look at this tweet:
Mitchell Hashimoto @mitchellh
I strongly believe there are entire companies right now under heavy AI psychosis and its impossible to have rational conversations about it with them. I can’t name any specific people because they include personal friends I deeply respect, but I worry about how this plays out.
[…]
It’s frightening, because the psychosis folks operate under an almost absolute “MTTR is all you need” mentality: “its fine to ship bugs because the agents will fix them so quickly and at a scale humans can’t do!” We learned in infrastructure that MTTR is great but you can’t yeet resilient systems entirely.
[…]
9:10 PM · May 15, 2026
I sympathise with the worry that tech people are getting overly excited about moving fast and breaking things with LLMs. But that’s not psychosis! Even calling it AI-induced mania would be overselling it. You just disagree with people’s cost-benefit analyses. A human psychiatrist I know in real life suggested you could call it AI hypomania, if you really insist on using a medical term.
This dilution of the meaning of “psychosis” bothers me because it makes it harder to talk about actual psychosis caused or made worse by AI. Psychosis is a real thing with an actual definition, and that definition is not “does stuff that I think is daft”.
25 August 2025
Modified: 8 October 2026
1264 words
After many months of interruption, we have finally finished the catio. Recall
that, last time, we left off at a rough sketch of
a plan:

And all that was left was getting the materials and actually building the whole
thing.
Continue reading
7 July 2025
445 words
Context
I was recently working on a little web-based audio recorder tool. To abstract
away all the code dealing with the MediaRecorder API, I made a React hook that
you could use like this:
const {
startRecording,
stopRecording,
getAudioData,
// etc
} = useAudioRecorder();
The hook would set up a MediaRecorder instance
and startRecording and stopRecording would call start and
stop.
Calling start(timeslice) will cause a dataavailable event to be triggered every timeslice milliseconds. I then had
an event listener that adds the new data Blob to an array in a useRef.
So far, everything was pretty straight-forward. Next, I wanted getAudioData to
give me all the audio recorded thus far, but MediaRecorder doesn’t have a
method for that. The easy solution would be to return whatever data is
currently stored in the audio chunks ref and set the timeslice value to
something low, like 100ms. Then, when you call getAudioData, the data you get
is at most 100ms out of date. But this also means you have an event handler
pointlessly firing every 100ms.
Fortunately, there is a method called requestData, but all it
does is queue up another dataavailable event. It does not, itself, return the
requested data, or tell you when the dataavailable event has been handled.
The solution
So I wanted a way to call requestData on my MediaRecorder and await until
my event handler fired and updated the audio chunks ref with the latest data.
The solution I picked was to create a new Promise, “steal” its resolve
function from the callback passed into the constructor, and store it in a
useRef. As long as the ref is in scope for the event handler, the event
handler can now resolve our promise.
This is what it looks like when you put it all in its own hook:
import { useMemo, useRef } from "react";
export function useAwaitTrigger<T = void>(): {
wait: () => Promise<T>;
resolve: (val: T) => void;
} {
const resolveRef = useRef<(val: T) => void | null>(null);
return useMemo(
() => ({
wait: async () => {
let resolve: (val: T) => void;
const promise = new Promise<T>((r) => {
resolve = r;
});
resolveRef.current = resolve!;
return promise;
},
resolve: (val: T) => {
if (resolveRef.current) {
const resolve = resolveRef.current;
resolveRef.current = null;
resolve(val);
}
},
}),
[]
);
}
You can use this hook for all sorts of things, like awaiting an onclick event on a button.
This same general technique also works to get an async function* from an event listener (which, idk, might be useful for some reason).