---
phase: 42-wallpapers-social-format-conversion-voice
plan: 06
subsystem: ui
tags: [react, typescript, drag-drop, format-conversion, deep-link, SSE, FormData, multipart, shadcn, lucide]

# Dependency graph
requires:
  - phase: 42-wallpapers-social-format-conversion-voice
    plan: 03
    provides: POST /api/companies/:companyId/convert (multipart), GET /api/system/converters, ConvertBundle type
  - phase: 40-content-job-infra
    provides: useContentJob hook, getContentJobAsset, SSE job tracking pattern

provides:
  - ui/src/api/convert.ts — submitConvertJob (multipart) and getConverterCapabilities API client
  - ui/src/components/ConvertPanel.tsx — drag-drop + grouped format chips + AI fallback notice + download
  - ui/src/pages/ConvertPage.tsx — /convert route with deep-link URL param reading
  - ui/src/App.tsx — three route variants /convert, /convert/:src, /convert/:src/:tgt

affects:
  - Any future phase adding nav links to /convert

# Tech tracking
tech-stack:
  added: []
  patterns:
    - Direct fetch (not api.post) for multipart submissions that need per-status-code inspection (422 vs 202)
    - Direct EventSource connection for pre-submitted jobs (bypasses useContentJob.submit which re-submits)
    - FORMAT_GROUPS constant exported from ConvertPanel for reuse in ConvertPage validation
    - normalizeFormatParam helper: toLowerCase + allowlist check, silently returns undefined for invalid params

key-files:
  created:
    - ui/src/api/convert.ts
    - ui/src/components/ConvertPanel.tsx
    - ui/src/pages/ConvertPage.tsx
  modified:
    - ui/src/App.tsx

key-decisions:
  - "Direct fetch used in submitConvertJob (not api.postForm) to inspect 422 vs 202 status before throwing — api.request() throws on !res.ok, but 422 is a valid business response with MIME error body"
  - "Direct EventSource in ConvertPanel for already-submitted jobs — useContentJob.submit() calls submitContentJob internally and cannot track an existing jobId without re-submitting"
  - "FORMAT_GROUPS exported from ConvertPanel so ConvertPage can import the same constant for allowlist validation without duplicating the list"
  - "AI fallback notice shown per target format group (Images/AV/Docs/Data) based on capabilities, not per individual format pair — matches CONV-08 spec that all paths fall to AI bridge when direct converter unavailable"

patterns-established:
  - "Format chip role=radio within role=radiogroup — matches accessibility spec from 42-UI-SPEC.md"
  - "Drop zone uses motion-safe: Tailwind variant to disable transition for prefers-reduced-motion"

requirements-completed: [CONV-01, CONV-02, CONV-03, CONV-04, CONV-05, CONV-06, CONV-07, CONV-08, CONV-09]

# Metrics
duration: 12min
completed: 2026-04-04
---

# Phase 42 Plan 06: Format Conversion UI Summary

**Drag-drop ConvertPanel with grouped format chips, AI fallback notice, MIME error display, SSE job tracking, and deep-link /convert/:src/:tgt routing**

## Performance

- **Duration:** 12 min
- **Started:** 2026-04-04T22:17:33Z
- **Completed:** 2026-04-04T22:29:00Z
- **Tasks:** 2
- **Files modified:** 4 (3 created, 1 modified)

## Accomplishments

- Created convert.ts API client: submitConvertJob with direct fetch for 422/202 status differentiation; getConverterCapabilities for startup capability probe
- Built ConvertPanel with all three visual zones: ConvertSourceZone (drag-drop with idle/dragover/error states), ConvertTargetSelector (grouped chips with AI fallback notice), ConvertActionBar (disabled until ready, download after done)
- FORMAT_GROUPS constant drives chip rendering and is exported for ConvertPage format validation
- ConvertPage reads URL params, normalizes to lowercase, silently ignores invalid format strings
- Three route variants wired in App.tsx: /convert, /convert/:sourceFormat, /convert/:sourceFormat/:targetFormat

## Task Commits

1. **Task 1: Create convert API client and ConvertPanel component** - `c0040f66` (feat)
2. **Task 2: Create ConvertPage and wire routes in App.tsx** - `b5587c03` (feat)

## Files Created/Modified

- `ui/src/api/convert.ts` - submitConvertJob (multipart POST, 422/202 handling), getConverterCapabilities
- `ui/src/components/ConvertPanel.tsx` - Full ConvertPanel: drag-drop zone, FORMAT_GROUPS chips, AI fallback notice, SSE progress, download
- `ui/src/pages/ConvertPage.tsx` - Route page reading sourceFormat/targetFormat URL params with case-insensitive normalization
- `ui/src/App.tsx` - Added ConvertPage lazy import and three /convert route variants inside boardRoutes()

## Decisions Made

- **Direct fetch in submitConvertJob** — api.request() throws on any !res.ok response, but 422 is a valid business response carrying the MIME mismatch error body. Using raw fetch lets us return it as a ConvertMimeError value instead of catching and re-parsing an ApiError.
- **Direct EventSource for pre-submitted jobs** — useContentJob.submit() calls submitContentJob internally; it has no "track existing job" API. Since submitConvertJob already dispatched the convert job via the multipart route (not contentJobs route), a standalone EventSource connection is used to track progress.
- **FORMAT_GROUPS exported from ConvertPanel** — ConvertPage needs the same allowlist for normalizeFormatParam validation. Exporting from ConvertPanel avoids duplication and keeps them in sync.
- **AI fallback notice per group** — The capabilities object returns converter availability per category (imageConverter, audioVideoConverter, docConverter, dataConverter), not per format pair. Showing the notice at group level matches the granularity available from /api/system/converters.

## Deviations from Plan

None — plan executed exactly as written.

## Known Stubs

None — all conversion flows are fully wired to real backend endpoints.

## Issues Encountered

None — tsc compiles cleanly for all new/modified files. 17 pre-existing errors in unrelated files (AgentConfigForm, useKeyboardShortcuts, useNexusMode, usePiperTts, useVadRecorder, ContentStudio, PersonalAssistant) were present before this plan and not introduced here.

## User Setup Required

None — no external service configuration required.

## Next Phase Readiness

- All CONV requirements fulfilled (CONV-01 through CONV-09)
- /convert, /convert/:src, and /convert/:src/:tgt routes live and navigable
- Phase 42 is complete — all 6 plans executed

---
*Phase: 42-wallpapers-social-format-conversion-voice*
*Completed: 2026-04-04*

## Self-Check: PASSED

- `ui/src/api/convert.ts` — FOUND
- `ui/src/components/ConvertPanel.tsx` — FOUND
- `ui/src/pages/ConvertPage.tsx` — FOUND
- `.planning/phases/42-wallpapers-social-format-conversion-voice/42-06-SUMMARY.md` — FOUND
- Task 1 commit `c0040f66` — FOUND
- Task 2 commit `b5587c03` — FOUND
