Phase 2c: remove audience + PostPush + PostNotification + AudienceRequest/Response
v0.6.2 wire fork: every persona-identifying direct push is gone. Public posts propagate only through the CDN (pull + header-diff neighbor propagation). Encrypted posts propagate only through pull with merged author-or-recipient match. There is no remaining sender→recipient traffic correlation signal on the wire for content. Protocol (network-breaking): - Retire MessageType 0x42 (PostNotification), 0x43 (PostPush), 0x44 (AudienceRequest), 0x45 (AudienceResponse). Their payload structs are deleted along with the handlers and senders. - SocialDisconnectNotice (0x71) / SocialAddressUpdate (0x70) sender functions targeting audience are deleted; the existing handlers stay (both already dead code on the send side). Core removals: - `push_to_audience`, `notify_post`, `push_delete`, `push_disconnect_to_audience`, `push_address_update_to_audience`, `send_audience_request`, `send_audience_response`, `send_to_audience` — all gone from network.rs. - `handle_post_notification` removed from connection.rs. - `request_audience`, `approve_audience`, `deny_audience`, `remove_audience`, `list_audience_members`, `list_audience` removed from Node. - `audience_pushed` step removed from post creation. - `AudienceDirection`, `AudienceStatus`, `AudienceRecord`, `AudienceApprovalMode` removed from types. - Storage: `store_audience`, `list_audience`, `list_audience_members`, `remove_audience`, `row_to_audience_record`, `audience_crud` test, the `audience` CREATE TABLE, and the audience-dependent social route rebuild branch all removed. Upgraded DBs retain the orphan `audience` table; nothing touches it. Follow-on cleanups: - `SocialRelation::Audience` + `::Mutual` collapsed into just `Follow`. The Display/FromStr impl accepts legacy "audience"/"mutual" strings from pre-v0.6.2 DBs and maps them to Follow. - Blob-eviction priority function drops the audience factor; relationship is now own-author vs followed vs other. Tests updated accordingly. - `CommentPermission::AudienceOnly` → `FollowersOnly`. Check uses the author's public follows (`list_public_follows`) rather than a separate audience table. `ModerationMode::AudienceOnly` similarly renamed. - Follow/unfollow routines simplified: no audience downgrade logic; unfollow removes the social route entirely. UI: - CLI: `audience*` commands removed. - Tauri: `AudienceDto`, `list_audience`, `list_audience_outbound`, `request_audience`, `approve_audience`, `remove_audience` commands removed from invoke_handler. Frontend: audience panel and audience/mutual badges removed; compose permission dropdown shows "Followers" instead of "Audience"; `loadAudience` is a no-op stub that hides any leftover DOM. Tests: 111 / 111 core tests pass. Breaking change: v0.6.2 nodes won't interoperate with v0.6.1 for delete propagation, visibility updates, direct post push, post notifications, or audience requests. Upgrade both ends.
This commit is contained in:
parent
36b6a466d2
commit
eabdb7ba4f
10 changed files with 98 additions and 1140 deletions
123
frontend/app.js
123
frontend/app.js
|
|
@ -1422,14 +1422,8 @@ async function loadPeerBios(container) {
|
|||
|
||||
async function loadFollows() {
|
||||
try {
|
||||
const [follows, outbound, inbound] = await Promise.all([
|
||||
invoke('list_follows'),
|
||||
invoke('list_audience_outbound'),
|
||||
invoke('list_audience'),
|
||||
]);
|
||||
const outboundSet = new Set(outbound.map(r => r.nodeId));
|
||||
const approvedSet = new Set(outbound.filter(r => r.status === 'approved').map(r => r.nodeId));
|
||||
const inboundApprovedSet = new Set(inbound.filter(r => r.status === 'approved').map(r => r.nodeId));
|
||||
// v0.6.2: audience removed. No more audience/mutual badges or request flow.
|
||||
const follows = await invoke('list_follows');
|
||||
|
||||
// Filter out self before rendering
|
||||
const others = follows.filter(f => f.nodeId !== myNodeId);
|
||||
|
|
@ -1443,34 +1437,21 @@ async function loadFollows() {
|
|||
const label = escapeHtml(peerLabel(f.nodeId, f.displayName));
|
||||
const isSelf = f.nodeId === myNodeId;
|
||||
|
||||
let audienceBadge = '';
|
||||
let mutualBadge = '';
|
||||
let lastSeenHtml = '';
|
||||
let actions = '';
|
||||
if (isSelf) {
|
||||
actions = '<span class="self-tag">(you)</span>';
|
||||
} else {
|
||||
if (inboundApprovedSet.has(f.nodeId)) {
|
||||
mutualBadge = '<span class="mutual-badge">mutual</span>';
|
||||
}
|
||||
if (approvedSet.has(f.nodeId)) {
|
||||
audienceBadge = '<span class="audience-badge">audience</span>';
|
||||
} else if (outboundSet.has(f.nodeId)) {
|
||||
audienceBadge = '<span class="audience-badge pending">requested</span>';
|
||||
}
|
||||
if (!f.isOnline && f.lastActivityMs > 0) {
|
||||
lastSeenHtml = `<span class="last-seen">Last online: ${formatTimeAgo(f.lastActivityMs)}</span>`;
|
||||
}
|
||||
const audienceBtn = !approvedSet.has(f.nodeId) && !outboundSet.has(f.nodeId)
|
||||
? `<button class="btn btn-ghost btn-sm request-audience-btn" data-node-id="${f.nodeId}">Ask to join audience</button>`
|
||||
: '';
|
||||
const syncBtn = `<button class="btn btn-ghost btn-sm sync-peer-btn" data-node-id="${f.nodeId}" title="Sync posts from this peer">Sync</button>`;
|
||||
const msgBtn = `<button class="btn btn-ghost btn-sm msg-peer-btn" data-node-id="${f.nodeId}" title="Send message">msg</button>`;
|
||||
const unfollowBtn = `<button class="btn btn-ghost btn-sm unfollow-btn" data-node-id="${f.nodeId}">Unfollow</button>`;
|
||||
actions = `${audienceBtn} ${syncBtn} ${msgBtn} ${unfollowBtn}`;
|
||||
actions = `${syncBtn} ${msgBtn} ${unfollowBtn}`;
|
||||
}
|
||||
return `<div class="peer-card" data-node-id="${f.nodeId}">
|
||||
<div class="peer-card-row">${icon} <a class="peer-name-link" data-node-id="${f.nodeId}">${label}</a> ${mutualBadge} ${audienceBadge}</div>
|
||||
<div class="peer-card-row">${icon} <a class="peer-name-link" data-node-id="${f.nodeId}">${label}</a></div>
|
||||
${lastSeenHtml ? `<div class="peer-card-lastseen">${lastSeenHtml}</div>` : ''}
|
||||
<div class="peer-card-bio"></div>
|
||||
<div class="peer-card-actions">${actions}</div>
|
||||
|
|
@ -1562,22 +1543,6 @@ async function loadFollows() {
|
|||
});
|
||||
});
|
||||
|
||||
// Attach audience request handlers
|
||||
followsList.querySelectorAll('.request-audience-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await invoke('request_audience', { nodeIdHex: btn.dataset.nodeId });
|
||||
toast('Audience request sent!');
|
||||
loadFollows();
|
||||
} catch (e) {
|
||||
toast('Error: ' + e);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Lazy-load bios
|
||||
loadPeerBios(followsList);
|
||||
}
|
||||
|
|
@ -1702,81 +1667,13 @@ async function loadRedundancy() {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Audience management ---
|
||||
// v0.6.2: audience removed. loadAudience is a no-op kept so existing call
|
||||
// sites don't break; DOM panels (if still in markup) are hidden.
|
||||
async function loadAudience() {
|
||||
try {
|
||||
const records = await invoke('list_audience');
|
||||
const pending = records.filter(r => r.status === 'pending');
|
||||
const approved = records.filter(r => r.status === 'approved');
|
||||
|
||||
if (pending.length === 0) {
|
||||
audiencePendingList.innerHTML = '<p class="empty-hint">No pending requests</p>';
|
||||
} else {
|
||||
audiencePendingList.innerHTML = pending.map(r => {
|
||||
const label = escapeHtml(peerLabel(r.nodeId, r.displayName));
|
||||
const icon = generateIdenticon(r.nodeId, 18);
|
||||
return `<div class="peer-card">
|
||||
<div class="peer-card-row">${icon} ${label}</div>
|
||||
<div class="peer-card-meta"><span>${relativeTime(r.requestedAt)}</span></div>
|
||||
<div class="peer-card-actions">
|
||||
<button class="btn btn-primary btn-sm approve-audience-btn" data-node-id="${r.nodeId}">Approve</button>
|
||||
<button class="btn btn-danger btn-sm deny-audience-btn" data-node-id="${r.nodeId}">Deny</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
audiencePendingList.querySelectorAll('.approve-audience-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await invoke('approve_audience', { nodeIdHex: btn.dataset.nodeId });
|
||||
toast('Audience approved');
|
||||
loadAudience();
|
||||
} catch (e) { toast('Error: ' + e); }
|
||||
});
|
||||
});
|
||||
audiencePendingList.querySelectorAll('.deny-audience-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await invoke('remove_audience', { nodeIdHex: btn.dataset.nodeId });
|
||||
toast('Audience denied');
|
||||
loadAudience();
|
||||
} catch (e) { toast('Error: ' + e); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (approved.length === 0) {
|
||||
audienceApprovedList.innerHTML = '<p class="empty-hint">No approved audience members</p>';
|
||||
} else {
|
||||
audienceApprovedList.innerHTML = approved.map(r => {
|
||||
const label = escapeHtml(peerLabel(r.nodeId, r.displayName));
|
||||
const icon = generateIdenticon(r.nodeId, 18);
|
||||
return `<div class="peer-card">
|
||||
<div class="peer-card-row">${icon} ${label}</div>
|
||||
<div class="peer-card-meta"><span>Approved ${r.approvedAt ? relativeTime(r.approvedAt) : ''}</span></div>
|
||||
<div class="peer-card-actions">
|
||||
<button class="btn btn-danger btn-sm remove-audience-btn" data-node-id="${r.nodeId}">Remove</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
audienceApprovedList.querySelectorAll('.remove-audience-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!confirm('Remove this audience member?')) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await invoke('remove_audience', { nodeIdHex: btn.dataset.nodeId });
|
||||
toast('Audience member removed');
|
||||
loadAudience();
|
||||
} catch (e) { toast('Error: ' + e); }
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
audiencePendingList.innerHTML = `<p class="status-err">Error: ${e}</p>`;
|
||||
}
|
||||
if (audiencePendingList) audiencePendingList.style.display = 'none';
|
||||
if (audienceApprovedList) audienceApprovedList.style.display = 'none';
|
||||
const headings = document.querySelectorAll('.audience-section, #audience-section');
|
||||
headings.forEach(el => { el.style.display = 'none'; });
|
||||
}
|
||||
|
||||
// --- Network diagnostics ---
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
<select id="circle-select" class="hidden"></select>
|
||||
<select id="comment-perm-select" title="Comment permission">
|
||||
<option value="public">Comments: All</option>
|
||||
<option value="audience_only">Comments: Audience</option>
|
||||
<option value="followers_only">Comments: Followers</option>
|
||||
<option value="none">Comments: Off</option>
|
||||
</select>
|
||||
<select id="react-perm-select" title="React permission">
|
||||
|
|
@ -132,16 +132,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<h3>Audience</h3>
|
||||
<p class="empty-hint">People who receive your public posts.</p>
|
||||
<h4 class="subsection-title">Pending Requests</h4>
|
||||
<div id="audience-pending-list"></div>
|
||||
<h4 class="subsection-title">Approved</h4>
|
||||
<div id="audience-approved-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="section-card" style="display:flex;gap:0.5rem;flex-wrap:wrap">
|
||||
<div class="section-card" style="display:flex;gap:0.5rem;flex-wrap:wrap">
|
||||
<button id="share-details-btn" class="btn btn-ghost btn-sm">Share my details</button>
|
||||
<button id="connect-toggle" class="btn btn-ghost btn-sm">Add peer manually</button>
|
||||
<div id="connect-body" class="hidden">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue