Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions packages/sveltekit/playground/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Test playground for Hawk Error Tracker integration with SvelteKit.
## Table of Contents

- [Getting Started](#getting-started)
- [Hawk Integration](#hawk-integration)
- [Error Handling](#error-handling)
- [Error Test Pages](#error-test-pages)

## Getting Started

Expand Down Expand Up @@ -40,3 +43,70 @@ Hawk automatically registers global error handlers for:

- `window.onerror`
- `window.onunhandledrejection`

**Note:** Hawk Catcher currently catches only client-side errors via global event listeners (🟡).

## Error Handling

### Global Error Handlers (🟡)

Global browser error handlers that catch unhandled errors:

- **`window.error`**
- **`window.unhandledrejection`**

**Caught by Hawk Catcher.**

### Error Boundaries (🟢)

Svelte `<svelte:boundary>` catches errors during:

- Component rendering (synchronous errors in component body)
- Component initialization

Example usage:

```svelte

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so we don't forget to change this

<svelte:boundary onerror={handleBoundaryError} {failed}>
<ErrorProneComponent />

{#snippet failed(error, reset)}
<p>Error: {error.message}</p>
<button onclick={reset}>Reset</button>
{/snippet}
</svelte:boundary>
```

**Not caught by Hawk Catcher.**

### SSR Errors (🔴)

Server-side errors in `load` functions are caught by `handleError` hook in `hooks.server.ts`.

**Not caught by Hawk Catcher.**

## Error Test Pages

The playground includes test pages to demonstrate each error catching mechanism:

### Global Error Handlers (🟡) - Caught by Hawk

1. **Runtime Error** (`/errors/runtime-error`)
- Demonstrates synchronous error in event handler
- Caught by window event listener `error`

2. **Promise Rejection** (`/errors/promise-rejection`)
- Demonstrates unhandled Promise rejection
- Caught by window event listener `unhandledrejection`

### Error Boundaries (🟢) - Not caught by Hawk

1. **Boundary Error** (`/errors/boundary`)
- Demonstrates svelte boundary feature
- Caught by `<svelte:boundary>`

### SSR Errors (🔴) - Not caught by Hawk

1. **Server-side Error** (`/errors/server-error`)
- Demonstrates error in `load` function
- Caught by `handleError` in `hooks.server.ts`
169 changes: 169 additions & 0 deletions packages/sveltekit/playground/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');

:root {
--bg-primary: #2f3341;
--bg-secondary: #242732;
--text-primary: #dbe6ff;
--text-secondary: rgba(219, 230, 255, 0.6);
--border-color: rgba(219, 230, 255, 0.1);
--button-primary: #4979e4;
--button-primary-hover: #4869d2;
}

body {
font-family: Roboto, system-ui, sans-serif;
margin: 0;
padding: 0;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 13px;
}

header {
padding: 30px;
background: var(--bg-secondary);
}

h1 {
font-weight: bold;
font-size: 20px;
margin: 0 0 15px;
color: var(--text-primary);
}

h2 {
font-weight: 500;
margin: 0 0 10px;
font-size: 13px;
color: var(--text-secondary);
letter-spacing: 0.24px;
text-transform: uppercase;
}

h3 {
margin: 0 0 10px 0;
color: var(--text-primary);
font-size: 16px;
font-weight: 500;
}

p {
margin: 0.5rem 0;
color: var(--text-primary);
}

a {
color: inherit;
text-decoration: underline;
}

a:hover {
color: var(--button-primary);
}

section {
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 4px;
margin: 15px;
}

button {
display: inline-block;
padding: 8px 20px;
border: 0;
border-radius: 5px;
background: var(--button-primary);
color: var(--text-primary);
font-weight: 500;
font-size: 14px;
cursor: pointer;
font-family: inherit;
}

button:hover {
background: var(--button-primary-hover);
}

button:disabled {
opacity: 0.5;
cursor: not-allowed;
}

code {
background: var(--bg-secondary);
padding: 2px 6px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9em;
}

.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}

.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 15px;
}

.test-card {
display: block;
padding: 20px;
border: 2px solid var(--border-color);
border-radius: 8px;
text-decoration: none;
color: inherit;
transition: all 0.2s ease;
background: var(--bg-secondary);
}

.test-card:hover {
border-color: var(--button-primary);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}

.test-card h3 {
margin: 0 0 10px 0;
font-size: 16px;
}

.test-card p {
margin: 0;
color: var(--text-secondary);
font-size: 13px;
line-height: 1.5;
}

.alert {
padding: 15px;
border-left: 4px solid;
border-radius: 4px;
margin: 15px 0;
}

.alert-warning {
background: rgba(255, 193, 7, 0.15);
border-color: #ffc107;
color: #ffd54f;
}

.error-fallback {
padding: 1.5rem;
background: rgba(244, 67, 54, 0.15);
border-left: 4px solid #f44336;
border-radius: 4px;
}

.error-fallback h3 {
margin: 0 0 0.5rem 0;
color: #ff8a80;
}

.error-fallback p {
margin: 0.5rem 0;
}
8 changes: 8 additions & 0 deletions packages/sveltekit/playground/src/hooks.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ if (import.meta.env.VITE_HAWK_TOKEN) {
token: import.meta.env.VITE_HAWK_TOKEN,
});
}

window.addEventListener('error', (event) => {
console.error('🟡 [window.error]', event.error, `at ${event.filename}:${event.lineno}`);
});

window.addEventListener('unhandledrejection', (event) => {
console.error('🟡 [window.unhandledrejection]', event.reason);
});
10 changes: 10 additions & 0 deletions packages/sveltekit/playground/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { HandleServerError } from '@sveltejs/kit';

export const handleError: HandleServerError = async ({ error, event, status, message }) => {
console.error('🔴 [server.handleError]', error, `at ${event.url.pathname}`);

return {
message,
status,
};
};
78 changes: 78 additions & 0 deletions packages/sveltekit/playground/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,3 +1,81 @@
<script lang="ts">
interface ErrorTest {
title: string;
description: string;
href: string;
category: string;
}

const errorTests: ErrorTest[] = [
// Window Error Handlers
{
title: 'Synchronous Runtime Error',
description: 'Error thrown in event handler',
href: '/errors/runtime-error',
category: 'Global Error Handlers (🟡)'
},
{
title: 'Unhandled Promise Rejection',
description: 'Promise rejected without catch handler',
href: '/errors/promise-rejection',
category: 'Global Error Handlers (🟡)'
},

// Error Boundaries
{
title: 'Boundary Error',
description: 'Error caught by svelte:boundary',
href: '/errors/boundary',
category: 'Error Boundaries (🟢)'
},

// SSR Errors
{
title: 'Server-side Error',
description: 'Error thrown in load function',
href: '/errors/server-error',
category: 'SSR Errors (🔴)'
},
];

const categories = Array.from(new Set(errorTests.map(t => t.category)));
</script>

<svelte:head>
<title>Hawk Javascript SvelteKit Integration Playground</title>
</svelte:head>

<div class="container">
<header>
<h1>🧪 SvelteKit Error Handling Test Suite</h1>
</header>

<div class="alert alert-warning">
<strong>⚠️ Testing Instructions:</strong>
<ul>
<li>Open your browser's DevTools Console to see error logs</li>
<li>Look for colored emoji markers:
<ul>
<li>🔴 = SSR error (caught by <code>handleError</code> in hooks.server.ts)</li>
<li>🟡 = Caught by global <code>window.error</code> or <code>window.unhandledrejection</code></li>
<li>🟢 = Caught by <code>&lt;svelte:boundary&gt;</code></li>
</ul>
</li>
<li>Each test demonstrates where errors are caught in the SvelteKit error handling hierarchy</li>
</ul>
</div>

{#each categories as category}
<section>
<h2>{category}</h2>
<div class="grid">
{#each errorTests.filter(t => t.category === category) as test}
<a href={test.href} class="test-card" data-sveltekit-preload-data="off">
<h3>{test.title}</h3>
<p>{test.description}</p>
</a>
{/each}
</div>
</section>
{/each}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import ErrorComponent from './ErrorComponent.svelte';

let showError = $state(false);

function triggerError() {
showError = true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider open/close behaviour with showError = !showError

}

function handleBoundaryError(error: Error) {
console.error('🟢 [svelte:boundary] Caught error:', error);
}
</script>

<div class="container">
<h1>Error Boundary Test</h1>
<p>Click the button to trigger a rendering error.</p>
<p><strong>Caught by:</strong> <code>&lt;svelte:boundary&gt;</code> (🟢 green dot in console)</p>

<button onclick={triggerError} disabled={showError}>
Trigger Error
</button>

{#snippet failed(error, reset)}
<div class="error-fallback">
<h3>Error Boundary Caught Error</h3>
<p>Message: {error.message}</p>
<button onclick={() => { showError = false; reset(); }}>Reset</button>
</div>
{/snippet}

<svelte:boundary onerror={handleBoundaryError} {failed}>
{#if showError}
<ErrorComponent shouldError={true} />
{/if}
</svelte:boundary>
</div>
Loading
Loading