# Plano de Implementação — V0.5 (Platform & Polish)

**Spec de referência:** `2026-05-17-post-v1-roadmap-design.md`  
**Pré-requisito:** V0.4 completo (Image, Z-index, Select, Slider implementados)  
**Critério de conclusão:** `cargo test --workspace` + `cargo clippy --workspace --all-targets -- -D warnings` passam. A11y e multi-window podem ser diferidos para V0.6 se necessário — os demais itens são independentes entre si.

---

## Visão geral

V0.5 tem 6 itens que são em grande parte independentes entre si. A ordem sugerida abaixo otimiza para "funcionalidade visível primeiro":

1. **Typed Props** — DX sem impacto de runtime, baixo risco
2. **Fragment no retained tree** — completude interna, baixo risco
3. **Themes** — visível ao usuário, base para polish visual
4. **Animações** — visível ao usuário, depende do frame loop
5. **Multi-window** — infraestrutura de plataforma
6. **A11y** — maior esforço; pode ser V0.6

---

## Etapa 1 — Typed Props

**Objetivo:** Permitir `Component::new(fn, Props)` com props tipadas, além do modelo atual de closures.

### 1.1 API alvo

```rust
#[derive(Clone, PartialEq)]
struct CardProps {
    title: String,
    subtitle: String,
}

fn card(cx: &Cx, props: &CardProps) -> Element { ... }

// uso:
Component::new_with_props(card, CardProps { title: "...".into(), subtitle: "...".into() })
```

### 1.2 Mudanças no core

Arquivo: `src/src/element/mod.rs`

- Adicionar variante `ComponentWithProps` no enum `Element` (ou estender `Component` com `props: Option<Box<dyn AnyProps>>`)
- `AnyProps`: trait object com `clone_box()` e `eq_box()` para diff de props

Arquivo: `src/src/diff/mod.rs`

- Quando mesmo type+key: comparar props via `AnyProps::eq_box()` — se iguais, skip re-render (otimização)

Arquivo: `src/src/retained/mod.rs`

- `ComponentSlot` armazena `Box<dyn AnyProps>` atual para comparação no próximo diff

### 1.3 Compatibilidade

O modelo de closures (`Component::new(fn)`) permanece — `Component::new_with_props` é adição, não substituição. Nenhum código existente quebra.

---

## Etapa 2 — Fragment no retained tree

**Objetivo:** Fragment funciona corretamente em todos os casos do retained tree, sem edge cases.

### 2.1 Casos a cobrir

- `InsertChild` / `RemoveChild` / `MoveChild` com Fragment como pai (filho direto de Fragment é inserido no avô)
- Layout traversal: Fragment não tem `taffy_id` — seus filhos são adicionados diretamente ao pai Taffy
- Paint traversal: Fragment é transparente — seus filhos herdam o contexto do avô
- Hit-test: Fragment é transparente

### 2.2 Auditoria

- Rodar todos os testes existentes com fragments aninhados
- Adicionar testes unitários específicos:
  - Fragment com filhos mistos (Element + outro Fragment)
  - Fragment como filho de lista keyed
  - Fragment removido/inserido via diff

### 2.3 Correções

Localizar no diff (`src/src/diff/`) e no retained (`src/src/retained/`) os pontos onde Fragment não é tratado corretamente e corrigir.

---

## Etapa 3 — Themes / Design Tokens

### 3.1 Struct `Theme`

Arquivo: `src/src/theme.rs`

```rust
#[derive(Clone, PartialEq)]
pub struct Theme {
    pub colors: ColorTokens,
    pub typography: TypographyTokens,
    pub spacing: SpacingTokens,
    pub radius: RadiusTokens,
}

#[derive(Clone, PartialEq)]
pub struct ColorTokens {
    pub background: Color,
    pub surface: Color,
    pub foreground: Color,
    pub foreground_secondary: Color,
    pub accent: Color,
    pub accent_hover: Color,
    pub error: Color,
    pub border: Color,
}

#[derive(Clone, PartialEq)]
pub struct TypographyTokens {
    pub font_size_sm: f32,   // 12.0
    pub font_size_md: f32,   // 14.0
    pub font_size_lg: f32,   // 18.0
    pub font_size_xl: f32,   // 24.0
    pub line_height: f32,    // 1.5
}

#[derive(Clone, PartialEq)]
pub struct SpacingTokens {
    pub xs: f32,  // 4.0
    pub sm: f32,  // 8.0
    pub md: f32,  // 12.0
    pub lg: f32,  // 16.0
    pub xl: f32,  // 24.0
}

#[derive(Clone, PartialEq)]
pub struct RadiusTokens {
    pub sm: f32,  // 4.0
    pub md: f32,  // 8.0
    pub lg: f32,  // 12.0
}

impl Theme {
    pub fn default_light() -> Self { ... }
    pub fn default_dark() -> Self { ... }
}
```

### 3.2 Hook `use_theme`

Arquivo: `src/src/runtime/cx.rs`

- `Cx` armazena `Rc<Theme>` (injetado pelo `AppState`)
- `cx.use_theme() -> Theme` retorna clone do tema atual
- Quando o tema muda (futuro), todos os componentes que o leram re-renderizam via `Signal<Theme>` interno

### 3.3 `lemon::run` com tema

```rust
pub fn run(config: WindowConfig, root_fn: fn(&Cx) -> Element) { ... }
pub fn run_with_theme(config: WindowConfig, theme: Theme, root_fn: fn(&Cx) -> Element) { ... }
```

### 3.4 Atualização dos widgets em `lemon::widget`

- `Button`, `TextInput`, `Select`, `Slider`, `Scroll` leem o tema via `cx.use_theme()` para cores e espaçamentos padrão
- Props explícitas do widget sobrescrevem o tema

---

## Etapa 4 — Animações / Transições

### 4.1 Hook `use_animation`

Arquivo: `src/src/runtime/cx.rs`

```rust
impl Cx {
    pub fn use_animation(&self, config: AnimationConfig) -> AnimationHandle { ... }
}

pub struct AnimationConfig {
    pub duration_ms: u32,
    pub easing: Easing,
    pub on_complete: Option<Rc<dyn Fn()>>,
}

pub enum Easing {
    Linear,
    EaseIn,
    EaseOut,
    EaseInOut,
}

pub struct AnimationHandle {
    pub progress: Signal<f32>,  // 0.0 → 1.0
    pub play: Rc<dyn Fn()>,
    pub reset: Rc<dyn Fn()>,
}
```

### 4.2 Frame loop

Arquivo: `src/src/platform/window.rs`

- `AppState` mantém `active_animations: Vec<AnimationState>`
- `AnimationState`: `{ start_time: Instant, duration_ms: u32, easing: Easing, progress: Signal<f32>, on_complete }`
- Após cada `present`, se há animações ativas: solicitar redraw imediato via `window.request_redraw()`
- A cada frame: atualizar `progress` de cada animação ativa; remover concluídas

### 4.3 Interpolação

Utilitário: `lemon::animation::lerp(a: f32, b: f32, t: f32) -> f32`  
Easing functions: `ease_in(t)`, `ease_out(t)`, `ease_in_out(t)` — fórmulas cúbicas padrão.

### 4.4 Uso típico

```rust
fn fade_in(cx: &Cx) -> Element {
    let anim = cx.use_animation(AnimationConfig { duration_ms: 300, easing: Easing::EaseOut, on_complete: None });
    cx.use_effect(move || { (anim.play)(); });
    Box_::new()
        .opacity(anim.progress.get())  // StyleProps::opacity (adicionar)
        .child(Text::new("Hello"))
        .into_element()
}
```

- Adicionar `opacity: f32` em `StyleProps` (padrão `1.0`)
- Paint pass: `scene.push_layer(Mix::Normal, opacity, ...)` quando `opacity < 1.0`

---

## Etapa 5 — Multi-window

### 5.1 API

```rust
// Janela inicial (sem mudança)
lemon::run(config, root_fn);

// Abrir janela adicional de dentro de um componente
cx.open_window(WindowConfig::default().title("Segunda janela"), second_root);
```

### 5.2 Implementação

Arquivo: `src/src/platform/mod.rs`

- `ApplicationHandler` do winit já suporta múltiplas janelas — adicionar `HashMap<WindowId, WindowState>` no `AppState`
- `WindowState` = o que hoje é o estado único da janela: retained tree, layout map, surface wgpu, etc.
- `Cx::open_window` envia `UserEvent::OpenWindow(config, root_fn)` via `EventLoopProxy`
- O handler de `UserEvent` cria novo `WindowState` e inicia o root component

### 5.3 Signals compartilhados entre janelas

Signals são `Rc`-based e single-threaded — já funcionam entre janelas na mesma thread.  
O runtime reativo existente não precisa de mudanças.

### 5.4 Fechar janelas

- `cx.close_window()` — envia `UserEvent::CloseWindow(window_id)`
- Handler: drop do `WindowState` correspondente + `window.close()` no winit

---

## Etapa 6 — Acessibilidade (a11y)

> **Atenção:** Este é o maior item do V0.5. Se o milestone ficar pesado, este item pode ser movido para V0.6 sem impactar os demais.

### 6.1 Integração AccessKit

- Adicionar `accesskit`, `accesskit_winit` em `[workspace.dependencies]`
- Inicializar `accesskit_winit::Adapter` no `WindowState`

### 6.2 Accessibility tree paralela

Arquivo: `src/src/retained/a11y.rs`

- Após cada `apply_patches`, varrer o retained tree e emitir `accesskit::TreeUpdate`
- Cada `RetainedNode` gera um `accesskit::Node` com:
  - `role`: derivado do tipo de nó (`Button`, `TextInput`, `StaticText`, `Group`, etc.)
  - `bounds`: rect do `LayoutMap`
  - `label`: conteúdo textual ou `aria_label` prop
  - `value`: para `TextInput`, `Slider`, `Select`
  - `focused`: nó com foco no `FocusManager`

### 6.3 Props de acessibilidade

Adicionar em `StyleProps` (ou `AccessibilityProps` separado):

- `aria_label: Option<String>` — rótulo para leitores de tela
- `aria_live: Option<AriaLive>` — regiões dinâmicas
- `role_override: Option<AriaRole>` — sobrescrever role inferido

### 6.4 Widgets

Atualizar `Button`, `TextInput`, `Select`, `Slider` em `lemon::widget` para declarar seus roles semânticos corretos.

### 6.5 Teste

- Testar no macOS com VoiceOver (Cmd+F5)
- Testar no Windows com Narrator ou NVDA
- Sem ferramenta automatizada de a11y por agora — inspeção manual

---

## Etapa 7 — Exemplo `examples/showcase`

Demonstra V0.5 completo: tema claro/escuro alternável, animação de fade, janela secundária, TextInput acessível via VoiceOver.

---

## Critério de done

- [ ] `cargo test --workspace` — sem falhas (incluindo novos testes de Fragment)
- [ ] `cargo clippy --workspace --all-targets -- -D warnings` — sem warnings
- [ ] `Component::new_with_props` funciona; código existente não quebra
- [ ] Fragment sem edge cases nos testes unitários
- [ ] `use_theme()` retorna tokens corretos; widgets usam tema por padrão
- [ ] `use_animation` anima `opacity` de 0 → 1 em 300ms
- [ ] `cx.open_window` abre segunda janela com seu próprio root
- [ ] *(se incluído)* VoiceOver lê botões e campos de texto corretamente
- [ ] `cargo run -p showcase` — exemplo funcional
