Skip to content

v0.41.0-beta.2

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Aug 09:45
· 103 commits to develop since this release

This is the local-first release. Atomic Data no longer needs a server to exist.

It is also the first release with new features since v0.40.0 in October 2024 — v0.40.2 and v0.40.3 were security-only patches. That is 1,222 commits and ~334,000 lines. The highlights below cover the whole 0.41 line; per-release detail continues in the v0.41.0-beta.1 and v0.41.0-beta.0 sections of the changelog, and everything front-end is in the browser changelog.

Highlights

Your data works without us. Create a drive, write to it, and read it back with no server in reach. Every write is a signed CRDT commit against a local database, so the app is fully usable offline and syncs when it can. This is not a cache in front of a server — the browser runs the same Rust storage and query engine through WASM, which is why an offline table computes the same totals as an online one.

did:ad — identifiers that don't belong to a hostname. Resources are addressed by a decentralized identifier rather than a URL on someone's domain, and resolved peer-to-peer over the Mainline DHT. No DNS, no certificate authority, no hosting company in the trust path. Combined with self-verifying genesis certificates, a resource proves who created it on its own terms. Your agent is now just a private key: no account on any server, nothing to be locked out of.

Peers, not clients. A sync protocol (#1178) and Iroh/Mainline peer sync let two devices converge directly. Pair a phone by scanning a QR code, mount a drive, and edits flow between them. A server, when you use one, is now described as just another device you sync with — it is a convenience, not the source of truth.

Live everything. Live queries (#1174) mean a collection updates as the data does, rather than when you reload. On top of that sits a full presence layer (#1229): see who else is here as avatars, follow someone through the app, watch "typing…" appear in chats and comments, and see live cursors while co-editing a document. Presence rides a drive-scoped ephemeral relay, so it never touches your stored data.

A new document editor. A ground-up rich text editor backed by a Loro CRDT, so two people can edit the same document at once without a lock or a merge conflict — with slash commands, resource mentions, tables, math and a collaborative canvas alongside it. And peer-to-peer video and audio calls in the meeting panel, where media never passes through a server at all.

Tables became apps. A Kanban board (#1198) with drag-and-drop and custom card ordering, plus calendar and timer views — all the same grid underneath, so cells stay editable and columns sortable whichever way you look at them. Then: dashboards that compose numbers, charts and embedded tables over your data; computed columns (a live duration, days-since, quantity × price); totals computed by the store across every matching row rather than the page on screen; row actions and one-tap create buttons that turn a table into something you press rather than something you fill in; and thirteen ready-made templates — issue tracker, CRM, expenses, grocery list, plant care, workout log and more. None of it ships a custom renderer: a mini-app is configuration, so the built-in assistant can build and reshape one for you.

Private by default. The browser's local database is now one encrypted store per agent (XChaCha20-Poly1305 at rest), so switching accounts or signing out genuinely closes the door. Account backup is passkey-first — one prompt, no secret to write down — and a device lock can seal the keypair after inactivity or when the browser closes.

And it got much faster. A query and indexing rework cut a 1000-member collection query by ~88% at the Rust level and ~70% over HTTP.

Also in this line: content localization with a LocalizedText datatype and locale-routed site templates, a desktop app that mounts your drives as a real virtual drive in Finder or your file manager, emoji and cover images on any resource, favorites, stateless invites, and the removal of SERVER_URL so a server can answer on several domains at once.

Beta, and honest about it: the storage format changed, so your database migrates on first run — take a backup first. See STATUS.md for what is expected to stay stable.

Security

All four fixes documented under v0.40.3 are present here — they reached this line through the develop merge, not through that tag. One deliberate divergence:

  • The commit-level check_server_managed_properties guard that accompanied the /download arbitrary-file-read fix is not part of this line. It rejected any commit writing internalId, which breaks local-first uploads — here the client legitimately assigns that content hash at genesis. /download resolves internalId as an opaque content-addressed blob key rather than a filesystem path, so the path-traversal primitive the guard defended against does not exist. The download.rs hardening itself is retained.

Added

  • Dashboard and Block classes in the default store, with dashboard-blocks / dashboard-layout and block-kind / block-source / block-view / block-query / block-aggregate / block-chart-spec. One Block class carrying a kind string, the way View does, so a new block kind needs no ontology change. This is the schema behind the browser's new dashboards; the numbers themselves come from the aggregation engine already in Query.
  • A view-row-actions property in the default store: the buttons a view puts on each row, as a JSON array of { id, label, kind, property, value }. kind is a closed vocabulary of patches (setNow / setValue / toggle / increment) rather than code, so a person can edit one in a dialog and an assistant can write one, and each press stays an ordinary commit.
  • A view-quick-add property in the default store: the button a view offers for creating a row, as a JSON object { label, field?, placeholder?, presets? }. Each preset is { kind, property, value? } using the same closed patch vocabulary as view-row-actions, applied to the row being created — so a button can stamp the current time and create in one press.
  • dashboard-layout holds { subject, w, h } — sizes only. It previously also carried x/y, which no renderer read.
  • A block-quick-add property and a 'create' block kind, so a Dashboard can carry a create button. It holds the same JSON shape as view-quick-add, which is what lets the table and the dashboard describe one button identically.
  • Aggregate queries: a Query can carry an aggregation, and the store answers it by walking its own index instead of returning rows for the caller to add up. Sum, count, average, min and max over every row a query matches — filters included, paging excluded — plus an optional breakdown giving one subtotal per distinct value of a column, with day and month buckets resolved in the caller's timezone. Results arrive on the Collection's new collection/aggregates property. Because the browser's local database runs this same code through wasm, a table shows identical totals with no server in reach. Note that count counts the rows the asking agent may actually read, which can be lower than totalMembers — that one counts raw index hits, before rights are applied.
  • Argon2id key derivation in atomic-lib as vault::keys (argon2id_derive_key), exposed to the browser as argon2idDeriveKey. This backs the passkey-wrapped ("envelope v2") backup of the agent secret: AES-GCM runs natively in WebCrypto, and Argon2id is the one primitive the Web platform's crypto API is missing. Defaults to ~64 MiB / 3 iterations / 1 lane.
  • EncryptedBackend wraps any redb StorageBackend and encrypts data at rest with XChaCha20-Poly1305 (4 KiB blocks, a fresh random nonce per write, block-index AAD, key-check header), so resources, Loro snapshots, blobs and derived indexes are all ciphertext. Backs the browser's per-agent OPFS databases.
  • A coverImage property in the default store — an image File shown as a decorative banner at the top of a resource's page — and a coverImageFocus float (0-1) holding that banner's vertical focal point, since a wide crop of a photo rarely has its subject in the middle.

Changed

  • Vector search is now opt-in (pass --enable-vector-index / ATOMIC_ENABLE_VECTOR_INDEX) instead of on by default. Loading embedding models and indexing every write on a plain create/edit had a real, measured performance cost that most deployments don't need.
  • BREAKING (build): vector-search is no longer a default cargo feature — build with --features vector-search for semantic search. It was already opt-in at runtime and already absent from every released binary, but cargo install atomic-server still compiled the whole fastembed/lancedb/arrow/ort stack for a feature that stays switched off unless asked for: a very large build, requiring protoc, and failing outright on musl where ort has no prebuilt ONNX Runtime binary. Default now matches both the runtime default and what actually ships. Passing --enable-vector-index to a build without the feature warns instead of silently doing nothing.
  • Query/index performance rework (atomic-lib): collection queries now read materialized rows instead of decoding a Loro CRDT snapshot per member, permission checks are memoized per request, and the query-members index uses compact query ids with typed, order-preserving sort keys. Verified paired before/after: a 1000-member collection query dropped ~88% at the Rust level (52ms → 6–7ms) and ~70% over HTTP in a single round trip (~104ms → ~30ms). See planning/index-performance.md.
  • Live-query index updates are routed by (drive, property), and multi-constraint (AND) queries pick their starting index by estimated selectivity — commits and first-time index builds touch far fewer filters/resources.
  • Sorted collections now include members that lack the sort property (they sort first). Previously such members were silently dropped from sorted listings.

Fixed

  • A row edited until it no longer satisfies a filter now leaves that filter's results. Filtered queries are answered from a cached member list per watched query, and the whole-resource write path (add_resource, used by the browser's local database for every write it makes, and by imports) evicted the previous values' entries against the new resource — so it asked whether the new value still matched the filter, and a row edited out of the view answered no, skipping the one deletion that mattered. The row then stayed listed until the index was rebuilt, a reload included. The same mistake also listed a row twice when an edit kept it in the view but changed its sort value: the entry was filed under the old key and deleted under the new one, so the stale entry survived alongside the fresh one. It now evicts against the resource being replaced, the way commit application and recursive deletes already did. Commits were never affected, which is why this only ever showed up in the browser.
  • #287 Sorting collections by numeric properties (integers, floats, timestamps) now orders numerically instead of lexicographically ("2" no longer sorts after "10").
  • /search?filters= actually filters again. Tantivy only scopes a JSON-field query to a path when the clause carries the field's own name, so the documented bare <property-uri>:<value> syntax silently matched nothing (no parse error) — filters=isA:File, used by the file picker, never found anything. Each AND/OR clause is now rewritten with the propvals. prefix before parsing, so the documented syntax works without any client change.
  • Fixed a deadlock in Db::apply_commit: the per-subject lock was held across the after-commit handler loop, so a plugin's after_commit issuing its own follow-up commit to the same subject re-entered apply_commit and waited forever on a lock only it could release. The lock is now released before handlers run.
  • ATOMIC_REPOPULATE_DEFAULTS now repopulates the base models (genesis, drive, the Commit class and the other fixed base-model properties/classes) as well as the JSON ontologies. Previously a store seeded before a base model was added could never receive it short of wiping the database.
  • _new: placeholder resources no longer loop stuck-commit drops. If anything cleared Resource.new before a client-only _new:<random> subject completed its genesis save, the next local edit reached the outbox's incremental-commit path with an unresolved subject, which the server rejects.
  • atomic-cli compiles again: the interactive new prompt's exhaustive DataType match had no arm for LocalizedText (added for #1069). Like the existing LoroDoc arm it now returns Ok(None), since a per-language map isn't something a CLI prompt can collect as one string.

Dependencies

  • Bumped wasmtime/wasmtime-wasi/wasmtime-wasi-http (45.0.0 → 45.0.3, RUSTSEC-2026-0182 and -0188, the WASM plugin sandbox), quinn-proto (0.11.14 → 0.11.16, RUSTSEC-2026-0185), crossbeam-epoch (0.9.18 → 0.9.20, RUSTSEC-2026-0204) and plist (1.9.0 → 1.10.0, RUSTSEC-2026-0194 and -0195). The remaining cargo-audit findings (hickory-proto, rustls-webpki) trace to the deferred iroh 0.35 transport migration — see planning/rust-dependency-upgrade-audit.md.