feat: reorganize settings tabs, right-align device labels, activity dots, sidebar glow, display config toggles
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
+70
-24
@@ -146,6 +146,10 @@ const DISPLAY_DEFAULTS = {
|
||||
bellSound: false,
|
||||
notificationPermission: 'default',
|
||||
viewMode: 'auto',
|
||||
showDeviceBadges: true, // show device name labels on tiles/sidebar
|
||||
showActivityGlow: true, // show amber border/glow on tiles/sidebar with bells
|
||||
showHoverPreview: true, // show hover preview popover on tile hover
|
||||
showActivityDot: true, // show activity dot on tiles/sidebar
|
||||
};
|
||||
|
||||
var VIEW_MODES = ['auto', 'fit'];
|
||||
@@ -455,38 +459,42 @@ function buildTileHTML(session, index, mobile) {
|
||||
const isBell = priority === 'bell';
|
||||
const unseen = session.bell && session.bell.unseen_count;
|
||||
|
||||
var ds = loadDisplaySettings();
|
||||
|
||||
let classes = 'session-tile';
|
||||
if (isBell) classes += ' session-tile--bell';
|
||||
if (isBell && ds.showActivityGlow !== false) classes += ' session-tile--bell';
|
||||
if (mobile) classes += ` session-tile--tier-${priority}`;
|
||||
|
||||
const name = session.name || '';
|
||||
const escapedName = escapeHtml(name);
|
||||
const timeStr = formatTimestamp(session.last_activity_at || null);
|
||||
|
||||
// Bell indicator
|
||||
let bellHtml = '';
|
||||
if (unseen && unseen > 0) {
|
||||
const countStr = unseen > 9 ? '9+' : String(unseen);
|
||||
bellHtml = `<span class="tile-bell">${countStr}</span>`;
|
||||
// Activity dot — absolute positioned in upper-right corner of tile (no count text)
|
||||
let bellDotHtml = '';
|
||||
if (unseen && unseen > 0 && ds.showActivityDot !== false) {
|
||||
bellDotHtml = '<span class="tile-bell-dot"></span>';
|
||||
}
|
||||
|
||||
// Device badge (shown when multiple sources configured and session has a device name)
|
||||
const badgeHtml = _sources.length > 1 && session.deviceName
|
||||
? `<span class="device-badge">${escapeHtml(session.deviceName)}</span>`
|
||||
: '';
|
||||
// Device badge — right-aligned in header, separate from name span
|
||||
// Shown when multiple sources configured AND session has a device name
|
||||
let badgeHtml = '';
|
||||
if (_sources.length > 1 && session.deviceName && ds.showDeviceBadges !== false) {
|
||||
badgeHtml = `<span class="device-badge">${escapeHtml(session.deviceName)}</span>`;
|
||||
}
|
||||
|
||||
// Last N lines of snapshot — show more in fit mode so tall tiles fill
|
||||
const snapshot = session.snapshot || '';
|
||||
var _tileDs = loadDisplaySettings();
|
||||
var _lineCount = (_tileDs.viewMode === 'fit') ? -80 : -20;
|
||||
var _lineCount = (ds.viewMode === 'fit') ? -80 : -20;
|
||||
const lastLines = snapshot.split('\n').slice(_lineCount).join('\n');
|
||||
|
||||
const sourceUrlAttr = session.sourceUrl ? ` data-source-url="${escapeHtml(session.sourceUrl)}"` : '';
|
||||
return (
|
||||
`<article class="${classes}" data-session="${escapedName}" data-session-key="${escapeHtml(session.sessionKey || name)}"${sourceUrlAttr} tabindex="0" role="listitem" aria-label="${escapedName}">` +
|
||||
bellDotHtml +
|
||||
`<div class="tile-header">` +
|
||||
`<span class="tile-name">${escapeHtml(name)}${badgeHtml}</span>` +
|
||||
`<span class="tile-meta">${bellHtml}<span class="tile-time">${escapeHtml(timeStr)}</span></span>` +
|
||||
`<span class="tile-name">${escapeHtml(name)}</span>` +
|
||||
badgeHtml +
|
||||
`<span class="tile-meta"><span class="tile-time">${escapeHtml(timeStr)}</span></span>` +
|
||||
`</div>` +
|
||||
`<div class="tile-body"><pre>${ansiToHtml(lastLines)}</pre></div>` +
|
||||
`<button class="tile-delete" data-session="${escapedName}" aria-label="Kill session">×</button>` +
|
||||
@@ -505,16 +513,25 @@ function buildSidebarHTML(session, currentSession) {
|
||||
const escapedName = escapeHtml(name);
|
||||
const isActive = name === currentSession;
|
||||
|
||||
let classes = 'sidebar-item';
|
||||
if (isActive) classes += ' sidebar-item--active';
|
||||
var ds = loadDisplaySettings();
|
||||
|
||||
const unseen = session.bell && session.bell.unseen_count;
|
||||
const isBell = unseen && unseen > 0;
|
||||
|
||||
// Bell indicator
|
||||
let bellHtml = '';
|
||||
if (unseen && unseen > 0) {
|
||||
const countStr = unseen > 9 ? '9+' : String(unseen);
|
||||
bellHtml = `<span class="tile-bell">${countStr}</span>`;
|
||||
let classes = 'sidebar-item';
|
||||
if (isActive) classes += ' sidebar-item--active';
|
||||
if (isBell && ds.showActivityGlow !== false) classes += ' sidebar-item--bell';
|
||||
|
||||
// Activity dot — inline in sidebar header (not absolute, since items are smaller)
|
||||
let bellDotHtml = '';
|
||||
if (isBell && ds.showActivityDot !== false) {
|
||||
bellDotHtml = '<span class="tile-bell-dot sidebar-bell-dot"></span>';
|
||||
}
|
||||
|
||||
// Device badge — between name and bell/delete, pushed right via flex
|
||||
let badgeHtml = '';
|
||||
if (_sources.length > 1 && session.deviceName && ds.showDeviceBadges !== false) {
|
||||
badgeHtml = `<span class="device-badge">${escapeHtml(session.deviceName)}</span>`;
|
||||
}
|
||||
|
||||
// Last 20 lines of snapshot
|
||||
@@ -524,8 +541,9 @@ function buildSidebarHTML(session, currentSession) {
|
||||
return (
|
||||
`<article class="${classes}" data-session="${escapedName}" data-source-url="${escapeHtml(session.sourceUrl || '')}" tabindex="0" role="listitem">` +
|
||||
`<div class="sidebar-item-header">` +
|
||||
`<span class="sidebar-item-name">${escapedName}${_sources.length > 1 && session.deviceName ? '<span class="device-badge">' + escapeHtml(session.deviceName) + '</span>' : ''}</span>` +
|
||||
`${bellHtml}` +
|
||||
`<span class="sidebar-item-name">${escapedName}</span>` +
|
||||
badgeHtml +
|
||||
bellDotHtml +
|
||||
`<button class="sidebar-delete" data-session="${escapedName}" aria-label="Kill session">×</button>` +
|
||||
`</div>` +
|
||||
`<div class="sidebar-item-body"><pre>${ansiToHtml(lastLines)}</pre></div>` +
|
||||
@@ -955,6 +973,8 @@ function _previewClickHandler(e) {
|
||||
|
||||
function showPreview(name) {
|
||||
if (!name || !_currentSessions) return;
|
||||
var _previewDs = loadDisplaySettings();
|
||||
if (_previewDs.showHoverPreview === false) return;
|
||||
var session = _currentSessions.find(function (s) { return s.name === name; });
|
||||
if (!session || !session.snapshot) return;
|
||||
|
||||
@@ -1657,6 +1677,18 @@ function onDisplaySettingChange() {
|
||||
ds.gridColumns = raw === 'auto' ? 'auto' : parseInt(raw, 10);
|
||||
}
|
||||
|
||||
var showDeviceBadgesEl = document.getElementById('setting-show-device-badges');
|
||||
if (showDeviceBadgesEl) ds.showDeviceBadges = showDeviceBadgesEl.checked;
|
||||
|
||||
var showActivityGlowEl = document.getElementById('setting-show-activity-glow');
|
||||
if (showActivityGlowEl) ds.showActivityGlow = showActivityGlowEl.checked;
|
||||
|
||||
var showHoverPreviewEl = document.getElementById('setting-show-hover-preview');
|
||||
if (showHoverPreviewEl) ds.showHoverPreview = showHoverPreviewEl.checked;
|
||||
|
||||
var showActivityDotEl = document.getElementById('setting-show-activity-dot');
|
||||
if (showActivityDotEl) ds.showActivityDot = showActivityDotEl.checked;
|
||||
|
||||
saveDisplaySettings(ds);
|
||||
applyDisplaySettings(ds);
|
||||
}
|
||||
@@ -1705,7 +1737,17 @@ function openSettings() {
|
||||
const viewModeEl = $('setting-view-mode');
|
||||
if (viewModeEl) viewModeEl.value = loadGridViewMode();
|
||||
|
||||
// Populate Notifications tab from display settings
|
||||
// Populate new display toggle checkboxes
|
||||
const showDeviceBadgesEl = $('setting-show-device-badges');
|
||||
if (showDeviceBadgesEl) showDeviceBadgesEl.checked = settings.showDeviceBadges !== false;
|
||||
const showActivityGlowEl = $('setting-show-activity-glow');
|
||||
if (showActivityGlowEl) showActivityGlowEl.checked = settings.showActivityGlow !== false;
|
||||
const showHoverPreviewEl = $('setting-show-hover-preview');
|
||||
if (showHoverPreviewEl) showHoverPreviewEl.checked = settings.showHoverPreview !== false;
|
||||
const showActivityDotEl = $('setting-show-activity-dot');
|
||||
if (showActivityDotEl) showActivityDotEl.checked = settings.showActivityDot !== false;
|
||||
|
||||
// Populate Sessions tab / bell sound from display settings
|
||||
const bellSoundEl = $('setting-bell-sound');
|
||||
if (bellSoundEl) bellSoundEl.checked = !!settings.bellSound;
|
||||
|
||||
@@ -2227,6 +2269,10 @@ function bindStaticEventListeners() {
|
||||
on($('setting-font-size'), 'change', onDisplaySettingChange);
|
||||
on($('setting-hover-delay'), 'change', onDisplaySettingChange);
|
||||
on($('setting-grid-columns'), 'change', onDisplaySettingChange);
|
||||
on($('setting-show-device-badges'), 'change', onDisplaySettingChange);
|
||||
on($('setting-show-activity-glow'), 'change', onDisplaySettingChange);
|
||||
on($('setting-show-hover-preview'), 'change', onDisplaySettingChange);
|
||||
on($('setting-show-activity-dot'), 'change', onDisplaySettingChange);
|
||||
on($('setting-view-mode'), 'change', function() {
|
||||
var el = $('setting-view-mode');
|
||||
if (el) {
|
||||
|
||||
+28
-22
@@ -16,7 +16,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ── Overview view ─────────────────────────────────────────────────────── -->
|
||||
<!-- ── Overview view ──────────────────────────────────────────────────── -->
|
||||
<div id="view-overview" class="view view--active">
|
||||
<header class="app-header">
|
||||
<h1 class="app-wordmark"><img src="/wordmark-on-dark.svg" alt="muxplex" height="24" /></h1>
|
||||
@@ -32,7 +32,7 @@
|
||||
<div id="empty-state" class="empty-state hidden">No active tmux sessions</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Expanded (terminal) view ──────────────────────────────────────────── -->
|
||||
<!-- ── Expanded (terminal) view ──────────────────────────────────────── -->
|
||||
<div id="view-expanded" class="view hidden">
|
||||
<header class="expanded-header">
|
||||
<button id="back-btn" class="back-btn" aria-label="Back">←</button>
|
||||
@@ -56,7 +56,7 @@
|
||||
<div id="reconnect-overlay" class="reconnect-overlay hidden" aria-live="polite">Reconnecting…</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Bottom sheet (session switcher) ───────────────────────────────────── -->
|
||||
<!-- ── Bottom sheet (session switcher) ────────────────────────────────── -->
|
||||
<div id="bottom-sheet" class="bottom-sheet hidden" role="dialog" aria-modal="true" aria-label="Switch session">
|
||||
<div class="bottom-sheet__backdrop" id="sheet-backdrop"></div>
|
||||
<div class="bottom-sheet__panel">
|
||||
@@ -65,19 +65,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Session pill (persistent overlay button) ──────────────────────────── -->
|
||||
<!-- ── Session pill (persistent overlay button) ────────────────────────── -->
|
||||
<button id="session-pill" class="session-pill hidden" aria-label="Switch session">
|
||||
<span id="session-pill-label" class="session-pill__label"></span>
|
||||
<span id="session-pill-bell" class="session-pill__bell hidden" aria-hidden="true">🔔</span>
|
||||
</button>
|
||||
|
||||
<!-- ── Mobile FAB (new session) ───────────────────────────────────────────── -->
|
||||
<!-- ── Mobile FAB (new session) ──────────────────────────────────────────── -->
|
||||
<button id="new-session-fab" class="new-session-fab" aria-label="New session">+</button>
|
||||
|
||||
<!-- ── Toast notification ─────────────────────────────────────────────────── -->
|
||||
<!-- ── Toast notification ──────────────────────────────────────────────── -->
|
||||
<div id="toast" class="toast hidden" role="status" aria-live="polite" aria-atomic="true"></div>
|
||||
|
||||
<!-- ── Settings ─────────────────────────────────────────────────────────────────────── -->
|
||||
<!-- ── Settings ──────────────────────────────────────────────────────────── -->
|
||||
<div id="settings-backdrop" class="settings-backdrop hidden"></div>
|
||||
<dialog id="settings-dialog" class="settings-dialog">
|
||||
<div class="settings-dialog-header">
|
||||
@@ -88,7 +88,6 @@
|
||||
<nav class="settings-tabs">
|
||||
<button class="settings-tab settings-tab--active" data-tab="display">Display</button>
|
||||
<button class="settings-tab" data-tab="sessions">Sessions</button>
|
||||
<button class="settings-tab" data-tab="notifications">Notifications</button>
|
||||
<button class="settings-tab" data-tab="new-session">Commands</button>
|
||||
<button class="settings-tab" data-tab="devices">Multi-Device</button>
|
||||
</nav>
|
||||
@@ -124,6 +123,26 @@
|
||||
<option value="4">4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-show-device-badges">Show device badges</label>
|
||||
<input type="checkbox" id="setting-show-device-badges" class="settings-checkbox" checked />
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-show-activity-glow">Show activity glow</label>
|
||||
<input type="checkbox" id="setting-show-activity-glow" class="settings-checkbox" checked />
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-show-hover-preview">Show hover preview</label>
|
||||
<input type="checkbox" id="setting-show-hover-preview" class="settings-checkbox" checked />
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-show-activity-dot">Show activity dot</label>
|
||||
<input type="checkbox" id="setting-show-activity-dot" class="settings-checkbox" checked />
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-device-name">Device Name</label>
|
||||
<input type="text" id="setting-device-name" class="settings-input" placeholder="This device" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-panel hidden" data-tab="sessions">
|
||||
<div class="settings-field">
|
||||
@@ -152,8 +171,6 @@
|
||||
<label class="settings-label" for="setting-auto-open">Auto-open created sessions</label>
|
||||
<input type="checkbox" id="setting-auto-open" class="settings-checkbox" checked />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-panel hidden" data-tab="notifications">
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-bell-sound">Bell Sound</label>
|
||||
<input type="checkbox" id="setting-bell-sound" class="settings-checkbox" />
|
||||
@@ -185,10 +202,6 @@
|
||||
<label class="settings-label" for="setting-multi-device-enabled">Enable multi-device support</label>
|
||||
<input type="checkbox" id="setting-multi-device-enabled" class="settings-checkbox" />
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-device-name">Device Name</label>
|
||||
<input type="text" id="setting-device-name" class="settings-input" placeholder="This device" />
|
||||
</div>
|
||||
<div id="multi-device-fields">
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-view-mode">View Mode</label>
|
||||
@@ -198,13 +211,6 @@
|
||||
<option value="filtered">Filtered</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label class="settings-label" for="setting-view-scope">View Scope</label>
|
||||
<select id="setting-view-scope" class="settings-select">
|
||||
<option value="all">All Devices</option>
|
||||
<option value="local">Local Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-field settings-field--column">
|
||||
<label class="settings-label">Remote Instances</label>
|
||||
<div id="setting-remote-instances" class="settings-remote-list"></div>
|
||||
@@ -216,7 +222,7 @@
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- ── Scripts ─────────────────────────────────────────────────────────────── -->
|
||||
<!-- ── Scripts ──────────────────────────────────────────────────────────── -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
|
||||
<script src="/app.js" defer></script>
|
||||
|
||||
@@ -499,6 +499,12 @@ body {
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
/* Bell / activity glow — matches .session-tile--bell */
|
||||
.sidebar-item--bell {
|
||||
border-color: var(--bell-border);
|
||||
box-shadow: 0 0 0 1px var(--bell-border), inset 0 0 12px var(--bell-glow);
|
||||
}
|
||||
|
||||
.sidebar-item-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -605,15 +611,29 @@ body {
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Bell count badge
|
||||
Activity dot — absolute positioned in upper-right corner of tile
|
||||
============================================================ */
|
||||
|
||||
.tile-bell-count {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--bell);
|
||||
min-width: 16px;
|
||||
text-align: right;
|
||||
.tile-bell-dot {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--bell);
|
||||
animation: bell-pulse 1.4s ease-in-out infinite;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Sidebar variant: inline (not absolute), fits within header row */
|
||||
.sidebar-bell-dot {
|
||||
position: static;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
@@ -451,24 +451,26 @@ test('buildTileHTML sets data-session attribute with escaped session name', () =
|
||||
assert.ok(html.includes('data-session="my<session>"'), 'data-session should be escaped');
|
||||
});
|
||||
|
||||
test('buildTileHTML shows 9+ when unseen_count exceeds 9', () => {
|
||||
test('buildTileHTML shows tile-bell-dot (not count) when unseen_count exceeds 9', () => {
|
||||
const session = {
|
||||
name: 's',
|
||||
bell: { unseen_count: 10, seen_at: null, last_fired_at: 100 },
|
||||
snapshot: '',
|
||||
};
|
||||
const html = app.buildTileHTML(session, 0, false);
|
||||
assert.ok(html.includes('9+'), 'should show 9+ for count > 9');
|
||||
assert.ok(html.includes('tile-bell-dot'), 'should show tile-bell-dot for count > 9');
|
||||
assert.ok(!html.includes('9+'), 'should NOT show 9+ count text (dot only)');
|
||||
});
|
||||
|
||||
test('buildTileHTML shows exact count when unseen_count is <= 9', () => {
|
||||
test('buildTileHTML shows tile-bell-dot (not count) when unseen_count is <= 9', () => {
|
||||
const session = {
|
||||
name: 's',
|
||||
bell: { unseen_count: 5, seen_at: null, last_fired_at: 100 },
|
||||
snapshot: '',
|
||||
};
|
||||
const html = app.buildTileHTML(session, 0, false);
|
||||
assert.ok(html.includes('>5<'), 'should show exact count 5');
|
||||
assert.ok(html.includes('tile-bell-dot'), 'should show tile-bell-dot for count > 0');
|
||||
assert.ok(!html.includes('>5<'), 'should NOT show exact count 5 (dot only)');
|
||||
});
|
||||
|
||||
test('buildTileHTML includes only last 20 lines of snapshot', () => {
|
||||
@@ -1544,17 +1546,17 @@ test('buildSidebarHTML does not add sidebar-item--active class for inactive sess
|
||||
assert.ok(!html.includes('sidebar-item--active'), 'inactive session should not have sidebar-item--active class');
|
||||
});
|
||||
|
||||
test('buildSidebarHTML renders bell badge with tile-bell class and count when unseen_count > 0', () => {
|
||||
test('buildSidebarHTML renders bell dot indicator when unseen_count > 0', () => {
|
||||
const session = { name: 's', snapshot: '', bell: { unseen_count: 3 } };
|
||||
const html = app.buildSidebarHTML(session, '');
|
||||
assert.ok(html.includes('tile-bell'), 'should contain tile-bell class when unseen_count > 0');
|
||||
assert.ok(html.includes('>3<'), 'should contain unseen count text');
|
||||
assert.ok(html.includes('tile-bell-dot'), 'should contain tile-bell-dot class when unseen_count > 0');
|
||||
assert.ok(!html.includes('>3<'), 'should NOT contain unseen count text (dot only)');
|
||||
});
|
||||
|
||||
test('buildSidebarHTML omits bell badge when unseen_count is 0', () => {
|
||||
test('buildSidebarHTML omits bell dot when unseen_count is 0', () => {
|
||||
const session = { name: 's', snapshot: '', bell: { unseen_count: 0 } };
|
||||
const html = app.buildSidebarHTML(session, '');
|
||||
assert.ok(!html.includes('tile-bell'), 'should not contain tile-bell class when unseen_count is 0');
|
||||
assert.ok(!html.includes('tile-bell-dot'), 'should not contain tile-bell-dot when unseen_count is 0');
|
||||
});
|
||||
|
||||
test('buildSidebarHTML HTML-escapes session name to prevent XSS', () => {
|
||||
@@ -3827,3 +3829,124 @@ test('bindStaticEventListeners does NOT bind change event to setting-view-scope'
|
||||
);
|
||||
});
|
||||
|
||||
// ─── Activity dot / sidebar glow / config toggles (UI/UX improvements) ───────
|
||||
|
||||
test('buildTileHTML bell dot is positioned before tile-header (absolute overlay)', () => {
|
||||
const session = {
|
||||
name: 's',
|
||||
bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 },
|
||||
snapshot: '',
|
||||
};
|
||||
const html = app.buildTileHTML(session, 0, false);
|
||||
const dotIdx = html.indexOf('tile-bell-dot');
|
||||
const headerIdx = html.indexOf('tile-header');
|
||||
assert.ok(dotIdx !== -1, 'tile-bell-dot must be present');
|
||||
assert.ok(dotIdx < headerIdx, 'tile-bell-dot must appear before tile-header in HTML');
|
||||
});
|
||||
|
||||
test('buildTileHTML omits tile-bell-dot when unseen_count is 0', () => {
|
||||
const session = { name: 's', bell: { unseen_count: 0 }, snapshot: '' };
|
||||
const html = app.buildTileHTML(session, 0, false);
|
||||
assert.ok(!html.includes('tile-bell-dot'), 'no tile-bell-dot when unseen_count is 0');
|
||||
});
|
||||
|
||||
test('buildSidebarHTML adds sidebar-item--bell class for bell sessions', () => {
|
||||
const session = { name: 's', snapshot: '', bell: { unseen_count: 3, seen_at: null, last_fired_at: 100 } };
|
||||
const html = app.buildSidebarHTML(session, '');
|
||||
assert.ok(html.includes('sidebar-item--bell'), 'sidebar item should have sidebar-item--bell class when bell is active');
|
||||
});
|
||||
|
||||
test('buildSidebarHTML does not add sidebar-item--bell when no unseen', () => {
|
||||
const session = { name: 's', snapshot: '', bell: { unseen_count: 0 } };
|
||||
const html = app.buildSidebarHTML(session, '');
|
||||
assert.ok(!html.includes('sidebar-item--bell'), 'should NOT have sidebar-item--bell when unseen_count is 0');
|
||||
});
|
||||
|
||||
test('DISPLAY_DEFAULTS includes showDeviceBadges key', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const defaultsStart = source.indexOf('DISPLAY_DEFAULTS');
|
||||
const defaultsEnd = source.indexOf('};', defaultsStart);
|
||||
const defaultsBody = source.substring(defaultsStart, defaultsEnd + 2);
|
||||
assert.ok(defaultsBody.includes('showDeviceBadges'), 'DISPLAY_DEFAULTS must include showDeviceBadges');
|
||||
});
|
||||
|
||||
test('DISPLAY_DEFAULTS includes showActivityGlow key', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const defaultsStart = source.indexOf('DISPLAY_DEFAULTS');
|
||||
const defaultsEnd = source.indexOf('};', defaultsStart);
|
||||
const defaultsBody = source.substring(defaultsStart, defaultsEnd + 2);
|
||||
assert.ok(defaultsBody.includes('showActivityGlow'), 'DISPLAY_DEFAULTS must include showActivityGlow');
|
||||
});
|
||||
|
||||
test('DISPLAY_DEFAULTS includes showHoverPreview key', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const defaultsStart = source.indexOf('DISPLAY_DEFAULTS');
|
||||
const defaultsEnd = source.indexOf('};', defaultsStart);
|
||||
const defaultsBody = source.substring(defaultsStart, defaultsEnd + 2);
|
||||
assert.ok(defaultsBody.includes('showHoverPreview'), 'DISPLAY_DEFAULTS must include showHoverPreview');
|
||||
});
|
||||
|
||||
test('DISPLAY_DEFAULTS includes showActivityDot key', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const defaultsStart = source.indexOf('DISPLAY_DEFAULTS');
|
||||
const defaultsEnd = source.indexOf('};', defaultsStart);
|
||||
const defaultsBody = source.substring(defaultsStart, defaultsEnd + 2);
|
||||
assert.ok(defaultsBody.includes('showActivityDot'), 'DISPLAY_DEFAULTS must include showActivityDot');
|
||||
});
|
||||
|
||||
test('HTML index.html has setting-show-device-badges checkbox', () => {
|
||||
const source = fs.readFileSync(new URL('../index.html', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes('setting-show-device-badges'), 'Display panel must have setting-show-device-badges checkbox');
|
||||
});
|
||||
|
||||
test('HTML index.html has setting-show-activity-glow checkbox', () => {
|
||||
const source = fs.readFileSync(new URL('../index.html', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes('setting-show-activity-glow'), 'Display panel must have setting-show-activity-glow checkbox');
|
||||
});
|
||||
|
||||
test('HTML index.html has setting-show-hover-preview checkbox', () => {
|
||||
const source = fs.readFileSync(new URL('../index.html', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes('setting-show-hover-preview'), 'Display panel must have setting-show-hover-preview checkbox');
|
||||
});
|
||||
|
||||
test('HTML index.html has setting-show-activity-dot checkbox', () => {
|
||||
const source = fs.readFileSync(new URL('../index.html', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes('setting-show-activity-dot'), 'Display panel must have setting-show-activity-dot checkbox');
|
||||
});
|
||||
|
||||
test('CSS style.css has .tile-bell-dot rule', () => {
|
||||
const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes('.tile-bell-dot'), 'style.css must have .tile-bell-dot rule');
|
||||
});
|
||||
|
||||
test('CSS style.css has .sidebar-item--bell rule', () => {
|
||||
const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes('.sidebar-item--bell'), 'style.css must have .sidebar-item--bell rule');
|
||||
});
|
||||
|
||||
test('CSS style.css has .sidebar-bell-dot rule', () => {
|
||||
const source = fs.readFileSync(new URL('../style.css', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes('.sidebar-bell-dot'), 'style.css must have .sidebar-bell-dot rule');
|
||||
});
|
||||
|
||||
test('showPreview checks showHoverPreview setting before showing popover', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const fnStart = source.indexOf('function showPreview(');
|
||||
assert.ok(fnStart !== -1, 'showPreview must exist');
|
||||
const fnEnd = source.indexOf('\n}', fnStart);
|
||||
const fnBody = source.substring(fnStart, fnEnd + 2);
|
||||
assert.ok(fnBody.includes('showHoverPreview'), 'showPreview must check showHoverPreview setting before showing popover');
|
||||
});
|
||||
|
||||
test('bindStaticEventListeners binds change events for new display toggle checkboxes', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const fnStart = source.indexOf('function bindStaticEventListeners(');
|
||||
assert.ok(fnStart !== -1, 'bindStaticEventListeners must exist');
|
||||
const fnEnd = source.indexOf('\nfunction ', fnStart + 1);
|
||||
const fnBody = source.substring(fnStart, fnEnd > fnStart ? fnEnd : fnStart + 10000);
|
||||
assert.ok(fnBody.includes('setting-show-device-badges'), 'must bind setting-show-device-badges');
|
||||
assert.ok(fnBody.includes('setting-show-activity-glow'), 'must bind setting-show-activity-glow');
|
||||
assert.ok(fnBody.includes('setting-show-hover-preview'), 'must bind setting-show-hover-preview');
|
||||
assert.ok(fnBody.includes('setting-show-activity-dot'), 'must bind setting-show-activity-dot');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user