---
id: T03
parent: S03
milestone: M002
provides: []
requires: []
affects: []
key_files: ["app/src/components/canvas/ShapeProperties.tsx", "app/src/views/DesignCanvas.tsx"]
key_decisions: ["Convert-to-paths uses a single onConvertToPath(textObjectId, imageObject) callback rather than separate onAddObject + onRemoveObject — simpler prop interface, atomic replacement", "Convert-to-paths generates an SVG Blob URL with the text path data and creates an ImageObject at the same position — reuses existing image rendering pipeline"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "Ran `cd app && npx tsc --noEmit` — zero TypeScript errors. Ran `cd app && npx vitest run --reporter=verbose` — all 95 tests pass across 7 test files in 2.2s."
completed_at: 2026-03-26T05:58:01.901Z
blocker_discovered: false
---

# T03: Added text-specific property controls (content, font family, size, letter spacing, line height) and Convert to Paths button to ShapeProperties, wired through DesignCanvas

> Added text-specific property controls (content, font family, size, letter spacing, line height) and Convert to Paths button to ShapeProperties, wired through DesignCanvas

## What Happened
---
id: T03
parent: S03
milestone: M002
key_files:
  - app/src/components/canvas/ShapeProperties.tsx
  - app/src/views/DesignCanvas.tsx
key_decisions:
  - Convert-to-paths uses a single onConvertToPath(textObjectId, imageObject) callback rather than separate onAddObject + onRemoveObject — simpler prop interface, atomic replacement
  - Convert-to-paths generates an SVG Blob URL with the text path data and creates an ImageObject at the same position — reuses existing image rendering pipeline
duration: ""
verification_result: passed
completed_at: 2026-03-26T05:58:01.911Z
blocker_discovered: false
---

# T03: Added text-specific property controls (content, font family, size, letter spacing, line height) and Convert to Paths button to ShapeProperties, wired through DesignCanvas

**Added text-specific property controls (content, font family, size, letter spacing, line height) and Convert to Paths button to ShapeProperties, wired through DesignCanvas**

## What Happened

Extended ShapeProperties with text-specific controls that render when the selected object has type 'text': a textarea for text content, a font family dropdown populated from fontService.getAvailableFonts(), numeric inputs for font size (8–200), letter spacing (-5–20), and line height (0.5–3). Added a "Convert to Paths" button that calls fontService.textToPathData() to convert the text to SVG path data, wraps it in an SVG string, creates a Blob URL, and replaces the TextObject with an ImageObject at the same position via a new onConvertToPath callback prop. The button shows a window.confirm dialog before proceeding (destructive action) and displays a "Converting…" disabled state during the async operation. Updated DesignCanvas to pass the onConvertToPath handler that removes the text object, adds the new image object, and selects it.

## Verification

Ran `cd app && npx tsc --noEmit` — zero TypeScript errors. Ran `cd app && npx vitest run --reporter=verbose` — all 95 tests pass across 7 test files in 2.2s.

## Verification Evidence

| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `cd app && npx tsc --noEmit` | 0 | ✅ pass | 2400ms |
| 2 | `cd app && npx vitest run --reporter=verbose` | 0 | ✅ pass | 2200ms |


## Deviations

Used a single onConvertToPath(textObjectId, imageObject) callback instead of separate onAddObject + onRemoveObject callbacks as the task plan suggested — simpler and keeps the replacement atomic.

## Known Issues

None.

## Files Created/Modified

- `app/src/components/canvas/ShapeProperties.tsx`
- `app/src/views/DesignCanvas.tsx`


## Deviations
Used a single onConvertToPath(textObjectId, imageObject) callback instead of separate onAddObject + onRemoveObject callbacks as the task plan suggested — simpler and keeps the replacement atomic.

## Known Issues
None.
