---
estimated_steps: 36
estimated_files: 5
skills_used: []
---

# T03: Implement parameter sliders and debounced live SVG preview with AbortController

Build the core interaction loop: parameter sliders derived from preset defaults, debounced re-trace on any change, AbortController-based request cancellation, and inline SVG rendering. This is the highest-risk piece of the slice.

## Steps

1. Create `app/src/hooks/useDebouncedTrace.ts` — a custom hook that encapsulates the debounce + abort logic:
   - Accepts: `file: File | null`, `preset: string`, `params: Record<string, unknown>`, `debounceMs: number` (default 300)
   - Maintains an `AbortController` ref. On each trigger: clear previous timeout, abort previous request, set new timeout
   - After debounce: call `traceImage(file, preset, params, signal)` from the API client
   - Returns: `{ svgOutput: string | null, metadata: TraceMetadata | null, isLoading: boolean, error: string | null }`
   - On unmount: abort any in-flight request and clear timeout
   - For SVG file uploads: call `simplifyVector()` instead of `traceImage()`
2. Create `app/src/components/ParameterSliders.tsx`:
   - Accepts current preset config and an `onChange(params)` callback
   - Renders labeled range inputs for key parameters:
     - **Detail Level** (epsilon): range 0.5–10, step 0.5, from postprocessing.epsilon
     - **Noise Filter** (filter_speckle/turdsize): range 0–50, step 1, from vectorization params
     - **Smooth Curves** (alphamax): range 0–1.334, step 0.1, from vectorization.potrace.alphamax
     - **Corner Threshold** (corner_threshold): range 0–180, step 1, from vectorization.vtracer.corner_threshold (only shown for vtracer mode)
   - Show/hide sliders based on vectorization mode (potrace vs vtracer) from preset
   - Each slider change calls `onChange` with the full current params object
   - Show current numeric value next to each slider
3. Create `app/src/components/SvgPreview.tsx`:
   - Accepts `svgOutput: string | null`, `isLoading: boolean`, `error: string | null`
   - When loading: show a spinner/overlay
   - When error: show error message with retry suggestion
   - When SVG available: render via `dangerouslySetInnerHTML` inside a container div
   - Strip `width` and `height` attributes from the SVG string (keep viewBox) for responsive scaling
   - Apply CSS: `width: 100%; height: auto; max-height: 70vh; object-fit: contain` on the container
4. Wire everything in `ImportConvert.tsx`:
   - Track `currentParams` state, initialized from selected preset defaults
   - When preset changes: reset params to new preset defaults, trigger re-trace
   - When slider changes: update params, trigger re-trace via the debounced hook
   - When file changes: trigger immediate trace (no debounce for initial upload)
   - Pass svgOutput/isLoading/error to SvgPreview
5. Write `app/src/hooks/__tests__/useDebouncedTrace.test.ts` — test the hook with mocked fetch:
   - Verify debounce behavior (multiple rapid calls → only last fires)
   - Verify abort: mock AbortController, confirm signal is passed and previous request aborted
   - Verify SVG mode detection routes to simplifyVector

## Inputs

- `app/src/api/engine.ts`
- `app/src/types/engine.ts`
- `app/src/views/ImportConvert.tsx`
- `app/src/components/PresetSelector.tsx`

## Expected Output

- `app/src/hooks/useDebouncedTrace.ts`
- `app/src/components/ParameterSliders.tsx`
- `app/src/components/SvgPreview.tsx`
- `app/src/views/ImportConvert.tsx`
- `app/src/hooks/__tests__/useDebouncedTrace.test.ts`

## Verification

cd app && npx vitest run --reporter=verbose 2>&1 | tail -20 && npx tsc --noEmit 2>&1 | tail -10
