Files
OSIT-AE-App-Svelte/GEMINI_Svelte_and_Me.md

8.2 KiB

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:
      someAsyncFunc()
          .then(() => { await anotherAsyncFunc(); }) // ERROR: .then() callback is not async
          .catch(() => { /* ... */ })
          .finally(() => { await lastAsyncFunc(); }); // ERROR: .finally() callback is not async
      
    • Correct:
      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 };