fix(mana/web): coerce $page.params.* away from string|undefined

SvelteKit types `$page.params.X` as `string | undefined` because the
runtime cannot prove a route param exists at the type level — even
if the route file lives at e.g. `[id]/+page.svelte` and TS knows the
folder name. Thirteen route files were passing the raw param into
functions that take `string`, producing 25 type errors of the shape:

  Argument of type 'string | undefined' is not assignable to
  parameter of type 'string'.

Fix: hoist the param into a local with `?? ''` at the top of the
script, then use the local everywhere downstream. Empty string is
a safe fallback because the consuming code (`useDeck('')`,
`getCollectionById([], '')`, etc.) all return null/undefined for
unknown ids — exactly what they'd do if the param were truly
missing at runtime, which can't happen given the matching route
folder.

Files touched (one param hoist each):
  calendar/event/[id]              eventId
  cards/decks/[id]                 deckId
  citycorners/.../locations/[id]   citySlug + locId
  citycorners/.../locations/[id]/edit  citySlug + locId
  gifts/redeem/[code]              code
  inventory/collections/[id]       collectionId
  inventory/collections/[id]/edit  collectionId
  inventory/items/[id]             itemId
  photos/albums/[id]               albumId
  picture/board/[id]               boardId
  storage/files/[folderId]         folderId
  zitare/lists/[id]                listId (new local, replaces inline use)
  g/[code]                         code

Net: -24 type errors. The lone remaining "string | undefined" error
is a different bug in inventory FieldDefinition typing — unrelated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-09 18:12:54 +02:00
parent ff6118fc3b
commit b32de06f2a
13 changed files with 22 additions and 20 deletions

View file

@ -13,7 +13,7 @@
const calendarsCtx: { readonly value: Calendar[] } = getContext('calendars');
const eventsCtx: { readonly value: CalendarEvent[] } = getContext('calendarEvents');
let eventId = $derived($page.params.id);
let eventId = $derived($page.params.id ?? '');
let event = $derived(getEventById(eventsCtx.value, eventId));
let calendar = $derived(
event ? getCalendarById(calendarsCtx.value, event.calendarId) : undefined

View file

@ -9,7 +9,7 @@
import { ArrowLeft, Trash, Plus, ShareNetwork } from '@mana/shared-icons';
import { ShareModal } from '@mana/shared-uload';
let deckId = $derived($page.params.id);
let deckId = $derived($page.params.id ?? '');
let showDeleteConfirm = $state(false);
let deleting = $state(false);
let showShare = $state(false);

View file

@ -36,7 +36,7 @@
const cityCtx = getContext<{ value: LocalCity | undefined }>('currentCity');
let city = $derived(cityCtx.value);
let citySlug = $derived($page.params.slug);
let citySlug = $derived($page.params.slug ?? '');
const allFavorites = useAllFavorites();
let favoriteIds = $derived(getFavoriteIds(allFavorites.value));
@ -96,7 +96,7 @@
onMount(async () => {
try {
const locId = $page.params.id;
const locId = $page.params.id ?? '';
const loc = await ccLocationTable.get(locId);
if (loc) {
location = {

View file

@ -10,7 +10,8 @@
const cityCtx = getContext<{ value: LocalCity | undefined }>('currentCity');
let city = $derived(cityCtx.value);
let citySlug = $derived($page.params.slug);
let citySlug = $derived($page.params.slug ?? '');
let locId = $derived(locId ?? '');
let loading = $state(true);
let name = $state('');
@ -43,7 +44,7 @@
onMount(async () => {
try {
const loc = await ccLocationTable.get($page.params.id);
const loc = await ccLocationTable.get(locId);
if (!loc) {
error = $_('edit.loadError');
return;
@ -68,14 +69,14 @@
error = '';
try {
await ccLocationTable.update($page.params.id, {
await ccLocationTable.update(locId, {
name: name.trim(),
category: category as any,
description: description.trim(),
address: address.trim() || null,
imageUrl: imageUrl.trim() || null,
});
goto(`/citycorners/cities/${citySlug}/locations/${$page.params.id}`);
goto(`/citycorners/cities/${citySlug}/locations/${locId}`);
} catch {
error = $_('edit.error');
} finally {
@ -91,7 +92,7 @@
<header class="mb-6">
<div class="flex items-center gap-2 mb-1">
<a
href="/citycorners/cities/{citySlug}/locations/{$page.params.id}"
href="/citycorners/cities/{citySlug}/locations/{locId}"
class="text-foreground-secondary hover:text-primary transition-colors"
>
<CaretLeft size={16} />
@ -112,7 +113,7 @@
<span class="mb-2 block text-4xl">🔒</span>
<p class="text-foreground-secondary">{$_('edit.forbidden')}</p>
<a
href="/citycorners/cities/{citySlug}/locations/{$page.params.id}"
href="/citycorners/cities/{citySlug}/locations/{locId}"
class="mt-4 inline-block text-sm text-primary hover:underline"
>
{$_('detail.back')}
@ -252,7 +253,7 @@
<div class="flex gap-3">
<a
href="/citycorners/cities/{citySlug}/locations/{$page.params.id}"
href="/citycorners/cities/{citySlug}/locations/{locId}"
class="rounded-lg border border-border bg-background px-4 py-3 text-sm font-medium text-foreground-secondary transition-colors hover:bg-background-card-hover"
>
{$_('edit.cancel')}

View file

@ -5,7 +5,7 @@
import { Card, PageHeader } from '@mana/shared-ui';
import { giftsService, type GiftCodeInfo } from '$lib/api/gifts';
let code = $derived($page.params.code);
let code = $derived($page.params.code ?? '');
let giftInfo = $state<GiftCodeInfo | null>(null);
let loading = $state(true);
let redeeming = $state(false);

View file

@ -19,7 +19,7 @@
const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
const itemsCtx: { readonly value: Item[] } = getContext('items');
let collectionId = $derived($page.params.id);
let collectionId = $derived($page.params.id ?? '');
let collection = $derived(getCollectionById(collectionsCtx.value, collectionId));
let items = $derived(getItemsByCollection(itemsCtx.value, collectionId));
let sortedItems = $derived(getSortedItems(items, viewStore.sort));

View file

@ -11,7 +11,7 @@
const collectionsCtx: { readonly value: Collection[] } = getContext('collections');
let collectionId = $derived($page.params.id);
let collectionId = $derived($page.params.id ?? '');
let collection = $derived(getCollectionById(collectionsCtx.value, collectionId));
let name = $state('');

View file

@ -28,7 +28,7 @@
const locationsCtx: { readonly value: Location[] } = getContext('locations');
const categoriesCtx: { readonly value: Category[] } = getContext('categories');
let itemId = $derived($page.params.id);
let itemId = $derived($page.params.id ?? '');
let item = $derived(getItemById(itemsCtx.value, itemId));
let collection = $derived(
item ? getCollectionById(collectionsCtx.value, item.collectionId) : undefined

View file

@ -13,7 +13,7 @@
const allAlbums: { readonly value: Album[] } = getContext('albums');
const allAlbumItems: { readonly value: AlbumItem[] } = getContext('albumItems');
const albumId = $derived($page.params.id);
const albumId = $derived($page.params.id ?? '');
let currentAlbum = $derived(getAlbumById(allAlbums.value, albumId));
let albumItems = $derived(getAlbumItemsForAlbum(allAlbumItems.value, albumId));
let albumPhotos = $derived(albumItems.map((item) => ({ id: item.mediaId }) as Photo));

View file

@ -10,7 +10,7 @@
const allBoards: { value: BoardWithCount[] } = getContext('allBoards');
let boardId = $derived($page.params.id);
let boardId = $derived($page.params.id ?? '');
let board = $derived(findBoardById(allBoards.value, boardId));
// Edit state

View file

@ -16,7 +16,7 @@
const allFiles: { readonly value: StorageFile[] } = getContext('storageFiles');
const allFolders: { readonly value: StorageFolder[] } = getContext('storageFolders');
let folderId = $derived($page.params.folderId);
let folderId = $derived($page.params.folderId ?? '');
// Current folder and its contents
let currentFolder = $derived(findFolderById(allFolders?.value ?? [], folderId));

View file

@ -27,7 +27,8 @@
let selectedQuoteIds = $state<Set<string>>(new Set());
// Reactive list from liveQuery context
let list = $derived<QuoteList | undefined>(findListById(allLists.value, $page.params.id));
let listId = $derived($page.params.id ?? '');
let list = $derived<QuoteList | undefined>(findListById(allLists.value, listId));
// Get quotes in this list
let listQuotes = $derived<Quote[]>(

View file

@ -4,7 +4,7 @@
import { page } from '$app/stores';
import { giftsService, type GiftCodeInfo } from '$lib/api/gifts';
let code = $derived($page.params.code);
let code = $derived($page.params.code ?? '');
let giftInfo = $state<GiftCodeInfo | null>(null);
let loading = $state(true);
let error = $state<string | null>(null);