GeistHaus
log in · sign up

https://macwright.com/micro/atom.xml

atom
10 posts
Polling state
Status active
Last polled May 19, 2026 04:33 UTC
Next poll May 20, 2026 05:27 UTC
Poll interval 86400s
ETag "c336303e96402ddc5720de6eb1339512-ssl-df"

Posts

Interview with Shelf Source
Show full content

Zachary Kai, space fantasy writer and cool indie web maker, interviewed me for his 'Shelf Source' series about people who like to read books. Read it to learn about some of my favorite books and stuff: Shelf Source: Tom MacWright.

https://macwright.com/2026/05/09/shelf-source.html
Brooklyn Experience Half Marathon
Show full content

This is partly inspired by Chris Krycho's report on the 2026 Boston Marathon. He is proper fast. He ran the marathon at a 6:32/mi pace. My effort here is far less impressive, but is still a PR and a significant improvement on what I was running before, so I'm writing it up. My running is solidly in the impressive to non-runners, but not competitive bucket.

Anyway:

  • Official time: 1:31:21
  • Pace: 6:59/mi
  • Overall: 583th
  • Male 35-39: 71th

My previous PR was 1:36:21 , so it was exactly 5 minutes faster. W in the chat.

How'd it go training-wise?

The gist of the training block for this race was simply running more. I skimmed the Daniels' Running Formula as well, which influenced the kinds of runs I was doing: fitting them into his categories of long, easy, tempo, interval, and repetition. That was very helpful structure.

In previous training blocks, I've maintained a rough target of 15 miles a week. This time around I aimed for 20-25, which I think is a lot more appropriate for the kinds of times I'm aiming for. In general I've performed better than training would predict by some combination of talent, pain tolerance, and experience.

By dialing back the intensity of my long runs and incorporating a lot of cross-training (40-60mi bike rides), it felt surprisingly sustainable to do the extra miles. That said, I didn't do a ton of very long runs and never hit the half-marathon distance in training. This is partly the Daniels influence which prioritizes a lot of runs (5-6/week) and so you have a lot of opportunities to get the distance in.

I did hit the usual stumbling blocks: some foot pain that required a trip to the local podiatrist and five days off two weeks before the race.

How'd the race go?

We lucked out with weather - started around 44°F and ended around 55°F. It rained before the race but didn't rain at all during, and the puddles were minimal.

I started in Wave 1 Corral A like last time, but instead of hanging way to the back, tried to find the spot where I'd be running a goal space - vaguely "low 1:30s." My previous PR was 1:36:21 but with more training and the confidence that I should be running faster, I was thinking that 1:32 or something would be nice. And a "7 minute mile" is mentally comfortable for me, so targeting that felt both feasible and optimistic.

Starting too far back has been a trap in the past: psychologically, it's nicer to pass people than to be passed, but it means a ton of weaving and dodging early in the race which is a lot of energy and kind of risky. Lesson learned: start at the roughly correct spot!

This course is kind of brutal, basically nice and downhill for the first half, then it hits south Brooklyn's most notorious hill in Prospect Park at the end. The hill is not a huge deal at the start of a 5k but after 11 hard miles, it's tough. So, early on my pace was hovering around 6:45 but on the hilly segments it hit 7:19.

The stretch out to Eastern Parkway especially feels interminable, right at the middle-end of the race and with a gradual hill. That's where my mental game fell off a bit, but it wasn't a catastrophe and I was able to bring back the pace later on.

Lessons learned

As you can tell by my time, I am not a professional runner, and not in the front of the pack, but, ya know, not bad for an old guy. I've had a bunch of basic realizations and improvements running-wise:

  • Just give in and get some super shoes. I run in Brooks Hyperion Max 2, which are kind of "stability super-trainers" but nevertheless have a nylon plate. They make a difference.
  • Fuel! Consume carbs. For a half-marathon, it's not as big a deal as for a full because the folk wisdom is that you need additional carbs after about 45 minutes of exertion. So halfway through I slugged down a Maurten 100 gel. Before the race in the early morning, overnight oats. Some caffeine too, which hits me extra-hard because my life is otherwise caffeine-free.
  • Note: not really carb-loading! Pulling from Bakline's episode about fueling, it kind of only works if you do huge quantities, barely works for women, and has to be a multi-day affair. Plus, glycogen goes along with water so it increases your weight a bit. Overall I'm not compelled to focus on it, but at the same time I wouldn't be surprised if I'm totally wrong about this and swear by it in the future.
  • Warm up a lot. Real runners are doing 3 miles of warm-up before their races. The setup for this race makes pre-race warmup kind of cramped, jogging in a tiny lane next to the corrals, but still - do it. It works.
  • Controversial take: eating a lot more protein has helped me overall. Big asterisk: I'm a strict vegan and have been for a long time so my intake is probably low unless I really focus on it. But yeah - spending a little time to measure what I'm actually getting versus what I'm aiming for showed a wide gulf and I've found that I recover a bit faster with more, around 100g/day at least, inconsistently hit.
  • The self-help stuff in the Confident Mind helps me a lot: in particular, trying to appreciate nervousness as a positive. I've also been enjoying the mantras of "slow is smooth and smooth is fast," and "relax and go faster." The mental stuff is invariably goofy to write about but it's real, it's a big part of the challenge.
  • Via Phily Bowden, who I really admire for being very level-headed for an absolutely insane endurance athlete, I care about getting decent sleep the night before the night before the race. Like any brutally early wake-up for a thing I'm nervous about - 5am in this case - I will try my hardest to sleep well the night before but realistically it's usually terrible. But the night before that, pretty good, and it evens out to be fine! I did totally biff it on fueling strategies you've already tried because I had never had that kind of Maurten gel before, but it turned out fine.

I've been running competitively on-and-off since high school and still find stuff like this which seems basic that makes everything better. It's fun! My goal is just to get another PR at some point, and to be strong in this year's 5k series.

https://macwright.com/2026/04/26/brooklyn-experience-half-marathon.html
How to contribute to open source
Show full content

Aut inveniam viam aut faciam: I shall either find a way or make one.

Another ranking of people on GitHub dropped, and for the odd metric of 'stars from repos where a developer has merged PRs' I rank third, as of this writing, in the United States. Like all rankings, it's mostly lies, statistics, coincidence, and a reflection of GitHub's top-heavy usage.

But it's a nice metric because that is what I've focused on for the last few years: instead of trying to create some popular new framework, I've been trying to contribute more to existing projects.

So, free advice to the new open source contributor: when you hit a bug or a limitation in some project, file an issue and volunteer to fix it if you think you can.

You'll learn a lot from working in lots of projects - how do they set up tests and linter rules, what are their code styles, etc?

Don't use LLMs to do this. Using an LLM especially to write the PR description or anything like that cheats both you and them: you're missing out on the learning and experience, and they will become wary of automated contributions. Don't be lazy.

Is this still an effective way to stay in open source and do good: I think so. So, when you hit a bug, instead of doing a workaround, a patch, or switching tools, try and make a way by fixing it. Treat all bugs like your responsibility because you're an active community member.

https://macwright.com/2026/03/26/oss-contributions.html
Effect notes: PRs, progress, and joys
Show full content

It's been three months since the last Effect devlog and I'm still incrementally adopting Effect in Val Town.

Things are going well but not spectacularly. My approval rating is a solid 'B' right now.

I'm far from the only or most important Effect user, but I'm bummed that a majority of my annoyances from October & November of 2025 are outstanding: a drizzle bug, a Cron bug, a vitest incompatibility, documentation improvements are all stalled. I write a brief fork that implemented Cron iteration in the reverse direction, Cron.prev, and Kit finished and merged it, but that took three months to get merged and has been stalled without release for another three months. The documentation for Effect.fn.Return got written by a core team member but that PR got closed without merging. I tried writing some docs for stream interop which never got merged or reviewed. I got a minor documentation improvement about generators merged with explicit coordination from the Effect team.

None of that stuff is a dealbreaker and I know from privately chatting with the Effect team that they prefer PRs to be coordinated because there's so much in flight and so much to understand. I think the current scenario, in which people don't know that because it isn't written anywhere, and put in effort into PRs that stall, isn't very good for community vibes.

Probably some of this delay is because Effect v4 has been a major focus of the Effectful team. v4 does seem exciting: a smaller, more unified, faster module is great news. We haven't migrated yet because we use some of the deprecated APIs, like Runtime, and I try to avoid using beta releases in general in production software.

Obligatory LLM discourse: my usual AI tool, Claude Code with Opus 4.6, is decent at using Effect but stumbles in the same places that the documentation is lacking, like that Effect.fn.Return type - it doesn't like to use that. I've used the LLM for roughly half of the refactors to Effect, but recently I've been finding it slower than doing things manually along with ast-grep. There are faster LLMs, but I've found quality and speed to be strongly inversely correlated, and fast models like Minimax tend to get themselves into corners faster.

Effect joys

The Duration and DateTime modules have been really nice for describing times and limits in the application. Soon, Duration will do a lot of the same things that those Effect utilities can do, but it's nice to have them a little early.

We have a lot of Drizzle queries that try to fetch one record, so .limit(1) and then .pipe(Effect.map(r => r.at(0)) and I recently created a nice little dual method as a helper. This was not especially easy to write!

/**
 * For the many many database queries where we want to take the first
 * element and return NotFoundError if it is not found.
 *
 * @example
 * db.query(...)
 *  .pipe(takeFirst('Project not found'));
 */
export const takeFirst = dual<
  (
    that: string
  ) => <T, Error, Requirements>(
    self: Effect.Effect<T[], Error, Requirements>
  ) => Effect.Effect<NonNullable<T>, Error | NotFoundError, Requirements>,
  <T, Error, Requirements>(
    self: Effect.Effect<T[], Error, Requirements>,
    that: string
  ) => Effect.Effect<NonNullable<T>, Error | NotFoundError, Requirements>
>(2, (self, message = "Not found") => {
  return Effect.flatMap(self, (value) => {
    const first = value.at(0);
    if (first !== undefined && first !== null) {
      return Effect.succeed(first);
    }
    return Effect.fail(new NotFoundError({ message }));
  });
});

The more methods get ported to Effect the more I can use Effect.gen or Effect.fn to combine them, which is nice - feels like a tipping point in many ways. This is something that I have noticed that LLMs are hesitant to do: they're pretty single-minded when working on a task and will happy let two Effect.runPromise statements sit on consecutive lines when they could be combined.

The friction of Effect at boundaries is still there: Val Town uses Fastify, tRPC, React Router, etc., and we have a lot of existing code, so we aren't achieving the brilliant purity that Effect docs insist upon. Tests still don't use @effect/vitest because of its missing feature and lack of vitest 4 support, so there's a lot of unwrapping Effects there too.

Overall: it rolls on, and I'm starting to slowly introduce Effect to the tough core of the application, the part that actually runs vals, and has many complex asynchronous flows. That is going fairly well: the last push was to replace our homemade implementation of Promise.withResolvers() that also had a Bun-inspired .peek() method to synchronously get a Promise value if there is one. Effect's Deferred was a drop-in replacement for that problem area.

https://macwright.com/2026/03/18/effect-devlog.html
Placemark & OSS Changelog
Show full content

What's new with Placemark and open source recently:

Placemark
  • The features table in Placemark now lets you sort by different columns in ascending or descending order.
  • The features table had resizable columns, which broke. I fixed that, and you can resize columns again.
  • I've created a documentation website for Placemark, recreating some of what existed with Webflow but with Astro Starlight.
tokml
  • I've adopted changesets for release automation, like I've been doing on all my projects. The combination of changesets with a release action that publishes to NPM using OIDC is really nice, and makes maintenance and updates easier.
  • I dropped the UMD build, making the NPM package roughly 30% smaller.
simple-statistics
  • Quantile computations now match what R does. Thanks zhengshui for the fix.
  • Also, adopted changesets, like the rest of the projects.

I've been trying to 'fix what I find' in projects - small fixes in stuff like fastify-sensible and 11ty-vento. Contributing to existing projects in small ways feels good, I think it would be nice if the average longevity of projects were higher, and small contributions are what keep me interested in my older projects, too.

https://macwright.com/2026/03/15/oss-changelog.html
ROOTS - Return Old Online Things to your own Site
Show full content

Lisa Charlotte Muth just coined ROOTS: "Return Old Online Things to your own Site." She's collecting all her old content and putting it all on her site.

Good idea! I share all the sentiments in that post, and hope to do the same, and also do some manual review of my old posts to fix some bitrotted iframe embeds. Inlining everything would be really nice - I want some self-hostable local playground element for interactive code examples.

https://macwright.com/2026/03/12/roots.html
Reactionary AI Centrism
Show full content

The current wave of AI discourse is what I'd call "Radical AI Centrism." The gist is:

  • "Good" can mean "morally or ethically good" or "effective and able to do what it says it does."
  • Many critics of AI have made arguments about both: that AI is both a 'glorified spellcheck that doesn't work very well' (not good at what it does) and 'increasing electricity prices, destroying jobs, etc.' (not good in a moral sense)
  • In the last five months or so, AI models have gotten better at what they do, according to most quantitative and qualitative measures.
  • This has made some past critiques of AI inaccurate, and some critics have been slow to adjust their rhetoric for the new reality of AI models that can write passable code.
  • Therefore, a new space for criticism has opened up: a "backlash to the backlash" in which AI critics are critiqued for insufficiently appreciating how AI is 'good at what it does.'
  • This realm of argument is useful strategically because:
    • It is narrowly true.
    • It satisfies the reactionary centrist impulse to avoid straightforward descriptions of 'bad things' and to instead find complex, sophisticated alternative takes.
    • For many people, it's more fun to argue with people in their own circle than to critique large corporations.
    • It shifts the argument away from the moral, ethical, and political realm (moral good) and toward the realm of technology and effectiveness (good at what it does).
https://macwright.com/2026/02/12/reactionary-ai-centrism.html
Media diet
Show full content

I've been tweaking my media diet recently for three goals:

  1. I don't want to be exposed to advertising.
  2. I'd like to avoid some big companies.
  3. I want more honest news.

I already pay for a few big newspapers and use uBlock with my browser Helium, but I still get ads on the mobile versions of news apps like the New York Times and Bloomberg. Considering how much these subscriptions cost, I think that's pretty silly.

And it's very clear that those papers haven't been writing honest coverage of some important world events.

So, here's what I'm listening/reading/watching more often:

  • NPR Plus is $96/yr and listening to 1A and Consider This cover most of my audio news needs.
  • The Guardian is $90/yr and is ad-free for subscribers.
  • News As Facts is good for global headlines and exclusively links to Wikipedia for terms, which is where I go anyway.
  • Nebula is not quite a YouTube alternative, but it has a few major creators who I follow, like Adam Neely, and lets me watch their videos without ads for a relatively cheap subscription ($30/yr).
https://macwright.com/2026/02/05/media-diet.html
What I haven't figured out
Show full content

Some technical things that I feel like I've never really figured out, that I'm trying to figure out:

How much should applications fail-fast and how much should they tolerate failure?

Failing fast feels right and I've implemented it in a lot of places - using envsafe or something similar is a necessity on any project I work on, for example: if an application isn't properly configured, it should fail at startup instead of limping along.

But should applications tolerate failed database queries in an elegant way? What about failed external services?

I think one clear line is that an application shouldn't allow internal inconsistency. For example, if you have some function that's being called with an incorrect argument type, you update the callers instead of making the function more flexible. This probably isn't the case when companies grow in size because eventually you can't tell a whole team to just update all their code when an API changes.

But the line keeps moving: in particular, I think the last two years has shown me that it's useful to have a system that can fail partially, and that every single external service will fail at some point, and you should have a plan for those things, whether it's tolerating failure or doing retries.

How should logs work?

Every application that I've worked on eventually just generates several 'flavors' of log messages out of stdout and stderr and logs stop being useful because they're filled with 'junk' like request logs.

I've tried structured JSON logs with pino, tried tslog, Betterstack, Axiom, and never got it. We've never had a team member that really got value out of logs. I've never really gotten value out of logs. I often wonder if servers should emit logs at all, and instead we should just do telemetry and metrics?


Update! Charles Harries responded with 'How to read and write application logs'. Also: loggingsucks.com.

https://macwright.com/2026/01/29/what-i-havent-figured-out.html
Changing my mind
Show full content

I've changed my mind about a lot of stuff recently:

  • I have been using Claude Code a bunch. I feel deeply conflicted about it: it isn't satisfying to use, it has all these ethical problems, but it works really well. Only the highest-level models work well for writing code: opencode is much nicer and less glitchy than Claude Code, but it uses Grok by default which is a lot worse. I'm only using it on work stuff. Partly the watershed moment was the Claude Code form factor: in-editor AI suggestions are annoying and disruptive, whereas the CLI editor-lite direction is a lot better. Note: please don't recommend another coding CLI to me, I've seen them all and despite using them, I am existentially tired by the LLM discourse.
  • I switched from Alfred to Raycast. The biggest reason was that I want to run less software, and Raycast replaces Session and Rectangle for me. I've also been impressed with 'Raycast-the-platform' - its Strava integration shows the last 4 weeks' mileage in my menu bar. Not tied directly to Raycast as a quick switcher, but nifty. To rewind, every time I tried Raycast in the past I was frustrated about its 'quick switching' abilities - typing a few characters of an application's name would be less reliable than with Alfred, presumably because Raycast was doing fancier string matching. But now I use LeaderKey for switching and that's not an issue.
  • I've been adopting Effect at work, after swearing it off for a year. I think my qualms with it are still pretty true (the team does not care enough about documentation and it can be difficult to use) but there are also a lot of benefits - a big ecosystem that's pretty mature, and the opportunity to remove other NPM modules because Effect includes nice implementations of things like DateTime, Duration, and Cron types.
  • I switched this blog to use 11ty after being a holdout using Jekyll for roughly 14 years. Jekyll kept encountering problems building the site. 11ty is fine and the migration was mostly smooth. I still have some incremental work to do with it. The site builds slightly faster but that wasn't a real motivation for the switch, it was more about switching to a static site builder that is actively maintained. Plus I had a bunch of post-optimization involved with the site that was already written in JavaScript, and using 11ty let me integrate that with the site build process.
https://macwright.com/2026/01/13/mind-changing.html