From 2b4b1bc79cd65f68834e39334741d80bc8ad5e37 Mon Sep 17 00:00:00 2001 From: ajianaz Date: Thu, 3 Sep 2026 11:56:37 +0700 Subject: [PATCH 1/2] feat(ui): issue detail page with event list and status actions - Add /issues/[issueId] page: metadata stats, resolve/ignore actions, paginated event list linking to event detail - Extract exception type/value summary from Sentry-format payloads - Extend e2e: event list navigation + resolve action coverage --- web/build/index.html | 16 +- web/e2e/issues.spec.ts | 29 ++ .../(dashboard)/issues/[issueId]/+page.svelte | 299 ++++++++++++++++++ 3 files changed, 336 insertions(+), 8 deletions(-) create mode 100644 web/src/routes/(dashboard)/issues/[issueId]/+page.svelte diff --git a/web/build/index.html b/web/build/index.html index d460663..61aa54e 100644 --- a/web/build/index.html +++ b/web/build/index.html @@ -4,32 +4,32 @@ - - + + - + - + - +
+ + + {issue ? issue.title : 'Issue'} · TrapFall + + + + +
+ {#if loading} +
+ + +
+ {#each Array(4) as _} + + {/each} +
+ +
+ {:else if error} +
+

{error}

+
+ {:else if issue} + +
+ + {#if projectSlug} + · + {projectSlug} + {/if} + · + Press ESC to go back +
+ + +
+
+ + {issue.level} + + + {issue.status} + +
+
+

{issue.title}

+
+ {#if issue.status === 'resolved'} + + {:else} + + {/if} + {#if issue.status === 'ignored'} + + {:else} + + {/if} +
+
+ {#if issue.culprit} +

{issue.culprit}

+ {/if} + {#if statusError} +

{statusError}

+ {/if} +
+ + +
+ + +

Events

+

{issue.count}

+
+
+ + +

Users affected

+

{issue.user_count}

+
+
+ + +

First seen

+

+ {timeAgo(issue.first_seen)} +

+
+
+ + +

Last seen

+

+ {timeAgo(issue.last_seen)} +

+
+
+
+ + +
+
+

Events

+ {#if totalEvents > 0} + {totalEvents} total + {/if} +
+ + {#if events.length === 0} +
+

No events recorded for this issue yet.

+
+ {:else} +
+ + + + Summary + + Received + + + + {#each events as event (event.id)} + goToEvent(event)} + > + + {eventSummary(event)} + + + + {timeAgo(event.received_at)} + + + {/each} + +
+
+ + + {#if totalEventPages > 1} +
+

+ Page {eventsPage} of {totalEventPages} +

+
+ + +
+
+ {/if} + {/if} +
+ {/if} +
From 7a9172802e4e39cf4af4412d0529b4a06cdb86e3 Mon Sep 17 00:00:00 2001 From: ajianaz Date: Thu, 3 Sep 2026 12:08:14 +0700 Subject: [PATCH 2/2] test(e2e): make resolve-action test idempotent across runs --- web/e2e/issues.spec.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/web/e2e/issues.spec.ts b/web/e2e/issues.spec.ts index 9f0176f..a39fffe 100644 --- a/web/e2e/issues.spec.ts +++ b/web/e2e/issues.spec.ts @@ -99,13 +99,17 @@ test.describe('Issue detail view', () => { await issueRows(page).first().click(); await expect(page).toHaveURL(/\/issues\/[a-f0-9-]+/i); - const resolveBtn = page.getByRole('button', { name: 'Resolve' }); - await expect(resolveBtn).toBeVisible({ timeout: 10000 }); - await resolveBtn.click(); + // Status persists across runs, so normalize to unresolved first. + const resolveBtn = page.getByRole('button', { name: 'Resolve', exact: true }); + const unresolveBtn = page.getByRole('button', { name: 'Unresolve', exact: true }); + await expect(resolveBtn.or(unresolveBtn)).toBeVisible({ timeout: 10000 }); + if (await unresolveBtn.isVisible()) { + await unresolveBtn.click(); + await expect(resolveBtn).toBeVisible({ timeout: 10000 }); + } - // After resolving, the status badge shows "resolved" and the primary - // action flips to Unresolve. - await expect(page.getByRole('button', { name: 'Unresolve' })).toBeVisible({ timeout: 10000 }); - await expect(page.locator('span.badge, span', { hasText: 'resolved' }).first()).toBeVisible(); + // Resolve, then the primary action flips to Unresolve. + await resolveBtn.click(); + await expect(unresolveBtn).toBeVisible({ timeout: 10000 }); }); });