From bba862fba387a525bf7a5d7f8edb134195db1e80 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Fri, 27 Mar 2026 17:11:18 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20add=20renderSidebar=20=E2=80=94=20popul?= =?UTF-8?q?ates=20sidebar=20list=20from=20session=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/app.js | 30 +++++++++++++++ frontend/tests/test_app.mjs | 74 +++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/frontend/app.js b/frontend/app.js index 00b8640..caab02f 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -339,6 +339,35 @@ function buildSidebarHTML(session, currentSession) { ); } +/** + * Render the session sidebar list. Only renders in fullscreen view. + * Shows empty state when no sessions exist. + * Binds click handlers on each sidebar-item to switch sessions. + * @param {object[]} sessions + * @param {string|null} currentSession - name of the currently active session + */ +function renderSidebar(sessions, currentSession) { + if (_viewMode !== 'fullscreen') return; + + const list = $('sidebar-list'); + if (!list) return; + + if (!sessions || sessions.length === 0) { + list.innerHTML = ''; + return; + } + + list.innerHTML = sessions.map((session) => buildSidebarHTML(session, currentSession)).join(''); + + // Bind click handlers on each sidebar item + list.querySelectorAll('.sidebar-item').forEach((item) => { + const name = item.dataset.session; + on(item, 'click', () => { + if (name !== currentSession) openSession(name); + }); + }); +} + /** * Render the session grid. Shows empty state when no sessions exist. * On mobile, sorts sessions by priority before rendering. @@ -937,6 +966,7 @@ if (typeof module !== 'undefined' && module.exports) { escapeHtml, buildTileHTML, buildSidebarHTML, + renderSidebar, renderGrid, requestNotificationPermission, handleBellTransitions, diff --git a/frontend/tests/test_app.mjs b/frontend/tests/test_app.mjs index bf7426f..81636a0 100644 --- a/frontend/tests/test_app.mjs +++ b/frontend/tests/test_app.mjs @@ -1692,3 +1692,77 @@ test('buildSidebarHTML includes snapshot preview in a pre element', () => { const html = app.buildSidebarHTML(session, ''); assert.ok(/
[\s\S]*line1[\s\S]*<\/pre>/.test(html), 'snapshot content should be inside a 
 element');
 });
+
+// --- renderSidebar ---
+
+test('renderSidebar populates sidebar-list innerHTML when viewMode is fullscreen', () => {
+  let capturedHTML = '';
+  const mockList = {
+    get innerHTML() { return capturedHTML; },
+    set innerHTML(v) { capturedHTML = v; },
+    querySelectorAll: () => [],
+  };
+  const origGetById = globalThis.document.getElementById;
+  globalThis.document.getElementById = (id) => {
+    if (id === 'sidebar-list') return mockList;
+    return null;
+  };
+
+  app._setViewMode('fullscreen');
+  const sessions = [
+    { name: 'session-a', snapshot: '', bell: { unseen_count: 0 } },
+    { name: 'session-b', snapshot: '', bell: { unseen_count: 0 } },
+  ];
+  app.renderSidebar(sessions, 'session-a');
+
+  assert.ok(capturedHTML.includes('sidebar-item'), 'innerHTML should contain sidebar-item');
+  assert.ok(capturedHTML.includes('sidebar-item--active'), 'innerHTML should contain sidebar-item--active for active session');
+
+  globalThis.document.getElementById = origGetById;
+  app._setViewMode('grid');
+});
+
+test('renderSidebar renders empty message when sessions array is empty', () => {
+  let capturedHTML = '';
+  const mockList = {
+    get innerHTML() { return capturedHTML; },
+    set innerHTML(v) { capturedHTML = v; },
+    querySelectorAll: () => [],
+  };
+  const origGetById = globalThis.document.getElementById;
+  globalThis.document.getElementById = (id) => {
+    if (id === 'sidebar-list') return mockList;
+    return null;
+  };
+
+  app._setViewMode('fullscreen');
+  app.renderSidebar([], null);
+
+  assert.ok(capturedHTML.includes('sidebar-empty'), 'innerHTML should contain sidebar-empty class');
+  assert.ok(capturedHTML.includes('No sessions'), 'innerHTML should contain "No sessions" text');
+
+  globalThis.document.getElementById = origGetById;
+  app._setViewMode('grid');
+});
+
+test('renderSidebar does nothing when view is not fullscreen', () => {
+  let innerHTMLSet = false;
+  const mockList = {
+    get innerHTML() { return ''; },
+    set innerHTML(v) { innerHTMLSet = true; },
+    querySelectorAll: () => [],
+  };
+  const origGetById = globalThis.document.getElementById;
+  globalThis.document.getElementById = (id) => {
+    if (id === 'sidebar-list') return mockList;
+    return null;
+  };
+
+  app._setViewMode('grid');
+  const sessions = [{ name: 'session-a', snapshot: '', bell: { unseen_count: 0 } }];
+  app.renderSidebar(sessions, null);
+
+  assert.strictEqual(innerHTMLSet, false, 'innerHTML setter should never be called when not in fullscreen');
+
+  globalThis.document.getElementById = origGetById;
+});