Files
Marcos Paulo 181aad91e6 feat: sync, accounts, live editing, vault launcher, compact layout
Sync and the server
- crates/sync: revision ledger (stale writers conflict, edit beats delete), client planner, three-way merge, conflict names, path and hash gates
- apps/notescraft-sync-server: pairing, device tokens, change log with long poll, blobs, accounts with many vaults, roles, invites, audit log, quota, GC, readiness, request ids
- server history (revisions, restore, retention), chunked resumable uploads, live editing endpoints (SSE rooms, presence, flush through the ledger)
- engine: HttpRemote, sync_once, SyncWorker, sync.* commands, share.* and server.* commands, live session client, note.capture, vault catalog and vaults.* commands

Editing together
- crates/collab: operational transformation core (TextOp, transform, client state machine, room, presence)
- ui-egui: Settings > Sync, sync pill, conflicts, Share dialog, live pill and remote carets, launcher Connect and Join flows

App shell
- vault launcher (desktop and phone layouts) wired into the app host (--launcher, File > Vaults...)
- compact (phone) layout with a bottom tab bar

Verified with cargo xtask ci (fmt, clippy -D warnings, workspace tests, layers, tree, i18n). Android and iOS shells are not built: the build machine has no NDK or Xcode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 07:46:24 -03:00

39 lines
1.4 KiB
Rust

//! `Vault::sibling`: a second handle without the lock, for worker threads of the holder.
mod common;
use std::time::Duration;
use notescraft_vault::{Vault, VaultEvent, WriteOutcome};
/// Compile-time proof that a `Vault` may travel to a worker thread.
#[allow(dead_code)]
fn assert_send<T: Send>() {}
const _: fn() = || assert_send::<Vault>();
#[test]
fn a_sibling_writes_while_the_parent_holds_the_lock() {
let (_dir, parent) = common::vault("sibling");
let watcher = parent.watch().expect("watch the vault");
let sibling = parent.sibling();
assert!(!sibling.is_read_only());
assert_eq!(sibling.root(), parent.root());
assert_eq!(sibling.write("sib.md", b"hello").unwrap(), WriteOutcome::Written);
// The parent reads what the sibling wrote, and its watcher reports the write.
assert_eq!(common::read(&parent, "sib.md"), b"hello");
let event = watcher.receiver().recv_timeout(Duration::from_secs(5)).expect("an event");
let hit = matches!(&event, VaultEvent::Created(path) | VaultEvent::Modified(path) if path == "sib.md");
assert!(hit, "the watcher should see the sibling's write: {event:?}");
}
#[test]
fn a_sibling_of_a_read_only_vault_is_read_only() {
let dir = common::TempDir::new("sibling-ro");
let parent = Vault::open_read_only(dir.path()).expect("open read-only");
let sibling = parent.sibling();
assert!(sibling.is_read_only());
assert!(sibling.write("x.md", b"y").is_err());
}