From b84b7f3a3d1c1c01d5d404c335b9b3969912f45f Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Mon, 10 Aug 2026 16:02:04 -0500 Subject: [PATCH 1/3] Prevent chart pointer update loops --- .../e2e/tests/stack-effects-chaos.e2e.ts | 25 +++++++++++++++++++ .../components/events-dashboard-chart.svelte | 2 -- .../sessions-dashboard-chart.svelte | 2 -- .../components/ui/chart/chart-tooltip.svelte | 2 +- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts index 6a99af1d4f..3f8a565737 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts @@ -91,6 +91,31 @@ test('stack effects stay bounded through background, paging, and navigation chao await expect(page.getByRole('heading', { name: 'Stacks' })).toBeVisible(); }); + await measureAction(diagnostics, 'stack timeline drag', async () => { + await page.locator('tbody tr:visible').first().click(); + const dialog = page.getByRole('dialog'); + await expect(dialog).toBeVisible(); + const timeline = dialog.locator('[data-slot="chart"]'); + await expect(timeline).toBeVisible(); + + const bounds = await timeline.boundingBox(); + expect(bounds).not.toBeNull(); + if (bounds) { + const y = bounds.y + bounds.height / 2; + await page.mouse.move(bounds.x + bounds.width * 0.2, y); + await page.waitForTimeout(250); + await page.mouse.move(bounds.x + bounds.width * 0.3, y); + await expect(page.locator('.lc-tooltip-root')).toHaveCSS('pointer-events', 'none'); + await page.mouse.down(); + await page.mouse.move(bounds.x + bounds.width * 0.8, y, { steps: 20 }); + await page.mouse.up(); + } + + await page.waitForTimeout(500); + await page.getByRole('button', { name: 'Close' }).click(); + }); + expect(actionSample(diagnostics, 'stack timeline drag').runtimeErrors).toBe(0); + await measureAction(diagnostics, 'selected stack refresh', async () => { const rowSelection = page.getByRole('checkbox', { name: 'Select row' }).first(); await rowSelection.click(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-dashboard-chart.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-dashboard-chart.svelte index 7be0e2b734..869049ccb7 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-dashboard-chart.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-dashboard-chart.svelte @@ -72,8 +72,6 @@ if (start instanceof Date && end instanceof Date) { onRangeSelect?.(start, end); } - - e.brush.reset(); } }} props={{ diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/sessions-dashboard-chart.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/sessions-dashboard-chart.svelte index 9b3601a8bd..b76f528af6 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/sessions-dashboard-chart.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/sessions/components/sessions-dashboard-chart.svelte @@ -72,8 +72,6 @@ if (start instanceof Date && end instanceof Date) { onRangeSelect?.(start, end); } - - e.brush.reset(); } }} props={{ diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/chart-tooltip.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/chart-tooltip.svelte index 8218eaf306..e930aa1cc9 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/chart-tooltip.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/chart/chart-tooltip.svelte @@ -103,7 +103,7 @@ {/if} {/snippet} - +
Date: Mon, 10 Aug 2026 16:29:09 -0500 Subject: [PATCH 2/3] Close stack details before saved view navigation --- .../e2e/tests/stack-effects-chaos.e2e.ts | 25 +++++++++++++++++++ .../src/routes/(app)/stack/+page.svelte | 7 ++++++ 2 files changed, 32 insertions(+) diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts index 3f8a565737..9e36b47a26 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts @@ -116,6 +116,31 @@ test('stack effects stay bounded through background, paging, and navigation chao }); expect(actionSample(diagnostics, 'stack timeline drag').runtimeErrors).toBe(0); + await measureAction(diagnostics, 'saved view navigation with stack detail open', async () => { + await page.locator('tbody tr:visible').first().click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + const savedViewHrefs = ['/next/stack/all', '/next/stack/most-frequent-errors']; + for (const href of savedViewHrefs) { + await expect(page.locator(`a[href="${href}"]`).first()).toBeAttached(); + } + + await page.evaluate((hrefs) => { + for (let index = 0; index < 20; index++) { + const href = hrefs[index % hrefs.length]!; + document.querySelector(`a[href="${href}"]`)?.click(); + } + }, savedViewHrefs); + + await expect(page).toHaveURL(/\/next\/stack\/most-frequent-errors(?:[?#]|$)/); + await expect(page.getByRole('dialog')).not.toBeVisible(); + await expect(page.getByRole('heading', { exact: true, name: 'Most Frequent Errors' })).toBeVisible(); + + await page.goto('/next/stack?limit=5'); + await expect(page.getByRole('button', { name: 'Go to next page' })).toBeEnabled(); + }); + expect(actionSample(diagnostics, 'saved view navigation with stack detail open').runtimeErrors).toBe(0); + await measureAction(diagnostics, 'selected stack refresh', async () => { const rowSelection = page.getByRole('checkbox', { name: 'Select row' }).first(); await rowSelection.click(); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte index 7fb2d7ec29..4d2ddf3c11 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte @@ -2,6 +2,7 @@ import type { EventSummaryModel, SummaryTemplateKeys } from '$features/events/components/summary/index'; import type { ProblemDetails } from '@foundatiofx/fetchclient'; + import { beforeNavigate } from '$app/navigation'; import { resolve } from '$app/paths'; import { page } from '$app/state'; import * as DataTable from '$comp/data-table'; @@ -94,6 +95,12 @@ return resolve('/(app)/stack/[stackId=objectid]', { stackId: row.id }); } + beforeNavigate(({ to }) => { + if (selectedStackId && to?.url.pathname !== page.url.pathname) { + selectedStackId = undefined; + } + }); + const DEFAULT_TIME_RANGE = '[now-7d TO now]'; const DEFAULT_FILTERS = [ new DateFilter('date', DEFAULT_TIME_RANGE), From 23623816a4f9e3692de51d8dfd5058a51fcbf6a7 Mon Sep 17 00:00:00 2001 From: "Eric J. Smith" Date: Mon, 10 Aug 2026 23:04:15 -0500 Subject: [PATCH 3/3] Revert "Close stack details before saved view navigation" This reverts commit d6378ad6e0f73f79bf60ce70adb02f759de7d5fd. --- .../e2e/tests/stack-effects-chaos.e2e.ts | 25 ------------------- .../src/routes/(app)/stack/+page.svelte | 7 ------ 2 files changed, 32 deletions(-) diff --git a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts index 9e36b47a26..3f8a565737 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/tests/stack-effects-chaos.e2e.ts @@ -116,31 +116,6 @@ test('stack effects stay bounded through background, paging, and navigation chao }); expect(actionSample(diagnostics, 'stack timeline drag').runtimeErrors).toBe(0); - await measureAction(diagnostics, 'saved view navigation with stack detail open', async () => { - await page.locator('tbody tr:visible').first().click(); - await expect(page.getByRole('dialog')).toBeVisible(); - - const savedViewHrefs = ['/next/stack/all', '/next/stack/most-frequent-errors']; - for (const href of savedViewHrefs) { - await expect(page.locator(`a[href="${href}"]`).first()).toBeAttached(); - } - - await page.evaluate((hrefs) => { - for (let index = 0; index < 20; index++) { - const href = hrefs[index % hrefs.length]!; - document.querySelector(`a[href="${href}"]`)?.click(); - } - }, savedViewHrefs); - - await expect(page).toHaveURL(/\/next\/stack\/most-frequent-errors(?:[?#]|$)/); - await expect(page.getByRole('dialog')).not.toBeVisible(); - await expect(page.getByRole('heading', { exact: true, name: 'Most Frequent Errors' })).toBeVisible(); - - await page.goto('/next/stack?limit=5'); - await expect(page.getByRole('button', { name: 'Go to next page' })).toBeEnabled(); - }); - expect(actionSample(diagnostics, 'saved view navigation with stack detail open').runtimeErrors).toBe(0); - await measureAction(diagnostics, 'selected stack refresh', async () => { const rowSelection = page.getByRole('checkbox', { name: 'Select row' }).first(); await rowSelection.click(); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte index 4d2ddf3c11..7fb2d7ec29 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/stack/+page.svelte @@ -2,7 +2,6 @@ import type { EventSummaryModel, SummaryTemplateKeys } from '$features/events/components/summary/index'; import type { ProblemDetails } from '@foundatiofx/fetchclient'; - import { beforeNavigate } from '$app/navigation'; import { resolve } from '$app/paths'; import { page } from '$app/state'; import * as DataTable from '$comp/data-table'; @@ -95,12 +94,6 @@ return resolve('/(app)/stack/[stackId=objectid]', { stackId: row.id }); } - beforeNavigate(({ to }) => { - if (selectedStackId && to?.url.pathname !== page.url.pathname) { - selectedStackId = undefined; - } - }); - const DEFAULT_TIME_RANGE = '[now-7d TO now]'; const DEFAULT_FILTERS = [ new DateFilter('date', DEFAULT_TIME_RANGE),