diff --git a/public/channels.js b/public/channels.js index 6000e286..b8daaf0c 100644 --- a/public/channels.js +++ b/public/channels.js @@ -274,6 +274,9 @@ for (let i = 0; i < str.length; i++) h = ((h << 5) - h + str.charCodeAt(i)) | 0; return Math.abs(h); } + function formatHashHex(hash) { + return typeof hash === 'number' ? '0x' + hash.toString(16).toUpperCase().padStart(2, '0') : hash; + } function getChannelColor(hash) { return CHANNEL_COLORS[hashCode(String(hash)) % CHANNEL_COLORS.length]; } function getSenderColor(name) { const isDark = document.documentElement.getAttribute('data-theme') === 'dark' || @@ -659,7 +662,7 @@ }); el.innerHTML = sorted.map(ch => { - const name = ch.name || `Channel ${ch.hash}`; + const name = ch.name || `Channel ${formatHashHex(ch.hash)}`; const color = getChannelColor(ch.hash); const time = ch.lastActivityMs ? formatSecondsAgo(Math.floor((Date.now() - ch.lastActivityMs) / 1000)) : ''; const preview = ch.lastSender && ch.lastMessage @@ -688,7 +691,7 @@ history.replaceState(null, '', `#/channels/${encodeURIComponent(hash)}`); renderChannelList(); const ch = channels.find(c => c.hash === hash); - const name = ch?.name || `Channel ${hash}`; + const name = ch?.name || `Channel ${formatHashHex(hash)}`; const header = document.getElementById('chHeader'); header.querySelector('.ch-header-text').textContent = `${name} — ${ch?.messageCount || 0} messages`; diff --git a/public/index.html b/public/index.html index b7cd30e8..a90a9b6d 100644 --- a/public/index.html +++ b/public/index.html @@ -22,9 +22,9 @@ - - - + + + @@ -85,30 +85,30 @@
- - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-frontend-helpers.js b/test-frontend-helpers.js index 2a00a402..313f6724 100644 --- a/test-frontend-helpers.js +++ b/test-frontend-helpers.js @@ -2876,6 +2876,35 @@ console.log('\n=== live.js: nextHop null guards ==='); }); } +// === channels.js: formatHashHex (#465) === +console.log('\n=== channels.js: formatHashHex (issue #465) ==='); +{ + const chSource = fs.readFileSync('public/channels.js', 'utf8'); + + test('formatHashHex exists in channels.js', () => { + assert.ok(chSource.includes('function formatHashHex('), 'formatHashHex function must exist'); + }); + + test('channel fallback name uses formatHashHex', () => { + assert.ok(chSource.includes('formatHashHex(ch.hash)'), 'renderChannelList must format hash as hex'); + assert.ok(chSource.includes('formatHashHex(hash)'), 'selectChannel must format hash as hex'); + }); + + test('formatHashHex produces correct hex output', () => { + // Extract and evaluate the function + const match = chSource.match(/function formatHashHex\(hash\)\s*\{[^}]+\}/); + assert.ok(match, 'should extract formatHashHex'); + const ctx = vm.createContext({}); + vm.runInContext(match[0], ctx); + const fmt = vm.runInContext('formatHashHex', ctx); + assert.strictEqual(fmt(10), '0x0A'); + assert.strictEqual(fmt(255), '0xFF'); + assert.strictEqual(fmt(0), '0x00'); + assert.strictEqual(fmt(1), '0x01'); + assert.strictEqual(fmt('LongFast'), 'LongFast'); // string hash passes through + }); +} + // ===== SUMMARY ===== Promise.allSettled(pendingTests).then(() => { console.log(`\n${'═'.repeat(40)}`);