From c38121faa60ebdeca2b98d734039d56b67321bd5 Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 20:38:05 +0100 Subject: [PATCH 01/11] =?UTF-8?q?=F0=9F=9A=80=20Migrate=20todo=20route=20a?= =?UTF-8?q?nd=20all=20corresponding=20components=20to=20Svelte=205=20runes?= =?UTF-8?q?=20syntax?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/components/todo/Todo.svelte | 2 ++ .../src/lib/components/todo/TodoInput.svelte | 6 +++++- .../src/lib/components/todo/TodoList.svelte | 19 ++++++++++++++++--- .../components/todo/TodoListOverlay.svelte | 19 +++++++++++++++---- frontend/src/routes/todo/+page.svelte | 19 ++++++++++++++----- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/frontend/src/lib/components/todo/Todo.svelte b/frontend/src/lib/components/todo/Todo.svelte index 1e755371..4bfe77af 100644 --- a/frontend/src/lib/components/todo/Todo.svelte +++ b/frontend/src/lib/components/todo/Todo.svelte @@ -1,10 +1,12 @@ {#if $initialized} diff --git a/frontend/src/lib/components/todo/TodoListOverlay.svelte b/frontend/src/lib/components/todo/TodoListOverlay.svelte index 74bdb1cd..ef0f1200 100644 --- a/frontend/src/lib/components/todo/TodoListOverlay.svelte +++ b/frontend/src/lib/components/todo/TodoListOverlay.svelte @@ -1,4 +1,5 @@
diff --git a/frontend/src/lib/components/todo/TodoInput.svelte b/frontend/src/lib/components/todo/TodoInput.svelte index ed1d69e9..f4c9b053 100644 --- a/frontend/src/lib/components/todo/TodoInput.svelte +++ b/frontend/src/lib/components/todo/TodoInput.svelte @@ -6,7 +6,7 @@ import InputWithButton from '../shared/custom-carbon-components/InputWithButton.svelte'; // 2. PROPS - export let listId; + let { listId } = $props(); // 4. STATE let newTodoName = $state(''); diff --git a/frontend/src/lib/components/todo/TodoList.svelte b/frontend/src/lib/components/todo/TodoList.svelte index 4d66cdbf..6fd81b1e 100644 --- a/frontend/src/lib/components/todo/TodoList.svelte +++ b/frontend/src/lib/components/todo/TodoList.svelte @@ -11,7 +11,7 @@ import TodoInput from './TodoInput.svelte'; // 2. PROPS - export let listId; + let { listId } = $props(); // 4. STATE let list = $state(undefined); diff --git a/frontend/src/lib/components/todo/TodoListOverlay.svelte b/frontend/src/lib/components/todo/TodoListOverlay.svelte index ef0f1200..0e07b02d 100644 --- a/frontend/src/lib/components/todo/TodoListOverlay.svelte +++ b/frontend/src/lib/components/todo/TodoListOverlay.svelte @@ -12,9 +12,7 @@ import Save from 'carbon-icons-svelte/lib/Save.svelte'; // 2. PROPS - export let listId; - export let listName = ''; - export let listEmoji = ''; + let { listId, listName = '', listEmoji = '' } = $props(); // 4. STATE let listIndex = $state(-1); From 41fcb5a4e3a16ff686697c14adbe49ba12b23d28 Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 21:28:36 +0100 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=90=9B=20Fix=20modalStates=20functi?= =?UTF-8?q?on=20call=20error=20in=20TodoListOverlay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/components/todo/TodoListOverlay.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/todo/TodoListOverlay.svelte b/frontend/src/lib/components/todo/TodoListOverlay.svelte index 0e07b02d..10e16f82 100644 --- a/frontend/src/lib/components/todo/TodoListOverlay.svelte +++ b/frontend/src/lib/components/todo/TodoListOverlay.svelte @@ -109,7 +109,7 @@ if (detail.text === $t('page.shared.abort')) closeOverlay(); if (detail.text === $t('page.shared.delete')) deleteList(listIndex); }} - class={modalStates()} + class={modalStates} > {#if $initialized} {#if $listOverlayOptionsStore.type === 'new'} From 1d61950714121a3d86c85f71d67c4ca49d0ffc61 Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 21:34:43 +0100 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=90=9B=20Fix=20console.log=20with?= =?UTF-8?q?=20=20proxies=20warning=20in=20TodoList?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/components/todo/TodoList.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/todo/TodoList.svelte b/frontend/src/lib/components/todo/TodoList.svelte index 6fd81b1e..4cf6a7ed 100644 --- a/frontend/src/lib/components/todo/TodoList.svelte +++ b/frontend/src/lib/components/todo/TodoList.svelte @@ -18,8 +18,8 @@ // 8. EFFECTS $effect(() => { - if (listId) console.log('listId', listId); - if (list) console.log('list', list); + if (listId) console.log('listId', $inspect(listId)); + if (list) console.log('list', $inspect(list)); }); // 5. FUNCTIONS From 43ccc3bdd545715f19900264380492a7cde48ebc Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 21:36:39 +0100 Subject: [PATCH 07/11] =?UTF-8?q?=F0=9F=90=9B=20Fix=20list=20not=20updatin?= =?UTF-8?q?g=20when=20listId=20changes=20in=20TodoList?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/components/todo/TodoList.svelte | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/todo/TodoList.svelte b/frontend/src/lib/components/todo/TodoList.svelte index 4cf6a7ed..9e71661b 100644 --- a/frontend/src/lib/components/todo/TodoList.svelte +++ b/frontend/src/lib/components/todo/TodoList.svelte @@ -18,8 +18,11 @@ // 8. EFFECTS $effect(() => { - if (listId) console.log('listId', $inspect(listId)); - if (list) console.log('list', $inspect(list)); + if (listId) { + console.log('listId', $inspect(listId)); + list = $todoStore.find((l) => l.id === listId); + if (list) console.log('list', $inspect(list)); + } }); // 5. FUNCTIONS From 0937a581b32254d5d36ed8f09255de8c3ff1dd6c Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 21:38:00 +0100 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=90=9B=20Fix=20infinite=20loop=20by?= =?UTF-8?q?=20using=20=20for=20list=20in=20TodoList?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/components/todo/TodoList.svelte | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/todo/TodoList.svelte b/frontend/src/lib/components/todo/TodoList.svelte index 9e71661b..7e290bc1 100644 --- a/frontend/src/lib/components/todo/TodoList.svelte +++ b/frontend/src/lib/components/todo/TodoList.svelte @@ -13,14 +13,13 @@ // 2. PROPS let { listId } = $props(); - // 4. STATE - let list = $state(undefined); + // 5. DERIVED + const list = $derived(listId ? $todoStore.find((l) => l.id === listId) : undefined); // 8. EFFECTS $effect(() => { if (listId) { console.log('listId', $inspect(listId)); - list = $todoStore.find((l) => l.id === listId); if (list) console.log('list', $inspect(list)); } }); From 0ff375c3082f15ce88b9a0f4bd0397925e71368d Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 23:39:07 +0100 Subject: [PATCH 09/11] =?UTF-8?q?=F0=9F=9A=80=20Enhance=20todo=20component?= =?UTF-8?q?s=20with=20improved=20UI,=20better=20state=20management,=20and?= =?UTF-8?q?=20bug=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/components/todo/Todo.svelte | 95 ++++++++++++--- .../src/lib/components/todo/TodoInput.svelte | 34 +++--- .../src/lib/components/todo/TodoList.svelte | 115 ++++++++++++------ .../components/todo/TodoListOverlay.svelte | 89 ++++++++------ frontend/src/routes/todo/+page.svelte | 29 +++-- 5 files changed, 241 insertions(+), 121 deletions(-) diff --git a/frontend/src/lib/components/todo/Todo.svelte b/frontend/src/lib/components/todo/Todo.svelte index d3134388..31025fa5 100644 --- a/frontend/src/lib/components/todo/Todo.svelte +++ b/frontend/src/lib/components/todo/Todo.svelte @@ -7,26 +7,49 @@ import TrashCan from 'carbon-icons-svelte/lib/TrashCan.svelte'; // 2. PROPS - let { todo, deleteTodo, todoChecked } = $props(); + let { + todo, + deleteTodo, + todoChecked, + }: { + todo: Todo; + deleteTodo: () => void; + todoChecked: () => void; + } = $props(); + + // 5. DERIVED + let todoTitle = $derived(todo.title); + let isDone = $derived(todo.done); -
- - - {todo?.title} +
+
diff --git a/frontend/src/lib/components/todo/TodoInput.svelte b/frontend/src/lib/components/todo/TodoInput.svelte index f4c9b053..0ae60261 100644 --- a/frontend/src/lib/components/todo/TodoInput.svelte +++ b/frontend/src/lib/components/todo/TodoInput.svelte @@ -6,34 +6,36 @@ import InputWithButton from '../shared/custom-carbon-components/InputWithButton.svelte'; // 2. PROPS - let { listId } = $props(); + let { listId }: { listId: string } = $props(); // 4. STATE let newTodoName = $state(''); - // 5. FUNCTIONS + // 8. FUNCTIONS const saveTodo = () => { if (newTodoName) { todoStore.update((todoListArray) => { - const list = todoListArray.find((list) => list.id === listId); - list.todos.push({ - id: crypto.randomUUID(), - title: newTodoName, - done: false, + return todoListArray.map((list) => { + if (list.id === listId) { + return { + ...list, + todos: [ + ...list.todos, + { + id: crypto.randomUUID(), + title: newTodoName, + done: false, + }, + ], + history: Array.from(new Set([...list.history, newTodoName])), + }; + } + return list; }); - return [...todoListArray]; }); - addToHistory(newTodoName); newTodoName = ''; } }; - const addToHistory = (todoName) => { - todoStore.update((todoListArray) => { - const list = todoListArray.find((list) => list.id === listId); - list.history = Array.from(new Set(list.history).add(todoName)); - return todoListArray; - }); - }; {#if $initialized} diff --git a/frontend/src/lib/components/todo/TodoList.svelte b/frontend/src/lib/components/todo/TodoList.svelte index 7e290bc1..ed878045 100644 --- a/frontend/src/lib/components/todo/TodoList.svelte +++ b/frontend/src/lib/components/todo/TodoList.svelte @@ -11,41 +11,69 @@ import TodoInput from './TodoInput.svelte'; // 2. PROPS - let { listId } = $props(); + let { listId }: { listId: string } = $props(); // 5. DERIVED - const list = $derived(listId ? $todoStore.find((l) => l.id === listId) : undefined); + let list = $derived.by(() => { + const allLists = $todoStore; + const foundList = listId ? allLists.find((l) => l.id === listId) : undefined; - // 8. EFFECTS - $effect(() => { - if (listId) { - console.log('listId', $inspect(listId)); - if (list) console.log('list', $inspect(list)); + if (foundList) { + // Sort todos: unchecked first, checked last + const sortedTodos = [...foundList.todos].sort((a, b) => { + if (a.done === b.done) return 0; + return a.done ? 1 : -1; + }); + + return { + ...foundList, + todos: sortedTodos, + }; } + + return undefined; }); - // 5. FUNCTIONS + // 8. FUNCTIONS const deleteTodo = (todoId: string) => { todoStore.update((todoListArray) => { - const list = todoListArray.find((list) => list.id === listId); - const todoIndex = list.todos.findIndex((todo) => todo.id === todoId); - list.todos.splice(todoIndex, 1); - return todoListArray; + return todoListArray.map((list) => { + if (list.id === listId) { + return { + ...list, + todos: list.todos.filter((todo) => todo.id !== todoId), + }; + } + return list; + }); }); }; const checkTodo = (todoId: string) => { todoStore.update((todoListArray) => { - const list = todoListArray.find((list) => list.id === listId); - const todoIndex = list.todos.findIndex((todo) => todo.id === todoId); - list.todos[todoIndex].done = !list.todos[todoIndex].done; - return todoListArray; + return todoListArray.map((list) => { + if (list.id === listId) { + return { + ...list, + todos: list.todos.map((todo) => + todo.id === todoId ? { ...todo, done: !todo.done } : todo, + ), + }; + } + return list; + }); }); }; const clearHistory = () => { todoStore.update((todoListArray) => { - const list = todoListArray.find((list) => list.id === listId); - list.history = []; - return todoListArray; + return todoListArray.map((list) => { + if (list.id === listId) { + return { + ...list, + history: [], + }; + } + return list; + }); }); }; const selectRandomTagColor = (): @@ -90,6 +118,7 @@ ]; return colors[Math.floor(Math.random() * colors.length)]; }; + const removeEntryFromHistory = (event) => { const tagText = event.explicitOriginalTarget.parentElement.parentElement.textContent.trim() || @@ -98,11 +127,15 @@ if (!tagText) return; todoStore.update((todoListArray) => { - const list = todoListArray.find((list) => list.id === listId); - list.history = list.history.filter((entry) => { - return entry !== tagText; + return todoListArray.map((list) => { + if (list.id === listId) { + return { + ...list, + history: list.history.filter((entry) => entry !== tagText), + }; + } + return list; }); - return todoListArray; }); }; const readdTodoFromHistory = (event) => { @@ -111,16 +144,20 @@ if (!tagText) return; todoStore.update((todoListArray) => { - const list = todoListArray.find((list) => list.id === listId); - list.todos.push({ id: crypto.randomUUID(), title: tagText, done: false }); - return [...todoListArray]; + return todoListArray.map((list) => { + if (list.id === listId) { + return { + ...list, + todos: [ + ...list.todos, + { id: crypto.randomUUID(), title: tagText, done: false }, + ], + }; + } + return list; + }); }); }; - - // 8. EFFECTS - $effect(() => { - list = $todoStore.find((list) => list.id === listId); - }); {#if $initialized} @@ -141,7 +178,7 @@ {#if list?.history?.length > 0}
- {#each list?.history as entry} + {#each list.history as entry (entry)}
- {#each list?.todos as todo} - {#if todo} - deleteTodo(todo.id)} - todoChecked={() => checkTodo(todo.id)} - /> - {/if} + {#each list?.todos || [] as todo (todo.id)} + deleteTodo(todo.id)} + todoChecked={() => checkTodo(todo.id)} + /> {/each}
diff --git a/frontend/src/lib/components/todo/TodoListOverlay.svelte b/frontend/src/lib/components/todo/TodoListOverlay.svelte index 10e16f82..a4add8d8 100644 --- a/frontend/src/lib/components/todo/TodoListOverlay.svelte +++ b/frontend/src/lib/components/todo/TodoListOverlay.svelte @@ -12,69 +12,81 @@ import Save from 'carbon-icons-svelte/lib/Save.svelte'; // 2. PROPS - let { listId, listName = '', listEmoji = '' } = $props(); + let { + listId, + listName = '', + listEmoji = '', + }: { + listId: string; + listName?: string; + listEmoji?: string; + } = $props(); // 4. STATE - let listIndex = $state(-1); - let modalStates = $state(''); + let localListName = $state(listName); + let localListEmoji = $state(listEmoji); - // 8. EFFECTS - $effect(() => { - listIndex = $todoStore.findIndex((list) => list.id === listId); - }); - - $effect(() => { + // 5. DERIVED + let listIndex = $derived($todoStore.findIndex((list) => list.id === listId)); + let modalStates = $derived.by(() => { let classes = ''; - - if (!listName || (!isEmoji(listEmoji) && listEmoji !== '')) + if (!localListName || (!isEmoji(localListEmoji) && localListEmoji !== '')) classes += 'modal_unsavable'; if ($listOverlayOptionsStore.type === 'new') classes += ' modal_undeletable'; + return classes; + }); - modalStates = classes; + // 6. EFFECTS + $effect(() => { + localListName = listName || ''; + localListEmoji = listEmoji || ''; }); - // 5. FUNCTIONS - // 5. FUNCTIONS + // 8. FUNCTIONS const createList = () => { - const list: TodoList = { + const newList: TodoList = { id: listId || crypto.randomUUID(), - name: listName, - emoji: listEmoji || '📝', + name: localListName, + emoji: localListEmoji || '📝', history: [], todos: [], }; todoStore.update((n) => { - return [...n, list]; + return [...n, newList]; }); closeOverlay(); celebrate(); + + // Dispatch event to parent to set this as active list + window.dispatchEvent(new CustomEvent('list-created', { detail: { id: newList.id } })); }; const updateList = () => { todoStore.update((n) => { - console.log(listIndex); - console.log(n); - n[listIndex].name = listName; - n[listIndex].emoji = listEmoji || '📝'; - return n; + return n.map((list, index) => { + if (index === listIndex) { + return { + ...list, + name: localListName, + emoji: localListEmoji || '📝', + }; + } + return list; + }); }); closeOverlay(); }; - const deleteList = (listId) => { - const deletionTodoListIndex = $todoStore.findIndex( - (todoList) => todoList.id === listId, - ); + const deleteList = () => { todoStore.update((n) => { - n.splice(deletionTodoListIndex, 1); - return n; + return n.filter((list) => list.id !== listId); }); closeOverlay(); }; - const proceedOnEnter = (event) => { + const proceedOnEnter = (event: KeyboardEvent) => { if (isEnter(event)) { - if (listName && (isEmoji(listEmoji) || listEmoji === '')) { + if (localListName && (isEmoji(localListEmoji) || localListEmoji === '')) { $listOverlayOptionsStore.type === 'new' ? createList() : updateList(); } } @@ -82,10 +94,9 @@ const closeOverlay = () => { $listOverlayOptionsStore.showOverlay = false; $listOverlayOptionsStore.type = undefined; - listId = undefined; - listIndex = undefined; - listName = ''; - listEmoji = ''; + listIndex = -1; + localListName = ''; + localListEmoji = ''; }; @@ -107,7 +118,7 @@ : updateList} on:click:button--secondary={({ detail }) => { if (detail.text === $t('page.shared.abort')) closeOverlay(); - if (detail.text === $t('page.shared.delete')) deleteList(listIndex); + if (detail.text === $t('page.shared.delete')) deleteList(); }} class={modalStates} > @@ -120,7 +131,7 @@

{$t('page.todos.overlay.createSubtitle', { - listName: listName, + listName: localListName, })}.

@@ -128,7 +139,7 @@
{ + locale = $languageStore; + }); + // 7. LIFECYCLE onMount(async () => { await setLocale($languageStore); - }); - // 8. EFFECTS - $effect(() => { - locale = $languageStore; + // Listen for list creation events + window.addEventListener('list-created', ((e: CustomEvent) => { + currentListId = e.detail.id; + }) as EventListener); }); - // 5. FUNCTIONS + // 8. FUNCTIONS const showListOverlay = (type: 'new' | 'edit', id?: string) => { openMenu = false; if (type === 'new') { @@ -127,7 +132,7 @@
From a851065b1d2d45149532daef177c60307819dd1e Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 23:40:16 +0100 Subject: [PATCH 10/11] =?UTF-8?q?=F0=9F=8C=8D=20Update=20translation=20for?= =?UTF-8?q?=20global=20menu=20button=20text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/config/de.json | 2 +- frontend/src/lib/config/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/config/de.json b/frontend/src/lib/config/de.json index b419f5d5..31376795 100644 --- a/frontend/src/lib/config/de.json +++ b/frontend/src/lib/config/de.json @@ -253,7 +253,7 @@ "page.shared.blue": "Blau", "page.shared.reset": "Zurücksetzen", "page.shared.button.idCopy": "ID kopieren", - "page.shared.button.globalMenu": "Menü öffnen", + "page.shared.button.globalMenu": "Alle Apps", "page.shared.button.github": "Github Seite öffnen", "page.shared.button.settings": "Einstellungen öffnen", "page.shared.button.stadtwerk": "stadt.werk Seite öffnen", diff --git a/frontend/src/lib/config/en.json b/frontend/src/lib/config/en.json index 7e1bb94d..c6016cd5 100644 --- a/frontend/src/lib/config/en.json +++ b/frontend/src/lib/config/en.json @@ -253,7 +253,7 @@ "page.shared.blue": "Blue", "page.shared.reset": "Reset", "page.shared.button.idCopy": "Copy ID", - "page.shared.button.globalMenu": "Open menu", + "page.shared.button.globalMenu": "All apps", "page.shared.button.github": "Open github page", "page.shared.button.stadtwerk": "Open stadtwerk page", "page.shared.button.settings": "Open settings", From d6203447d0eea2101ec59cbb8856f5481e7ed1b8 Mon Sep 17 00:00:00 2001 From: timlohse1104 Date: Sun, 22 Feb 2026 23:40:26 +0100 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG=20with?= =?UTF-8?q?=20comprehensive=20todo=20migration=20and=20enhancement=20detai?= =?UTF-8?q?ls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db292355..106beb62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - [todo] Migrated todo route and all corresponding components (TodoList, TodoInput, Todo, TodoListOverlay, +page) to Svelte 5 runes syntax ($props, $state, $derived, $effect) for improved reactivity and type safety. +- [todo] Enhanced Todo component with improved UI/UX including clickable rows, better visual feedback, and proper accessibility. +- [todo] Improved TodoList with sorted todos (unchecked first), better state management using $derived, and enhanced history management. +- [todo] Enhanced TodoListOverlay with better form handling, proper state management, and automatic list selection after creation. +- [todo] Updated translations for global menu button text to be more descriptive. - [food-scan] Migrated food-scan route and all corresponding components (FoodScan, ContentOutput, DebugInformation) to Svelte 5 runes syntax ($props, $state, $derived, $effect) for improved reactivity and type safety. - [uno-sort] Migrated uno-sort route and ToggledApplicationInfo component to Svelte 5 runes syntax ($props, $state, $derived, $effect) for improved reactivity and type safety. - [settings] Migrated settings route and all corresponding components (+page, SettingsDashboard, OnlinePersistenceCheck, ThemeSwitch, LanguageSwitch, BackgroundSwitch, IdentifierInformation) to Svelte 5 runes syntax ($props, $state, $derived, $effect) for improved reactivity and type safety.