feat(fof-layer2): Tauri commands + frontend compose/comment routing

Three new Tauri commands surface Layer 2 to the frontend:

- create_post_with_fof_comments(content) → { postId }
  Builds the FoF gating block from the default persona's keyring and
  publishes a Mode 2 post (public body, FoF-gated comments).
- comment_on_fof_post(post_id_hex, body)
  Backwards-compat wrapper: comment_on_post now auto-detects FoF
  gating on the parent post and dispatches internally. Existing
  comment-send UI works unchanged.
- revoke_fof_commenter(post_id_hex, pub_x_index, reason_code)
  Wraps the Node helper. UI for per-comment revocation can hang off
  this without further Node changes.

Node::comment_on_post now branches on parent post fof_gating presence
— this is the central routing point so neither the frontend nor
existing comment-send code needs to know about FoF specifics. FoF
posts get FoF comments automatically; non-FoF posts get the legacy
path. Same Tauri command, same frontend handler.

Frontend:
- Compose: comment-perm-select gains a "Friends of Friends" option.
  When picked, compose dispatches to create_post_with_fof_comments
  instead of the standard create_post. Standard set_comment_policy
  diff fires after so receivers' four-check accept rule activates.
- Attachments + non-default-persona FoF posts are out-of-v1; compose
  reports + aborts rather than silently producing a non-FoF post.

Layer 2 backend + minimal-viable UI complete. Granular per-comment
revoke UI and access-grant UI deferred — Node + Tauri primitives
exist; the surfaces can be added without further crypto work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-05-14 16:07:07 -04:00
parent 96118d7ce8
commit 10de3f6108
4 changed files with 144 additions and 4 deletions

View file

@ -1145,6 +1145,53 @@ async fn list_vouches_received(state: State<'_, AppNode>) -> Result<Vec<VouchRec
}).collect())
}
// --- FoF Layer 2: comment-gated post + commenting ---
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct FoFPostCreatedDto {
post_id: String,
}
#[tauri::command]
async fn create_post_with_fof_comments(
state: State<'_, AppNode>,
content: String,
) -> Result<FoFPostCreatedDto, String> {
let node = get_node(&state).await;
let (post_id, _post, _vis, _cek) = node
.create_post_with_fof_comments(content, vec![])
.await
.map_err(|e| e.to_string())?;
Ok(FoFPostCreatedDto { post_id: hex::encode(post_id) })
}
#[tauri::command]
async fn comment_on_fof_post(
state: State<'_, AppNode>,
post_id_hex: String,
body: String,
) -> Result<(), String> {
let node = get_node(&state).await;
let pid = parse_node_id(&post_id_hex)?; // PostId is also [u8; 32]
node.comment_on_fof_post(pid, body).await
.map(|_| ())
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn revoke_fof_commenter(
state: State<'_, AppNode>,
post_id_hex: String,
pub_x_index: u32,
reason_code: u8,
) -> Result<(), String> {
let node = get_node(&state).await;
let pid = parse_node_id(&post_id_hex)?;
node.revoke_fof_commenter(pid, pub_x_index, reason_code).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;
@ -3163,6 +3210,9 @@ pub fn run() {
revoke_vouch_for_peer,
list_vouches_given,
list_vouches_received,
create_post_with_fof_comments,
comment_on_fof_post,
revoke_fof_commenter,
list_circles,
create_circle,
delete_circle,