From 475d848ec45f38bb219f756450d107008f53c0ff Mon Sep 17 00:00:00 2001 From: Willi Budzinski Date: Thu, 18 Jun 2026 21:39:08 +0200 Subject: [PATCH 1/3] fix(viewer): preserve dashboard scroll and show api errors --- .../plan.md | 173 ++++++++++++++++++ .../todo.md | 81 ++++++++ src/viewer/index.html | 114 +++++++++++- test/helpers/viewer-sandbox.ts | 111 +++++++++-- test/viewer-session-id.test.ts | 109 +++++++++++ 5 files changed, 570 insertions(+), 18 deletions(-) create mode 100644 docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/plan.md create mode 100644 docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md diff --git a/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/plan.md b/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/plan.md new file mode 100644 index 00000000..78343bb5 --- /dev/null +++ b/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/plan.md @@ -0,0 +1,173 @@ +# Issue 507 Viewer Dashboard Errors Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Preserve dashboard scroll position during refreshes and show safe user-visible API error notifications in the viewer. + +**Architecture:** Keep the fix inside the existing single-file viewer script. Add a small toast host/renderer beside the existing auth prompt patterns, add client-side timeout handling inside `api()`, and wrap dashboard refresh rendering with scroll capture/restore on the dashboard scroll container. + +**Tech Stack:** TypeScript tests with Vitest, VM-executed browser script from `src/viewer/index.html`, DOM stubs in `test/helpers/viewer-sandbox.ts`, browser JavaScript in the viewer template. + +--- + +## Files + +- Modify: `src/viewer/index.html` +- Modify: `test/helpers/viewer-sandbox.ts` +- Modify: `test/viewer-session-id.test.ts` +- Update: `docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md` + +## Task 1: Red Tests For Viewer API Error Toasts + +**Files:** +- Modify: `test/helpers/viewer-sandbox.ts` +- Modify: `test/viewer-session-id.test.ts` + +- [ ] **Step 1: Extend the sandbox enough to observe abort timeouts and toast DOM** + +Add mock element fields used by the viewer code: `scrollTop`, `scrollHeight`, `clientHeight`, `appendChild`, `remove`, `children`, and a minimal `AbortController`/`AbortSignal.timeout` surface if needed by tests. + +- [ ] **Step 2: Write failing HTTP error toast test** + +Add a test that sets `sandbox.fetch` to return `{ ok: false, status: 503 }`, calls `sandbox.apiGet("memories?")`, and asserts a toast host contains escaped path/status text and no raw `offline")`, calls `sandbox.apiGet("health")`, and asserts visible escaped text in the toast host with no raw `"); + }; + + const result = await sandbox.apiGet("health"); + + const toastHtml = getElement("viewer-toasts").innerHTML; + expect(result).toBeNull(); + expect(toastHtml).toContain("API request failed"); + expect(toastHtml).toContain("health"); + expect(toastHtml).toContain("<script>offline</script>"); + expect(toastHtml).not.toContain(""); + }); + + it("shows a timeout toast when API requests exceed the viewer timeout", async () => { + const { sandbox, getElement, flushTimers } = loadViewerSandbox(); + sandbox.fetch = async (_url: string, opts?: { signal?: { addEventListener?: (type: string, listener: () => void) => void } }) => { + if (!opts?.signal?.addEventListener) { + return { ok: true, json: async () => ({ unexpectedlyCompletedWithoutTimeout: true }) }; + } + return new Promise((_resolve, reject) => { + opts.signal?.addEventListener?.("abort", () => { + const err = new Error("aborted"); + err.name = "AbortError"; + reject(err); + }); + }); + }; + + const resultPromise = sandbox.apiGet("health"); + flushTimers(); + const result = await resultPromise; + + const toastHtml = getElement("viewer-toasts").innerHTML; + expect(result).toBeNull(); + expect(toastHtml).toContain("API request timed out"); + expect(toastHtml).toContain("health"); + }); + it("shows an explicit dashboard error banner when endpoint groups fail", async () => { const { sandbox, getElement } = loadViewerSandbox(); const responses: Record = { @@ -79,6 +159,35 @@ describe("viewer session rendering", () => { expect(dashboard).toContain("ses_dash"); }); + it("preserves dashboard scroll position during manual refresh", async () => { + const { sandbox, getElement } = loadViewerSandbox(); + installDashboardFetch(sandbox); + const dashboard = getElement("view-dashboard"); + await sandbox.loadDashboard(); + dashboard.scrollTop = 240; + + await sandbox.refreshDashboard(); + + expect(dashboard.innerHTML).toContain("Recent Sessions"); + expect(dashboard.scrollTop).toBe(240); + }); + + it("preserves dashboard scroll position during debounced websocket or polling refresh", async () => { + const { sandbox, getElement, flushTimers } = loadViewerSandbox(); + installDashboardFetch(sandbox); + sandbox.state.activeTab = "dashboard"; + const dashboard = getElement("view-dashboard"); + await sandbox.loadDashboard(); + dashboard.scrollTop = 180; + + sandbox.scheduleDashboardRefresh(); + flushTimers(); + await new Promise((resolve) => setImmediate(resolve)); + + expect(dashboard.innerHTML).toContain("Recent Sessions"); + expect(dashboard.scrollTop).toBe(180); + }); + it("does not throw when dashboard sessions are missing ids", () => { const { sandbox, getElement } = loadViewerSandbox(); sandbox.state.dashboard = { From eb4e1bddc2a13b536cf361c88cf39f89b5b60c52 Mon Sep 17 00:00:00 2001 From: Willi Budzinski Date: Thu, 18 Jun 2026 21:42:49 +0200 Subject: [PATCH 2/3] docs: record issue 507 verification --- .../2026-06-18-issue-507-viewer-dashboard-errors/todo.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md b/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md index dae115b0..1cd5a19d 100644 --- a/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md +++ b/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md @@ -79,3 +79,10 @@ - `corepack pnpm run lint` exited 0. - `semgrep scan --config p/default --error --metrics=off .` completed with 0 findings. - `git diff --check` exited 0. +- 2026-06-18: Merged `origin/main` (`ee72dba7`) into `issue/507-viewer-dashboard-errors` with no conflicts. +- 2026-06-18: Post-merge verification: + - `corepack pnpm exec vitest run test/viewer-session-id.test.ts test/viewer-security.test.ts test/viewer-graph-cooldown.test.ts test/viewer-token-savings.test.ts` passed 4 files / 41 tests. + - `corepack pnpm test` passed 199 files / 2773 tests. + - `corepack pnpm run build` exited 0 with existing tsdown plugin timing and dynamic-import warnings. + - `corepack pnpm run lint` exited 0. + - `semgrep scan --config p/default --error --metrics=off .` completed with 0 findings. From f62aea40c3647c77be3c558821f47e8762a368c1 Mon Sep 17 00:00:00 2001 From: Willi Budzinski Date: Thu, 18 Jun 2026 21:46:17 +0200 Subject: [PATCH 3/3] docs: record issue 507 final verification --- .../2026-06-18-issue-507-viewer-dashboard-errors/todo.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md b/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md index 1cd5a19d..a8eaef8b 100644 --- a/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md +++ b/docs/todos/2026-06-18-issue-507-viewer-dashboard-errors/todo.md @@ -86,3 +86,10 @@ - `corepack pnpm run build` exited 0 with existing tsdown plugin timing and dynamic-import warnings. - `corepack pnpm run lint` exited 0. - `semgrep scan --config p/default --error --metrics=off .` completed with 0 findings. +- 2026-06-18: Branch protection reported the PR branch behind `origin/main`; merged latest `origin/main` again with no conflicts. +- 2026-06-18: Final local verification after second base merge: + - `corepack pnpm exec vitest run test/viewer-session-id.test.ts test/viewer-security.test.ts test/viewer-graph-cooldown.test.ts test/viewer-token-savings.test.ts` passed 4 files / 41 tests. + - `corepack pnpm test` passed 199 files / 2773 tests. + - `corepack pnpm run lint` exited 0. + - `semgrep scan --config p/default --error --metrics=off .` completed with 0 findings. + - `corepack pnpm run build` exited 0 with existing tsdown plugin timing and dynamic-import warnings.