Skip to content

Commit 372142a

Browse files
Add tag metadata snapshot sync
Introduce end-to-end support for syncing tag metadata (pinned/icon) via encrypted Nostr snapshots. Adds a new comet_tag_metadata_snapshot adapter for payload (de)serialization and encryption, a tag_metadata_apply service to apply/merge incoming snapshots and reapply stored payloads, and a push_tag_metadata_snapshot flow to publish local metadata with vector clock handling. Integrates tag metadata into the snapshot sync connection and bootstrap process, adds a SyncCommand::PushTagMetadata path and UI command set_tag_icon that marks metadata locally modified and advances the vector clock. Updates SQLite schema/migrations and note repository to persist tag icons, stores vector clock and stored payload in app_settings, and includes tests for the new behavior. This ensures pinned/icon metadata is consistently synchronized and re-applied when tags are created during bootstrap.
1 parent f7a602f commit 372142a

22 files changed

Lines changed: 1239 additions & 42 deletions

app/src-tauri/src/adapters/nostr/comet_note_snapshot.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ pub struct ParsedNoteSnapshotEvent {
7979
pub payload: Option<NoteSnapshotPayload>,
8080
}
8181

82-
fn is_false(value: &bool) -> bool {
82+
pub(crate) fn is_false(value: &bool) -> bool {
8383
!value
8484
}
8585

86-
fn self_conversation_key(keys: &Keys) -> Result<ConversationKey, AppError> {
86+
pub(crate) fn self_conversation_key(keys: &Keys) -> Result<ConversationKey, AppError> {
8787
ConversationKey::derive(keys.secret_key(), &keys.public_key())
8888
.map_err(|e| AppError::custom(format!("Failed to derive self conversation key: {e}")))
8989
}
@@ -251,9 +251,8 @@ pub fn decrypt_note_snapshot_payload(
251251
NoteSnapshotPayload::from_canonical_json(&json)
252252
}
253253

254-
fn build_visible_vector_clock_tags(payload: &NoteSnapshotPayload) -> Result<Vec<Tag>, AppError> {
255-
let vector_clock =
256-
canonicalize_vector_clock(&payload.vector_clock).map_err(AppError::custom)?;
254+
pub(crate) fn build_visible_vector_clock_tags(clock: &VectorClock) -> Result<Vec<Tag>, AppError> {
255+
let vector_clock = canonicalize_vector_clock(clock).map_err(AppError::custom)?;
257256
if vector_clock.is_empty() {
258257
return Err(AppError::custom(
259258
"Note snapshot payload vector_clock must be non-empty",
@@ -268,7 +267,7 @@ fn build_visible_vector_clock_tags(payload: &NoteSnapshotPayload) -> Result<Vec<
268267
.collect())
269268
}
270269

271-
fn parse_visible_vector_clock_tags(event: &Event) -> Result<VectorClock, AppError> {
270+
pub(crate) fn parse_visible_vector_clock_tags(event: &Event) -> Result<VectorClock, AppError> {
272271
let mut vector_clock = BTreeMap::new();
273272
let mut previous_device_id: Option<String> = None;
274273

@@ -362,7 +361,7 @@ pub fn build_note_snapshot_tags(
362361
Tag::identifier(&meta.document_id),
363362
Tag::custom(TagKind::custom("o"), vec![meta.operation.clone()]),
364363
];
365-
tags.extend(build_visible_vector_clock_tags(payload)?);
364+
tags.extend(build_visible_vector_clock_tags(&payload.vector_clock)?);
366365

367366
if let Some(collection) = &meta.collection {
368367
if collection.trim().is_empty() {

0 commit comments

Comments
 (0)