fix: move device badge inside tile-meta to prevent × overlap

This commit is contained in:
Brian Krabach
2026-04-01 16:21:57 -07:00
parent d7bcf49283
commit f18caf5d27
3 changed files with 55 additions and 10 deletions
+1 -2
View File
@@ -488,8 +488,7 @@ function buildTileHTML(session, index, mobile) {
`<article class="${classes}" data-session="${escapedName}" data-session-key="${escapeHtml(session.sessionKey || name)}"${remoteIdAttr} tabindex="0" role="listitem" aria-label="${escapedName}">` + `<article class="${classes}" data-session="${escapedName}" data-session-key="${escapeHtml(session.sessionKey || name)}"${remoteIdAttr} 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)}</span>` +
badgeHtml + `<span class="tile-meta">${badgeHtml}${badgeHtml ? `<span class="tile-meta-sep">\xb7</span>` : ''}<span class="tile-time">${escapeHtml(timeStr)}</span></span>` +
`<span class="tile-meta"><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>` +
`<button class="tile-delete" data-session="${escapedName}" aria-label="Kill session">&times;</button>` + `<button class="tile-delete" data-session="${escapedName}" aria-label="Kill session">&times;</button>` +
+8 -4
View File
@@ -227,13 +227,17 @@ body {
margin-left: 8px; margin-left: 8px;
} }
/* × crossfade with timestamp on hover */ .tile-meta-sep {
.tile-time { margin: 0 2px;
}
/* × crossfade with badge + timestamp on hover */
.tile-meta {
transition: opacity 150ms ease; transition: opacity 150ms ease;
} }
.session-tile:hover .tile-time, .session-tile:hover .tile-meta,
.session-tile:focus-within .tile-time { .session-tile:focus-within .tile-meta {
opacity: 0; opacity: 0;
} }
+46 -4
View File
@@ -2515,6 +2515,40 @@ test('buildTileHTML escapes HTML in deviceName within device-badge', () => {
app._setServerSettings(null); app._setServerSettings(null);
}); });
// --- buildTileHTML device badge placement (task-3) ---
test('buildTileHTML places device-badge inside tile-meta span', () => {
app._setServerSettings({ multi_device_enabled: true });
const session = { name: 'work', deviceName: 'Laptop', sessionKey: '::work', snapshot: '' };
const html = app.buildTileHTML(session, 0, false);
const tileMetaStart = html.indexOf('<span class="tile-meta">');
const tileMetaEnd = html.indexOf('</span>', tileMetaStart);
assert.ok(tileMetaStart !== -1, 'tile-meta span should exist');
const deviceBadgePos = html.indexOf('device-badge');
assert.ok(
deviceBadgePos > tileMetaStart && deviceBadgePos < tileMetaEnd,
`device-badge should be inside tile-meta span (tile-meta starts at ${tileMetaStart}, device-badge at ${deviceBadgePos}, tile-meta closes at ${tileMetaEnd})`
);
app._setServerSettings(null);
});
test('buildTileHTML includes tile-meta-sep with middle dot when badge present', () => {
app._setServerSettings({ multi_device_enabled: true });
const session = { name: 'work', deviceName: 'Laptop', sessionKey: '::work', snapshot: '' };
const html = app.buildTileHTML(session, 0, false);
assert.ok(html.includes('tile-meta-sep'), 'should include tile-meta-sep element when badge is present');
assert.ok(html.includes('\u00b7'), 'should include middle dot separator (\u00b7)');
app._setServerSettings(null);
});
test('buildTileHTML does not include tile-meta-sep when no badge', () => {
app._setServerSettings({ multi_device_enabled: false });
const session = { name: 'work', deviceName: 'Laptop', sessionKey: '::work', snapshot: '' };
const html = app.buildTileHTML(session, 0, false);
assert.ok(!html.includes('tile-meta-sep'), 'should NOT include tile-meta-sep when no badge');
app._setServerSettings(null);
});
// --- renderGrid grouped mode (task-11) --- // --- renderGrid grouped mode (task-11) ---
@@ -3951,13 +3985,21 @@ test('CSS style.css has .sidebar-item-meta rule', () => {
assert.ok(source.includes('.sidebar-item-meta'), 'style.css must have .sidebar-item-meta rule'); assert.ok(source.includes('.sidebar-item-meta'), 'style.css must have .sidebar-item-meta rule');
}); });
test('CSS style.css .tile-time has opacity transition for crossfade', () => { test('CSS style.css .tile-meta has opacity transition for crossfade (badge + timestamp together)', () => {
const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8'); const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
assert.ok(source.includes('.tile-time'), 'style.css must have .tile-time rule');
assert.ok( assert.ok(
source.includes("session-tile:hover .tile-time"), source.includes("session-tile:hover .tile-meta"),
'style.css must have session-tile:hover .tile-time for crossfade' 'style.css must have session-tile:hover .tile-meta for crossfade'
); );
assert.ok(
source.includes("session-tile:focus-within .tile-meta"),
'style.css must have session-tile:focus-within .tile-meta for crossfade'
);
});
test('CSS style.css has .tile-meta-sep style', () => {
const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
assert.ok(source.includes('.tile-meta-sep'), 'style.css must have .tile-meta-sep rule');
}); });
// --- Trailing blank line trimming in snapshot previews --- // --- Trailing blank line trimming in snapshot previews ---