# Gemini's Svelte 5 Learnings and Best Practices This document outlines key insights and strategies developed during Svelte 5 (with runes mode) refactoring and bug-fixing tasks. It specifically addresses common pitfalls and effective patterns for an AI agent working with Svelte. ## 1. Async/Await in Svelte (Runes Mode) The most frequent source of errors has been related to asynchronous operations and the `await` keyword. - **Rule:** Any function or block containing `await` _must_ be explicitly marked `async`. - This applies to callbacks within promise chains (`.then()`, `.catch()`, `.finally()`), DOM event handlers (`onclick`, `onchange`), and Svelte lifecycle functions (`onMount`). - **Common Error:** `Cannot use keyword 'await' outside an async function`. This happens when `await` is used in a function/block that is not `async`. - **Solution:** Ensure the surrounding function or the callback itself is declared `async`. - `somePromise.finally(async () => { await someAsyncOperation(); });` - `onclick={async () => { await someAsyncOperation(); }}` - `onMount(async () => { await someAsyncOperation(); });` - **Asynchronous Navigation (`goto`):** - The correct pattern for asynchronous navigation using `goto` in Svelte 5 is `await goto(await resolve(path), options);`. - `import { resolve } from '$app/paths';` is crucial when using `resolve()`. A missing import will lead to `no-undef` errors for `resolve`. - `resolve(path)` is crucial to ensure the path is correctly resolved before navigation, especially in universal applications. - The `await` before `goto` is necessary if subsequent code depends on the navigation completing or if `invalidateAll` is used. - **Clearing Stale Caches:** When encountering confusing linting or build errors that don't seem to match the current code, especially after significant refactorings or dependency changes, always try running `npm run clean` to clear stale build artifacts and then re-lint/re-build. - **Refactoring Promise Chains:** - When converting `.then().catch().finally()` chains to `async/await` structure, encapsulate the asynchronous operation within a `try...catch...finally` block. - **Incorrect:** ```javascript someAsyncFunc() .then(() => { await anotherAsyncFunc(); }) // ERROR: .then() callback is not async .catch(() => { /* ... */ }) .finally(() => { await lastAsyncFunc(); }); // ERROR: .finally() callback is not async ``` - **Correct:** ```javascript async () => { try { const result = await someAsyncFunc(); await anotherAsyncFunc(result); } catch (error) { console.error(error); } finally { await lastAsyncFunc(); } }; ``` - **Note:** If the entire handler (e.g., `onclick`) is already `async`, you can `await` the promise directly within it. ## 2. Reactive Declarations & Scoping Svelte 5's runes mode introduces new ways of managing reactivity, and understanding variable lifecycles is key. - **`$state` for Reactive Variables:** - Variables that are expected to trigger re-renders when their values change, or whose changes need to be observed, should be declared with `$state`. - **Common Error:** Warnings like "This reference only captures the initial value of `data`. Did you mean to reference it inside a closure instead?" or "Variable `x` is updated, but is not declared with `$state(...)`." - **Solution:** Declare the variable using `$state(initialValue)`. - **Context for `data` prop:** When a `data` prop (from a SvelteKit `load` function) is accessed outside of reactive declarations (like `$effect` or event handlers), Svelte might warn that it only captures its initial value. To ensure reactivity or to correctly process it, use it within `$effect` or derive a `$state` variable from it. - **Function Scoping and Redeclaration:** - **Common Error:** `Identifier 'function_name' has already been declared`. This occurs when functions with the same name are defined in overlapping scopes. - **Solution:** - If a function is only needed within a specific block (e.g., an `onMount` callback), define it _inside_ that block to limit its scope. - If a function is truly global and needs to be accessible from templates and multiple `onMount` blocks, define it once outside of any `onMount` and ensure it doesn't conflict with other definitions. - Be mindful of helper functions that might be implicitly pulled into global scope by the Svelte compiler if not correctly encapsulated. ## 3. `replace` Tool Usage Strategy (Critical for AI) My efficiency heavily relies on the `replace` tool, and precision is paramount. - **Exact `old_string` Matching:** - The `old_string` parameter _must_ precisely match the target text in the file, including all whitespace, indentation, newlines, and comments. Even a single character difference will cause the tool to fail with "0 occurrences found". - For multi-line replacements, always copy the exact block from the `read_file` output. - **Contextual Specificity:** - Avoid generic `old_string` patterns (e.g., `onclick={() => {`) if there are many such occurrences in a file. Instead, expand the `old_string` to include enough surrounding unique context (e.g., the entire button element or parent `div`) to ensure it matches only one instance (`expected_replacements: 1`). - **Iterative Refinement:** - For complex refactorings involving multiple changes in a file, perform changes in small, atomic steps. - **Always `read_file` before each `replace` operation.** This ensures the `old_string` is based on the absolute latest content of the file, preventing mismatches due to previous modifications or unexpected formatting. - After each `replace` operation, immediately run `npm run build` (or `npm run lint`) to validate the change and catch new errors early. This is crucial for catching cascading issues introduced by partial refactorings. ## 4. Error Debugging Workflow - **Prioritize Compiler/Build Errors:** These are blocking issues that prevent the application from running. Address them first. - **Analyze Error Messages:** Read the full error message carefully, including line numbers, and look for keywords (e.g., `await`, `async`, `declared`, `undefined`). - **Consult Svelte Documentation:** The Svelte compiler often provides helpful links (`https://svelte.dev/e/js_parse_error`) which should be a first point of reference if the error is unfamiliar. - **Re-read File Content:** If a `replace` operation fails or produces unexpected results, immediately use `read_file` to verify the exact state of the file before attempting another change. ## 5. Safe Property Binding (Preventing `props_invalid_value`) Svelte 5 enforces strict contracts for bound properties (`bind:prop`). If a component expects a property to have a fallback/default value, you cannot bind `undefined` to it. - **The Error:** `Uncaught Svelte error: props_invalid_value. Cannot do bind:prop={undefined} when prop has a fallback value`. - **The Cause:** Attempting to bind a variable that is currently `undefined` to a component prop that has a default value (e.g., `let { prop = false } = $props()`). Svelte cannot determine whether to use the bound `undefined` or the component's internal default, so it throws an error. - **The Fix:** **Always initialize bound variables.** - **Initialization:** Ensure the variable you are binding to is initialized to a valid value (matching the prop's type) *before* the component mounts. - Example: `let myVar = $state(false);` instead of `let myVar = $state();`. - **Data Fetching:** If the data comes from an asynchronous source (API, DB), ensure the object properties have default values *immediately* upon assignment, even before the data is fully populated. - **Good:** `$slct.obj = { ...apiResult, myBoundProp: apiResult.myBoundProp ?? false };` - **Bad:** `$slct.obj = apiResult;` (if `apiResult.myBoundProp` is missing/undefined). - **Updates/resets:** When resetting or updating the object, explicitly re-initialize the bound properties. - `obj = {};` -> `obj = { myBoundProp: false };`