From c31ce4448f2b98a2d76d15b3eeeb3b375566598c Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 9 Apr 2026 20:24:05 +0200 Subject: [PATCH] fix(packages): modal keydown handlers, $derived.by usage, UserData fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eight more package-level type errors that all came from the same small handful of patterns. Modal escape-key handlers calling click-style functions Four modals (AuthGateModal, GuestWelcomeModal, ConfirmationPopover, ShareModal) had `onkeydown={(e) => { if (e.key === 'Escape') handleBackdropClick(); }}` — but handleBackdropClick took a MouseEvent parameter, so the no-arg call failed with "Expected 1 arguments, got 0". Fix: route the keyboard escape path through the right no-arg helper (`onClose` / `handleClose` / `handleContinueAsGuest`) or pass the keyboard event through with a cast for the popover trigger that genuinely shares its handler with the click path. WallpaperModal $derived `currentLayout` and `currentBackground` were declared with `$derived(() => {...})` — passing a function expression. The variant that takes a thunk is `$derived.by(...)`; plain `$derived` expects a single value expression. Result: the variables held the arrow function itself, the call sites had to invoke them as `currentLayout()`, and TS rejected the function value where Layout was expected. Switch to `$derived.by`, drop the call-site parens. TagList.svelte Generic param was named `Tag` in the handler signature (`tag: Tag`) but the imported type was aliased as `TagType`. Tag was undefined → "Cannot find name 'Tag'". Renamed to TagType. TagStrip.svelte `dropAccepts?: string[]` is too wide for `passiveDropZone`'s `accepts: DragType[]`. Narrowed the prop type to `DragType[]` and added the missing import. shared-auth/types: UserData.{name,image}? Two more optional fields for the public user shape. Both come from the JWT user_metadata claim when the user has filled in their profile during onboarding. Without these the ProfileStep.svelte onboarding component couldn't read `authStore.user?.name` / `?.image` without `as any`. Added alongside `twoFactorEnabled` from the previous shared-auth commit; same Optional rationale (guest tokens omit the claim). Net: -10 type errors. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/AuthGateModal.svelte | 2 +- .../src/components/GuestWelcomeModal.svelte | 2 +- packages/shared-auth/src/types/index.ts | 7 +++++++ .../src/molecules/ConfirmationPopover.svelte | 2 +- .../src/molecules/tags/TagList.svelte | 2 +- .../shared-ui/src/navigation/TagStrip.svelte | 4 ++-- packages/shared-uload/src/ShareModal.svelte | 2 +- .../src/svelte/WallpaperModal.svelte | 18 ++++++++++++------ 8 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/shared-auth-ui/src/components/AuthGateModal.svelte b/packages/shared-auth-ui/src/components/AuthGateModal.svelte index 9d7917709..d5af4ff68 100644 --- a/packages/shared-auth-ui/src/components/AuthGateModal.svelte +++ b/packages/shared-auth-ui/src/components/AuthGateModal.svelte @@ -207,7 +207,7 @@ class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4" onclick={handleBackdropClick} onkeydown={(e) => { - if (e.key === 'Escape') handleBackdropClick(); + if (e.key === 'Escape') onClose(); }} role="presentation" tabindex="-1" diff --git a/packages/shared-auth-ui/src/components/GuestWelcomeModal.svelte b/packages/shared-auth-ui/src/components/GuestWelcomeModal.svelte index 2ec0e3934..6040a605a 100644 --- a/packages/shared-auth-ui/src/components/GuestWelcomeModal.svelte +++ b/packages/shared-auth-ui/src/components/GuestWelcomeModal.svelte @@ -242,7 +242,7 @@ class="modal-backdrop" onclick={handleBackdropClick} onkeydown={(e) => { - if (e.key === 'Escape') handleBackdropClick(); + if (e.key === 'Escape') handleContinueAsGuest(); }} role="dialog" aria-modal="true" diff --git a/packages/shared-auth/src/types/index.ts b/packages/shared-auth/src/types/index.ts index 515ca0c82..b26b40177 100644 --- a/packages/shared-auth/src/types/index.ts +++ b/packages/shared-auth/src/types/index.ts @@ -59,6 +59,13 @@ export interface UserData { * button on this should default to `false` when undefined. */ twoFactorEnabled?: boolean; + /** + * Display name + avatar URL, populated from the JWT's user_metadata + * claim when the user has filled in their profile. Both are + * optional because the onboarding flow lets users skip this step. + */ + name?: string; + image?: string; } /** diff --git a/packages/shared-ui/src/molecules/ConfirmationPopover.svelte b/packages/shared-ui/src/molecules/ConfirmationPopover.svelte index f85f561f4..829e5eec4 100644 --- a/packages/shared-ui/src/molecules/ConfirmationPopover.svelte +++ b/packages/shared-ui/src/molecules/ConfirmationPopover.svelte @@ -222,7 +222,7 @@ bind:this={triggerRef} onclick={handleTriggerClick} onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') handleTriggerClick(); + if (e.key === 'Enter' || e.key === ' ') handleTriggerClick(e as unknown as MouseEvent); }} role="button" tabindex="0" diff --git a/packages/shared-ui/src/molecules/tags/TagList.svelte b/packages/shared-ui/src/molecules/tags/TagList.svelte index e86539c73..6e2c5e422 100644 --- a/packages/shared-ui/src/molecules/tags/TagList.svelte +++ b/packages/shared-ui/src/molecules/tags/TagList.svelte @@ -29,7 +29,7 @@ return tag.color ?? tag.style?.color ?? DEFAULT_TAG_COLOR; } - function handleKeyDown(e: KeyboardEvent, tag: Tag, action: 'click' | 'edit' | 'delete') { + function handleKeyDown(e: KeyboardEvent, tag: TagType, action: 'click' | 'edit' | 'delete') { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (action === 'click' && onClick) onClick(tag); diff --git a/packages/shared-ui/src/navigation/TagStrip.svelte b/packages/shared-ui/src/navigation/TagStrip.svelte index a67924822..d34595459 100644 --- a/packages/shared-ui/src/navigation/TagStrip.svelte +++ b/packages/shared-ui/src/navigation/TagStrip.svelte @@ -3,7 +3,7 @@ import { Tag, Plus, X } from '@mana/shared-icons'; import { dragSource } from '../dnd/drag-source'; import { passiveDropZone } from '../dnd/passive-drop'; - import type { DragPayload } from '../dnd/types'; + import type { DragPayload, DragType } from '../dnd/types'; interface TagItem { id: string; @@ -23,7 +23,7 @@ /** Called when an item (task, card, etc.) is dropped on a tag pill */ onTagDrop?: (tagId: string, payload: DragPayload) => void; /** Drag types accepted for drop-on-tag (default: ['task']) */ - dropAccepts?: string[]; + dropAccepts?: DragType[]; /** Link for "Tags verwalten" pill */ managementHref?: string; /** Loading state */ diff --git a/packages/shared-uload/src/ShareModal.svelte b/packages/shared-uload/src/ShareModal.svelte index 533b10047..957790e91 100644 --- a/packages/shared-uload/src/ShareModal.svelte +++ b/packages/shared-uload/src/ShareModal.svelte @@ -106,7 +106,7 @@ style="z-index: 9990;" onclick={handleBackdropClick} onkeydown={(e) => { - if (e.key === 'Escape') handleBackdropClick(); + if (e.key === 'Escape') handleClose(); }} role="presentation" tabindex="-1" diff --git a/packages/wallpaper-generator/src/svelte/WallpaperModal.svelte b/packages/wallpaper-generator/src/svelte/WallpaperModal.svelte index 73c8cbe3e..1a65fcca7 100644 --- a/packages/wallpaper-generator/src/svelte/WallpaperModal.svelte +++ b/packages/wallpaper-generator/src/svelte/WallpaperModal.svelte @@ -48,7 +48,13 @@ const currentDevices = $derived(devicesByCategory[selectedCategory] || []); - const currentLayout = $derived(() => { + // `$derived.by(...)` is the variant that takes a thunk and runs it + // inside the derivation. Plain `$derived(expr)` only takes a single + // expression — passing an arrow function there made `currentLayout` + // itself a function value, which is why the call sites below had to + // invoke it as `currentLayout()`. Both call sites now read it as a + // plain value. + const currentLayout = $derived.by(() => { if (layoutType === 'center') { return { type: 'center', scale: layoutScale }; } else if (layoutType === 'corner') { @@ -58,7 +64,7 @@ } }); - const currentBackground = $derived(() => { + const currentBackground = $derived.by(() => { if (backgroundType === 'solid') { return { type: 'solid', color: solidColor }; } else { @@ -83,8 +89,8 @@ { type: 'dataUrl', data: imageDataUrl }, { device: selectedDeviceId, - layout: currentLayout(), - background: currentBackground(), + layout: currentLayout, + background: currentBackground, } ); previewUrl = url; @@ -102,8 +108,8 @@ { type: 'dataUrl', data: imageDataUrl }, { device: selectedDeviceId, - layout: currentLayout(), - background: currentBackground(), + layout: currentLayout, + background: currentBackground, format: 'png', } );