sync: shared notes in the feed, revocations, and text pushes from an edit share
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 2s
Android / Kotlin + Rust (APK) (push) Skipped
CI & Build / Web typecheck and unit tests (push) Successful in 9s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Build & push image (push) Successful in 54s

The change feed answers `?shares=1` with every note the caller can see, each
saying how it is held, plus a `revoked` list of notes that left them. Granting
a share moves the note past the recipient's cursor; ending one, or the owner
deleting the note, leaves a revocation on the same cursor. A recipient at edit
may push the note's text, and nothing else. Protocol 6, feature `shares`;
opt-in, so the floor stays at 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 15:25:13 -04:00
co-authored by Claude Opus 5.5
parent 2e2d8667dd
commit 75928c7afd
10 changed files with 407 additions and 46 deletions
+11 -4
View File
@@ -68,32 +68,39 @@ def test_clamp_limit():
def test_page_cursor_all_drained():
# Neither stream is full → cursor is the max revision seen; nothing more to page.
cursor, more = _page_cursor([1, 3, 5], [2, 4], since=0, limit=500)
cursor, more = _page_cursor([[1, 3, 5], [2, 4], []], since=0, limit=500)
assert cursor == 5
assert more is False
def test_page_cursor_empty():
# No changes since the cursor → cursor stays put, no more pages.
cursor, more = _page_cursor([], [], since=7, limit=500)
cursor, more = _page_cursor([[], [], []], since=7, limit=500)
assert cursor == 7
assert more is False
def test_page_cursor_one_stream_full_advances_to_its_boundary():
# Notes came back full (limit=3) → truncate at its boundary; later labels defer.
cursor, more = _page_cursor([1, 2, 3], [4, 5], since=0, limit=3)
cursor, more = _page_cursor([[1, 2, 3], [4, 5], []], since=0, limit=3)
assert cursor == 3
assert more is True
def test_page_cursor_both_full_uses_min_boundary():
# Both full → advance only to the SMALLER boundary so neither stream skips a gap.
cursor, more = _page_cursor([1, 2, 10], [3, 4, 5], since=0, limit=3)
cursor, more = _page_cursor([[1, 2, 10], [3, 4, 5], []], since=0, limit=3)
assert cursor == 5
assert more is True
def test_page_cursor_a_full_revocation_stream_holds_the_cursor_back():
# Revocations page on the same cursor: a full one truncates like any other stream.
cursor, more = _page_cursor([[1, 9], [], [2, 3, 4]], since=0, limit=3)
assert cursor == 4
assert more is True
# --- protocol handshake (M10.6) ---------------------------------------------