diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js
index 7341654..89aa9be 100644
--- a/muxplex/frontend/app.js
+++ b/muxplex/frontend/app.js
@@ -1851,7 +1851,7 @@ function renderSheetList() {
(s.bell.seen_at === null || s.bell.last_fired_at > s.bell.seen_at);
var isActive = s.name === _viewingSession;
var escapedName = escapeHtml(s.name || '');
- var remoteIdAttr = s.remoteId ? ' data-remote-id="' + escapeHtml(s.remoteId) + '"' : '';
+ var remoteIdAttr = s.remoteId != null ? ' data-remote-id="' + escapeHtml(s.remoteId) + '"' : '';
return '
' +
'' + escapedName + '' +
diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs
index c44c788..6a3e6ba 100644
--- a/muxplex/frontend/tests/test_app.mjs
+++ b/muxplex/frontend/tests/test_app.mjs
@@ -1604,6 +1604,20 @@ test('renderSheetList escapes HTML special chars in sheet-item__name span', () =
globalThis.document.getElementById = origGetById;
});
+// --- Fix 3: renderSheetList uses null check for remoteId (not falsy) ---
+
+test('renderSheetList uses null check for remoteId (not falsy)', () => {
+ const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
+ const fnStart = source.indexOf('function renderSheetList');
+ const fnEnd = source.indexOf('\n}\n', fnStart + 100);
+ const fnBody = source.substring(fnStart, fnEnd);
+ // Must use != null check, not truthy check
+ assert.ok(fnBody.includes('remoteId != null') || fnBody.includes('remoteId !== null'),
+ 'renderSheetList must use null check for remoteId (0 is valid)');
+ assert.ok(!fnBody.match(/s\.remoteId\s*\?[^=]/),
+ 'renderSheetList must NOT use truthy check for remoteId (0 is falsy)');
+});
+
// --- Fix 2: openBottomSheet/closeBottomSheet use static backdrop binding ---
test('openBottomSheet does not dynamically add click listener to sheet-backdrop', () => {