ux: Friend-button default + profile-rename plumbing + export/import clarity
Three of the v0.7.0 device-testing feedback items, deferred for later rebuild/redeploy (Scott opted to batch UI fixes). (#1) Friend button on bio modal: - Primary action when neither following nor vouched: [Friend] (= follow + vouch in one click) plus secondary [Follow only]. - When following without a vouch: [Add Vouch] primary, [Unfollow] secondary. - When both follow + vouch (= friends): [Unfriend] (= revoke vouch + unfollow, with the rotation-cost confirm wording). - The standalone [Vouch] / [Revoke Vouch] flows stay reachable from the existing Vouches list in Settings. (#2) Profile shows "unnamed" — bug fix: - set_profile updated the profiles table + emitted a profile post, but never updated posting_identities.display_name. list_posting_ identities returns from the latter, so the Personas list kept showing "(unnamed)" forever after the first-run auto-persona was named. - Now set_profile also upserts the posting_identities row with the new display_name (secret_seed + created_at preserved). (#5) Export/import + persona-vs-device clarity: - Settings reorder: new "Your data on this device" explainer card up top (Personas = who you are to peers; Identities = device's network address, rarely useful to touch). - "Move to another device" section renamed + given a plain-English description; primary [Export personas] / [Import from another device] buttons. - "Identities (advanced)" demoted below; warning text added. - Export wizard heading: "Export your personas"; radio labels use persona/keys language consistently. - Import wizard heading: "Import from another device"; explainer notes that the default action restores personas. Tracking memory created at memory/project_v071_followups.md for deferred items (#4 PQ vouch delivery, #6 rename, #7 redundancy, #8/#9/#10 awaiting clarification). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f714a17385
commit
346d23d4d8
3 changed files with 79 additions and 30 deletions
|
|
@ -1793,6 +1793,20 @@ impl Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Keep posting_identities.display_name in sync with the
|
||||||
|
// profile post so the Personas list and any UI reading
|
||||||
|
// PostingIdentity sees the current name (not the original
|
||||||
|
// empty/auto-gen one). The upsert preserves the persona's
|
||||||
|
// secret_seed / created_at; only display_name changes.
|
||||||
|
if let Ok(Some(existing)) = storage.get_posting_identity(&posting_id) {
|
||||||
|
let updated = crate::types::PostingIdentity {
|
||||||
|
node_id: existing.node_id,
|
||||||
|
secret_seed: existing.secret_seed,
|
||||||
|
display_name: display_name.clone(),
|
||||||
|
created_at: existing.created_at,
|
||||||
|
};
|
||||||
|
let _ = storage.upsert_posting_identity(&updated);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate via neighbor-manifest header diffs like any other post.
|
// Propagate via neighbor-manifest header diffs like any other post.
|
||||||
|
|
|
||||||
|
|
@ -1687,12 +1687,13 @@ async function openBioModal(nodeId, preloadedName) {
|
||||||
${bio ? `<p style="font-size:0.9rem;line-height:1.45;margin:0 0 0.75rem">${escapeHtml(bio)}</p>` : '<p class="empty-hint" style="margin:0 0 0.75rem">No bio.</p>'}
|
${bio ? `<p style="font-size:0.9rem;line-height:1.45;margin:0 0 0.75rem">${escapeHtml(bio)}</p>` : '<p class="empty-hint" style="margin:0 0 0.75rem">No bio.</p>'}
|
||||||
<div style="display:flex;gap:0.4rem;flex-wrap:wrap">
|
<div style="display:flex;gap:0.4rem;flex-wrap:wrap">
|
||||||
<button id="bio-view-posts" class="btn btn-primary btn-sm">View Posts</button>
|
<button id="bio-view-posts" class="btn btn-primary btn-sm">View Posts</button>
|
||||||
${following
|
${(following && isVouched)
|
||||||
? `<button id="bio-unfollow" class="btn btn-ghost btn-sm">Unfollow</button>`
|
? `<button id="bio-unfriend" class="btn btn-ghost btn-sm">Unfriend</button>`
|
||||||
: `<button id="bio-follow" class="btn btn-primary btn-sm">Follow</button>`}
|
: (following
|
||||||
${isVouched
|
? `<button id="bio-vouch" class="btn btn-primary btn-sm">Add Vouch</button>
|
||||||
? `<button id="bio-revoke-vouch" class="btn btn-ghost btn-sm">Revoke Vouch</button>`
|
<button id="bio-unfollow" class="btn btn-ghost btn-sm">Unfollow</button>`
|
||||||
: `<button id="bio-vouch" class="btn btn-ghost btn-sm">Vouch</button>`}
|
: `<button id="bio-friend" class="btn btn-primary btn-sm">Friend</button>
|
||||||
|
<button id="bio-follow" class="btn btn-ghost btn-sm">Follow only</button>`)}
|
||||||
<button id="bio-message" class="btn btn-ghost btn-sm">Message</button>
|
<button id="bio-message" class="btn btn-ghost btn-sm">Message</button>
|
||||||
${isIgnored
|
${isIgnored
|
||||||
? `<button id="bio-unignore" class="btn btn-ghost btn-sm">Unignore</button>`
|
? `<button id="bio-unignore" class="btn btn-ghost btn-sm">Unignore</button>`
|
||||||
|
|
@ -1749,16 +1750,34 @@ async function openBioModal(nodeId, preloadedName) {
|
||||||
} catch (e) { toast('Error: ' + e); }
|
} catch (e) { toast('Error: ' + e); }
|
||||||
finally { vouch.disabled = false; }
|
finally { vouch.disabled = false; }
|
||||||
};
|
};
|
||||||
const revokeVouch = document.getElementById('bio-revoke-vouch');
|
// Friend = follow + vouch in one click. Default action per v0.7.x UX.
|
||||||
if (revokeVouch) revokeVouch.onclick = async () => {
|
const friend = document.getElementById('bio-friend');
|
||||||
if (!confirm(`Revoke vouch for ${name}? This rotates your vouch key — they keep access to existing posts but not future ones.`)) return;
|
if (friend) friend.onclick = async () => {
|
||||||
revokeVouch.disabled = true;
|
friend.disabled = true;
|
||||||
|
try {
|
||||||
|
await invoke('follow_node', { nodeIdHex: nodeId });
|
||||||
|
await invoke('vouch_for_peer', { nodeIdHex: nodeId });
|
||||||
|
toast(`Friended ${name}`);
|
||||||
|
close();
|
||||||
|
loadFollows();
|
||||||
|
loadFeed(true);
|
||||||
|
} catch (e) { toast('Error: ' + e); }
|
||||||
|
finally { friend.disabled = false; }
|
||||||
|
};
|
||||||
|
// Unfriend = revoke vouch + unfollow. Rotation cost is real; confirm.
|
||||||
|
const unfriend = document.getElementById('bio-unfriend');
|
||||||
|
if (unfriend) unfriend.onclick = async () => {
|
||||||
|
if (!confirm(`Unfriend ${name}? This revokes your vouch (rotates your vouch key — they keep access to existing posts but not future ones) AND unfollows them.`)) return;
|
||||||
|
unfriend.disabled = true;
|
||||||
try {
|
try {
|
||||||
await invoke('revoke_vouch_for_peer', { nodeIdHex: nodeId });
|
await invoke('revoke_vouch_for_peer', { nodeIdHex: nodeId });
|
||||||
toast('Revoked and rotated');
|
await invoke('unfollow_node', { nodeIdHex: nodeId });
|
||||||
|
toast(`Unfriended ${name}`);
|
||||||
close();
|
close();
|
||||||
|
loadFollows();
|
||||||
|
loadFeed(true);
|
||||||
} catch (e) { toast('Error: ' + e); }
|
} catch (e) { toast('Error: ' + e); }
|
||||||
finally { revokeVouch.disabled = false; }
|
finally { unfriend.disabled = false; }
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
bodyEl.innerHTML = `<p class="status-err">Error: ${e}</p>`;
|
bodyEl.innerHTML = `<p class="status-err">Error: ${e}</p>`;
|
||||||
|
|
@ -4041,14 +4060,14 @@ $('#export-btn').addEventListener('click', () => {
|
||||||
overlay.className = 'image-lightbox';
|
overlay.className = 'image-lightbox';
|
||||||
overlay.style.cursor = 'default';
|
overlay.style.cursor = 'default';
|
||||||
overlay.innerHTML = `
|
overlay.innerHTML = `
|
||||||
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:1.5rem;max-width:400px;width:90%;text-align:center">
|
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:1.5rem;max-width:420px;width:90%;text-align:center">
|
||||||
<h3 style="color:#7fdbca;margin:0 0 0.75rem">Export Data</h3>
|
<h3 style="color:#7fdbca;margin:0 0 0.5rem">Export your personas</h3>
|
||||||
<p style="font-size:0.75rem;color:#888;margin-bottom:0.75rem">Choose what to include in the export ZIP.</p>
|
<p style="font-size:0.75rem;color:#888;margin-bottom:0.75rem">Save your personas + (optionally) your posts to a ZIP file so you can import them on another device.</p>
|
||||||
<div style="display:flex;flex-direction:column;gap:0.5rem;text-align:left;margin-bottom:1rem">
|
<div style="display:flex;flex-direction:column;gap:0.5rem;text-align:left;margin-bottom:1rem">
|
||||||
<label class="checkbox-label"><input type="radio" name="export-scope" value="identity_only" /> Identity key only (tiny backup)</label>
|
<label class="checkbox-label"><input type="radio" name="export-scope" value="identity_only" /> Persona keys only (tiny backup — restores your identity but not your posts)</label>
|
||||||
<label class="checkbox-label"><input type="radio" name="export-scope" value="posts_only" /> Posts + media (no key — safe to share)</label>
|
<label class="checkbox-label"><input type="radio" name="export-scope" value="posts_only" /> Posts + media only (no keys — safe to share publicly)</label>
|
||||||
<label class="checkbox-label"><input type="radio" name="export-scope" value="posts_with_identity" checked /> Posts + media + identity key</label>
|
<label class="checkbox-label"><input type="radio" name="export-scope" value="posts_with_identity" checked /> Posts + media + persona keys (typical “move to new device”)</label>
|
||||||
<label class="checkbox-label"><input type="radio" name="export-scope" value="everything" /> Everything (posts, key, follows, settings)</label>
|
<label class="checkbox-label"><input type="radio" name="export-scope" value="everything" /> Everything (posts, keys, follows, settings)</label>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-bottom:0.75rem">
|
<div style="margin-bottom:0.75rem">
|
||||||
<label style="font-size:0.75rem;color:#888">Save to folder:</label>
|
<label style="font-size:0.75rem;color:#888">Save to folder:</label>
|
||||||
|
|
@ -4118,8 +4137,8 @@ $('#import-btn').addEventListener('click', () => {
|
||||||
overlay.style.cursor = 'default';
|
overlay.style.cursor = 'default';
|
||||||
overlay.innerHTML = `
|
overlay.innerHTML = `
|
||||||
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:1.5rem;max-width:420px;width:90%;text-align:center">
|
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:1.5rem;max-width:420px;width:90%;text-align:center">
|
||||||
<h3 style="color:#7fdbca;margin:0 0 0.75rem">Import Data</h3>
|
<h3 style="color:#7fdbca;margin:0 0 0.5rem">Import from another device</h3>
|
||||||
<p style="font-size:0.75rem;color:#888;margin-bottom:0.75rem">Select an ItsGoin export ZIP file.</p>
|
<p style="font-size:0.75rem;color:#888;margin-bottom:0.75rem">Select an ItsGoin export ZIP. Default action restores the exported personas onto this device so you can post as them.</p>
|
||||||
<div style="display:flex;gap:0.25rem;margin-bottom:0.75rem">
|
<div style="display:flex;gap:0.25rem;margin-bottom:0.75rem">
|
||||||
<input id="import-zip-path" type="text" placeholder="/path/to/itsgoin-export.zip" style="flex:1;font-size:0.8rem" />
|
<input id="import-zip-path" type="text" placeholder="/path/to/itsgoin-export.zip" style="flex:1;font-size:0.8rem" />
|
||||||
<button class="btn btn-ghost btn-sm" id="import-browse">Browse</button>
|
<button class="btn btn-ghost btn-sm" id="import-browse">Browse</button>
|
||||||
|
|
|
||||||
|
|
@ -231,13 +231,13 @@
|
||||||
<button id="check-updates-btn" class="btn btn-ghost btn-sm" style="margin-top:0.5rem">Check now</button>
|
<button id="check-updates-btn" class="btn btn-ghost btn-sm" style="margin-top:0.5rem">Check now</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-card" style="text-align:center">
|
<div class="section-card" style="text-align:left">
|
||||||
<h3 style="margin-bottom:0.5rem">Identities</h3>
|
<h3 style="margin-bottom:0.4rem;text-align:center">Your data on this device</h3>
|
||||||
<div id="identities-list" style="margin-bottom:0.5rem"></div>
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.78rem;line-height:1.5">
|
||||||
<div style="display:flex;gap:0.5rem;justify-content:center;flex-wrap:wrap">
|
<strong style="color:#7fdbca">Personas</strong> are who you are to peers — the keys you post and message with. Most people only need one. To move your account to a new device, you <em>export your personas</em> from this device and <em>import them</em> on the new one.
|
||||||
<button id="create-identity-btn" class="btn btn-ghost btn-sm">New Identity</button>
|
<br><br>
|
||||||
<button id="import-identity-btn" class="btn btn-ghost btn-sm">Import Key</button>
|
<strong style="color:#888">Identities</strong> below are this device's own network address — usually not what you want to move. Leave them alone unless you know why you're touching them.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-card" style="text-align:center">
|
<div class="section-card" style="text-align:center">
|
||||||
|
|
@ -248,9 +248,25 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-card" style="text-align:center">
|
<div class="section-card" style="text-align:center">
|
||||||
|
<h3 style="margin-bottom:0.4rem">Move to another device</h3>
|
||||||
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.78rem">
|
||||||
|
Export creates a ZIP containing your personas (and optionally posts/follows). Import on the other device's Settings > Move to another device.
|
||||||
|
</p>
|
||||||
<div style="display:flex;gap:0.5rem;justify-content:center;flex-wrap:wrap">
|
<div style="display:flex;gap:0.5rem;justify-content:center;flex-wrap:wrap">
|
||||||
<button id="export-btn" class="btn btn-ghost btn-sm">Export</button>
|
<button id="export-btn" class="btn btn-primary btn-sm">Export personas</button>
|
||||||
<button id="import-btn" class="btn btn-ghost btn-sm">Import</button>
|
<button id="import-btn" class="btn btn-ghost btn-sm">Import from another device</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-card" style="text-align:center">
|
||||||
|
<h3 style="margin-bottom:0.4rem;font-size:0.85rem;color:#888">Identities (advanced)</h3>
|
||||||
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.72rem">
|
||||||
|
This device's network address. Changing this is rarely useful — it lets you move the device's QUIC endpoint, NOT your posting identity.
|
||||||
|
</p>
|
||||||
|
<div id="identities-list" style="margin-bottom:0.5rem"></div>
|
||||||
|
<div style="display:flex;gap:0.5rem;justify-content:center;flex-wrap:wrap">
|
||||||
|
<button id="create-identity-btn" class="btn btn-ghost btn-sm">New Identity</button>
|
||||||
|
<button id="import-identity-btn" class="btn btn-ghost btn-sm">Import Key</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue