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.
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.
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:
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:
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.
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.
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.
What's new with Placemark and open source recently:
PlacemarkI'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.
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.
The current wave of AI discourse is what I'd call "Radical AI Centrism." The gist is:
I've been tweaking my media diet recently for three goals:
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:
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.
I've changed my mind about a lot of stuff recently: