Skip to content

Commit 700eac4

Browse files
committed
UX: Volume selector polish, HMR crash recovery
- Volume selector: space bar area is now clickable and hoverable (selects the volume) - Volume selector: use `position: fixed` so the dropdown renders over the function key bar, not behind it - Volume selector: dropdown resizes on window resize via `resize` event listener - Volume selector: submenu ("Connect directly") rendered outside the scrollable dropdown to prevent clipping - HMR crash recovery: catch SvelteKit TDZ crash on root layout HMR and auto-reload with `sessionStorage` debounce - Move `virtual:uno.css` HMR handler to `hmr-recovery.ts` (stable module that survives layout re-evaluation) - Fix `effect_orphan`: move `initAiToastSync()` to synchronous `onMount` (before any `await`) - UnoCSS: restrict `content.filesystem` to the 3 files that use icon classes (stops redundant HMR updates)
1 parent 76671bf commit 700eac4

4 files changed

Lines changed: 93 additions & 55 deletions

File tree

apps/desktop/src/lib/file-explorer/navigation/VolumeBreadcrumb.svelte

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,23 @@
109109
async function fitDropdownToViewport() {
110110
await tick()
111111
const dropdown = dropdownRef?.querySelector('.volume-dropdown') as HTMLElement | null
112-
if (dropdown) {
113-
const top = dropdown.getBoundingClientRect().top
112+
const anchor = dropdownRef?.querySelector('.volume-name, .breadcrumb-options-trigger') as HTMLElement | null
113+
if (dropdown && anchor) {
114+
const rect = anchor.getBoundingClientRect()
115+
const top = rect.bottom + 4 // spacing below the breadcrumb
116+
dropdown.style.top = `${String(top)}px`
117+
dropdown.style.left = `${String(rect.left)}px`
114118
dropdown.style.maxHeight = `${String(window.innerHeight - top - 8)}px`
115119
}
116120
}
117121
122+
// Re-fit dropdown on window resize so it adapts to the available space
123+
function handleResize() {
124+
if (isOpen) {
125+
void fitDropdownToViewport()
126+
}
127+
}
128+
118129
async function updateContainingVolume(path: string) {
119130
const { volume: containing } = await resolvePathVolume(path)
120131
containingVolumeId = containing?.id ?? volumeId
@@ -326,6 +337,7 @@
326337
document.addEventListener('click', handleBreadcrumbPopupClickOutside)
327338
document.addEventListener('keydown', handleDocumentKeyDown)
328339
document.addEventListener('keydown', handleBreadcrumbPopupKeyDown)
340+
window.addEventListener('resize', handleResize)
329341
})
330342
331343
onDestroy(() => {
@@ -334,6 +346,7 @@
334346
document.removeEventListener('click', handleBreadcrumbPopupClickOutside)
335347
document.removeEventListener('keydown', handleDocumentKeyDown)
336348
document.removeEventListener('keydown', handleBreadcrumbPopupKeyDown)
349+
window.removeEventListener('resize', handleResize)
337350
})
338351
339352
function getConnectionTooltip(state: SmbConnectionState): string {
@@ -531,40 +544,17 @@
531544
{/if}
532545
{/if}
533546
</div>
534-
{#if submenuVolumeId === volume.id && submenuPosition}
535-
<!-- svelte-ignore a11y_no_static_element_interactions -->
536-
<!-- svelte-ignore a11y_click_events_have_key_events -->
537-
<div
538-
class="connection-submenu"
539-
bind:this={submenuRef}
540-
style:position="fixed"
541-
style:top="{submenuPosition.top}px"
542-
style:left="{submenuPosition.left}px"
543-
onmouseleave={() => {
544-
submenuHighlighted = false
545-
closeSubmenu()
546-
}}
547-
>
548-
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
549-
<div
550-
class="connection-submenu-item"
551-
class:is-highlighted={submenuHighlighted}
552-
onmouseover={() => {
553-
submenuHighlighted = true
554-
}}
555-
onclick={(e: MouseEvent) => {
556-
e.stopPropagation()
557-
void handleSubmenuAction()
558-
}}
559-
>
560-
Connect directly for faster access
561-
</div>
562-
</div>
563-
{/if}
564547
{#if volumeSpaceMap.has(volume.id)}
565548
{@const space = volumeSpaceMap.get(volume.id)}
566549
{#if space}
567-
<div class="volume-space-info">
550+
<!-- svelte-ignore a11y_click_events_have_key_events -->
551+
<!-- svelte-ignore a11y_no_static_element_interactions -->
552+
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
553+
<div
554+
class="volume-space-info"
555+
onclick={() => { void handleVolumeSelect(volume) }}
556+
onmouseover={() => { handleVolumeHover(volume) }}
557+
>
568558
<div class="volume-space-bar">
569559
<div
570560
class="volume-space-fill"
@@ -630,6 +620,36 @@
630620
</div>
631621
{/if}
632622
</div>
623+
{#if submenuVolumeId && submenuPosition}
624+
<!-- svelte-ignore a11y_no_static_element_interactions -->
625+
<!-- svelte-ignore a11y_click_events_have_key_events -->
626+
<div
627+
class="connection-submenu"
628+
bind:this={submenuRef}
629+
style:position="fixed"
630+
style:top="{submenuPosition.top}px"
631+
style:left="{submenuPosition.left}px"
632+
onmouseleave={() => {
633+
submenuHighlighted = false
634+
closeSubmenu()
635+
}}
636+
>
637+
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
638+
<div
639+
class="connection-submenu-item"
640+
class:is-highlighted={submenuHighlighted}
641+
onmouseover={() => {
642+
submenuHighlighted = true
643+
}}
644+
onclick={(e: MouseEvent) => {
645+
e.stopPropagation()
646+
void handleSubmenuAction()
647+
}}
648+
>
649+
Connect directly for faster access
650+
</div>
651+
</div>
652+
{/if}
633653
{/if}
634654
</div>
635655

@@ -699,18 +719,15 @@
699719
}
700720
701721
.volume-dropdown {
702-
position: absolute;
703-
top: 100%;
704-
left: 0;
705-
margin-top: var(--spacing-xs);
722+
position: fixed;
706723
min-width: 220px;
707724
max-height: calc(100vh - 30px); /* Fallback — overridden dynamically by fitDropdownToViewport() */
708725
overflow-y: auto;
709726
background-color: var(--color-bg-secondary);
710727
border: 1px solid var(--color-border-strong);
711728
border-radius: var(--radius-md);
712729
box-shadow: var(--shadow-md);
713-
z-index: var(--z-dropdown);
730+
z-index: var(--z-overlay); /* Above function key bar and other pane elements */
714731
padding: var(--spacing-xs) 0;
715732
}
716733
@@ -1004,8 +1021,8 @@
10041021
border: 1px solid var(--color-border-strong);
10051022
border-radius: var(--radius-md);
10061023
box-shadow: var(--shadow-md);
1007-
/* Must be above the dropdown (--z-dropdown: 100) */
1008-
z-index: calc(var(--z-dropdown) + 1);
1024+
/* Must be above the dropdown (--z-overlay: 200) */
1025+
z-index: calc(var(--z-overlay) + 1);
10091026
padding: var(--spacing-xs) 0;
10101027
}
10111028
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* SvelteKit's client router crashes with "Cannot access 'component' before
3+
* initialization" when HMR updates propagate through the root layout
4+
* (virtual:uno.css, app.css changes). This handler catches the crash and
5+
* forces a clean page reload. Dev-mode only.
6+
*
7+
* Must be imported from a stable module (not the root layout itself) so the
8+
* listener survives layout component re-evaluation during HMR.
9+
*/
10+
if (import.meta.hot) {
11+
const DEBOUNCE_KEY = '__hmr_last_reload'
12+
const DEBOUNCE_MS = 3000
13+
14+
window.addEventListener('unhandledrejection', (event) => {
15+
if (event.reason instanceof ReferenceError && event.reason.message.includes('component')) {
16+
event.preventDefault()
17+
18+
// Debounce via sessionStorage (survives page reloads, unlike JS variables
19+
// which reset when HMR invalidates the module)
20+
const now = Date.now()
21+
const lastReload = Number(sessionStorage.getItem(DEBOUNCE_KEY) ?? '0')
22+
if (now - lastReload < DEBOUNCE_MS) {
23+
console.warn('[HMR] SvelteKit TDZ crash detected, skipping reload (debounce)')
24+
return
25+
}
26+
27+
sessionStorage.setItem(DEBOUNCE_KEY, String(now))
28+
console.warn('[HMR] SvelteKit component TDZ crash detected, reloading page')
29+
location.reload()
30+
}
31+
})
32+
}

apps/desktop/src/routes/+layout.svelte

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,6 @@
99
import '../app.css'
1010
import { initLogger } from '$lib/logging/logger'
1111
12-
// SvelteKit's client router crashes with "Cannot access 'component' before
13-
// initialization" when HMR updates hit the root layout (virtual:uno.css, app.css).
14-
// Catch the crash and force a clean page reload.
15-
if (import.meta.hot) {
16-
window.addEventListener('unhandledrejection', (event) => {
17-
if (
18-
event.reason instanceof ReferenceError &&
19-
event.reason.message.includes('component')
20-
) {
21-
event.preventDefault()
22-
location.reload()
23-
}
24-
})
25-
}
26-
2712
onMount(() => {
2813
void initLogger()
2914
})

apps/desktop/src/routes/+layout.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@
33
// See: https://svelte.dev/docs/kit/single-page-apps
44
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
55
export const ssr = false
6+
7+
// Register HMR crash recovery (must be outside the Svelte component so the
8+
// listener survives layout re-evaluation during HMR)
9+
import '$lib/hmr-recovery'

0 commit comments

Comments
 (0)