# Implementation Plan: jcv v0

> **Spec:** [`SPEC.md`](./SPEC.md) · **Brief:** [`PRODUCT_BRIEF.md`](./PRODUCT_BRIEF.md)
> **Status:** Draft · **Date:** 2026-04-29

## Overview

Construir o CLI `jcv` — viewer de ficheiros `.canvas` (JSON Canvas 1.0) no terminal, com live reload. v0 é viewer puro: parse → layout → render TUI → input → watch. Sem edição, sem PNG, sem app nativa. Alvo: <1 mês.

## Architecture Decisions

- **Crate único** (`jcv`) — extracção para crate reutilizável adia-se para v0.2 (Tauri). Evita over-engineering.
- **Vertical slicing por capability**: cada slice entrega algo executável (parse → render estático → navegação → todos os tipos → edges → zoom → watch). Cada checkpoint deixa `cargo run` funcional.
- **Render imediato, sem cache de layout em v0**: 1000 nodes a <100 ms é viável recalcular por frame. Optimização entra só se profiler mostrar.
- **Watcher em thread separada → mpsc → event loop**: já no SPEC §7. Não bloquear render.
- **Snapshot tests com `insta`** para render: cobrir regressões visuais sem fragilidade de pixel-perfect.
- **Plataformas**: macOS + Linux. Windows fora do v0.
- **Licença**: MIT.

## Task List

### Phase 1: Foundation

- [x] **Task 1: Scaffold do projecto Rust**
  - **Descrição:** Inicializar `Cargo.toml`, estrutura de directórios da SPEC §4, `main.rs` mínimo, `clap` com `<file>` + `--version` + `--help`, `tracing` a ficheiro em `$XDG_STATE_HOME/jcv/log` (ou `~/.local/state/jcv/log`), `LICENSE` (MIT), `.gitignore`.
  - **Acceptance:**
    - [ ] `cargo build` passa
    - [ ] `cargo run -- --version` imprime versão
    - [ ] `cargo run -- --help` lista flags e (placeholder) keymap
    - [ ] `cargo run -- nonexistent.canvas` falha com erro claro (não panic)
  - **Verification:** `cargo fmt --check && cargo clippy -- -D warnings && cargo build`
  - **Dependencies:** None
  - **Files:** `Cargo.toml`, `src/main.rs`, `LICENSE`, `.gitignore`
  - **Scope:** S

- [x] **Task 2: Modelo + parser JSON Canvas 1.0**
  - **Descrição:** Tipos `Canvas`, `Node` (enum tagged: text/file/link/group), `Edge` em `src/canvas/model.rs`. Parser com `serde_json` em `parse.rs`. Constante de versão em `spec_version.rs`. Erro de parse aponta linha/coluna do JSON. Fixtures: `empty`, `single-text`, `all-node-types`, `obsidian-export` (precisamos exportar uma a partir do Obsidian — TODO).
  - **Acceptance:**
    - [ ] Carrega `tests/fixtures/all-node-types.canvas` sem erro
    - [ ] JSON malformado → erro com path + linha
    - [ ] JSON válido mas sem campos obrigatórios → erro identificando o campo
    - [ ] Unit tests cobrem cada variant de `Node`
  - **Verification:** `cargo test --test parse_test`
  - **Dependencies:** Task 1
  - **Files:** `src/canvas/{mod.rs,model.rs,parse.rs,spec_version.rs}`, `tests/fixtures/*.canvas`, `tests/parse_test.rs`
  - **Scope:** M

### Checkpoint A: Parsing sólido
- [ ] `cargo test` verde
- [ ] Fixture do Obsidian real existe e parseia
- [ ] Erros de parse são humanamente legíveis

### Phase 2: Render estático

- [x] **Task 3: Viewport + transform**
  - **Descrição:** `Viewport { offset_x, offset_y, zoom, term_size }`. `transform.rs`: canvas px → cell grid usando `cells_per_px` (default 1/8 horizontal, 1/16 vertical, conforme SPEC §9). Função `node_bbox_in_cells(node, viewport) -> Option<Rect>` (None se fora do viewport).
  - **Acceptance:**
    - [ ] Unit tests: node em (0,0) com viewport em (0,0) → bbox correcto
    - [ ] Node fora do viewport → None
    - [ ] Zoom afecta tamanho proporcionalmente
    - [ ] Aspect ratio mantém-se em zoom
  - **Verification:** `cargo test --test layout_test`
  - **Dependencies:** Task 2
  - **Files:** `src/layout/{mod.rs,viewport.rs,transform.rs}`, `tests/layout_test.rs`
  - **Scope:** M

- [x] **Task 4: Render de text node + app loop mínimo**
  - **Descrição:** `app.rs` com event loop ratatui (init crossterm raw mode, alt screen, restore on drop). `render/nodes.rs` desenha `TextNode` como `Block` + `Paragraph` (texto plain). Tema básico em `theme.rs` respeitando `NO_COLOR`. `q` sai limpo. Snapshot test: render de `single-text.canvas` para um `Buffer` ratatui, comparar com `insta`.
  - **Acceptance:**
    - [ ] `jcv tests/fixtures/single-text.canvas` mostra a caixa com texto
    - [ ] `q` restaura o terminal sem lixo
    - [ ] Panic no event loop → log + restore (não deixa terminal partido)
    - [ ] `NO_COLOR=1` desactiva cores
    - [ ] Snapshot test passa
  - **Verification:** `cargo test` + manual: abrir e fechar
  - **Dependencies:** Task 3
  - **Files:** `src/app.rs`, `src/render/{mod.rs,nodes.rs,theme.rs}`, `src/main.rs` (wire-up), snapshot em `src/render/snapshots/`
  - **Scope:** M

### Checkpoint B: Render mínimo funciona
- [ ] Demo: `cargo run -- tests/fixtures/single-text.canvas` mostra um node, `q` sai limpo
- [ ] Terminal nunca fica corrompido (mesmo em panic)

### Phase 3: Interacção e tipos completos

- [x] **Task 5: Navegação por teclado (hjkl/setas + zoom)**
  - **Descrição:** `input.rs` com keymap: `h/j/k/l` e setas para mover viewport (step configurável internamente, ~5 cells), `+`/`-` para zoom (steps geométricos, ex.: × ou ÷ 1.25, com clamps min/max), `?` mostra help overlay, `q`/Ctrl-C sai. Re-render em cada input.
  - **Acceptance:**
    - [ ] hjkl move viewport visualmente
    - [ ] +/- amplia/reduz, com limites (não passa de ~10× nem fica abaixo de 0.1×)
    - [ ] `?` abre overlay com lista de keybindings; segundo `?` ou Esc fecha
    - [ ] Ctrl-C trata-se como `q` (clean exit)
  - **Verification:** Manual + test de input dispatch (mock de KeyEvent)
  - **Dependencies:** Task 4
  - **Files:** `src/input.rs`, `src/app.rs`, `src/render/mod.rs` (overlay)
  - **Scope:** M

- [x] **Task 6: Render dos 4 tipos de node**
  - **Descrição:** Estender `render/nodes.rs` para `FileNode` (path + ícone tipo `📄` ou `[F]` se `NO_COLOR`/sem unicode), `LinkNode` (URL com OSC 8 hyperlink onde suportado), `GroupNode` (bordas a tracejado a conter visualmente os filhos). Cores do node respeitam `color` field do JSON Canvas + tema fallback.
  - **Acceptance:**
    - [ ] Fixture `all-node-types.canvas` renderiza os 4 tipos distintamente
    - [ ] Snapshot test cobre cada tipo
    - [ ] Group desenha-se atrás (z-order) dos seus filhos
    - [ ] Cores do JSON (`"1"`–`"6"` e hex) mapeiam para o tema
  - **Verification:** `cargo test` + visual em `all-node-types.canvas`
  - **Dependencies:** Task 5
  - **Files:** `src/render/nodes.rs`, `src/render/theme.rs`, snapshots
  - **Scope:** M

- [x] **Task 7: Render de edges (Manhattan ASCII)**
  - **Descrição:** `render/edges.rs`: routing Manhattan simples — sair pelo `fromSide`, entrar pelo `toSide`, com 1 cotovelo. Box-drawing chars (`─│┌┐└┘`) com fallback ASCII (`-|+`) se `NO_COLOR` ou TERM básico. Setas (`▶◀▲▼` ou `>`/`<`/`^`/`v`). Labels desenham-se a meio do segmento mais longo.
  - **Acceptance:**
    - [ ] Edges entre nodes em `all-node-types.canvas` aparecem ligados
    - [ ] Setas apontam na direcção certa (toEnd / fromEnd)
    - [ ] Labels visíveis quando há espaço; truncar com `…` quando não há
    - [ ] Snapshot test com edges
  - **Verification:** `cargo test` + visual
  - **Dependencies:** Task 6
  - **Files:** `src/render/edges.rs`, `src/render/mod.rs`
  - **Scope:** M

### Checkpoint C: Visualização completa
- [ ] Canvas exportado do Obsidian renderiza reconhecível
- [ ] Todos os 4 tipos + edges visíveis
- [ ] Navegação fluida em fixture grande

### Phase 4: Live reload e robustez

- [x] **Task 8: File watcher**
  - **Descrição:** `watcher.rs`: thread com `notify` watcher, debounce ~100 ms, envia evento `Reload` por `mpsc::channel` para o event loop. App recebe → re-parse → se OK actualiza estado, se erro mostra banner de erro mas mantém estado anterior (não crasha). Targets <500 ms da gravação ao re-render.
  - **Acceptance:**
    - [ ] Editar fixture externamente actualiza vista em <500 ms
    - [ ] Ficheiro inválido durante edição → banner de erro, sem crash, sem perder vista
    - [ ] Múltiplas gravações rápidas debounceadas (1 reparse, não 5)
    - [ ] `notify` thread não vaza ao sair
  - **Verification:** Manual (editar + cronómetro) + integration test que escreve fixture e espera evento
  - **Dependencies:** Task 7
  - **Files:** `src/watcher.rs`, `src/app.rs`
  - **Scope:** M

- [x] **Task 9: Robustez + UX de erros**
  - **Descrição:** Erros de parse mostram path + linha + coluna em banner. `--help` documenta keymap completo. Ficheiro inexistente: erro CLI claro (não TUI). `panic` handler global → restore terminal + log + exit não-zero. Verificar perf alvo (<100 ms para 100 nodes em release) com `cargo bench` ad-hoc ou `time` simples.
  - **Acceptance:**
    - [ ] Painel mental review da SPEC §8 — todos os bullets a ✓ excepto release/README
    - [ ] Panic ao parsing/render não corrompe terminal
    - [ ] `--help` mostra todas as keys de Task 5
    - [ ] Bench ad-hoc: 100 nodes < 100 ms em release
  - **Verification:** `cargo run --release -- tests/fixtures/big.canvas` (criar fixture com 100 nodes via script ou à mão) + `time`
  - **Dependencies:** Task 8
  - **Files:** `src/main.rs`, `src/app.rs`, possível `tests/fixtures/big.canvas`
  - **Scope:** S

### Checkpoint D: Feature-complete
- [ ] SPEC §8 success criteria 1–6 todos ✓
- [ ] Live reload <500 ms confirmado
- [ ] 100 nodes <100 ms confirmado

### Phase 5: Ship

- [x] **Task 10: CI**
  - **Descrição:** GitHub Actions: matrix macOS + Linux, jobs `fmt`, `clippy -D warnings`, `test`. Cache de `~/.cargo` e `target/`. Trigger em PRs e push para `main`.
  - **Acceptance:**
    - [ ] CI verde num PR de teste
    - [ ] Falha real em fmt/clippy/test bloqueia
  - **Verification:** Abrir PR dummy
  - **Dependencies:** Task 9
  - **Files:** `.github/workflows/ci.yml`
  - **Scope:** S

- [x] **Task 11: README + release 0.1.0**
  - **Descrição:** README com: o que é, instalação (`cargo install jcv`), uso (`jcv file.canvas`), keymap, GIF/screencast (gravar com `vhs` ou `asciinema` + ferramenta de conversão). GitHub release `v0.1.0` com binários macOS (x86_64 + arm64) e Linux (x86_64) cross-compiled. Publicar no crates.io.
  - **Acceptance:**
    - [ ] `cargo install jcv` num ambiente limpo funciona
    - [ ] Binários da release executam em macOS e Linux
    - [ ] README tem GIF visível
  - **Verification:** Instalação numa VM/container limpos
  - **Dependencies:** Task 10
  - **Files:** `README.md`, `.github/workflows/release.yml`, asset do GIF
  - **Scope:** M

### Checkpoint E: v0 shipped
- [ ] SPEC §8 todos os bullets ✓
- [ ] Anúncio (HN/r/commandline/Obsidian discord) — fora do PLAN

## Risks and Mitigations

| Risk | Impact | Mitigation |
|------|--------|------------|
| Calibração `cells_per_px` produz layout ilegível | High | Task 3 isola transform; ajusta defaults com fixture do Obsidian em Task 6/7 |
| `notify` comporta-se diferente em macOS vs Linux | Med | Task 8 testa nas duas plataformas; usa `RecommendedWatcher` (default por OS) |
| Routing Manhattan trivial é ilegível com muitos edges | Med | v0 aceita ilegibilidade com >50 edges sobrepostos; melhorias adiadas para v0.1 |
| Cross-compile de binários para release | Med | Usar `cargo-dist` ou matrix simples; não bloqueia se `cargo install` funcionar |
| Performance >100 nodes recalcular tudo por frame | Low | Profile em Task 9; cache de bbox só se necessário |

## Open Questions (da SPEC §11 — resolver antes de v0.1, não bloqueiam start)

- [ ] Distribuição: tap Homebrew? (assumido: só `cargo install` + GH releases para v0)
- [ ] Markdown rico em text nodes (assumido: plain v0, `pulldown-cmark` em v0.1)
- [ ] Mouse support (assumido: keyboard-only v0)
- [ ] Config file (assumido: hardcoded v0)
- [ ] Telemetria de adopção: só observar GH stars/issues/PRs

## Parallelization Notes

Quase tudo é sequencial (cada task depende da anterior visualmente). Possíveis paralelos quando entrar segundo executor:
- Task 6 (4 tipos de node) e Task 7 (edges) podem dividir-se por sub-tipo se necessário
- Task 10 (CI) e Task 11 (README) podem correr em paralelo após Task 9

## Verification (gating para sair desta fase)

- [x] Cada task tem acceptance criteria testáveis
- [x] Cada task tem step de verificação
- [x] Dependências identificadas e ordenadas
- [x] Nenhuma task toca >5 ficheiros (Task 11 é a maior, ainda dentro do limite efectivo)
- [x] Checkpoints entre fases
- [ ] Humano (Henrique) reviu e aprovou
