You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add public shares design docs and remote-access server improvements
- Add SHARE-000..011 task specs and docs/design/shares.md defining
the sd.app public sharing architecture and wire protocols
- Rework INDEX-010 to scope ephemeral UUID reconciliation per-library
instead of a single global map, since core can have multiple
libraries loaded at once
- Add sidecar serving and configurable bind host to apps/server, and
wire the web platform's daemon status to the server's own origin
- Add quick preview button to the file inspector, with a null-safe
useOptionalExplorer for use outside the explorer context
@@ -16,6 +16,8 @@ The ephemeral and persistent indexes currently share UUIDs in one direction only
16
16
17
17
This task makes the ephemeral index a true superset layer on top of the persistent index by reusing persistent UUIDs when they exist. This is the foundational primitive for file sync, smart copy, and path intersection operations.
18
18
19
+
The core can have multiple libraries loaded at the same time and does not have a global "active library". Reconciliation must therefore be library-scoped: the filesystem structure can stay shared in the global ephemeral index, but UUID identity must be resolved per library.
20
+
19
21
## Problem
20
22
21
23
- Volume indexing an already-persistent location generates new UUIDs, duplicating identity
Library-scoped ephemeral browse → Assign temporary UUIDs for that library
41
+
→ [reconcile] → Check that library's persistent index
42
+
├── Match found → adopt persistent UUID
43
+
└── No match → keep library-local v4 UUID
41
44
```
42
45
43
46
### Design Constraints
44
47
45
48
1.**Do not slow down ephemeral discovery.** The ephemeral indexer must remain fast (~50K files/sec). No database queries during the filesystem walk.
46
49
2.**Reconciliation is a separate pass.** After ephemeral discovery completes, run a background reconciliation against the persistent index for overlapping paths.
47
50
3.**Lazy resolution as fallback.** If reconciliation hasn't run yet, UUID lookups can check the persistent index on demand.
48
-
4.**Single EphemeralIndex instance.** The global `EphemeralIndexCache` holds one shared index. Reconciliation updates UUIDs in place.
51
+
4.**Single shared filesystem index.** The global `EphemeralIndexCache` should keep one shared path and metadata structure for memory efficiency.
52
+
5.**Library-scoped UUID overlay.** Reconciliation must not overwrite one global UUID per path. UUIDs are scoped by `(library_id, entry_id)` so two loaded libraries can map the same physical path to different persistent entry UUIDs.
53
+
54
+
### Library Scoping
55
+
56
+
The ephemeral cache is process-local and shared across all loaded libraries. Persistent databases are per-library. The same absolute path may exist in more than one loaded library, and each library can have different entry UUIDs, tags, metadata, sync state, and permissions.
57
+
58
+
The correct model is:
59
+
60
+
```text
61
+
Shared ephemeral structure:
62
+
path -> EntryId
63
+
EntryId -> metadata
64
+
EntryId -> content kind
65
+
66
+
Library identity overlay:
67
+
library_id -> EntryId -> UUID
68
+
```
69
+
70
+
This lets expensive filesystem discovery stay shared while keeping persistent identity correct for each loaded library.
49
71
50
72
## Implementation Steps
51
73
52
-
### 1. Add Persistent UUID Lookup to EphemeralIndex
74
+
### 1. Add Library-Scoped UUID Storage to EphemeralIndex
53
75
54
-
Add a method that accepts pre-resolved UUIDs from an external source (the persistent DB) and patches them into the ephemeral index's `entry_uuids` map.
76
+
Replace the single global `entry_uuids: HashMap<EntryId, Uuid>` with a library-scoped overlay. Existing call sites should pass the library ID when reading or assigning UUIDs.
For large persistent locations this query could return thousands of entries. Batch the path resolution and use the `directory_paths` cache (O(1) per directory) to keep it fast.
126
175
127
-
### 3. Reconciliation Pass on EphemeralIndexCache
176
+
### 3. Library-Scoped Reconciliation Pass on EphemeralIndexCache
128
177
129
-
After ephemeral discovery completes for a path, check if any persistent locations overlap with the scanned path and run reconciliation.
178
+
After ephemeral discovery completes for a path, reconcile against the library that requested the operation. Do not scan all loaded libraries and overwrite global UUIDs.
130
179
131
180
```rust
132
181
// core/src/ops/indexing/ephemeral/cache.rs
133
182
134
183
implEphemeralIndexCache {
135
-
/// Run after ephemeral indexing completes for a path.
136
-
/// Checks all libraries for persistent locations that overlap with the
Spawning as a background task keeps the indexing job fast. The UI shows ephemeral UUIDs immediately, then silently corrects them when reconciliation completes. Since the ephemeral index is the browsing layer, UUID changes propagate to the UI via the existing `ResourceChanged` event system.
223
+
Spawning as a background task keeps the indexing job fast. The UI shows library-local ephemeral UUIDs immediately, then corrects them when reconciliation completes. Since UUIDs are library-scoped, another loaded library viewing the same path is unaffected.
195
224
196
225
### 5. Lazy Fallback: On-Demand UUID Resolution
197
226
@@ -205,11 +234,12 @@ impl EphemeralIndex {
205
234
/// Used when reconciliation hasn't completed yet.
When a UUID changes from a temporary v4 to a persistent UUID, emit a `ResourceChanged` event so the frontend updates references.
276
+
When a UUID changes from a temporary library-local v4 to a persistent UUID, emit a `ResourceChanged` event for that library/session so the frontend updates references.
244
277
245
278
```rust
246
279
// In reconcile_persistent_uuids(), collect changed entries:
@@ -262,6 +295,7 @@ This is important because the frontend may have cached the temporary UUID in sel
A persistent location at `/Users/james/Documents` overlaps with an ephemeral scan of `/Users/james` (the ephemeral path is a parent). The reconciliation needs to check both directions: persistent roots that are children of the scanned path, and persistent roots that are parents of the scanned path.
293
329
330
+
### Multiple Loaded Libraries
331
+
332
+
Core does not know an active library. It only knows loaded libraries and library-scoped operations. Directory listing, search, and volume indexing must pass the library ID from their operation context into ephemeral UUID access.
333
+
334
+
Do not reconcile against all loaded libraries into a single global `path -> uuid` map. That would make whichever library reconciles last win, causing the wrong tags and metadata to appear for other libraries.
335
+
294
336
### Memory Impact
295
337
296
-
The `entry_uuids` HashMap already exists in the ephemeral index. Reconciliation doesn't add new entries — it replaces v4 UUIDs with persistent ones. No additional memory overhead.
338
+
The path tree, metadata, name cache, and content kind storage remain shared. Only UUID mappings become per-library. Memory overhead is proportional to the number of ephemeral entries that have been viewed or reconciled in each loaded library, not to the full filesystem metadata structure.
Public sharing of Spacedrive Spaces (and individual files/folders) via sd.app. A user creates a share from their local instance, gets a `sd.app/s/{token}` link, and visitors browse/download content in a web viewer. sd.app provides the Iroh relay, share registry, viewer SPA, and account management. Bytes never transit sd.app — the browser dials the user's core directly through our relay over QUIC.
14
+
15
+
## Architecture (decided)
16
+
17
+
-**Bytes path**: direct-dial. Browser → sd.app relay → user's core (QUIC). sd.app does not proxy file content.
18
+
-**sd.app responsibilities**: Iroh relay infrastructure, share registry (token → node_id/relay_url), public viewer SPA, user accounts for share management.
19
+
-**Share unit**: Spaces (primary), plus single files and arbitrary folder selections.
-`list_contents(path)` — directory listing scoped to share root
28
+
-`get_metadata(path)` — file metadata
29
+
-`read_range(path, offset, length)` — byte range stream
30
+
- All requests gated by share scope on the core side
31
+
4.**Token format**: 128-bit CSPRNG → base32 (no padding, no ambiguous chars). URL shape: `https://sd.app/s/{token}`. Optional `#k={key}` fragment reserved for client-side decryption keys (never sent to server).
32
+
5.**Password handling**: argon2id hash stored on core. Password proof = HMAC over server-issued challenge nonce; password never sent in cleartext.
33
+
6.**Public metadata**: name, item count, optional cover image, password_required flag. Owner identity is NOT exposed by default.
34
+
35
+
## Deliverables
36
+
37
+
-`docs/design/shares.md` with diagrams and protocol specs
38
+
- ALPN string reserved for guest protocol
39
+
- API schema (typed Rust structs + JSON schema) shared between core and sd.app
40
+
41
+
## Acceptance Criteria
42
+
43
+
-[ ] Design document covers all six sequence diagrams listed above
44
+
-[ ] Wire protocols are typed and machine-checkable on both sides
45
+
-[ ] Token / URL / password protocols are specified with rationale
0 commit comments