I Shipped...
September 2026
In September I fixed several ways that offline work could get stuck. A restarted browser can now resume pending edits once gaps in their earlier history have been repaired. Long histories replay without overflowing the call stack. I also made simultaneous tabs agree on one saved identity before starting their sessions. Cancelling a stalled transaction releases the work queued behind it, while permanent send failures reach the application so it can respond.
I spent time on query results that looked plausible but were wrong. Pagination through related records now preserves the requested rows, and SQL joins no longer treat two missing values as a match. I fixed averages that overflowed while adding otherwise valid numbers, and made the Inspector display and edit large integers without rounding them. These are easy mistakes to miss in small examples, but they change the data an application shows.
In the development tools, I added the Inspector overlay to SvelteKit and stopped the Inspector saving admin credentials alongside reusable connection details. Project creation no longer fails simply because a dependency installer produces too much output, while failed installations retain useful diagnostic messages.
August 2026
Permissions were a recurring theme in August. I fixed reconnects that could miss rows a user had gained access to, or keep revoked rows in their subscription results. Authentication checks now reject server tokens intended for the wrong service, and cached signing keys expire instead of being trusted indefinitely. I also bound chat messages in the example app to the person signed in, and closed a permissions gap that let anonymous sessions write.
Storage and query work continued alongside that. I made failed storage commits retryable when nothing had been saved, and kept local edits visible while subscriptions loaded a server snapshot. I removed repeated work from queries over related records and fixed an indexed lookup that was falling back to scanning the whole table. I added benchmark timings that broke startup and first-query time into stages, giving further optimisation a clearer basis.
The public API became more consistent too. I added live single-row query helpers across React, Vue, Solid and Svelte, brought database writes onto one result format, and clarified which providers create and close their own database clients. Better Auth gained support for ordinary string user IDs, and the guides and examples caught up with the revised APIs, including how to wait for a transaction to be safely stored.
July 2026
July was a month of making writes and recovery fail safely. I made rejected writes terminal during replay, saved direct-batch recovery before exposing rows, restored migrated row history when deletes were replayed, and stopped live browser sessions being reclaimed while they were still saving. These changes reduce the chance that reconnects, retries or concurrent tests turn partial state into lost data.
At the type and platform edges, I updated Jazz and Classic Jazz to patched Next.js releases, fixed Better Auth 1.6 client-type compatibility, made integer validation explicit before native writes, and preserved TypeScript types through column modifiers. These are small-looking changes with wide impact: fewer security surprises, clearer errors, and less type information lost as APIs are composed.
Docs and release hygiene rounded it out. I refreshed guides and examples to match current behaviour, including the packaged MCP docs, and removed stale permission examples. Eleven distinct contributions landed across Jazz and Classic Jazz.
June 2026
June was about making Jazz safer at the boundaries: the places where data arrives from elsewhere, where multiple runtimes meet, and where a bad assumption can turn into a crash or data loss. I fixed deep-history forwarding so hundreds of ancestors no longer overflow the call stack, stopped a permissions race from denying legitimate local writes, and prevented rejected owner updates from hard-deleting rows created by the backend. I also made multiple Jazz clients share one WASM runtime and bundled the SharedWorker into a self-contained file, fixing production crashes in Svelte, Vue, Webpack, Turbopack, and Vite.
Developer experience got a similar upgrade. Starter projects now build and pass browser tests before release, documentation examples are type-checked, and a new public shared entry point makes it possible to build bindings for other frameworks. Session state now stays reactive in Svelte and Vue, and query and subscription hooks behave consistently across React, Svelte and Vue. The new grow-only set merge strategy lets concurrent list updates converge without one device wiping out another.
Finally, I removed admin secrets from startup logs, added a warning for missing production authentication, and fixed Svelte Language Tools declaration files being written outside their intended output directory. Thirteen distinct contributions landed in June.
May 2026
May split into three kinds of work: hardening the sync server against abuse, ironing out the rough edges where the library meets each front-end framework, and rebuilding how the documentation explains itself. Thirty pull requests in all.
The most consequential were two security fixes on the sync server. The handshake
frame that opens a WebSocket connection was being decompressed before the server
knew who was connecting, so a single tiny frame claiming to expand to gigabytes
could exhaust memory and bring the server down — a decompression bomb,
exploitable with no authentication at all. I capped the decompressed handshake
at 1 MB, comfortably above anything legitimate. The companion change defends
against reconnect-storm denial of service: at most four simultaneous connections
per client — oldest evicted, with a RateLimited code returned — and a
ten-second timeout on connections that start a handshake but never finish, so
half-open sockets can't accumulate.
Most of the rest was about robustness across frameworks, where the fixes that
mattered most were the quiet ones buried in core paths. A Next.js helper was
mis-externalising jazz-tools, loading two copies of React at once and crashing
every app during server-side pre-rendering. On React Native, database queries
were blocking the JavaScript thread, freezing the UI and occasionally
deadlocking; I made them asynchronous. Deeply nested subscriptions had quietly
stopped being reactive — mutations more than one level down never triggered a
re-render — which I traced to the dependency graph only registering top-level
tables. And the CLI was silently doing nothing when invoked through pnpm's
symlinks, so schema pushes and migrations looked like they were succeeding while
changing nothing. Around those sat a run of smaller integration fixes across
Expo, SvelteKit and the type layer, plus new starter templates and examples
spanning plain TypeScript, Vue, Svelte and Next.js.
The documentation gained a bespoke diagram engine. I replaced Mermaid across the
docs with a custom declarative one built around <Graph> and <Sequence>
components: a single definition renders cleanly on both desktop and mobile, it
animates, and the geometry and layout logic is covered by 35 unit tests. On top
of it I built a set of interactive diagrams — how a write propagates across
durability tiers, how data flows through a migration's lens chain — that explain
Jazz's harder ideas better than prose can. I also taught the docs site to serve
any page as clean Markdown to AI coding assistants, continuing a thread that
began with the llms.txt work the previous autumn.
April 2026
April was the busiest month so far by a distance, and the breadth is what stands
out — the work spanned the engine's Rust core, build tooling, onboarding,
correctness, infrastructure and docs. The newest of those was the Rust: after months
working mostly in TypeScript, I added From<T> implementations on the Value type
and a row_input! macro for constructing rows, which between them removed around 250
lines of boilerplate across the codebase, and Rust examples began appearing in
the docs alongside the TypeScript ones.
A large slice of the month went on getting people from nothing to a running app.
I built the create-jazz scaffolder — npm create jazz, which prompts for
framework and auth mode, fetches the right template, resolves dependencies,
initialises git, and can provision Jazz Cloud hosting in the same step — along
with a set of starter templates to go with it, including account backup and
restore by recovery phrase or passkey. Underneath sat a good deal of
build-tooling plumbing: a SvelteKit Vite plugin that manages the dev-server
lifecycle, a switch to static new URL(...) worker bootstrapping so Vite,
webpack and Turbopack co-bundle the worker and WASM correctly, and automatic
injection of the fiddly Vite config most people would otherwise write by hand.
The fixes kept coming, and several were the kind that quietly lose data or lock
people out. Rows could vanish after a schema migration because the resolved
table name wasn't threaded through the query engine; a React Native local-first
account couldn't be upgraded to a full one because the proof method reached for
WebAssembly that isn't available there; a Better Auth race let a slow
/get-session response log a freshly-signed-in user straight back out. On the
infrastructure side I added deploy-time warnings for tables missing permission
policies, content-based deduplication so an unchanged permissions bundle doesn't
bump a version, and — in the classic codebase — an experimental clock-sync
feature that estimates a drifting device's clock offset from existing ping
traffic and quietly corrects timestamps.
The documentation grew an Advanced Internals page — data model, browser
architecture, query engine, sync protocol, schema evolution — and the auth docs
were reorganised around mental models, with the theory (Ed25519 key derivation,
JWT structure) split into its own reference. The AI-agent thread carried on:
AGENTS.md files across all nine starters pointing assistants at the live docs,
and a note in CLAUDE.md asking agents to investigate failing tests rather than
quietly rewrite them. And there were examples to show it all off — a Vue 3
relational-database app, and a multiplayer moon-lander built with React and
Phaser.
March 2026
By March the new engine was where most of the work happened, and its character
was different from anything before: database internals and distributed systems
rather than docs and developer experience. In the SQL policy layer I added
support for EXISTS (SELECT ...) subqueries — letting permission rules express
a great deal more — and fixed how the outer row's ID resolved inside policy
conditions. I implemented batching for the sync protocol's wire format, so the
client accumulates payloads and flushes them in a single POST rather than one
request apiece. And I chased down a clutch of subtle correctness bugs:
.include() corrupting base columns when a subscription fired twice, multi-table
migrations only emitting DROP COLUMN for the first table, a subscription
callback racing ahead of the map meant to track it, and anonymous clients
getting empty results because their session wasn't falling back to the one from
the handshake.
Some of it reached into security and infrastructure. I added JWKS rotation for
self-hosted servers — a TTL-cached key set with on-demand refresh and a
stale-if-error fallback, so signing keys roll over without a restart — and, in
the classic codebase, fixed an InvalidSignature error on concurrent writes
that turned out to be a textbook off-by-one: a count used where an index was
needed, fetching one transaction beyond what the stored signature actually
covered. I also moved both repositories off the npm wasm-pack package to a
cargo install, dropping an axios transitive dependency and a slice of
supply-chain risk with it.
The thread of making the docs machine-readable came to a head as well. I built a
Model Context Protocol server — runnable as npx jazz-tools mcp — that exposes
the documentation to AI assistants through search_docs, get_doc and
list_pages tools, backed by SQLite full-text search with a keyword fallback,
so an assistant looks things up instead of guessing from stale training data.
Across frameworks I brought Vue and Svelte up to parity with React and added a
reconcileArray diff so list updates only re-render what actually changed. And
there were flagship examples: a full React chat app with reactions, file uploads
and row-level permissions, and Wequencer, a collaborative real-time music
sequencer built with Svelte 5 and Tone.js.
February 2026
February is where the ground shifted. Alongside the usual classic-Jazz
maintenance, I started contributing to the new Jazz — a from-scratch rewrite
built around a real query engine, with typed columns, a code generator,
SQL-backed permission policies and a Rust core underneath. My first
contributions there marked wading deeper into the new core: a React
quickstart to find my feet, Svelte 5 bindings (a reactive query-subscription
class, context helpers, a user switcher), and a run of correctness fixes in the
query and policy layers — .include() queries reading from the wrong columns,
floating-point columns that TypeScript allowed but the Rust SQL parser rejected,
and query() and subscribe() quietly skipping permission checks because the
session wasn't being threaded through.
The classic codebase was still very much alive, and the notable changes there leaned towards safety and clarity. I replaced an unmaintained, vulnerable ProseMirror dependency with a small purpose-built version, turned silent failures into loud ones — writing to a group you can only read now throws immediately rather than failing to sync later — and cleaned up a React Native logout that was leaving a session ID behind in secure storage.
January 2026
January was where library work became routine rather than occasional. The headline was first-class Better Auth support for Svelte — a full provider, with an example app and documentation — extending to Svelte an authentication story that had until then been mostly React's. Underneath it I fixed an auth-header serialisation bug, where secret keys were being encoded as an object instead of a plain list of numbers, which had broken passphrase and passkey login on the client.
On the docs side the writing turned more conceptual. I wrote a guide to modelling data in Jazz aimed at developers arriving from traditional databases, a proper API reference page, and explanations of the trickier React integration points — Suspense hooks, error boundaries, and circular references that can send data loading into a loop. I also added a set of "skills" — structured reference documents that AI assistants like Claude and Cursor can draw on to write better Jazz code, covering performance, permissions, UI integration and testing — which carried forward the make-the-docs-machine-readable thread from the autumn.
December 2025
A lighter month, but not an idle one. I proposed and launched "Advent of Jazz" on the community Discord and gave it a homepage banner, and I shipped the smallest useful thing I could think of: a working, syncing to-do list in under a hundred lines of code, placed on the docs landing page as the fastest possible demonstration of what Jazz actually is.
A couple of the fixes were genuinely fiddly. Safari doesn't release its Web
Locks immediately on navigation, which was hanging the docs landing page; I
solved it by lifting the route coordination up into a parent component. I also
patched a Next.js security issue inherited from a React vulnerability, tidied the
React Native polyfills into a single importable module, and added guidance on
keeping selector functions cheap — moving heavy computation into a useMemo
outside the selector so it benefits from batching. The rest was steady upkeep:
responsive fixes to the showcase, a navigation regression, and corrected
hash-fragment URLs so invite links keep sensitive data out of server requests.
November 2025
November had three distinct strands. The first was the culmination of the docs-site engineering: I tore out Twoslash — the plugin that type-checked every code sample but made builds crawl — and replaced it with extracted, separately verified snippet files. The payoff was large. Roughly five minutes came off every Vercel build, about fourteen hours of compute a month, and dev-mode compiles dropped from 110 seconds to 7 — a fifteen-fold speed-up that made the docs far less painful to work on.
The second strand was stepping outside Jazz. I landed a small fix in Zed, the code editor — dimming the commit button while a commit is in flight, so you stop clicking it wondering whether anything happened — and a build-ignore tweak in Vercel's Workflow SDK.
The third was Groove, built for SyncConf 2025: a collaborative step sequencer in
SvelteKit, where several iPads share an eight-beat grid and trigger samples in
time with one another. Keeping the devices audibly in sync was the hard part — I
ended up estimating clock skew from a heartbeat on the Jazz mesh. Under all of
that ran the usual current of library and docs work: a bug in the
$onError: 'catch' resolve path, a proposed pattern for building set-like
collections, an XSS warning for apps that keep secrets in localStorage, and
the documentation for Jazz's Workflow World feature.
October 2025
This was the month the documentation stopped being something I wrote and became
something I engineered. The site had outgrown its two-level navigation, so I
extended it to three levels with collapsible sections and then reorganised every
page to match — internal links updated, redirects in place so old bookmarks
still land in the right spot. I added whole new sections — testing, advanced
performance tips, a guide for framework-less "vanilla" users — and consolidated
sprawling pages like the four-file ImageDefinition docs into one.
I also spent some efforts to make the docs more legible to
language models. I rebuilt the llms.txt export so pages render to clean
Markdown first, mocking the interactive components so things like the tabbed code
groups come out sensibly, and added per-framework llms.txt and llms-full.txt
files plus .md-suffix serving of any page — so an assistant only ever sees the
framework it asked about. The create-jazz-app scaffolder had been shipping a
stale, baked-in copy of the docs to users' AI agents; I pointed it at the live
site instead.
The library work continued at the edges. I added a proper invites API — createInvite
and acceptInvite for working with invite secrets directly — generalised the
server-side-rendering agent so it was no longer React-only and
worked under SvelteKit, fixed a CoFeed type that demanded options it never
actually used, and cleared a batch of dependency security vulnerabilities, with
the Better Auth API changes that came with them. A scattering of CSS and build
fixes — overlay scrollbars on macOS trackpads, route collisions breaking the
build, a search-equipped Not Found page — rounded the month out.
September 2025
Documentation in earnest. The centrepiece was a set of quickstart guides — React and Svelte built from one shared source, a follow-up for running Jazz on the server in a worker, and practical walkthroughs for the two things most real apps need: groups and permissions, and passkey authentication. The existing docs were reference-heavy and short on the "aha" moment; these were meant to supply it.
Around the writing came a steady run of smaller fixes to the docs site and the framework itself. I extracted a reusable confirmation dialog and added a "clear local data" button to the dev tools, made invite links register when the URL's hash changes rather than only on first load, and fixed the progressive-image component, which flashed the image's alt text during a slow load — I swapped in a transparent pixel until the real placeholder is ready.
Two changes reached further into the library. I lengthened the WebAuthn challenge from three bytes to twenty: legal within spec at three, but short enough to trip up third-party passkey managers like KeePassXC. And I added an option to the Svelte provider to namespace where credentials are stored, so two Jazz apps on the same domain no longer clash.
August 2025
A quiet start to my work at Garden and almost entirely documentation. I expanded the Jazz
guidance on optional references — the easily-confused difference between Jazz's own
optional and the one from the validation library — and cleared a couple of
broken links and examples along the way. The only code change was a
one-character fix to a package's configuration, where a wrong path was stopping
Svelte developers from importing Jazz's testing utilities. Away from Jazz, I
added pagination and fixed some table-layout and number-formatting bugs in my
own drinks-tracking app.