# Editor-Split Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Split the monolithic `RecipeEditor.svelte` (628 L) and pull one readability-oriented block out of `RecipeView.svelte` (398 L) by extracting 4 focused Svelte components: `ImageUploadBox`, `IngredientRow`, `StepList`, `TimeDisplay`. No behavior changes, just structure. **Architecture:** Parent-owned state stays in the parent (`RecipeEditor` still owns `ingredients: DraftIng[]`, `steps: DraftStep[]`). Sub-components receive props + callbacks and render their own template + scoped CSS. Shared draft types land in `src/lib/components/recipe-editor-types.ts` so sub-components and parent agree on the shape. `RecipeView.TimeDisplay` is pure presentational with no state. **Tech Stack:** Svelte 5 runes (`$props`, `$state`, `$derived`), TypeScript-strict, no new runtime deps. --- ## Why this is worth doing - `RecipeEditor.svelte:42-89` (Bild-Upload) and `RecipeEditor.svelte:313-334` (Zubereitung) are each self-contained logic-islands with their own state and handlers. Extracting them caps the file a Claude can reason about in one shot. - `IngredientRow` renders 10 lines of template with 5 ARIA labels and 6 grid-columns — a natural single-responsibility unit. - `TimeDisplay` is pure formatting; owning it as a component lets future phases (preview, card hover) reuse it. ## What we are NOT doing - No refactor of `RecipeView`'s tabs / servings-stepper / ingredient-display. Those work fine as-is; roadmap only names the 4 above. - No component unit tests (kochwas has none for components; the e2e `recipe-detail.spec.ts` still covers View behavior, and edit-flow is manually smoked). - No ` ``` - [ ] **Step 2: Wire up `RecipeEditor.svelte`** Remove lines 30–89 (imagePath/uploading/fileInput state, imageSrc derived, onFileChosen, removeImage). Remove these imports at the top: ```ts import { Plus, Trash2, ChevronUp, ChevronDown, ImagePlus, ImageOff } from 'lucide-svelte'; import { confirmAction } from '$lib/client/confirm.svelte'; import { asyncFetch } from '$lib/client/api-fetch-wrapper'; import { requireOnline } from '$lib/client/require-online'; ``` Replace with (Task 1 needs only Plus + Trash2 + Chevrons — the image-specific imports move to the sub-component; `confirmAction`/`asyncFetch`/`requireOnline` stay for future tasks): ```ts import { Plus, Trash2, ChevronUp, ChevronDown } from 'lucide-svelte'; import ImageUploadBox from '$lib/components/ImageUploadBox.svelte'; ``` Remove the image-related CSS (`.image-row`, `.image-preview*`, `.image-actions`, `.image-actions .btn`, `.upload-status`, `.file-input`, `.image-hint`, `.image-block` — those live in the sub-component now). Replace the Bild section in the template: ```svelte

Bild

onimagechange?.(p)} />
``` - [ ] **Step 3: Run checks** ```bash npm run check npm test ``` Expected: 0 errors, 196/196 tests pass. - [ ] **Step 4: Manual smoke** ```bash npm run dev ``` Open any saved recipe → edit → upload an image → verify it shows up and `onimagechange` fires (parent's state updates). Remove the image → confirms the confirm-dialog and removes. Bail out if either flow breaks. - [ ] **Step 5: Commit** ```bash git add src/lib/components/ImageUploadBox.svelte src/lib/components/RecipeEditor.svelte git commit -m "$(cat <<'EOF' refactor(editor): ImageUploadBox als eigenstaendige Component Isoliert den Bild-Upload-Flow (File-Input, Preview, Entfernen-Dialog) aus dem RecipeEditor. Parent haelt nur noch den
-Wrapper und reicht recipe.id + image_path rein, kriegt Aenderungen per onchange callback zurueck. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` --- ## Task 2: Extract types + `IngredientRow` **Files:** - Create: `src/lib/components/recipe-editor-types.ts` - Create: `src/lib/components/IngredientRow.svelte` - Modify: `src/lib/components/RecipeEditor.svelte` - [ ] **Step 1: Types file** ```ts // src/lib/components/recipe-editor-types.ts export type DraftIng = { qty: string; unit: string; name: string; note: string; }; export type DraftStep = { text: string }; ``` - [ ] **Step 2: IngredientRow component** ```svelte
  • ``` - [ ] **Step 3: Wire up `RecipeEditor.svelte`** Replace the local `DraftIng` / `DraftStep` type declarations (lines 100–106) with: ```ts import type { DraftIng, DraftStep } from '$lib/components/recipe-editor-types'; import IngredientRow from '$lib/components/IngredientRow.svelte'; ``` In the template, swap the `
  • ` block for: ```svelte {#each ingredients as ing, idx (idx)} moveIngredient(idx, dir)} onremove={() => removeIngredient(idx)} /> {/each} ``` Remove the CSS for `.ing-row`, `.move`, `.move-btn`, `.ing-row input`, `.del`, and the `@media (max-width: 560px)` block — all now live in `IngredientRow.svelte`. Remove the unused imports `ChevronUp`, `ChevronDown`, `Trash2` from RecipeEditor (they moved to the sub-component, but wait — `Trash2` is also used for step-remove. Keep `Trash2`, remove the two Chevrons). - [ ] **Step 4: Run checks** ```bash npm run check npm test ``` - [ ] **Step 5: Manual smoke** Open any recipe in edit mode. Add an ingredient, type into all 4 fields, reorder up/down, remove one. Verify save persists the ordering. - [ ] **Step 6: Commit** ```bash git add src/lib/components/recipe-editor-types.ts src/lib/components/IngredientRow.svelte src/lib/components/RecipeEditor.svelte git commit -m "$(cat <<'EOF' refactor(editor): IngredientRow + shared types IngredientRow rendert eine einzelne editierbare Zutat-Zeile. DraftIng und DraftStep sind jetzt in recipe-editor-types.ts, damit Parent und Sub-Components auf dieselbe Form referenzieren. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` --- ## Task 3: Extract `StepList` **Files:** - Create: `src/lib/components/StepList.svelte` - Modify: `src/lib/components/RecipeEditor.svelte` - [ ] **Step 1: StepList component** ```svelte
      {#each steps as step, idx (idx)}
    1. {idx + 1}
    2. {/each}
    ``` - [ ] **Step 2: Wire up `RecipeEditor.svelte`** Add import: ```ts import StepList from '$lib/components/StepList.svelte'; ``` Replace the entire Zubereitung `
    ` template block (starting `
    ` with `

    Zubereitung

    ` through the add-step button): ```svelte

    Zubereitung

    ``` **CSS audit — what stays and what goes in the parent:** Parent's template after Tasks 1–3 still contains: - `

    Bild

    ` — no `.block` inner styles needed beyond what's in parent. - `
    ` — still here. Keep `.meta`, `.field`, `.row`, `.small`, `.lbl`. - `

    Zutaten

      {#each ..}{/each}
    ` — still uses `.ing-list` and `.add`. - `

    Zubereitung

    ` — no inner CSS. - `
    ` — keeps `.foot`, `.btn`, `.btn.ghost`, `.btn.primary`, `.btn:disabled`. So parent CSS after Task 3 keeps: `.editor`, `.meta`, `.field`, `.lbl`, `.row`, `.small`, `.block`, `.block h2`, `.ing-list` (the `
      ` wrapper), `.add` (for "Zutat hinzufügen"), `.foot`, `.btn` and variants. Drop from parent CSS in Task 3: `.step-list`, `.step-row`, `.num`, `.step-row textarea`, `.del`. - [ ] **Step 3: Run checks** ```bash npm run check npm test ``` - [ ] **Step 4: Manual smoke** Open any recipe → edit → add a step, type, remove, save. Verify steps persist with correct ordering. - [ ] **Step 5: Commit** ```bash git add src/lib/components/StepList.svelte src/lib/components/RecipeEditor.svelte git commit -m "$(cat <<'EOF' refactor(editor): StepList als eigenstaendige Component Zubereitungs-Liste mit Add + Remove als Sub-Component. Parent steuert nur noch den Wrapper und reicht steps + die zwei Callbacks rein. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` --- ## Task 4: Extract `TimeDisplay` (RecipeView) **Files:** - Create: `src/lib/components/TimeDisplay.svelte` - Modify: `src/lib/components/RecipeView.svelte` - [ ] **Step 1: TimeDisplay component** ```svelte {#if summary}

      {summary}

      {/if} ``` - [ ] **Step 2: Wire up `RecipeView.svelte`** Add import: ```ts import TimeDisplay from '$lib/components/TimeDisplay.svelte'; ``` Remove the local `timeSummary()` function (lines 45–52). Replace the `{#if timeSummary()}

      ...

      {/if}` block in the template with: ```svelte ``` Remove the `.times` CSS from RecipeView (it's in the sub-component now). - [ ] **Step 3: Run checks** ```bash npm run check npm test ``` - [ ] **Step 4: Manual smoke** Open any recipe → verify the time line still shows the same content (Vorb. / Kochen / Gesamt). - [ ] **Step 5: Commit** ```bash git add src/lib/components/TimeDisplay.svelte src/lib/components/RecipeView.svelte git commit -m "$(cat <<'EOF' refactor(view): TimeDisplay als eigenstaendige Component timeSummary-Formatierung in eine wiederverwendbare Component gezogen. RecipeView liefert nur noch die drei Werte — zukuenftige Call-Sites (Preview, Hover-Cards) koennen dieselbe Logik reusen. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` --- ## Task 5: Self-review + push - [ ] **Step 1: Line-count audit** ```bash wc -l src/lib/components/RecipeEditor.svelte src/lib/components/RecipeView.svelte src/lib/components/ImageUploadBox.svelte src/lib/components/IngredientRow.svelte src/lib/components/StepList.svelte src/lib/components/TimeDisplay.svelte ``` Expected shape (approximate, ±10%): - `RecipeEditor.svelte`: 628 → ~330–370 - `RecipeView.svelte`: 398 → ~380 - `ImageUploadBox.svelte`: ~160 - `IngredientRow.svelte`: ~110 - `StepList.svelte`: ~100 - `TimeDisplay.svelte`: ~30 - [ ] **Step 2: Full test + typecheck** ```bash npm test npm run check ``` Both green. - [ ] **Step 3: Git log review** ```bash git log --oneline main..HEAD ``` Expected 4 commits: 1. `refactor(editor): ImageUploadBox als eigenstaendige Component` 2. `refactor(editor): IngredientRow + shared types` 3. `refactor(editor): StepList als eigenstaendige Component` 4. `refactor(view): TimeDisplay als eigenstaendige Component` - [ ] **Step 4: Remote E2E after push** ```bash git push -u origin editor-split ``` CI builds branch-tagged image. After deploy to `kochwas-dev.siegeln.net`: ```bash npm run test:e2e:remote ``` Expected: 40/42 green (same as Search-State-Store baseline). `recipe-detail.spec.ts` (6 tests) specifically exercises the View side — must be clean. Manual UAT pass on `https://kochwas-dev.siegeln.net/`: - Edit a recipe → upload + remove image. - Add / reorder / remove an ingredient → save → verify persistence on reload. - Add / remove a step → save → verify. - Check time-summary rendering on any recipe with prep/cook/total times set. - [ ] **Step 5: Merge to main** Once UAT is clean: ```bash git checkout main git merge --no-ff editor-split git push origin main ``` --- ## Risk Notes - **Prop-reference mutability.** `IngredientRow` and `StepList` receive `ing` / `steps` by reference and use `bind:value` on their own `` / `