Skip to content

Add sync state visibility for P2P collections #17

@monneyboi

Description

@monneyboi

Problem

Users have no visibility into the sync state of their collections:

  • Is this collection shared or local?
  • Did I create it or import it from someone else?
  • Are any peers currently online?
  • Is sync happening?

The P2P infrastructure (iroh-docs) works, but it's a black box to users.

Solution

Expose sync state information that iroh already tracks, plus derive ownership from entry authorship.

Data Available from iroh (no extra storage needed)

Info Source How
Capability docs.list() Returns (NamespaceId, CapabilityKind) - Write or Read
Ownership _collection entry Compare entry.author() with our author_id
Peers online Event stream Track NeighborUp / NeighborDown events
Sync activity Event stream SyncFinished, InsertRemote, ContentReady events

UI States

Capability Owner Peers Display
Write Us 0 Local
Write Us N Sharing · N online
Write Other 0 Joined (offline)
Write Other N Joined · N online
Read Other 0 Read-only (offline)
Read Other N Read-only · N online

Implementation

Backend Changes

1. New module: sync_state.rs

Track per-collection sync state derived from iroh events:

pub struct CollectionSyncState {
    pub capability: CapabilityKind,      // Write or Read
    pub is_creator: bool,                // We signed _collection entry
    pub neighbors: HashSet<PublicKey>,   // Currently connected peers
    pub last_sync: Option<SystemTime>,   // Last SyncFinished event
}

2. Modify SyncWatcher (jobs/watcher.rs)

Currently only handles InsertRemote for document processing. Extend to also track:

  • NeighborUp(PublicKey) → add to neighbors set
  • NeighborDown(PublicKey) → remove from neighbors set
  • SyncFinished → update last_sync timestamp

3. New storage method

Add to Storage:

pub async fn get_collection_owner(&self, namespace_id: NamespaceId) -> Result<AuthorId>

Returns the author who signed the _collection entry.

4. New Tauri commands

#[tauri::command]
pub async fn get_collection_sync_status(collection_id: String) -> CollectionSyncStatus

#[tauri::command]  
pub async fn get_node_info() -> NodeInfo  // Our node_id, for debugging/display

5. New Tauri events

Emit to frontend when state changes:

  • sync-neighbor-changed - peer count changed
  • sync-finished - sync completed with a peer

Frontend Changes

1. New store: sync-state.svelte.ts

interface CollectionSyncState {
  status: 'local' | 'sharing' | 'joined' | 'read-only';
  peerCount: number;
  lastSync: Date | null;
}

// Map of collection ID → sync state
const syncStates = $state<Map<string, CollectionSyncState>>(new Map());

Pattern follows existing stores (model-state.svelte.ts, import-state.svelte.ts):

  • Module-level $state for persistence across navigation
  • setupListeners() for Tauri events
  • Getter functions for reactive access

2. Update files page (routes/(app)/files/+page.svelte)

Add sync status badge to collection cards:

<span class="text-xs text-neutral-500">
  {#if syncState.status === 'local'}
    Local
  {:else if syncState.status === 'sharing'}
    Sharing · {syncState.peerCount} online
  {:else if syncState.status === 'joined'}
    Joined {syncState.peerCount > 0 ? `· ${syncState.peerCount} online` : '(offline)'}
  {:else}
    Read-only {syncState.peerCount > 0 ? `· ${syncState.peerCount} online` : '(offline)'}
  {/if}
</span>

Files to Modify

Backend

  • crates/insight-core/src/lib.rs - Add sync state tracking to AppState
  • crates/insight-core/src/jobs/watcher.rs - Track neighbor events
  • crates/insight-core/src/storage/mod.rs - Add get_collection_owner() method
  • src-tauri/src/commands/mod.rs - New commands for sync status

Frontend

  • src/lib/stores/sync-state.svelte.ts - New store (create)
  • src/routes/(app)/files/+page.svelte - Display sync status on collections

Non-Goals

  • Persisting "ever shared" state - not important, rely on iroh's live state
  • Detailed sync progress bars - simple status is enough for now
  • Per-document sync status - collection-level is sufficient

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions