ux+fix: rename Network Identity → Device Address (UI) + redundancy authors

#6: UI rename — Network Identity → Device Address.
Just labels/wording, no backend changes. Tauri command names
(list_identities, create_identity, etc.) and DTO fields stay as-is;
the rename is purely user-facing. Settings labels, lightbox titles,
toasts, danger-zone text, and welcome-screen "Import an Identity"
button all updated. Distinguishes more clearly from Personas (which
ARE the posting identity peers see).

#7: Bug fix — redundancy showed 0 for new posts.
Root cause: get_redundancy_summary queried
  SELECT p.id FROM posts WHERE p.author = ?1
with ?1 = self.node_id (the device's network NodeId). After the
v0.6.0 network/posting-ID split, posts are authored by posting
identities (personas), not the network NodeId — so the query
returned 0 of my posts and the redundancy panel showed all zeros.

Fix: storage::get_redundancy_summary now takes &[NodeId] and uses
WHERE author IN (?, ?, ...) over every persona on the device.
Node::get_redundancy_summary gathers them via
list_posting_identities() before delegating.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-05-14 22:49:54 -06:00
parent 83fd30753f
commit 100ea55a15
4 changed files with 50 additions and 29 deletions

View file

@ -3543,11 +3543,11 @@ if (exportKeyBtn) exportKeyBtn.addEventListener('click', async () => {
const key = await invoke('export_identity');
try {
await navigator.clipboard.writeText(key);
toast('Identity key copied to clipboard. KEEP IT SECRET!');
toast('Device address key copied to clipboard. KEEP IT SECRET!');
} catch (clipErr) {
// Clipboard API may fail in some webview contexts — show the key instead
console.error('Clipboard write failed:', clipErr);
prompt('Copy your identity key (KEEP IT SECRET!):', key);
prompt('Copy your device address key (KEEP IT SECRET!):', key);
}
} catch (e) {
console.error('export_identity failed:', e);
@ -3586,14 +3586,16 @@ document.querySelectorAll('.text-size-opt').forEach(btn => {
});
});
// --- Identity management ---
// --- Device address management (formerly "Identity") ---
// The underlying Tauri commands keep their `identity` names for now —
// only the user-facing labels rename. Backend rename can follow.
async function loadIdentities() {
const list = $('#identities-list');
if (!list) return;
try {
const identities = await invoke('list_identities');
if (identities.length === 0) {
list.innerHTML = '<p class="empty-hint">No identities</p>';
list.innerHTML = '<p class="empty-hint">No device addresses</p>';
return;
}
list.innerHTML = identities.map(id => {
@ -3616,7 +3618,7 @@ async function loadIdentities() {
btn.textContent = 'Switching...';
try {
await invoke('switch_identity', { nodeIdHex: btn.dataset.id });
toast('Identity switched — reloading...');
toast('Device address switched — reloading...');
setTimeout(() => location.reload(), 1000);
} catch (e) { toast('Error: ' + e); btn.disabled = false; btn.textContent = 'Switch'; }
});
@ -3625,10 +3627,10 @@ async function loadIdentities() {
// Wire delete buttons
list.querySelectorAll('.delete-id-btn').forEach(btn => {
btn.addEventListener('click', async () => {
if (!confirm('Delete this identity? This cannot be undone.')) return;
if (!confirm('Delete this device address? This cannot be undone.')) return;
try {
await invoke('delete_identity', { nodeIdHex: btn.dataset.id });
toast('Identity deleted');
toast('Device address deleted');
loadIdentities();
} catch (e) { toast('Error: ' + e); }
});
@ -3644,8 +3646,9 @@ $('#create-identity-btn').addEventListener('click', () => {
overlay.style.cursor = 'default';
overlay.innerHTML = `
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:1.5rem;max-width:350px;width:90%;text-align:center">
<h3 style="color:#7fdbca;margin:0 0 0.75rem">New Identity</h3>
<input id="new-id-name" type="text" placeholder="Display name" maxlength="50" style="width:100%;margin-bottom:0.75rem" />
<h3 style="color:#7fdbca;margin:0 0 0.5rem">New Device Address</h3>
<p style="font-size:0.72rem;color:#888;margin-bottom:0.75rem">A new QUIC network endpoint for this device. Your personas are unaffected.</p>
<input id="new-id-name" type="text" placeholder="Label (your reference, not shared)" maxlength="50" style="width:100%;margin-bottom:0.75rem" />
<div style="display:flex;gap:0.5rem;justify-content:center">
<button class="btn btn-primary btn-sm" id="new-id-create">Create</button>
<button class="btn btn-ghost btn-sm" id="new-id-cancel">Cancel</button>
@ -3654,10 +3657,10 @@ $('#create-identity-btn').addEventListener('click', () => {
document.body.appendChild(overlay);
overlay.querySelector('#new-id-create').addEventListener('click', async () => {
const name = overlay.querySelector('#new-id-name').value.trim();
if (!name) { toast('Name is required'); return; }
if (!name) { toast('Label is required'); return; }
try {
const nodeId = await invoke('create_identity', { name });
toast(`Identity created: ${nodeId.substring(0, 12)}`);
toast(`Device address created: ${nodeId.substring(0, 12)}`);
overlay.remove();
loadIdentities();
} catch (e) { toast('Error: ' + e); }
@ -3672,10 +3675,10 @@ $('#import-identity-btn').addEventListener('click', () => {
overlay.style.cursor = 'default';
overlay.innerHTML = `
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:1.5rem;max-width:400px;width:90%;text-align:center">
<h3 style="color:#7fdbca;margin:0 0 0.75rem">Import Identity</h3>
<p style="font-size:0.75rem;color:#888;margin-bottom:0.5rem">Paste the 64-character hex key from an identity export.</p>
<input id="import-id-key" type="text" placeholder="Identity key (64 hex chars)" maxlength="64" style="width:100%;margin-bottom:0.5rem;font-family:monospace;font-size:0.7rem" />
<input id="import-id-name" type="text" placeholder="Display name" maxlength="50" style="width:100%;margin-bottom:0.75rem" />
<h3 style="color:#7fdbca;margin:0 0 0.5rem">Import Device Address Key</h3>
<p style="font-size:0.75rem;color:#888;margin-bottom:0.5rem">Paste a 64-character hex key from a previous device-address export. This is NOT how you move your personas &mdash; use Export/Import personas above for that.</p>
<input id="import-id-key" type="text" placeholder="Device address key (64 hex chars)" maxlength="64" style="width:100%;margin-bottom:0.5rem;font-family:monospace;font-size:0.7rem" />
<input id="import-id-name" type="text" placeholder="Label (your reference, not shared)" maxlength="50" style="width:100%;margin-bottom:0.75rem" />
<div style="display:flex;gap:0.5rem;justify-content:center">
<button class="btn btn-primary btn-sm" id="import-id-go">Import</button>
<button class="btn btn-ghost btn-sm" id="import-id-cancel">Cancel</button>
@ -3688,7 +3691,7 @@ $('#import-identity-btn').addEventListener('click', () => {
if (keyHex.length !== 64) { toast('Key must be 64 hex characters'); return; }
try {
const nodeId = await invoke('import_identity_key', { keyHex, name });
toast(`Identity imported: ${nodeId.substring(0, 12)}`);
toast(`Device address imported: ${nodeId.substring(0, 12)}`);
overlay.remove();
loadIdentities();
} catch (e) { toast('Error: ' + e); }
@ -4406,7 +4409,7 @@ async function init() {
<p style="color:#888;font-size:0.85rem;margin-bottom:1.5rem">How would you like to get started?</p>
<div style="display:flex;flex-direction:column;gap:0.75rem">
<button id="first-run-new" class="btn btn-primary" style="padding:0.75rem">Start Fresh</button>
<button id="first-run-import" class="btn btn-ghost" style="padding:0.75rem">Import an Identity</button>
<button id="first-run-import" class="btn btn-ghost" style="padding:0.75rem">Import from another device</button>
</div>
</div>`;
document.body.appendChild(chooser);

View file

@ -236,7 +236,7 @@
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.78rem;line-height:1.5">
<strong style="color:#7fdbca">Personas</strong> are who you are to peers &mdash; 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.
<br><br>
<strong style="color:#888">Identities</strong> below are this device's own network address &mdash; usually not what you want to move. Leave them alone unless you know why you're touching them.
<strong style="color:#888">Device Address</strong> below is this device's own network endpoint &mdash; usually not what you want to move. Leave it alone unless you know why you're touching it.
</p>
</div>
@ -259,14 +259,14 @@
</div>
<div class="section-card" style="text-align:center">
<h3 style="margin-bottom:0.4rem;font-size:0.85rem;color:#888">Identities (advanced)</h3>
<h3 style="margin-bottom:0.4rem;font-size:0.85rem;color:#888">Device Address (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 &mdash; it lets you move the device's QUIC endpoint, NOT your posting identity.
This device's network endpoint &mdash; the QUIC address peers use to reach you. Changing this rotates the device's network identifier but does NOT change your posting identity (personas). Rarely useful.
</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>
<button id="create-identity-btn" class="btn btn-ghost btn-sm">New Device Address</button>
<button id="import-identity-btn" class="btn btn-ghost btn-sm">Import Address Key</button>
</div>
</div>
@ -316,7 +316,7 @@
<div class="section-card">
<h3>Danger Zone</h3>
<p class="empty-hint">Delete all local data. Identity key preserved.</p>
<p class="empty-hint">Delete all local data. Device address key preserved.</p>
<button id="reset-data-btn" class="btn btn-danger btn-full">Reset All Data</button>
</div>
</section>