Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
28
Last week I joined val.town as Steve Krouse’s cofounder. Val.town is a system for programming in the small, for writing short programs in TypeScript that can link together into something larger. It’s for writing that scheduled web scraper or basic API that you wanted to make but didn’t want to add to your pile of servers on AWS or GCP. It’s already a lot of fun to use, and is going to keep getting better. Placemark lives. It’s still getting better every week. It’s been about two years since I formally started it, and it’s grown into a great product with the help of supportive and creative customers. I’ve been relatively quiet about Placemark-the-business, despite writing a steady flow of blog posts on features & improvements on placemark.io. But, soon - I’ll publish a state of Placemark in the near future. I’m tremendously excited about Val.town. It’s a small team in Brooklyn and Seattle, with lots of creative energy. I think we’ll be able to take this opportunity and make something...
over a year ago

More from macwright.com

2025 Predictions

I was just enjoying Simon Willison’s predictions and, heck, why not. 1: The web becomes adversarial to AI The history of search engines is sort of an arms race between websites and search engines. Back in the early 2000s, juicing your ranking on search engines was pretty easy - you could put a bunch of junk in your meta description tags or put some text with lots of keywords on each page and make that text really tiny and transparent so users didn’t notice it but Google did. I doubt that Perplexity’s userbase is that big but Perplexity users are probably a lot wealthier on average than Google’s, and there’s some edge to be achieved by getting Perplexity to rank your content highly or recommend your website. I’ve already noticed some search results including links to content farms. There are handful of startups that do this already, but the prediction is: the average marketing exec at a consumer brand will put some of their budget to work on fooling AI. That means serving different content to AI scrapers, maybe using some twist on Glaze and other forms of adversarial image processing to make their marketing images more tantalizing to the bots. Websites will be increasingly aware that they’re being consumed by AI, and they will have a vested interest in messing with the way AI ‘perceives’ them. As Simon notes in his predictions, AIs are gullible: and that’s before there are widespread efforts to fool them. There’s probably some way to detect an AI scraper, give it a special payload, and trick it into recommending your brand of razors whenever anyone asks, and once someone figures it out this will be the marketing trend of the decade. 2: Copyright nihilism breeds a return to physical-only media The latest lawsuit about Meta’s use of pirated books, allegedly with Mark Zuckerberg’s explicit permission, if true, will be another reason to lose faith in the American legal system’s intellectual property system entirely. We’ve only seen it used to punish individuals and protect corporations, regardless of the facts and damages, and there’s no reason to believe it will do anything different (POSIWID). The result, besides an uptick in nihilism, could be a rejuvenation of physical-only releases. New albums only released on vinyl. Books only available in paperback format. More private screenings of hip movies. When all digital records are part of the ‘training dataset,’ a niche, hipster subset will be drawn to things that aren’t as easily captured and reproduced. This is parallel, to the state of closed-source models from Anthropic or OpenAI. They’re never distributed or run locally. They exist as bytes on some hard drive and in some massive GPU’s memory in some datacenter, and there aren’t Bittorrents pirating them because they’re kept away from people, not because of the power of copyright law. What can be accessed can be copied, so secrecy and inaccessibility is valuable. 3: American tech companies will pull out of Europe because they want to do acquisitions The incoming political administration will probably bring an end to Lina Khan’s era of the FTC, and era in which the FTC did stuff. We will go back to a ‘hands off’ policy in which big companies will acquire each other pretty often without much government interference. But, even in Khan’s era, the real nail in the coffin for one of the biggest acquisitions - Adobe’s attempt to buy Figma – was regulators from the EU and UK. Those regulators will probably keep doing stuff, so I think it’s likely that the next time some company wants to acquire a close competitor, they just close up shop in the EU, maybe with a long-term plan to return. 4: The tech industry’s ‘DEI backlash’ will run up against reality The reality is that the gap between women and men in terms of college degrees is really big: “Today, 47% of U.S. women ages 25 to 34 have a bachelor’s degree, compared with 37% of men.” And that a great deal of the tech industry’s workforce is made of up highly-skilled people who are on H-1B visas. The synthesis will be that tech workers will be more diverse, in some respects, but by stripping away the bare-bones protections around their presence, companies will keep them in a more vulnerable and exploitable position. But hard right-wingers will have plenty to complain about because these companies will continue to look less white and male, because the labor pool is not that. 5: Local-first will have a breakthrough moment I think that Zero Sync has a good chance at cracking this really hard problem. So does electric and maybe jazz, too. The gap between the dream of local-first apps and the reality has been wide, but I think projects are starting to come to grips with a few hard truths: Full decentralization is not worth it. You need to design for syncing a subset of the data, not the entire dataset. You need an approach to schema evolution and permission checking These systems are getting there. We could see a big, Figma-level application built on Zero this year that will set the standard for future web application architecture. 6: Local, small AI models will be a big deal Embedding models are cool as heck. New text-to-speech and speech-to-text models are dramatically better than what came before. Image segmentation is getting a lot better. There’s a lot of stuff that is coming out of this boom that will be able to scale down to a small model that runs on a phone, browser, or at least on our own web servers without having to call out to OpenAI or Anthropic APIs. It’ll make sense for costs, performance, and security. Candle is a really interesting effort in this area. Mini predictions Substack will re-bundle news. People are tired of subscribing to individual newsletters. Substack will introduce some ~$20/month plan that gives you access to all of the newsletters that participate in this new pricing model. TypeScript gets a zeitwork equivalent and lots of people use it. Same as how prettier brought full code formatting from TypeScript, autoloading is the kind of thing that once you have it, it’s magic. What if you could just write <SomeComponent /> in your React app and didn’t have to import it? I think this would be extremely addictive and catch on fast. Node.js will fend off its competitors. Even though Val Town is built around Deno’s magic, I’ve been very impressed that Node.js is keeping up. They’ve introduced permissions, just like Deno, and native TypeScript support, just like the upstarts. Bun and Deno will keep gaining adherents, but Node.js has a long future ahead of it. Another US city starts seriously considering congestion pricing. For all the chatter and terrible discourse around the plan, it is obviously a good idea and it will work, as it has in every other case, and inspire other cities to do the same. Stripe will IPO. They’re still killing it, but they’re killing it in an established, repeatable way that public markets will like, and will let up the pressure on the many, many people who own their stock.

3 weeks ago 29 votes
Recently 2024

Happy end-of-2024! It’s been a pretty good year overall. I’m thankful. There’s no way that I’ll be able to remember and carve out the time around New Years to write this, so here’s some end-of-year roundup, ahead of schedule! Running This was my biggest year for running on record: 687 miles as of today. I think the biggest difference with this year was just that nothing stood in the way of my being pretty consistent and putting in the miles: the weather has been mild, I haven’t had any major injuries, and long runs have felt pretty good. I was happy to hit a half-marathon PR (1:36:21), but my performance in 5Ks was far short of the goal of sub-20 – partly because Brooklyn’s wonderful 5K series was run at the peak of summer, with multiple races at over 85°F. I learned the value of good lightweight running gear: Bakline’s singlets and Goodr sunglasses were super helpful in getting me through the summer. Work Val Town raised a seed round and hired a bunch of excellent people. We moved into a new office of our own, which has a great vibe. It’s been good: we’re doing a lot of ground-up work wrangling cgroups and low-level worker scheduling, and a lot of UX-in work, just trying to make it a pleasant tool. Frankly, with every product I’ve worked on, I’ve never had a feeling that it was good enough, and accordingly, for me, Val Town feels like it has a long way to go. It’s probably a good tendency to be sort of unsatisfied and motivated to constantly improve. New York It’s still such a wonderful place to live. Late this year, I’ve been rediscovering my obsession with cycling, and realizing how much I whiffed the opportunity to ride more when I lived in San Francisco. I guess that’s the first time I felt genuinely nostalgic for the West coast. I miss DC a bit too: it’s one of the few cities where my friends have been able to stay in the city proper while raising children, and I miss the accessible, underdog punk scene. But Brooklyn is just a remarkable place to live. My walk score is 100. The degree to which people here are in the city because they want to be, not because they have to, shapes so much of what makes it great. Other ‘metrics’ Relative to my old level obsession about self-quantification, my ‘metrics’ are pretty moderate now. Everything’s just backward-looking: I’m not paying much attention to the numbers as I go, it’s just fun to look at them year-over-year trends. That said, this was a lackluster year for reading: just 18 books so far. I think I just read an above-average number of books that I didn’t enjoy very much. Next year I’m going to return to authors who I already love, and stay away from genres that – the data shows – I don’t like. Whereas this was a banner year for watching movies: not great! Next year, I want to flip these results. Of everything I saw, Kinds of Kindness will probably stick with me the most. Placemark It seems like a decade ago that I released Placemark as open source software, as developing it as a closed-source SaaS application for a few years. But I did that in January. There have been a few great open source contributions since then, but it’s pretty quiet. Which is okay, somewhat expected: there is no hidden crowd of people with extra time on their hands and unending enthusiasm for ‘geospatial software’ waiting to contribute to that kind of project. Placemark is also, even with my obsessive focus on simplicity, a pretty complicated codebase. The learning curve is probably pretty significant. Maps are a challenging problem area: that’s what attracts a lot of people to them, but people who use maps persistently have the feeling that it couldn’t be that complicated, which means that few users convert into contributors. There are a few prominent efforts chasing similar goals as Placemark: Atlas.co is aiming to be an all-in-one editing/analysis platform, Felt a cloud-native GIS platform, and then there are plenty of indiehackers-style projects. I hope these projects take off! Figma plugins I also kept maintaining the Figma plugins I developed under the Placemark name. Potentially a lot of people are using them, but I don’t really know. The problem with filling in water shapes in the plugins is still unsolved: it’s pretty hard and I haven’t had the time or motivation to fix it. The most energy into those plugins this year, unfortunately, was when someone noticed that the dataset I was using - Natural Earth – marked Crimea as part of Russia. Which obviously: I don’t draw the countries in datasets, but it’s a reasonable thing to point out (but to assume that the author is malicious was a real downer, again, like, I don’t draw the countries). This decision from Natural Earth’s maintainer is heavily discussed and they aren’t planning on changing it, so I switched to world-atlas, which doesn’t have that problem. Which was fine, but a reminder of the days when I worked on maps full-time and this kind of unexpected “you’re the baddie” realization came up much more often. Sometimes it was silly: people who complain about label priority in the sense of “why, at zoom level 3, does one country’s name show up and not anothers?” was just silly. The answer, ahem, was that there isn’t enough space for the two labels and one country had a higher population or a geometry that gave their label more distance from the other country’s centroid. But a lot of the territorial disputes are part of people’s long cultural, political, military history and the source of intergenerational strife. Of course that’s serious stuff. Making a tool that shows a globe with labels on it will probably always trigger some sort of moment like that, and it’s a reason to not work on it that much because you’re bound to unintentionally step on something contentious. Other projects I released Obsidian Freeform, and have been using it a bit myself. Obsidian has really stuck for me. My vault is well over 2,000 notes, and I’ve created a daily note for almost every day for the last year. Freeform was a fun project and I have other ideas that are Obsidian plugin-shaped, though I’ve become a little bit let down by the plugin API - the fact that Obsidian-flavored-Markdown is nonstandard and the parser/AST is not accessible to plugins is a pretty big drawback for the kinds of things I want to build. Elsewhere recently I’ve been writing a bit: Recently I’ve written about dependency bloat and a developer analytics tool we built at Val Town, and started writing some supplementary documentation for Observable Plot about parts of its API that I think are unintuitive. On the micro blog, I wrote about not using GitHub Copilot and how brands should make a comeback. This blog got a gentle redesign in May, to show multiple categories of posts on the home page, and then in August I did a mass update to switch all YouTube embeds to lite-youtube-embed to make pages load faster. I’m still running Jekyll, like I have been for the last decade, and it works great. Oh, and I’ve basically stopped using Twitter and am only on Mastodon and Bluesky. Bluesky more than Mastodon recently because it seems like it’s doing a better job at attracting a more diverse community. I’m looking forward to 2025, to cycling a lot more and a new phase of startup-building. See you in the new year.

a month ago 60 votes
Bandcamp wrapped

I still use Bandcamp almost exclusively to buy music, and keep a big library of MP3s. The downside is that this marks me as a weirdo, but otherwise it’s great and has been working well for me. Since I last wrote about it, Bandcamp was acquired by Epic games (?) and then acquired from them by Songtradr, and its employees are trying to get recognized as a union. Times are changing and Bandcamp is no longer a lovely indie company, but it’s still a heck of a lot better than Spotify. People (who?) are sharing their ‘Spotify wrapped’ auto-generated compilations and I wanted the same, for my Bandcamp purchases, so I built it on Val Town. You can create your own! Or edit the code of the tool that generates them. Because of API limitations – really, the absence of an API – it requires you to copy & paste content from your purchases page, but isn’t copy-and-paste really a kind of API? Anyway: Vampire Empire / Born For Loving You by Big Thief Patterns by Pool Boys Acadia by Yasmin Williams Cascade by Floating Points of course i still love you by Darwin Deez (pre-order) 4 | 2 | 3 by MIZU Son by Rosie Lowe & Duval Timothy Imaginal Disk by Magdalena Bay Dirty Projectors by Dirty Projectors Green Disco by Justine Electra Daedalus by Daedelus You Look A Lot Like Me (2016) by Mal Blum Big City Boys by Cailin Pitt Promises by Floating Points, Pharoah Sanders & The London Symphony Orchestra Windswept by Photay Jessie Mae Hemphill by Jessie Mae Hemphill Rituals by Ishmael Ensemble 1992 - 2001 by Acetone Final Summer by Cloud Nothings Bright Future by Adrianne Lenker La For​ê​t (2024) by Xiu Xiu Frog Poems by Mister Goblin Living is Easy by Agriculture Again by Oneohtrix Point Never Put The Shine On by CocoRosie The Light Is On You Return by Ben Levin Mercurial World by Magdalena Bay Burn It Down by Lovebirds Room 25 by Noname Wall Of Eyes by The Smile Forest Scenes by MIZU Looking back on the year, I like how I can remember a few of these albums from my first exposure to them in odd places - I heard Jessie Mae Hemphill playing in a Chipotle, and Rosie Lowe playing in my hair salon. It was apparently a big year for instrumental, electronic, minimalist music. The only ‘rock’ album that hooked me was Wall of Eyes, and the only pop album that made an impact was Imaginal Disk - the fuzzy outro of Image is something I keep re-listening to. MIZU has been on heavy rotation, too – the only of these artists that I learned about by seeing them live - she opened for Tim Hecker and I think made a lot of fans there with a really theatric and heavy performance. Buy some music! Listen to it repeatedly, and put it in your MP3 player!

a month ago 44 votes
Recently: Cycling and Autumn

I haven’t been posting much to the ‘main blog’ recently, but I have been keeping the micro blog updates humming. If you want more content in your RSS reader, you can subscribe to those posts, which are shorter, more scattered, and even less copyedited. It feels bad to have multiple “Recently” headings in the blog listing, so I’ll give them short subtitles from now on. Anyway, what’s up? October was all right. At Val Town, we spent a lot of time interviewing job candidates and improving the AI assistant, Townie. I also got some time to tackle long-awaited technical debt cleanups: I conquered the ‘big scary function’ that did the actual ‘running’ of val code. Cycling Outside of work, a lot of my October-related excitement was related to being outdoors. It’s been a great year for running – I just passed 600 miles so far and will probably hit 650 barring any injuries or life complications. But cycling is on the mind. We just rode the Old Croton Aqueduct trail from Ossining back to Brooklyn. It’s a fairly rough trail: plenty of rocks and terrain. Rideable on my ~32mm tires, but it’d be a lot easier with a mountain bike. We rode past some osage orange trees with their funky-looking and inedible fruit the size of large grapefruits. The trail passed right next to the Lyndhurst Estate, which was owned by a series of rich and powerful people, including Jay Gould, who is especially hated. Upstate, a lot of the attractions are like this, other big historic houses. The trail was mostly really beautiful, though the parts closer to Yonkers have a lot of trash. It’s much more popular with hikers than with cyclists. Even though bikes are explicitly permitted, locals seemed a little surprised by our presence, even though we were ringing bells, going slow, and making lots of space. It’s kind of funny to compare the general spatial awareness of people upstate to those in the city: we encountered a lot of people upstate who were standing in the center of the trail, completely zoned out and surprised by the presence of another human, and then on the way back were on city streets with four people within a few feet of us on foot, bikes, cars, scooters, all mostly aware and ready to silently negotiate how to move together through a shared space. I remarked that I think that when some people move out of the city because of the ‘inconvenience’, the inconvenience is people, and once you leave, you lose a certain ability to live around other people - from then on, you expect to have a suburban yard-sized perimeter around your personal space. Micro I wrote a lot on the microblog this month: about the Arc browser’s recent news that it’ll be abandoned, Reddit adopting Web Components, domain squatting, Python datascience tech, and Knip, a tool for finding dead code in TypeScript systems. Content I watched a bunch of films, which are on my Letterboxd, and the only new album on my rotation is Yasmin Williams’s Acadia: Acadia by Yasmin Williams This YouTube channel is showing all of the steps involved in doing a multi-day bikepacking trip through India. It’s a lot of fun: And that’s it for this month! I’ll write a full-fledged blog post one of these days.

3 months ago 45 votes
Recently

Reading It is very easy to get ChatGPT to emit a series of words such as “I am happy to see you.” There are many things we don’t understand about how large language models work, but one thing we can be sure of is that ChatGPT is not happy to see you. Ted Chiang’s article about AI in the New Yorker started slow for me, and having read a lot of other articles from the same genre it hit some familiar points, but still it was compelling and a gift to read another piece by one of my favorite authors. I read a lot of articles about the Internet Archive lawsuit in September. The Verge had a good piece about it. I’m a longtime fan of the archive – who isn’t – but it’s hard to absorb the weight of the decision and the fact that, despite being the clear-cut villains, the publishers at least have a good argument. The threat of losing the archive would be a gigantic change in the whole shape of the internet. Exciting month for New Yorkers, obviously, with the mayor getting indicted. I’m feeling thankful for the New York media ecosystem: with Hell Gate and The City, we have really good local, independent journalism. It’s both deep - Hell Gate’s Table of Success is an incredible reference for Adams’s corrupt circle of friends - and punchy. Watching I guess I watched a lot of movies this month? The big hit was Death Becomes Her, which is an absolute classic - my favorite kind of film, ‘a romp.’ Beetlejuice was just okay, surprisingly. I’ve been doing a lot of daydreaming about cycling more. In the near future, I want to ride the Empire State Trail again, and the C&O, and the GAP, and more. I just want to get out on a bike. And I’ve been enjoying some smaller YouTube channels about riding. Dwayne Pedals is a good one: Tim Fitzwater has a good video about the Old Croton Aqueduct Trail. These sorts of videos are great: as the geospatial dorks say, “the map is not the territory.” That is especially true for hiking, biking, running, and all forms of non-vehicular transporation. A trail on a map can mean a lot of different things. A ‘bike lane’ on a map can be blissful or non-existent in real life. So I’ve long had the habit of cross-referencing run & bike plans with streetview. You can’t do streetview on bike trails usually, because Google sucks at making maps for non-drivers doesn’t have data there. Stay tuned for more bicycle content. It’s been a lifelong thing for me but I am getting much more serious about it, again. Had a really great ride this month - after biking through Long Island in August, we wanted to experience some good bicycle infrastructure, so the Shore Parkway and Jamaica Bay Greenway hit the spot. This photo is from Brooklyn! Shirley Chisholm State Park is a fresh new park, opened in 2019. You can bike through the park just by veering off of the Jamaica Bay Greenway, and easily get back on the Greenway afterward. Sure, it’s built on landfills, but after $235 million of remediation and $35 million of renovations, it’s a really nice spot. It was like we stumbled upon an oasis. Listening Pretty decent month for music, too! Son by Rosie Lowe & Duval Timothy I heard this at the place where I get my hair cut. A good sign. Cascade by Floating Points Great new electronic music - this has been powering my focus at work recently. I had thought incorrectly that Death From Above stopped playing music a while ago. Happy to learn that I was wrong and this album - Outrage! Is Now exists. Elsewhere In the micro blog, I wrote about React, syncing light and dark mode in Neovim, Crypto’s missing plateau of productivity and NYC cycling paths.

4 months ago 44 votes

More in programming

Adding auto-generated cover images to EPUBs downloaded from AO3

I was chatting with a friend recently, and she mentioned an annoyance when reading fanfiction on her iPad. She downloads fic from AO3 as EPUB files, and reads it in the Kindle app – but the files don’t have a cover image, and so the preview thumbnails aren’t very readable: She’s downloaded several hundred stories, and these thumbnails make it difficult to find things in the app’s “collections” view. This felt like a solvable problem. There are tools to add cover images to EPUB files, if you already have the image. The EPUB file embeds some key metadata, like the title and author. What if you had a tool that could extract that metadata, auto-generate an image, and use it as the cover? So I built that. It’s a small site where you upload EPUB files you’ve downloaded from AO3, the site generates a cover image based on the metadata, and it gives you an updated EPUB to download. The new covers show the title and author in large text on a coloured background, so they’re much easier to browse in the Kindle app: If you’d find this helpful, you can use it at alexwlchan.net/my-tools/add-cover-to-ao3-epubs/ Otherwise, I’m going to explain how it works, and what I learnt from building it. There are three steps to this process: Open the existing EPUB to get the title and author Generate an image based on that metadata Modify the EPUB to insert the new cover image Let’s go through them in turn. Open the existing EPUB I’ve not worked with EPUB before, and I don’t know much about it. My first instinct was to look for Python EPUB libraries on PyPI, but there was nothing appealing. The results were either very specific tools (convert EPUB to/from format X) or very unmaintained (the top result was last updated in April 2014). I decied to try writing my own code to manipulate EPUBs, rather than using somebody else’s library. I had a vague memory that EPUB files are zips, so I changed the extension from .epub to .zip and tried unzipping one – and it turns out that yes, it is a zip file, and the internal structure is fairly simple. I found a file called content.opf which contains metadata as XML, including the title and author I’m looking for: <?xml version='1.0' encoding='utf-8'?> <package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="uuid_id"> <metadata xmlns:opf="http://www.idpf.org/2007/opf" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata"> <dc:title>Operation Cameo</dc:title> <meta name="calibre:timestamp" content="2025-01-25T18:01:43.253715+00:00"/> <dc:language>en</dc:language> <dc:creator opf:file-as="alexwlchan" opf:role="aut">alexwlchan</dc:creator> <dc:identifier id="uuid_id" opf:scheme="uuid">13385d97-35a1-4e72-830b-9757916d38a7</dc:identifier> <meta name="calibre:title_sort" content="operation cameo"/> <dc:description><p>Some unusual orders arrive at Operation Mincemeat HQ.</p></dc:description> <dc:publisher>Archive of Our Own</dc:publisher> <dc:subject>Fanworks</dc:subject> <dc:subject>General Audiences</dc:subject> <dc:subject>Operation Mincemeat: A New Musical - SpitLip</dc:subject> <dc:subject>No Archive Warnings Apply</dc:subject> <dc:date>2023-12-14T00:00:00+00:00</dc:date> </metadata> … That dc: prefix was instantly familiar from my time working at Wellcome Collection – this is Dublin Core, a standard set of metadata fields used to describe books and other objects. I’m unsurprised to see it in an EPUB; this is exactly how I’d expect it to be used. I found an article that explains the structure of an EPUB file, which told me that I can find the content.opf file by looking at the root-path element inside the mandatory META-INF/container.xml file which is every EPUB. I wrote some code to find the content.opf file, then a few XPath expressions to extract the key fields, and I had the metadata I needed. Generate a cover image I sketched a simple cover design which shows the title and author. I wrote the first version of the drawing code in Pillow, because that’s what I’m familiar with. It was fine, but the code was quite flimsy – it didn’t wrap properly for long titles, and I couldn’t get custom fonts to work. Later I rewrote the app in JavaScript, so I had access to the HTML canvas element. This is another tool that I haven’t worked with before, so a fun chance to learn something new. The API felt fairly familiar, similar to other APIs I’ve used to build HTML elements. This time I did implement some line wrapping – there’s a measureText() API for canvas, so you can see how much space text will take up before you draw it. I break the text into words, and keeping adding words to a line until measureText tells me the line is going to overflow the page. I have lots of ideas for how I could improve the line wrapping, but it’s good enough for now. I was also able to get fonts working, so I picked Georgia to match the font used for titles on AO3. Here are some examples: I had several ideas for choosing the background colour. I’m trying to help my friend browse her collection of fic, and colour would be a useful way to distinguish things – so how do I use it? I realised I could get the fandom from the EPUB file, so I decided to use that. I use the fandom name as a seed to a random number generator, then I pick a random colour. This means that all the fics in the same fandom will get the same colour – for example, all the Star Wars stories are a shade of red, while Star Trek are a bluey-green. This was a bit harder than I expected, because it turns out that JavaScript doesn’t have a built-in seeded random number generator – I ended up using some snippets from a Stack Overflow answer, where bryc has written several pseudorandom number generators in plain JavaScript. I didn’t realise until later, but I designed something similar to the placeholder book covers in the Apple Books app. I don’t use Apple Books that often so it wasn’t a deliberate choice to mimic this style, but clearly it was somewhere in my subconscious. One difference is that Apple’s app seems to be picking from a small selection of background colours, whereas my code can pick a much nicer variety of colours. Apple’s choices will have been pre-approved by a designer to look good, but I think mine is more fun. Add the cover image to the EPUB My first attempt to add a cover image used pandoc: pandoc input.epub --output output.epub --epub-cover-image cover.jpeg This approach was no good: although it added the cover image, it destroyed the formatting in the rest of the EPUB. This made it easier to find the fic, but harder to read once you’d found it. An EPUB file I downloaded from AO3, before/after it was processed by pandoc. So I tried to do it myself, and it turned out to be quite easy! I unzipped another EPUB which already had a cover image. I found the cover image in OPS/images/cover.jpg, and then I looked for references to it in content.opf. I found two elements that referred to cover images: <?xml version="1.0" encoding="UTF-8"?> <package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="PrimaryID"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf"> <meta name="cover" content="cover-image"/> … </metadata> <manifest> <item id="cover-image" href="images/cover.jpg" media-type="image/jpeg" properties="cover-image"/> … </manifest> </package> This gave me the steps for adding a cover image to an EPUB file: add the image file to the zipped bundle, then add these two elements to the content.opf. Where am I going to deploy this? I wrote the initial prototype of this in Python, because that’s the language I’m most familiar with. Python has all the libraries I need: The zipfile module can unpack and modify the EPUB/ZIP The xml.etree or lxml modules can manipulate XML The Pillow library can generate images I built a small Flask web app: you upload the EPUB to my server, my server does some processing, and sends the EPUB back to you. But for such a simple app, do I need a server? I tried rebuilding it as a static web page, doing all the processing in client-side JavaScript. That’s simpler for me to host, and it doesn’t involve a round-trip to my server. That has lots of other benefits – it’s faster, less of a privacy risk, and doesn’t require a persistent connection. I love static websites, so can they do this? Yes! I just had to find a different set of libraries: The JSZip library can unpack and modify the EPUB/ZIP, and is the only third-party code I’m using in the tool Browsers include DOMParser for manipulating XML I’ve already mentioned the HTML <canvas> element for rendering the image This took a bit longer because I’m not as familiar with JavaScript, but I got it working. As a bonus, this makes the tool very portable. Everything is bundled into a single HTML file, so if you download that file, you have the whole tool. If my friend finds this tool useful, she can save the file and keep a local copy of it – she doesn’t have to rely on my website to keep using it. What should it look like? My first design was very “engineer brain” – I just put the basic controls on the page. It was fine, but it wasn’t good. That might be okay, because the only person I need to be able to use this app is my friend – but wouldn’t it be nice if other people were able to use it? If they’re going to do that, they need to know what it is – most people aren’t going to read a 2,500 word blog post to understand a tool they’ve never heard of. (Although if you have read this far, I appreciate you!) I started designing a proper page, including some explanations and descriptions of what the tool is doing. I got something that felt pretty good, including FAQs and acknowledgements, and I added a grey area for the part where you actually upload and download your EPUBs, to draw the user’s eye and make it clear this is the important stuff. But even with that design, something was missing. I realised I was telling you I’d create covers, but not showing you what they’d look like. Aha! I sat down and made up a bunch of amusing titles for fanfic and fanfic authors, so now you see a sample of the covers before you upload your first EPUB: This makes it clearer what the app will do, and was a fun way to wrap up the project. What did I learn from this project? Don’t be scared of new file formats My first instinct was to look for a third-party library that could handle the “complexity” of EPUB files. In hindsight, I’m glad I didn’t find one – it forced me to learn more about how EPUBs work, and I realised I could write my own code using built-in libraries. EPUB files are essentially ZIP files, and I only had basic needs. I was able to write my own code. Because I didn’t rely on a library, now I know more about EPUBs, I have code that’s simpler and easier for me to understand, and I don’t have a dependency that may cause problems later. There are definitely some file formats where I need existing libraries (I’m not going to write my own JPEG parser, for example) – but I should be more open to writing my own code, and not jumping to add a dependency. Static websites can handle complex file manipulations I love static websites and I’ve used them for a lot of tasks, but mostly read-only display of information – not anything more complex or interactive. But modern JavaScript is very capable, and you can do a lot of things with it. Static pages aren’t just for static data. One of the first things I made that got popular was find untagged Tumblr posts, which was built as a static website because that’s all I knew how to build at the time. Somewhere in the intervening years, I forgot just how powerful static sites can be. I want to build more tools this way. Async JavaScript calls require careful handling The JSZip library I’m using has a lot of async functions, and this is my first time using async JavaScript. I got caught out several times, because I forgot to wait for async calls to finish properly. For example, I’m using canvas.toBlob to render the image, which is an async function. I wasn’t waiting for it to finish, and so the zip would be repackaged before the cover image was ready to add, and I got an EPUB with a missing image. Oops. I think I’ll always prefer the simplicity of synchronous code, but I’m sure I’ll get better at async JavaScript with practice. Final thoughts I know my friend will find this helpful, and that feels great. Writing software that’s designed for one person is my favourite software to write. It’s not hyper-scale, it won’t launch the next big startup, and it’s usually not breaking new technical ground – but it is useful. I can see how I’m making somebody’s life better, and isn’t that what computers are for? If other people like it, that’s a nice bonus, but I’m really thinking about that one person. Normally the one person I’m writing software for is me, so it’s extra nice when I can do it for somebody else. If you want to try this tool yourself, go to alexwlchan.net/my-tools/add-cover-to-ao3-epubs/ If you want to read the code, it’s all on GitHub. [If the formatting of this post looks odd in your feed reader, visit the original article]

8 hours ago 4 votes
Non-alcoholic apéritifs

I’ve been doing Dry January this year. One thing I missed was something for apéro hour, a beverage to mark the start of the evening. Something complex and maybe bitter, not like a drink you’d have with lunch. I found some good options. Ghia sodas are my favorite. Ghia is an NA apéritif based on grape juice but with enough bitterness (gentian) and sourness (yuzu) to be interesting. You can buy a bottle and mix it with soda yourself but I like the little cans with extra flavoring. The Ginger and the Sumac & Chili are both great. Another thing I like are low-sugar fancy soda pops. Not diet drinks, they still have a little sugar, but typically 50 calories a can. De La Calle Tepache is my favorite. Fermented pineapple is delicious and they have some fun flavors. Culture Pop is also good. A friend gave me the Zero book, a drinks cookbook from the fancy restaurant Alinea. This book is a little aspirational but the recipes are doable, it’s just a lot of labor. Very fancy high end drink mixing, really beautiful flavor ideas. The only thing I made was their gin substitute (mostly junipers extracted in glycerin) and it was too sweet for me. Need to find the right use for it, a martini definitely ain’t it. An easier homemade drink is this Nonalcoholic Dirty Lemon Tonic. It’s basically a lemonade heavily flavored with salted preserved lemons, then mixed with tonic. I love the complexity and freshness of this drink and enjoy it on its own merits. Finally, non-alcoholic beer has gotten a lot better in the last few years thanks to manufacturing innovations. I’ve been enjoying NA Black Butte Porter, Stella Artois 0.0, Heineken 0.0. They basically all taste just like their alcoholic uncles, no compromise. One thing to note about non-alcoholic substitutes is they are not cheap. They’ve become a big high end business. Expect to pay the same for an NA drink as one with alcohol even though they aren’t taxed nearly as much.

2 days ago 5 votes
It burns

The first time we had to evacuate Malibu this season was during the Franklin fire in early December. We went to bed with our bags packed, thinking they'd probably get it under control. But by 2am, the roaring blades of fire choppers shaking the house got us up. As we sped down the canyon towards Pacific Coast Highway (PCH), the fire had reached the ridge across from ours, and flames were blazing large out the car windows. It felt like we had left the evacuation a little too late, but they eventually did get Franklin under control before it reached us. Humans have a strange relationship with risk and disasters. We're so prone to wishful thinking and bad pattern matching. I remember people being shocked when the flames jumped the PCH during the Woolsey fire in 2017. IT HAD NEVER DONE THAT! So several friends of ours had to suddenly escape a nightmare scenario, driving through burning streets, in heavy smoke, with literally their lives on the line. Because the past had failed to predict the future. I feel into that same trap for a moment with the dramatic proclamations of wind and fire weather in the days leading up to January 7. Warning after warning of "extremely dangerous, life-threatening wind" coming from the City of Malibu, and that overly-bureaucratic-but-still-ominous "Particularly Dangerous Situation" designation. Because, really, how much worse could it be? Turns out, a lot. It was a little before noon on the 7th when we first saw the big plumes of smoke rise from the Palisades fire. And immediately the pattern matching ran astray. Oh, it's probably just like Franklin. It's not big yet, they'll get it out. They usually do. Well, they didn't. By the late afternoon, we had once more packed our bags, and by then it was also clear that things actually were different this time. Different worse. Different enough that even Santa Monica didn't feel like it was assured to be safe. So we headed far North, to be sure that we wouldn't have to evacuate again. Turned out to be a good move. Because by now, into the evening, few people in the connected world hadn't started to see the catastrophic images emerging from the Palisades and Eaton fires. Well over 10,000 houses would ultimately burn. Entire neighborhoods leveled. Pictures that could be mistaken for World War II. Utter and complete destruction. By the night of the 7th, the fire reached our canyon, and it tore through the chaparral and brush that'd been building since the last big fire that area saw in 1993. Out of some 150 houses in our immediate vicinity, nearly a hundred burned to the ground. Including the first house we moved to in Malibu back in 2009. But thankfully not ours. That's of course a huge relief. This was and is our Malibu Dream House. The site of that gorgeous home office I'm so fond to share views from. Our home. But a house left standing in a disaster zone is still a disaster. The flames reached all the way up to the base of our construction, incinerated much of our landscaping, and devoured the power poles around it to dysfunction. We have burnt-out buildings every which way the eye looks. The national guard is still stationed at road blocks on the access roads. Utility workers are tearing down the entire power grid to rebuild it from scratch. It's going to be a long time before this is comfortably habitable again. So we left. That in itself feels like defeat. There's an urge to stay put, and to help, in whatever helpless ways you can. But with three school-age children who've already missed over a months worth of learning from power outages, fire threats, actual fires, and now mudslide dangers, it was time to go. None of this came as a surprise, mind you. After Woolsey in 2017, Malibu life always felt like living on borrowed time to us. We knew it, even accepted it. Beautiful enough to be worth the risk, we said.  But even if it wasn't a surprise, it's still a shock. The sheer devastation, especially in the Palisades, went far beyond our normal range of comprehension. Bounded, as it always is, by past experiences. Thus, we find ourselves back in Copenhagen. A safe haven for calamities of all sorts. We lived here for three years during the pandemic, so it just made sense to use it for refuge once more. The kids' old international school accepted them right back in, and past friendships were quickly rebooted. I don't know how long it's going to be this time. And that's an odd feeling to have, just as America has been turning a corner, and just as the optimism is back in so many areas. Of the twenty years I've spent in America, this feels like the most exciting time to be part of the exceptionalism that the US of A offers. And of course we still are. I'll still be in the US all the time on both business, racing, and family trips. But it won't be exclusively so for a while, and it won't be from our Malibu Dream House. And that burns.

2 days ago 6 votes
Slow, flaky, and failing

Thou shalt not suffer a flaky test to live, because it’s annoying, counterproductive, and dangerous: one day it might fail for real, and you won’t notice. Here’s what to do.

3 days ago 7 votes
Name that Ware, January 2025

The ware for January 2025 is shown below. Thanks to brimdavis for contributing this ware! …back in the day when you would get wares that had “blue wires” in them… One thing I wonder about this ware is…where are the ROMs? Perhaps I’ll find out soon! Happy year of the snake!

3 days ago 5 votes