# S01: Import & Convert UI (View 1)

**Goal:** Build the Import & Convert view (View 1) as a React SPA that integrates with the Engine API — users upload a raster image, select a vectorization preset, tune parameters via sliders, see a live SVG preview updated via debounced re-trace, and click "Use This" to advance to the Design Canvas.
**Demo:** After this: Upload a PNG, see live preview with preset selection and slider controls, click 'Use This' to advance to canvas

## Tasks
- [x] **T01: Added CORSMiddleware to engine, scaffolded Vite+React+TS app with dev proxy, and built typed API client with 9 passing tests** — This task unblocks all frontend work by: (1) adding CORSMiddleware to engine/main.py so browser requests succeed, (2) scaffolding the app/ directory as a Vite + React + TypeScript project with dev proxy to the engine, and (3) building a typed API client with TypeScript interfaces matching the engine's request/response shapes.

## Steps

1. Add `CORSMiddleware` to `engine/main.py` — import from `fastapi.middleware.cors`, allow all origins for dev (restrict in production later). Place it before the router include.
2. Scaffold `app/` using `npm create vite@latest app -- --template react-ts`. Run `cd app && npm install`.
3. Configure `app/vite.config.ts` with a dev proxy: requests to `/engine/*` forward to `http://localhost:8000`.
4. Create `app/src/types/engine.ts` with TypeScript interfaces:
   - `PresetConfig` — matches the JSON structure in `engine/presets/*.json` (name, description, preprocessing, vectorization, postprocessing sections)
   - `TraceResponse` — `{ output: string, format: string, metadata: TraceMetadata }`
   - `TraceMetadata` — `{ format: string, path_count: number, node_count_total: number, open_paths: number, island_count: number, warnings: string[], processing_ms: number }`
   - `PresetsResponse` — `{ presets: Record<string, PresetConfig> }`
5. Create `app/src/api/engine.ts` with typed functions:
   - `getPresets(): Promise<PresetsResponse>` — calls `GET /engine/presets`
   - `traceImage(file: File, preset: string, params: Record<string, unknown>, signal?: AbortSignal): Promise<TraceResponse>` — builds `FormData` with file, mode, output_format='svg', preset, and params (JSON.stringify), calls `POST /engine/trace`
   - `simplifyVector(file: File, epsilon: number, signal?: AbortSignal): Promise<TraceResponse>` — calls `POST /engine/simplify`
6. Remove Vite boilerplate: delete `app/src/App.css`, the counter component content from `App.tsx`, and the Vite/React logos.
7. Add Vitest as dev dependency: `cd app && npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom`. Configure in `vite.config.ts` with `test: { environment: 'jsdom', globals: true }`.
8. Write `app/src/api/__tests__/engine.test.ts` — unit tests for the API client using `vi.fn()` to mock fetch. Test that `traceImage()` builds correct FormData, `getPresets()` calls the right URL, and AbortSignal is passed through.
  - Estimate: 45m
  - Files: engine/main.py, app/package.json, app/vite.config.ts, app/tsconfig.json, app/src/types/engine.ts, app/src/api/engine.ts, app/src/api/__tests__/engine.test.ts, app/src/App.tsx
  - Verify: cd app && npx vitest run --reporter=verbose 2>&1 | tail -20
- [x] **T02: Built app shell with ViewState routing, drag-and-drop FileUpload with thumbnail preview, PresetSelector fetching from engine API, and two-column ImportConvert layout** — Build the first user-facing components: the app shell with view-state routing (Import → Canvas → Export), a drag-and-drop file upload zone, and a preset selector that fetches presets from the engine and populates parameter defaults.

## Steps

1. Create `app/src/views/ImportConvert.tsx` — the main View 1 container. Layout: left panel (upload + controls) and right panel (preview area, placeholder for now). Use CSS modules or a single `app/src/views/ImportConvert.module.css`.
2. Update `app/src/App.tsx` with view-state routing using React useState:
   - `type ViewState = 'import' | 'canvas' | 'export'`
   - Render `ImportConvert` when state is 'import', placeholder `<div>View 2: Design Canvas</div>` for 'canvas', placeholder for 'export'
   - Pass a callback `onUseThis(svgOutput: string, metadata: TraceMetadata)` that transitions to 'canvas'
3. Create `app/src/components/FileUpload.tsx`:
   - Drag-and-drop zone with `onDragOver`/`onDrop` handlers + hidden file `<input>` fallback
   - Accept: `.png,.jpg,.jpeg,.bmp,.tiff,.webp,.svg` via accept attribute
   - On file selection: store `File` in parent state, show filename + file size, render thumbnail via `URL.createObjectURL()`
   - Detect SVG uploads (check file type or extension) and surface this to parent for mode switching
4. Create `app/src/components/PresetSelector.tsx`:
   - On mount: call `getPresets()` from `app/src/api/engine.ts`
   - Render preset cards — each shows name and description from the preset config
   - Clicking a card selects it (visual highlight) and calls `onPresetSelect(presetName, presetConfig)` callback
   - Default selection: 'sign' preset
5. Create `app/src/App.css` (or global styles) with minimal layout: flexbox two-column layout for View 1, card styles for presets, drop zone styling with dashed border + hover state.
6. Wire components together in `ImportConvert.tsx`: FileUpload at top, PresetSelector below it, both in the left panel. Store selected file and preset in local state.
  - Estimate: 1h
  - Files: app/src/App.tsx, app/src/App.css, app/src/views/ImportConvert.tsx, app/src/views/ImportConvert.module.css, app/src/components/FileUpload.tsx, app/src/components/PresetSelector.tsx
  - Verify: cd app && npx vitest run --reporter=verbose 2>&1 | tail -20 && npx tsc --noEmit 2>&1 | tail -10
- [x] **T03: Built useDebouncedTrace hook with AbortController cancellation, ParameterSliders with mode-aware controls, SvgPreview with responsive rendering, and wired the complete live re-trace loop in ImportConvert** — 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
  - Estimate: 1h30m
  - Files: 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
  - Verify: cd app && npx vitest run --reporter=verbose 2>&1 | tail -20 && npx tsc --noEmit 2>&1 | tail -10
- [x] **T04: Created OutputInfoBar with color-coded stats, wired Use This button with disabled state, added 7 component tests — 23/23 tests pass, zero TS errors** — Complete the slice by adding the output info bar with color-coded stats, the 'Use This' button that advances to View 2, error/empty states, and a full-stack integration verification.

## Steps

1. Create `app/src/components/OutputInfoBar.tsx`:
   - Accepts `metadata: TraceMetadata | null`
   - When metadata is null: render nothing or a muted placeholder
   - Render stats: Path Count, Total Nodes, Open Paths, Processing Time
   - Color coding: green for normal values, yellow when node_count_total > 5000, red when open_paths > 0
   - Show warnings from metadata.warnings array if any
2. Add 'Use This' button to `ImportConvert.tsx`:
   - Disabled when no SVG output is available
   - On click: call `onUseThis(svgOutput, metadata)` prop, which App.tsx uses to transition view state to 'canvas'
   - Pass the SVG string forward (stored in App.tsx state for S02 to consume)
3. Add empty/error states throughout:
   - No file selected: show upload prompt in preview area
   - API error: show error message in preview area with status code
   - Loading: show spinner overlay on preview
4. Write `app/src/components/__tests__/OutputInfoBar.test.tsx` — test color coding logic:
   - Normal metadata → green indicators
   - High node count → yellow indicator
   - Open paths > 0 → red indicator
   - Null metadata → no crash, placeholder shown
5. Final verification: ensure `npx tsc --noEmit` passes with zero errors, `npx vitest run` passes all tests, and all components are wired together end-to-end.
  - Estimate: 45m
  - Files: app/src/components/OutputInfoBar.tsx, app/src/components/__tests__/OutputInfoBar.test.tsx, app/src/views/ImportConvert.tsx, app/src/App.tsx
  - Verify: cd app && npx vitest run --reporter=verbose 2>&1 | tail -20 && npx tsc --noEmit 2>&1 | tail -10
