Evan Hahn's blog

https://evanhahn.com/blog/

My blog, mostly about programming.

フィード

記事のアイキャッチ画像
95% is very different from 99%
Evan Hahn's blog
This post is a rant.I feel a vast difference between interfaces that work most of the time and ones that seem to work every time.Some interfaces that mostly work:AutocorrectGenerative AIFraud/bot detectionVideo game motion controlsWireless connections like wifi, BluetoothAudio transcriptionThese tools can be useful, but I think lots of people have been frustrated by them. Many of us have sent the word “ducking” in a text when they meant to say…a different word. Who hasn’t gotten annoyed at unreliable wifi or Bluetooth? Who hasn’t seen an AI “hallucination”?Other interfaces feel like they work 100% of the time:Pen and paperPhysical keyboards and miceTraditional video game controllers with buttonsWired connectionsFiles on your computerKitchen appliancesNone of these systems are perfect—I’ve had pens with low ink, files corrupted, wires frayed—but I think these frustrations are of a different class.I use my computer’s file system every day, and though I occasionally run into problems, the
9日前
記事のアイキャッチ画像
A little trick to reduce Zoom bandwidth
Evan Hahn's blog
In short: use Zoom’s floating thumbnail window to use less bandwidth.Video calls use a lot of data, and Zoom is no exception.If you’re on a call with a bunch of people and you want to reduce your data usage, here’s a simple trick: put Zoom into a mini floating thumbnail window. Instead of showing everybody on the call, it will just show one person in a tiny window, and it will only load that person’s tiny video.Here’s what that little window looks like:When doing this, I noticed my downloads go from about 900 kb/s to about 175 kb/s. That’s about ⅕ the size!Of course, it has a big disadvantage: you can only see one person in a tiny box! But it’s sometimes useful to do this when you’re having connection issues or you’re on a poor network. It can be a good alternative to asking everyone to turn their video off.This is a small tip, but I hope it’s useful!
12日前
記事のアイキャッチ画像
How does SQLite's "changes" API handle no-op UPDATEs?
Evan Hahn's blog
SQLite has an API to count the number of rows modified by an INSERT, UPDATE, or DELETE. For example, if you delete 10 rows, SELECT changes() will return 10.I recently had a question: what happens if you do an UPDATE on some rows that don’t actually change?For example, let’s say you have a table with 3 colors inside: red, yellow, and blue.CREATE TABLE colors (name TEXT);INSERT INTO colors (name) VALUES ('red');INSERT INTO colors (name) VALUES ('yellow');INSERT INTO colors (name) VALUES ('blue');What happens if you update every row to “yellow”? This only changes 2 of the 3 rows…but what does changes() return?UPDATE colors SET name = 'yellow';SELECT changes();-- 3Turns out the answer is 3.You might expect the answer to be 2. After all, only 2 of the rows actually change! But the answer is 3, because it’s the number of rows “matched” by the UPDATE.If you want to avoid this behavior, you can change your UPDATE to avoid matching rows you want to exclude. For example:UPDATE colors SET name =
1ヶ月前
記事のアイキャッチ画像
MIME types and atom bombs
Evan Hahn's blog
I was recently doing some work with MIME types. As I was scrolling through a big list of them, one piqued my interest as it passed by: text/uri-list.The less interesting part: how this format works. It’s documented in a 1999 proposal marked “experimental” and is not an Internet standard “of any kind”. Basically, it’s a list of URLs separated by newlines, and it can have comments. Here’s what such a file might look like:# this is a commenthttps://example.com/https://evanhahn.com/blog/Seems fine. It is “intended to be a very simple format”, and I think it does its job.The more interesting part: it was proposed by someone named Ron Daniel Jr. who worked at Los Alamos National Laboratory here in the United States. I did not expect my little MIME search to take me to what is sometimes called “birthplace of the atomic bomb”, nor did I expect to learn that we’re still making weapons there.So not only did Los Alamos produce humanity’s deadliest weapon, they also created a simple file format fo
1ヶ月前
記事のアイキャッチ画像
My notes from the Lua 5.4 reference manual
Evan Hahn's blog
I recently read the whole reference manual for Lua 5.4 and took a bunch of notes.Perhaps more accurately, I made a subset of the manual that was interesting to me. (Lots of it was interesting, so I copied a lot.)As you’ll see, I didn’t write most of the below…see Lua’s license.2: Basic Concepts2.1: Values and TypesThere are eight basic types in Lua: nil, boolean, number, string, function, userdata, thread, and table.nil and false are the only falsy values.Despite its name, false is frequently used as an alternative to nil, with the key difference that false behaves like a regular value in a table, while a nil in a table represents an absent key.Lua usually uses 64-bit numbers (floats and ints) but can be compiled to use 32 bits instead.Unless stated otherwise, any overflow when manipulating integer values wrap aroundThe type string represents immutable sequences of bytes. Lua is 8-bit clean: strings can contain any 8-bit value, including embedded zeros (’\0’). Lua is also encoding-agno
1ヶ月前
記事のアイキャッチ画像
How to download every game from js13kgames
Evan Hahn's blog
Here’s a hacky set of instructions to download all of the ZIP files for a given js13kgames year.Navigate to the page for a particular year. For example, js13kgames.com/2024/games.Run the following in your browser’s dev console:(() => { const results = []; document.querySelectorAll("a").forEach(({ href }) => { if (/\/\d{4}\/games\/.+$/.test(href)) { results.push(href + ".zip"); } }); console.log(results.join("\n"));})();Copy the output into a file. I called this file urls.txt but the name doesn’t matter. Make sure this file contains one link per line and nothing else. (I had to clean up a little cruft when copy-pasting from my browser.)Run xargs --arg-file=urls.txt firefox to open every download URL in Firefox, which should initiate a download for each page. This caused my computer to hang for a few seconds because it was opening almost 200 tabs at once, but it eventually worked. (You can’t just fetch the URLs with something like wget, annoyingly.)There are lots of ways this could be im
1ヶ月前
記事のアイキャッチ画像
setBigTimeout
Evan Hahn's blog
In short: JavaScript’s setTimeout breaks after ~25 days. I made setBigTimeout, a silly module to get around this problem. See the package on npm or the source code.setTimeout is JavaScript’s way of delaying code. You provide a timeout, in milliseconds, and a function to call after that time has elapsed.1setTimeout(() => { console.log("This runs after 2 seconds");}, 2000);So far, so good.In most JavaScript runtimes, this duration is represented as a 32-bit signed integer. That means the maximum timeout is about 2.1 billion milliseconds, or about 24.9 days. That’s plenty of time for most people, but not for freaks like me. If you try to set a bigger timeout, weird things happen.For example, if you try setting a timeout to 40 days, the function runs immediately.const FORTY_DAYS_IN_MILLISECONDS = 3.456e9;setTimeout(() => { console.log("This executes immediately!?");}, FORTY_DAYS_IN_MILLISECONDS);This feels like a bug to me. Shouldn’t this wait 40 days?Enter setBigTimeout, a cursed module I
1ヶ月前
記事のアイキャッチ画像
Mapping Mario Kart 8's real-life courses
Evan Hahn's blog
I was playing Mario Kart 8 recently, trying out one of the courses based on a real city. I wondered: how were these courses distributed throughout the world?Mario Kart has 14 tracks based on real cities1. Here’s a breakdown by continent:Europe: 7 courses (Amsterdam Drift, Athens Dash, Berlin Byways, London Loop, Madrid Drive, Paris Promenade, Rome Avanti)Asia: 3 courses (Bangkok Rush, Singapore Speedway, Tokyo Tour)North America: 3 courses (Los Angeles Laps, New York Minute, Vancouver Velocity)Oceania: 1 course (Sydney Sprint)Africa: noneAntarctica: noneSouth America: noneI suppose it makes sense that there are no Antarctican courses, but there’s nothing in Africa or South America. I hope the next game features Lagos Laps or São Paulo Speedway.This doesn’t include tracks that seem inspired by real places, but aren’t explicit references. For example, Toad Harbor has a lot in common with San Francisco but I didn’t include it…I would have if it were named something like “San Francisco Spe
1ヶ月前
記事のアイキャッチ画像
Remember The Milk script to get task permalink
Evan Hahn's blog
This post is aimed at fellow Remember The Milk users.I sometimes want to get the URL for a particular task with Remember The Milk. It’s a little tricky to do this in the UI, so I wrote a simple MilkScript to do it:function main() { const tasks = rtm.getSelectedTasks(); switch (tasks.length) { case 0: return rtm.newMessage("No task selected", "Select a task."); case 1: return rtm.newMessage("Task permalink", tasks[0].getPermalink()); default: return rtm.newMessage( "Too many tasks selected", `You selected ${tasks.length}. Select just one to get its permalink.` ); }}main();To use this, select a single task and run the script. It will put the permalink on the screen. For example, https://www.rememberthemilk.com/app/#all/9733210112.Hope this can be useful to someone else doing the same thing.
2ヶ月前
記事のアイキャッチ画像
Announcing Helmet v8
Evan Hahn's blog
Helmet.js is a Node.js security module I maintain. I just released version 8.0.0, a new major version.There aren’t any new features in this version, just small breaking changes. Because Helmet is a sorta-popular package, I try to keep breaking changes to a minimum. I don’t want developers to have to do a bunch of unnecessary work!The biggest change is either increasing Strict-Transport-Security’s max age or bumping the minimum Node version to v18. For the full list of changes, check out the changelog.I’ve been maintaining this module for over a decade at this point, which is wild to think about! Hope everyone enjoys the new version, as much as you can enjoy something small like Helmet. As always, feel free to reach out to me about anything.
2ヶ月前