# 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)}
{idx + 1}
{/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.
- `
` — 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 `` / `