I Shipped...

19 June 2026 (1PR)

a grow-only set merge strategy for array columns

When multiple devices write to the same list at the same time in a collaborative app, the default behaviour is "last writer wins" — one write silently wipes out the others. I added a new merge strategy called a "grow-only set" for array columns: instead of one write winning, the list always grows to include everything anyone ever wrote, and all devices converge to the same sorted result. Once an element is added it can never be removed by a concurrent write, making it safe for things like tags or participant lists where losing data would be worse than seeing a duplicate.

18 June 2026 (2PRs)

a new `jazz-tools/shared` entry point for building custom framework bindings

Jazz has bindings for React, Svelte, and Vue, but if you wanted to build a binding for another framework, you'd have to dig into the package's internals. I added a new jazz-tools/shared entry point that exposes the core utilities — the helpers that turn a live query into a reactive, in-place-updated result — so external developers can build custom bindings without touching anything private. The React, Svelte, and Vue bindings were also updated to import from this new public surface instead of deep internal paths.

a fix for a SharedWorker crash in production bundler builds

Jazz apps use a SharedWorker — a background browser thread — to coordinate data between tabs. The worker file was shipped with references to other files using bare paths, but bundlers like Webpack, Turbopack, and Vite in production mode couldn't recognise it as a worker entry, so they copied it verbatim and those references would 404, crashing every Jazz app with "Browser broker SharedWorker failed to start." I fixed this by bundling the worker into a single self-contained file at build time, so it works correctly regardless of which bundler the app uses.

5 June 2026 (2PRs)

a fix for WASM out-of-bounds errors from multiple Jazz runtimes on the same page

The Svelte and Vue versions of Jazz were creating a brand new WASM runtime (a compiled binary module for running encryption and sync logic) for every client created on the page. If you mounted multiple Jazz-powered components for the same user, you'd end up with several runtimes all fighting over shared memory — causing "out of bounds" crashes. I fixed this by sharing a single WASM runtime instance across all clients on the page.

a fix for useAll and QuerySubscription correctness and parity across React, Svelte, and Vue

I fixed a cluster of bugs in useAll and QuerySubscription — the hooks that let you query and subscribe to collections of data — across the React, Svelte, and Vue versions of Jazz. The bugs meant these hooks behaved differently depending on which framework you were using, and some edge cases returned incorrect results. I brought all three frameworks into parity and fixed the underlying orchestrator that coordinates subscriptions.

4 June 2026 (2PRs)

reactive session handles for Svelte's getSession and Vue's useSession

I fixed a problem where session changes weren't properly propagating in the Svelte and Vue integrations for Jazz. In Svelte, getSession() now returns a stable handle that always reads from the underlying reactive state, so switching sessions updates every consumer without tearing down the provider. In Vue, useSession() now returns a computed reference derived from the injected client, so any template or computed value that uses it automatically re-renders when the session changes.

a fix for owner updates incorrectly hard-deleting backend-created rows

There was a tricky bug where a row created by a backend server would sometimes get permanently deleted on both the server and the client. The root cause was that when the server sends a row to a client without its full history, and the client later edits it, the server rejects the edit because it looks like the client is trying to create a new row without permission — and that rejection would flip an already-accepted row into a deleted state. I fixed the logic so that owner updates no longer trigger this incorrect hard-delete path.

2 June 2026 (5PRs)

type-checking for docs example snippets in the build

I wired up type-checking for all the code examples embedded in Jazz's documentation — TypeScript, Svelte, and Vue variants. Before this, a snippet could have a type error and nobody would notice until a user tried to copy it. Now a broken snippet fails the build, so the docs always reflect working code.

an end-to-end CI harness gating the starters release

I added a full end-to-end testing harness for Jazz's starter templates — the scaffolded projects a new user creates with create-jazz. The CI now actually builds each starter and runs Playwright browser tests against it before any release goes out, so a broken starter template can no longer silently ship to users.

a fix for admin secrets being exposed in server startup logs

The Jazz server was logging its full inspector URL at startup, which had the admin secret embedded directly in the link — meaning anyone with access to the logs could see the credential. I removed the secret from the logged URL and added a warning that fires when the server starts without production auth settings configured, so developers aren't silently running in an open state.

a fix for a stack overflow when forwarding rows with deep parent history

When a row has a very long chain of ancestors — around 300 levels deep — the code that forwards rows was calling itself recursively, eventually running out of call-stack space and crashing. I replaced the recursive approach with an iterative post-order walk, so deeply nested row histories no longer cause a stack overflow.

a fix for a permissions head-before-bundle race causing local write denials

When the permission header for a piece of data arrived before the data itself, Jazz was incorrectly rejecting local writes. I removed a redundant authorization call so that Jazz waits until the full data bundle is received before enforcing permissions, eliminating a race condition that caused intermittent test failures in CI.

1 June 2026 (1PR)

a fix for declaration files being emitted outside declarationDir in Svelte's language tools

When a Svelte component imported a TypeScript file from outside the configured source root, the emitDts tool would create .d.ts type declaration files in the wrong place — sometimes nesting them deep in the source tree with doubled absolute paths. I fixed the file-writing hook to skip any declarations that fall outside the intended output directory and to only prepend relative path prefixes when the filename is already relative.

29 May 2026 (1PR)

connection-per-client caps and idle handshake timeouts for the Jazz sync server

I added two defences to the Jazz sync server to prevent a type of denial-of-service attack called a "reconnect storm". First, I capped the number of simultaneous WebSocket connections allowed per client at 4, automatically dropping the oldest connection when the limit is hit and sending a "RateLimited" error code. Second, I added a 10-second timeout for connections that start their handshake but never finish, so idle half-open connections can't pile up indefinitely.

28 May 2026 (2PRs)

a security fix for unauthenticated OOM via WebSocket handshake decompression

I found that the WebSocket handshake frame (the first message when a new connection opens) was being decompressed before we'd checked who was connecting. A malicious actor could send a tiny compressed frame that claimed to expand to gigabytes, crashing the server by running out of memory. I added a check that rejects any handshake frame claiming to be larger than 1 MB — since a real handshake is only a few kilobytes of data, anything bigger is suspicious.

a fix for a Next.js SSR crash caused by duplicate React instances in withJazz

When using jazz with Next.js, our withJazz helper was mistakenly telling Next.js to load jazz-tools as a separate server-side module instead of bundling it together with your app. This caused two separate copies of React to be loaded at the same time — one for the server-rendered pages, one for the client components — which crashes Next.js during pre-rendering. I fixed it by only excluding jazz-napi (the native binary) from bundling, so jazz-tools gets bundled normally with your app and there's just one React instance throughout.

22 May 2026 (3PRs)

documentation for several new Jazz features including the schema hash CLI

I wrote docs for several previously-undocumented Jazz features: a CLI command for inspecting local schema hashes, guidance on what values to use in JWT sub claims (and why changing them later causes problems), how permission columns like $canRead work within nested queries, and two performance optimizations — .indexOnly() for leaner index lookups and .merge("counter") for summing concurrent integer edits without conflicts.

an interactive write-tier diagram for the Jazz docs

I added an interactive diagram to the Jazz documentation that lets you pick a durability tier — local, edge, or global — and watch how a data write propagates across a network of devices. This makes it easier to understand how Jazz handles consistency at different levels and what happens when a device is offline, without having to read through dense text explanations.

a custom declarative diagram engine to replace Mermaid in the Jazz docs

I replaced the Mermaid diagram library in the Jazz docs with a custom-built engine using <Graph> and <Sequence> components. Mermaid wasn't great for interactive, mobile-friendly diagrams — theming and layout control were too limited. The new engine handles desktop and mobile layouts in a single definition and supports animations. I also migrated the four existing diagrams and added 35 unit tests covering the geometry and layout logic.

21 May 2026 (1PR)

a fix for MCP server crashes when node:sqlite is unavailable

I fixed a crash in the Jazz MCP server that happened on Node.js versions older than 22. The crash was caused by the text-search fallback accidentally importing node:sqlite — a database built-in that doesn't exist in older Node versions — before it even had a chance to activate. I extracted the SQLite-free parsing helpers into a new standalone file so the fallback module's dependency chain never touches the database code.