+88
docs/frontend/navigation.md
+88
docs/frontend/navigation.md
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
# client-side navigation
2
+
3
+
## preserving player state across navigation
4
+
5
+
the player lives in the root layout (`+layout.svelte`) and persists across all page navigations. to maintain uninterrupted playback, client-side navigation must work correctly.
6
+
7
+
## critical rule: never use `stopPropagation()` on links
8
+
9
+
**problem**: calling `e.stopPropagation()` on link click handlers breaks SvelteKit's client-side router, causing full page reloads that unmount and remount the player.
10
+
11
+
```svelte
12
+
<!-- ❌ WRONG - causes full page reload, interrupts playback -->
13
+
<a href="/tag/{tag}" onclick={(e) => e.stopPropagation()}>{tag}</a>
14
+
```
15
+
16
+
```svelte
17
+
<!-- ✅ CORRECT - client-side navigation works, playback continues -->
18
+
<a href="/tag/{tag}">{tag}</a>
19
+
```
20
+
21
+
## handling links inside clickable containers
22
+
23
+
when you have links nested inside a clickable button/div, check the event target instead of using `stopPropagation()`:
24
+
25
+
```svelte
26
+
<button
27
+
onclick={(e) => {
28
+
// skip if user clicked a link inside
29
+
if (e.target instanceof HTMLAnchorElement || (e.target as HTMLElement).closest('a')) {
30
+
return;
31
+
}
32
+
doSomething();
33
+
}}
34
+
>
35
+
<span>click me</span>
36
+
<a href="/other-page">or click this link</a>
37
+
</button>
38
+
```
39
+
40
+
this pattern:
41
+
1. lets the link trigger proper client-side navigation
42
+
2. only calls `doSomething()` when clicking non-link elements
43
+
3. preserves all global state including the player
44
+
45
+
## why this matters
46
+
47
+
SvelteKit's client-side router intercepts `<a>` tag clicks to:
48
+
- avoid full page reload
49
+
- preserve global state (player, queue, auth)
50
+
- enable smooth transitions
51
+
52
+
when `stopPropagation()` is called, the click event never reaches SvelteKit's router, falling back to native browser navigation which:
53
+
- performs a full page reload
54
+
- unmounts and remounts all components
55
+
- resets audio playback
56
+
57
+
## examples from the codebase
58
+
59
+
### TrackItem.svelte
60
+
61
+
the track container is a button that plays the track on click. it contains multiple links (artist, album, tags) that should navigate without affecting playback:
62
+
63
+
```svelte
64
+
<button
65
+
class="track"
66
+
onclick={(e) => {
67
+
// only play if clicking the track itself, not a link inside
68
+
if (e.target instanceof HTMLAnchorElement || (e.target as HTMLElement).closest('a')) {
69
+
return;
70
+
}
71
+
onPlay(track);
72
+
}}
73
+
>
74
+
<a href="/u/{track.artist_handle}" class="artist-link">{track.artist}</a>
75
+
<a href="/tag/{tag}" class="tag-badge">{tag}</a>
76
+
</button>
77
+
```
78
+
79
+
## debugging navigation issues
80
+
81
+
**symptom**: clicking a link stops music playback
82
+
83
+
**diagnosis**:
84
+
1. check if the link has `onclick={(e) => e.stopPropagation()}`
85
+
2. check parent elements for event handling that might interfere
86
+
3. verify the route uses SvelteKit conventions (`+page.svelte`, `+page.ts`)
87
+
88
+
**fix**: remove `stopPropagation()` and use event target checking in parent handlers instead
+1
-1
frontend/src/lib/components/TrackItem.svelte
+1
-1
frontend/src/lib/components/TrackItem.svelte