refactor(editor): untrack() fuer form-lokale Snapshots (Item I)
Some checks failed
Build & Publish Docker Image / build-and-push (push) Has been cancelled

Alle 10 pre-existing svelte-check WARNINGs ("state_referenced_locally")
in RecipeEditor.svelte und recipes/[id]/+page.svelte addressiert.

Die betroffenen `let foo = $state(recipe.bar)`-Pattern sind
intentional Snapshots: der Editor soll User-Edits behalten und nicht
von prop-Updates ueberschrieben werden. untrack() macht die Intent
explizit und silenced die Warnung sauber statt sie unter den Teppich
zu kehren.

Scope: imagePath, title, description, servings, prepMin, cookMin,
totalMin, ingredients, steps (RecipeEditor) + recipeState
(recipes/[id]/+page).

Gate: svelte-check 0 Warnings (war 10), Tests 184/184.

Refs docs/superpowers/plans/2026-04-19-post-review-roadmap.md Item I.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-19 11:38:35 +02:00
parent 9ee8efa479
commit 5a1ffee3bb
2 changed files with 21 additions and 16 deletions

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { untrack } from 'svelte';
import { Plus, Trash2, ChevronUp, ChevronDown, ImagePlus, ImageOff } from 'lucide-svelte';
import type { Recipe, Ingredient, Step } from '$lib/types';
import { alertAction, confirmAction } from '$lib/client/confirm.svelte';
@@ -25,7 +26,7 @@
let { recipe, saving = false, onsave, oncancel, onimagechange }: Props = $props();
let imagePath = $state<string | null>(recipe.image_path);
let imagePath = $state<string | null>(untrack(() => recipe.image_path));
let uploading = $state(false);
let fileInput: HTMLInputElement | null = $state(null);
@@ -94,12 +95,14 @@
}
}
let title = $state(recipe.title);
let description = $state(recipe.description ?? '');
let servings = $state<number | ''>(recipe.servings_default ?? '');
let prepMin = $state<number | ''>(recipe.prep_time_min ?? '');
let cookMin = $state<number | ''>(recipe.cook_time_min ?? '');
let totalMin = $state<number | ''>(recipe.total_time_min ?? '');
// Form-lokaler Zustand: Initialwerte aus dem Prop snapshotten (untrack),
// damit User-Edits nicht von prop-Updates ueberschrieben werden.
let title = $state(untrack(() => recipe.title));
let description = $state(untrack(() => recipe.description ?? ''));
let servings = $state<number | ''>(untrack(() => recipe.servings_default ?? ''));
let prepMin = $state<number | ''>(untrack(() => recipe.prep_time_min ?? ''));
let cookMin = $state<number | ''>(untrack(() => recipe.cook_time_min ?? ''));
let totalMin = $state<number | ''>(untrack(() => recipe.total_time_min ?? ''));
type DraftIng = {
qty: string;
@@ -110,15 +113,17 @@
type DraftStep = { text: string };
let ingredients = $state<DraftIng[]>(
untrack(() =>
recipe.ingredients.map((i) => ({
qty: i.quantity !== null ? String(i.quantity).replace('.', ',') : '',
unit: i.unit ?? '',
name: i.name,
note: i.note ?? ''
}))
)
);
let steps = $state<DraftStep[]>(
recipe.steps.map((s) => ({ text: s.text }))
untrack(() => recipe.steps.map((s) => ({ text: s.text })))
);
function addIngredient() {

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount, onDestroy, tick } from 'svelte';
import { onMount, onDestroy, tick, untrack } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import {
@@ -41,7 +41,7 @@
let editMode = $state(false);
let saving = $state(false);
let recipeState = $state(data.recipe);
let recipeState = $state(untrack(() => data.recipe));
// Einmalige Pulse-Animation beim Aktivieren (nicht beim Wieder-Abwählen).
// Per tick()-Zwischenschritt "aus → an" erzwingen, damit die Animation