Profile-post backfill + prune disposable first-run persona on import
Two bugs the v0.6.2 Discover story was going to expose: 1) Many named personas don't have a profile post yet, so the planned profile-post-driven Discover listing would show them as headless. Existing personas predate the Phase 2d profile-post primitive, and imported personas arrive with a display_name but no matching post. 2) Fresh install always generates a blank disposable persona before the user can pick fresh-vs-import. If the user picks import, that blank persona lingers forever — visible in the Personas list, a potential default, confusing. Fixes: Profile-post backfill (node.rs): - New `Node::backfill_profile_posts_for_named_personas`: scans posting identities, skips unnamed or already-covered ones, and emits a signed profile post at the persona's own `created_at` timestamp. Run once from `Node::open_with_bind` after migrations. Idempotent via the new `Storage::has_profile_post_by_author` check. Chronology is preserved (post timestamp matches persona creation), so a later genuine profile update the user authors always wins the `> old_ts` monotonicity check on receivers. - `Node::create_posting_identity(name)`: if `name` is non-empty, emits the profile post inline so new named personas are Discover-able the moment they're created. Uses the new `publish_profile_post_as` helper, which signs with the persona's own secret (not the default posting secret) and propagates via `update_neighbor_manifests_as`. First-run persona marker + targeted prune (node.rs + storage.rs + import): - `Node::open_with_bind`'s auto-gen block now also writes `first_run_auto_persona_id = <hex>` into the settings kv. This marks the specific disposable persona from the fresh-install flow; later prune logic uses this id, not a "any empty persona" rule, so manual empty personas are never touched. - `Node::try_prune_first_run_auto_persona`: deletes the marked id iff all four gates pass — still exists, no display_name, no authored posts, no authored reactions/comments, and it's no longer the current default. Any one failure → clear the marker and keep the persona. New storage helpers `has_any_post_by_author`, `has_any_engagement_by_author` back the check. - `set_profile` clears the marker when a non-empty name lands on the marked persona (user claimed it). - `storage.delete_setting(key)` — new one-line helper. - `import_as_personas_cmd` in tauri-app calls the prune after import completes; the cmd's return message reports "(cleared blank starter persona)" when it fires. - New `get_first_run_auto_persona_id` Tauri command so the frontend can filter the blank persona out of the Personas list while it still exists. - Frontend `loadPersonas` filters the marker id out of `personasCache` before rendering. Tests: 124 / 124 core tests pass.
This commit is contained in:
parent
d990da5bda
commit
e74bd4e6c6
4 changed files with 308 additions and 6 deletions
|
|
@ -1677,6 +1677,11 @@ impl Storage {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_setting(&self, key: &str) -> anyhow::Result<()> {
|
||||
self.conn.execute("DELETE FROM settings WHERE key = ?1", params![key])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Seen engagement tracking ---
|
||||
|
||||
/// Get the seen engagement counts for a post (react_count, comment_count).
|
||||
|
|
@ -2662,6 +2667,49 @@ impl Storage {
|
|||
}
|
||||
|
||||
/// Find posts authored by us that were intended for a specific circle.
|
||||
/// True if the given author has at least one `VisibilityIntent::Profile`
|
||||
/// post stored. Used by the startup backfill to avoid re-emitting a
|
||||
/// profile post for personas that already have one.
|
||||
pub fn has_profile_post_by_author(&self, author: &NodeId) -> anyhow::Result<bool> {
|
||||
let count: i64 = self.conn.query_row(
|
||||
"SELECT COUNT(*) FROM posts
|
||||
WHERE author = ?1 AND visibility_intent = '\"Profile\"'
|
||||
LIMIT 1",
|
||||
params![author.as_slice()],
|
||||
|row| row.get(0),
|
||||
).unwrap_or(0);
|
||||
Ok(count > 0)
|
||||
}
|
||||
|
||||
/// True if `author` has any row in the posts table (any intent / visibility).
|
||||
/// Used to decide whether the fresh-install auto-gen persona has been
|
||||
/// "used" and should therefore NOT be pruned on import.
|
||||
pub fn has_any_post_by_author(&self, author: &NodeId) -> anyhow::Result<bool> {
|
||||
let count: i64 = self.conn.query_row(
|
||||
"SELECT COUNT(*) FROM posts WHERE author = ?1 LIMIT 1",
|
||||
params![author.as_slice()],
|
||||
|row| row.get(0),
|
||||
).unwrap_or(0);
|
||||
Ok(count > 0)
|
||||
}
|
||||
|
||||
/// True if `author` has authored a reaction or a comment. Same purpose as
|
||||
/// `has_any_post_by_author` but for the engagement tables.
|
||||
pub fn has_any_engagement_by_author(&self, author: &NodeId) -> anyhow::Result<bool> {
|
||||
let reacts: i64 = self.conn.query_row(
|
||||
"SELECT COUNT(*) FROM reactions WHERE reactor = ?1 LIMIT 1",
|
||||
params![author.as_slice()],
|
||||
|row| row.get(0),
|
||||
).unwrap_or(0);
|
||||
if reacts > 0 { return Ok(true); }
|
||||
let comments: i64 = self.conn.query_row(
|
||||
"SELECT COUNT(*) FROM comments WHERE author = ?1 LIMIT 1",
|
||||
params![author.as_slice()],
|
||||
|row| row.get(0),
|
||||
).unwrap_or(0);
|
||||
Ok(comments > 0)
|
||||
}
|
||||
|
||||
pub fn find_posts_by_circle_intent(
|
||||
&self,
|
||||
circle_name: &str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue