This week was mostly about making Seed feel less fragile while you are in the middle of creating something.
That sounds simple, but it touches almost every part of the product I care about: the editor, document state machines, comments, embeds, web publishing, and the weird in-between moments where a local-first app is syncing, discovering peers, loading drafts, or trying to preserve your intent while the network is not perfectly cooperating.
The big theme was: do not punish the writer for temporary uncertainty.
I started the week with a small but very visible UI pass: narrowing the prose breakout width so long-form documents read better instead of feeling like they spill too far across the page (commit d847304cd). It is the kind of design detail that is easy to underestimate. In a writing tool, line length is product strategy. If the page feels calmer, people write more.
The larger version of that same principle landed in PR #698: I changed the document machine so already-loaded documents stay mounted during transient resource errors. Before this, discovery flaps, stale latest-pointer failures, or temporary not-found states could blank the page even though we had a perfectly good last-loaded document in memory. The fix stores the last good document/version in the state machine, surfaces transient failure as a non-fatal banner, and keeps the document visible while the system recovers. That is a very Seed kind of UX problem: local-first systems have more nuanced states than loading/success/error, and the UI has to model that nuance.
The other big thread was making comments more precise. In PR #700, I added support for comments that target exact text ranges inside a block. Instead of only saying “this comment is about this block,” comments can now carry structured quote targets with codepoint ranges. That work touched desktop comments, web comments, pending intents, optimistic comments, editor selection plugins, fragment highlights, embeds, and conversion between editor blocks and Hypermedia blocks. The end result is much closer to how people actually review writing: not “I have feedback on this paragraph,” but “I have feedback on these eight words.”
There were also a few small fixes that came directly from living inside the product. Slugs now treat punctuation like +, &, /, ;, and | as separators instead of dropping them and producing weird double-dash paths (commit 0967bc653). Embed clicks now navigate correctly instead of getting swallowed by editor behavior (commit 0c671f9a3). These are not flashy features, but they remove paper cuts from the core reading/writing loop.
By the end of the week, the web editor got a more complete document creation path in PR #706. Inline child drafts now work on web with IndexedDB-backed draft actions, query blocks can show local child draft slots, empty draft names are not accidentally published, and publish errors log more useful daemon error bodies. This matters because Seed should not feel like two products — desktop and web should share the same mental model, even when their persistence mechanics differ.
And then there was the performance/debugging rabbit hole: embeds flashing whenever an ancestor rendered. PR #714 stabilized render-resource context values so embedded documents no longer re-render just because some parent state changed. The bug was a classic frontend architecture problem: a context array was memoized on an inline object reference, which meant it changed every render even when the actual resource identity had not changed. The fix was to memoize on primitive identity keys instead. I also made Spinner render as a phrasing-safe <span> to avoid invalid <div>-inside-<p> DOM nesting, and made local web development a bit easier with a more self-contained dev run-web.
The startup-life lesson this week was that “reliability” is usually not one heroic change. It is a stack of small decisions: preserve the last known good document, keep a user’s selection stable, prevent empty draft names, make embeds stop flashing, and design the state machine so uncertainty is represented instead of hidden.
That is the product I want Seed to become: a writing environment that feels calm even while the underlying system is doing something ambitious.
Code trail
If you are building local-first or collaborative software, I am curious:
how do you decide when an error should interrupt the user versus become a recoverable background state?
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime