Repository navigation
Tag ambiguous strings as string and keep JSON null in nested Loro values - #2206
Conversation
|
Complexity and compatibility review (+87/-6 over 6 files; I read the diff, not the surrounding reader code or the signing path). Since this touches how all new values are written, I'd hold the merge for items 1 and 2.
The rest is proportionate: null handling is a one-line arm in each helper and no new layers are added. Generated by Claude Code |
…ompat tests The TS datatypeTag tagged atomic:// links while Rust did not. The untagged reader leaves atomic:// as a plain string, so TS now excludes it too. Both sides test against lib/test_files/string-datatype-tags.json. Add Rust tests for old untagged snapshots, unknown tags and Null in nested containers, and TS tests for unknown tags and nested Null.
|
Both holds are addressed in 5d36809.
Locally: Generated by Claude Code |
The Dagger lib unit-test container only mounts browser/lib, so the TS test could not read lib/test_files. Rust now reads it with include_str!, like authentication_v2_vectors.json.
* Sign incremental commits as Loro deltas, not full snapshots CommitBuilder::sign_at rebuilt the doc from the resource's existing snapshot, applied the pending set/remove, and signed export_snapshot(). Every incremental commit therefore carried the whole document in loroUpdate, so its size grew with the document (and with every Loro peer that had touched it). Cloud Vault delta packs ship envelopes, so their per-edit cost grew too: over 40 edits of one note, latest retention went 4171 -> 16378 bytes per pass and all retention 7063 -> 31687. sign_at now exports only the changes since a base version when the resource has prior state; genesis / new resources still sign a snapshot. The base is AtomicLoroDoc::tokened_base_vv of the persisted snapshot: the version of the newest tokened change, not the full version. A node stamps lastCommit into the doc after applying a commit, as an untokened local change no signed commit carried; the next edit builds on it, so a delta from the full version made the server park it as pending for a missing dependency (caught by multi_client_gallery_sync). Exporting from the tokened base carries the stamp along. Legacy docs without any token keep signing snapshots. sync_loro_changes_to_commit_builder uses the same base. The sign() fallback that exported the open doc now exports the delta too. Measured with vault_envelope_growth (40 edits): last-edit delta now 1864 (latest) and 3080 (all) bytes instead of 16275 and 32068, flat across edits. The test now asserts the last delta stays within 1500 bytes of the first. The planning table is refreshed from vault_incremental_cost. The vault envelope floor stays >= (the previous pass's newest envelope is re-shipped once): dedupe would need envelope identity, and a strict > could lose a commit sharing the same millisecond. * Don't re-enroll an already hosted drive when opening the sync page The sync page auto-enrolled any drive the account had enrolled until the node-side proof of hosting (live sync + drive usage) arrived. On a drive already on Cloud Server that re-ran enrollment, reconnected the socket (a spurious 'Working offline' toast) and announced 'Connected to Cloud Server'. Skip it when the current server is already a managed node, and read the user-disconnected flag per event in NetworkIndicator. * Cloud Vault: compress the backup and discard its history Compress now forces the next backup pass to be a full checkpoint, publishes it, then frees what it replaced. Discard history does the same and also frees the undo window, behind a confirmation dialog. Free-up never runs if the checkpoint did not land. The forced pass reuses the exporter's "no anchor in the vault" input, so cursors and lane state are unchanged. * Cloud Vault: say what discarding backup history really frees A checkpoint still holds each resource's whole Loro history, so the old copy about keeping only the current state over-promised. Discard history removes older copies, the delta chain and the undo window. * Cloud Vault: download the backup to a file and restore from one "Download backup" saves the drive's sealed vault objects as one .atomic-vault file (versioned header, then the objects in listing order, each with a SHA-256). "Restore from file" validates it and feeds the objects through the same import as a cloud restore, which now takes an injectable object source. Nothing is uploaded; the file stays sealed and needs the drive key. * Vault: Discard history keeps only the current state in the backup A checkpoint can now export each resource as a Loro shallow snapshot at its current frontiers and ship no signed envelopes (HistoryKeep::CurrentStateOnly). Default stays Full, so ordinary passes and Compress now are unchanged. Threaded through the WASM, worker and Tauri export entries and the Discard history flow. Shallow docs export a snapshot to peers behind the cut instead of a delta that would be parked as pending, fork_at on a shallow doc is a clear error, and the History page tolerates a shallow doc. Copy, locales and the format doc updated; tests cover restore, sync with a full-history device and size. * Keep a conversation with only yourself on this device startConversation(store, []) is now a note to yourself: the only member is the creator, and the drive is registered browser-only before the first save, so its genesis never enters the outbox and a managed node is never asked to host it. A non-empty list that holds only yourself still throws. healRefusedDrive may now move a conversation whose read list is exactly the signed-in agent. Every other conversation is still never moved. * Add file encryption for conversation attachments * Tag ambiguous strings as string and keep JSON null in nested Loro maps and lists * Share string datatype tag fixture between Rust and TS; add backward-compat tests The TS datatypeTag tagged atomic:// links while Rust did not. The untagged reader leaves atomic:// as a plain string, so TS now excludes it too. Both sides test against lib/test_files/string-datatype-tags.json. Add Rust tests for old untagged snapshots, unknown tags and Null in nested containers, and TS tests for unknown tags and nested Null. * Report one Sentry session per page load, not one per route change * Ask the control plane about the session once, not on every retry and flush A signed-out visitor called /api/me about nine times in the first 25 s and then every few seconds for as long as the page stayed open: the hosted-AI status retried, the vault backup scheduler re-queued, and every other caller asked again. getManagedAccount now serves the last settled answer for 30 s, forgets it on focus and when a sign-in lands in this tab, and callers that wait for a sign-in read fresh. * Ask for a missing external property once, and bundle the demo speaker property A property the remote answers 404 for was fetched again on every put of a message that carries it, twice per miss (the fetch, then handle_not_found fetching the same URL). Db now remembers a definitive 404/410 for the session and no longer retries it; 5xx and network errors are still retried. The demo speaker property is added to the bundled defaults so it resolves locally and is never asked for. * Let a remembered 404 expire after five minutes, and share one listener registry Review of the redundant-requests PR: a property that 404ed once stayed missing for the life of the Db. The memory now expires after 5 minutes. Also proves a failed session read is not cached, merges the two listener sets in session.ts, and states what the not-found-once test covers. * Format the remote-not-found test server response * Format and pad the combined batch's frontend changes for lint * Take #2206 out of the combined batch: its datatypes test reads a fixture outside browser/lib * Forget the cached session before each refused-drive test * Integration tests: close agent-profile-sync's stores before the servers go Same teardown failure as defb758: stores left connected log after the file finishes, and vitest exits 1 with every test green.
…ead-3oq7ug # Conflicts: # CHANGELOG.md
The Dagger Rust container does not mount browser/lib/src, so include_str! failed. lib/tests/fixtures is where the other Rust/TS shared fixtures live and is mounted in both the Rust and the lib unit-test containers.
|
Run 5206 failed on c670647 because Fixed in fee0236: the fixture now lives in Generated by Claude Code |
Before: a string property whose value starts with
[,{,atomic:,did:ad:orhttp(s)://was stored untagged, and readers guessed its type, so["track","osc"]came back as a nested resource andhttps://example.comas a URL. JSONnullinside nested maps and lists was silently dropped on write.After: such strings get the
stringtag in the Lorodatatypesmap and read back as plain strings in Rust and TS. JSONnullsurvives a round trip in nested maps and lists.This is the one part of #2045 (frozen app schemas) that #2151 did not carry over, and it affects all data, not only schemas. Atomic Audio relies on it, because it uses JSON-array strings as entity IDs.
How:
datatype_tag(Rust) anddatatypeTag(TS) tag ambiguous strings, andatomic_value_from_tagreads the tag. The Rustjson_value_to_loro_*helpers and the TSwriteJsonToLoroMap/Listwritenullinstead of skipping it. Older readers don't know thestringtag and fall back to their old guess, which is no worse than today.Related Issues
Part of #2045, which #2151 replaced.
Checklist
ambiguous_strings_preserve_their_declared_type,json_null_is_preserved_in_nested_maps_and_lists; TS:datatypes.test.ts, apushListItemnull test that fails without the fix)Local:
atomic_liblib tests 774 passed,atomic-serverlib tests 386 passed,@tomic/lib1516 passed.Generated by Claude Code