feat: add device badge and data attributes to buildTileHTML (task-9)

- Show <span class="device-badge"> after tile name when _sources.length > 1
  and session.deviceName is present (multi-source federation context)
- Add data-session-key and data-source-url attributes to tile article
  element for unique session identification across sources
- Tests: 3 new tests covering badge visibility, badge omission for single
  source, and presence of new data attributes
This commit is contained in:
Brian Krabach
2026-03-30 23:34:22 -07:00
parent d897c05c61
commit 5980a6c080
2 changed files with 39 additions and 2 deletions
+2 -2
View File
@@ -468,9 +468,9 @@ function buildTileHTML(session, index, mobile) {
const lastLines = snapshot.split('\n').slice(-20).join('\n'); const lastLines = snapshot.split('\n').slice(-20).join('\n');
return ( return (
`<article class="${classes}" data-session="${escapedName}" tabindex="0" role="listitem" aria-label="${escapedName}">` + `<article class="${classes}" data-session="${escapedName}" data-session-key="${escapeHtml(session.sessionKey || name)}" data-source-url="${escapeHtml(session.sourceUrl || '')}" tabindex="0" role="listitem" aria-label="${escapedName}">` +
`<div class="tile-header">` + `<div class="tile-header">` +
`<span class="tile-name">${escapeHtml(name)}</span>` + `<span class="tile-name">${escapeHtml(name)}${_sources.length > 1 && session.deviceName ? '<span class="device-badge">' + escapeHtml(session.deviceName) + '</span>' : ''}</span>` +
`<span class="tile-meta">${bellHtml}<span class="tile-time">${escapeHtml(timeStr)}</span></span>` + `<span class="tile-meta">${bellHtml}<span class="tile-time">${escapeHtml(timeStr)}</span></span>` +
`</div>` + `</div>` +
`<div class="tile-body"><pre>${ansiToHtml(lastLines)}</pre></div>` + `<div class="tile-body"><pre>${ansiToHtml(lastLines)}</pre></div>` +
+37
View File
@@ -2338,6 +2338,43 @@ test('getVisibleSessions hides local sessions by name but not remote sessions wi
app._setServerSettings(null); app._setServerSettings(null);
}); });
// --- buildTileHTML device badge (task-9) ---
test('buildTileHTML shows device-badge when session has deviceName and multiple sources', () => {
app._setSources([
{ url: '', name: 'This device', type: 'local', status: 'authenticated', backoffMs: 2000 },
{ url: 'https://remote.example.com', name: 'Remote', type: 'remote', status: 'authenticated', backoffMs: 2000 },
]);
const session = { name: 'work', deviceName: 'Laptop', sourceUrl: '', sessionKey: '::work', snapshot: '' };
const html = app.buildTileHTML(session, 0, false);
assert.ok(html.includes('device-badge'), 'should show device-badge when _sources.length > 1 and session has deviceName');
assert.ok(html.includes('Laptop'), 'device-badge should contain the deviceName');
app._setSources([]);
});
test('buildTileHTML omits device-badge when only one source configured', () => {
app._setSources([
{ url: '', name: 'This device', type: 'local', status: 'authenticated', backoffMs: 2000 },
]);
const session = { name: 'work', deviceName: 'Laptop', sourceUrl: '', sessionKey: '::work', snapshot: '' };
const html = app.buildTileHTML(session, 0, false);
assert.ok(!html.includes('device-badge'), 'should NOT show device-badge when _sources.length is 1');
app._setSources([]);
});
test('buildTileHTML includes data-session-key and data-source-url attributes on article element', () => {
const session = {
name: 'work',
deviceName: 'Laptop',
sourceUrl: 'https://remote.example.com',
sessionKey: 'https://remote.example.com::work',
snapshot: '',
};
const html = app.buildTileHTML(session, 0, false);
assert.ok(html.includes('data-session-key='), 'article should have data-session-key attribute');
assert.ok(html.includes('data-source-url='), 'article should have data-source-url attribute');
});
test('pollSessions sets unreachable and applies exponential backoff on network error', async () => { test('pollSessions sets unreachable and applies exponential backoff on network error', async () => {
const mockStatusEl = { textContent: '', className: '' }; const mockStatusEl = { textContent: '', className: '' };
const mockGrid = { innerHTML: '' }; const mockGrid = { innerHTML: '' };