feat(fof-layer3): Mode 1 publish + read + Tauri + UI wiring
End-to-end FoFClosed (Mode 1: encrypted body + FoF comments): Node API: - create_post_fof_closed(content) -> (PostId, Post, cek) Builds gating, encrypts body via fof::encrypt_fof_body, base64s it into post.content, stores with visibility=FoFClosed + intent=Public, propagates via update_neighbor_manifests_as. - read_fof_closed_body(post_id) -> Option<String> Trial-unlocks via find_unlock_for_post, decrypts body, returns plaintext. Returns None for non-FoFClosed or non-member readers. Tauri commands: - create_post_fof_closed, read_fof_closed_body. Registered in generate_handler!. Feed rendering: - PostDto.visibility carries the new "fof-closed" string. - renderPost(): FoFClosed posts render with a locked placeholder (data-fof-closed-pending=post_id span). Visual badge added. - unlockFoFClosedPlaceholders(rootEl): post-render async pass that scans for placeholder spans and dispatches read_fof_closed_body for each. Fills in body for FoF readers; falls back to a "not in this FoF set" notice otherwise. - Wired into feed-list and my-posts-list render paths. Compose: - "Body+Comments: FoF only (Mode 1)" option in comment-perm-select. Selected → dispatches to create_post_fof_closed. CLI feed renderer + Tauri feed-DTO match arms updated to handle FoFClosed. New end-to-end test brings total to 146: - fof_closed_body_end_to_end: Alice authors FoFClosed body; Bob (with Alice's V_me in his keyring) unlocks + decrypts; Carol (no matching V_x) cannot unlock and sees only ciphertext. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
856f386231
commit
66b78041fc
6 changed files with 255 additions and 4 deletions
|
|
@ -256,6 +256,10 @@ async fn post_to_dto(
|
|||
Some(text) => ("encrypted-for-me".to_string(), Some(text.to_string())),
|
||||
None => ("encrypted".to_string(), None),
|
||||
},
|
||||
// FoF Layer 3: FoFClosed body. Decrypted is None from the sync
|
||||
// feed-pre-decrypt helper; the frontend calls read_fof_closed_body
|
||||
// for any post with visibility == "fof-closed" to fill in the body.
|
||||
PostVisibility::FoFClosed => ("fof-closed".to_string(), None),
|
||||
};
|
||||
let recipients = match vis {
|
||||
PostVisibility::Encrypted { recipients } => {
|
||||
|
|
@ -346,6 +350,10 @@ async fn decrypt_just_created(
|
|||
None
|
||||
}
|
||||
}
|
||||
// FoF Layer 3: FoFClosed body decrypt happens via the dedicated
|
||||
// async read_fof_closed_body command. This sync helper returns
|
||||
// None and the frontend dispatches the FoF read explicitly.
|
||||
PostVisibility::FoFClosed => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -910,6 +918,7 @@ async fn post_to_dto_batch(
|
|||
Some(text) => ("encrypted-for-me".to_string(), Some(text.clone())),
|
||||
None => ("encrypted".to_string(), None),
|
||||
},
|
||||
PostVisibility::FoFClosed => ("fof-closed".to_string(), None),
|
||||
};
|
||||
let recipients = match vis {
|
||||
PostVisibility::Encrypted { recipients } => {
|
||||
|
|
@ -1192,6 +1201,33 @@ async fn revoke_fof_commenter(
|
|||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// FoF Layer 3: Mode 1 (FoFClosed) — encrypted body + FoF comments.
|
||||
|
||||
#[tauri::command]
|
||||
async fn create_post_fof_closed(
|
||||
state: State<'_, AppNode>,
|
||||
content: String,
|
||||
) -> Result<FoFPostCreatedDto, String> {
|
||||
let node = get_node(&state).await;
|
||||
let (post_id, _post, _cek) = node
|
||||
.create_post_fof_closed(content)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(FoFPostCreatedDto { post_id: hex::encode(post_id) })
|
||||
}
|
||||
|
||||
/// Returns the decrypted body of a FoFClosed post if any local persona
|
||||
/// can unlock it. `None` means "ciphertext only" (not in the FoF set).
|
||||
#[tauri::command]
|
||||
async fn read_fof_closed_body(
|
||||
state: State<'_, AppNode>,
|
||||
post_id_hex: String,
|
||||
) -> Result<Option<String>, String> {
|
||||
let node = get_node(&state).await;
|
||||
let pid = parse_node_id(&post_id_hex)?;
|
||||
node.read_fof_closed_body(&pid).await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn list_follows(state: State<'_, AppNode>) -> Result<Vec<PeerDto>, String> {
|
||||
let node = get_node(&state).await;
|
||||
|
|
@ -3213,6 +3249,8 @@ pub fn run() {
|
|||
create_post_with_fof_comments,
|
||||
comment_on_fof_post,
|
||||
revoke_fof_commenter,
|
||||
create_post_fof_closed,
|
||||
read_fof_closed_body,
|
||||
list_circles,
|
||||
create_circle,
|
||||
delete_circle,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue