Skip to content

Commit 694f03d

Browse files
authored
fix(gui): show discovered agent names with contact label precedence (#559)
1 parent b2d3166 commit 694f03d

2 files changed

Lines changed: 46 additions & 5 deletions

File tree

src/gui/x0x-gui.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@
854854
function u8encode(s){return btoa(unescape(encodeURIComponent(s)))}
855855
function u8decode(s){return decodeURIComponent(escape(atob(s)))}
856856
function short(s,n){return s?(s.substring(0,n||10)+'…'):'—'}
857+
function agentDisplayName(agentId,contact,discovered){return (contact&&contact.label)||(discovered&&discovered.self_name)||short(agentId)}
857858
function copyId(el){navigator.clipboard.writeText(el.textContent.replace('agent:','').replace('…','')).then(()=>toast('Copied to clipboard','info'))}
858859
function copyText(text){navigator.clipboard.writeText(text).then(()=>toast('Copied','info'))}
859860
function fmtTime(ts){return ts?new Date(typeof ts==='number'&&ts<1e12?ts*1000:ts).toLocaleTimeString():'—'}
@@ -1874,7 +1875,7 @@ <h2 style="margin:0">🏠 ${esc(hd.name||'Home')}</h2>
18741875
S.set('discovered',d.agents);
18751876
const tb=document.getElementById('h-agents');
18761877
if(tb)tb.innerHTML=d.agents.map(x=>`<tr>
1877-
<td><span class="aid-short" style="cursor:pointer" onclick="showAgentDetail('${x.agent_id}')">${short(x.agent_id)}</span></td>
1878+
<td><span style="cursor:pointer" onclick="showAgentDetail('${x.agent_id}')">${esc(agentDisplayName(x.agent_id,(S.get('contacts')||[]).find(c=>c.agent_id===x.agent_id),x))}</span><div class="aid-short">${short(x.agent_id)}</div></td>
18781879
<td>${x.user_id?'<span style="color:var(--lv)">'+short(x.user_id,8)+'</span>':'—'}</td>
18791880
<td style="font-size:11px">${(x.addresses||[]).join(', ')||'—'}</td>
18801881
<td>${x.nat_type||'—'}</td>
@@ -3152,7 +3153,7 @@ <h3>Pages</h3><div id="web-pages"></div>`;
31523153
return `<div class="agent-row" onclick="showAgentDetail('${c.agent_id}')">
31533154
<span style="font-size:14px">&#129302;</span>
31543155
<div style="flex:1">
3155-
<div style="font-weight:500">${esc(c.label||short(c.agent_id))}</div>
3156+
<div style="font-weight:500">${esc(agentDisplayName(c.agent_id,c,c.discovered))}</div>
31563157
<div class="aid-short">${short(c.agent_id,12)}</div>
31573158
</div>
31583159
${trustHtml(c.trust_level)}
@@ -4604,7 +4605,7 @@ <h3>Thread</h3>
46044605
const c=data.contact;
46054606
const d=data.disc;
46064607
const aid=data.agentId;
4607-
const name=c?c.label||short(aid):short(aid);
4608+
const name=agentDisplayName(aid,c,d);
46084609
const trust=c?c.trust_level:'Unknown';
46094610
const idType=c?c.identity_type:'Unknown';
46104611
el.innerHTML=`<div class="detail-header">

tests/helpers/gui_proof.spec.mjs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { test, expect } from '@playwright/test';
2-
import { existsSync } from 'node:fs';
1+
import { test, expect } from 'playwright/test';
2+
import { existsSync, readFileSync } from 'node:fs';
33

44
const chromeCandidates = [
55
process.env.CHROME_BIN,
@@ -70,3 +70,43 @@ test('gui can import card and send direct message', async ({ page }) => {
7070
return !!msgs && msgs.textContent.includes(message);
7171
}, message);
7272
});
73+
74+
// No daemon: run the real renderers in a browser with inert startup IO.
75+
test('agent names preserve labels, update peer names and escape HTML', async ({ page }) => {
76+
await page.route('**/*', route => route.abort());
77+
const html = readFileSync(new URL('../../src/gui/x0x-gui.html', import.meta.url), 'utf8');
78+
const script = html.match(/<script>([\s\S]*?)<\/script>/)[1];
79+
// Keep every real function; omit only the startup calls and interval timers.
80+
await page.setContent('<table><tbody id="h-agents"></tbody></table><div id="rows"></div><div id="detail"></div>');
81+
await page.addScriptTag({ content: script.split('// Apply theme on startup')[0] });
82+
const agentId = 'a1'.repeat(32);
83+
async function render(label, selfName) {
84+
await page.evaluate(async ({ agentId, label, selfName }) => {
85+
const contact = { agent_id: agentId, label, machines: [], trust_level: 'Known' };
86+
const discovered = { agent_id: agentId, self_name: selfName };
87+
S.set('contacts', [contact]);
88+
api = async path => path === '/agents/discovered' ? {agents:[discovered]} : null;
89+
refreshAgentIdentity = async () => {};
90+
refreshUpgradeBanner = async () => {};
91+
await pollDash();
92+
document.getElementById('rows').innerHTML = renderAgentRow({...contact, discovered});
93+
renderAgentDetail(document.getElementById('detail'), {agentId, contact, disc:discovered});
94+
}, {agentId, label, selfName});
95+
}
96+
for (const [label, peer, expected] of [
97+
['', 'Remote name', 'Remote name'],
98+
['My label', 'Remote name', 'My label'],
99+
['', 'Updated name', 'Updated name'],
100+
['', '', agentId.slice(0,10)+'…'],
101+
['', '<img src=x onerror="window.nameInjected=true">', '<img src=x onerror="window.nameInjected=true">'],
102+
['<svg onload="window.nameInjected=true">', 'Peer', '<svg onload="window.nameInjected=true">'],
103+
]) {
104+
await render(label, peer);
105+
for (const selector of ['#h-agents', '#rows', '#detail']) {
106+
await expect(page.locator(selector)).toContainText(expected);
107+
await expect(page.locator(selector)).toContainText(agentId.slice(0,10));
108+
await expect(page.locator(selector+' img, '+selector+' svg')).toHaveCount(0);
109+
}
110+
expect(await page.evaluate(() => window.nameInjected)).toBeUndefined();
111+
}
112+
});

0 commit comments

Comments
 (0)