# Spec: jcv — JSON Canvas Viewer (v0)

> **Status:** Draft (aguarda revisão)
> **Author:** Henrique Lima
> **Date:** 2026-04-29
> **Brief:** [`PRODUCT_BRIEF.md`](./PRODUCT_BRIEF.md)
> **Scope:** v0 (MVP, lançamento em <1 mês)

---

## 1. Objective

Construir `jcv`, uma CLI que renderiza ficheiros `.canvas` (formato [JSON Canvas 1.0](https://jsoncanvas.org/)) directamente no terminal, com live reload sobre alterações em disco. O v0 foca-se exclusivamente em **visualização** — edição no terminal, export para PNG e app nativa (Tauri) ficam fora do v0 e voltam a partir de v0.1.

**Quem:** devs e power users de terminal que precisam inspeccionar `.canvas` em sessões SSH, máquinas headless, ou simplesmente preferem não sair do CLI.

**Sucesso v0 = um utilizador consegue, num único comando (`jcv ./file.canvas`), abrir e navegar visualmente um canvas produzido no Obsidian, ver as alterações reflectirem-se em tempo real, e fechar com `q` sem corromper o ficheiro.**

### Cortes face ao brief

| Must-have do brief | v0 | Razão |
|---|---|---|
| Visualizar `.canvas` no terminal + live reload | ✅ | Núcleo do produto |
| Editar `.canvas` no terminal | ❌ → v0.1 | Risco de prazo + complexidade UX em TTY |
| Exportar para PNG | ❌ → v0.1 | Pode reaproveitar o engine de layout depois |
| App nativa (Tauri) | ❌ → v0.2 | Tauri reusa o core Rust; entra quando o core estabilizar |

Mitigação alinhada com o risco #1 do brief ("Cut native app or PNG export from v0 if needed; ship CLI viewer first").

---

## 2. Tech Stack

- **Linguagem:** Rust (edition 2021, MSRV a definir — provavelmente 1.75+)
- **TUI:** [`ratatui`](https://github.com/ratatui-org/ratatui) + [`crossterm`](https://github.com/crossterm-rs/crossterm) como backend
- **JSON parsing:** `serde` + `serde_json`
- **File watching:** [`notify`](https://github.com/notify-rs/notify) (cross-platform fsnotify)
- **CLI args:** `clap` (derive)
- **Logging:** `tracing` + `tracing-subscriber` (a `~/.local/state/jcv/log` ou `$XDG_STATE_HOME`)
- **Erros:** `anyhow` (binário) + `thiserror` (libs internas)

**Versões pinned no `Cargo.toml`** com `cargo update` mensal manual.

---

## 3. Commands

```bash
# Build
cargo build              # debug
cargo build --release    # produção

# Run
cargo run -- <file.canvas>
./target/release/jcv <file.canvas>

# Test
cargo test
cargo test -- --nocapture          # com stdout
cargo nextest run                  # opcional, mais rápido

# Lint
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --check                  # em CI
cargo fmt                          # em local

# Dev loop
cargo watch -x run                 # opcional
```

CLI de utilizador final:

```bash
jcv <path/to/file.canvas>          # abre o canvas
jcv --version
jcv --help
```

---

## 4. Project Structure

```
jcv/
├── Cargo.toml
├── Cargo.lock
├── PRODUCT_BRIEF.md
├── SPEC.md                         # este ficheiro
├── README.md
├── LICENSE
├── src/
│   ├── main.rs                     # entrypoint, parse args, init tracing, lança app
│   ├── app.rs                      # event loop ratatui (input, tick, render)
│   ├── canvas/
│   │   ├── mod.rs
│   │   ├── model.rs                # tipos JSON Canvas 1.0 (Node, Edge, Canvas)
│   │   ├── parse.rs                # serde load/validate
│   │   └── spec_version.rs         # constante da versão da spec suportada
│   ├── layout/
│   │   ├── mod.rs
│   │   ├── viewport.rs             # estado do viewport (offset x/y, zoom)
│   │   └── transform.rs            # canvas px → cell grid
│   ├── render/
│   │   ├── mod.rs
│   │   ├── nodes.rs                # text/file/link/group → ratatui widgets
│   │   ├── edges.rs                # routing de linhas + setas em ASCII
│   │   └── theme.rs                # cores (respeita NO_COLOR)
│   ├── input.rs                    # keymap (hjkl, setas, +/-, q, ?)
│   └── watcher.rs                  # notify → canal mpsc → app
├── tests/
│   ├── fixtures/
│   │   ├── empty.canvas
│   │   ├── single-text.canvas
│   │   ├── all-node-types.canvas
│   │   └── obsidian-export.canvas  # captura real do Obsidian
│   ├── parse_test.rs
│   └── layout_test.rs
└── docs/
    └── adr/                        # ADRs (decisions)
```

---

## 5. Code Style

Padrão idiomático Rust + clippy `-D warnings`. Exemplo do estilo esperado:

```rust
use serde::Deserialize;

/// JSON Canvas 1.0 node. Spec: https://jsoncanvas.org/spec/1.0/
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Node {
    Text(TextNode),
    File(FileNode),
    Link(LinkNode),
    Group(GroupNode),
}

#[derive(Debug, Clone, Deserialize)]
pub struct TextNode {
    pub id: String,
    pub x: i32,
    pub y: i32,
    pub width: u32,
    pub height: u32,
    pub text: String,
    #[serde(default)]
    pub color: Option<String>,
}

impl Node {
    pub fn id(&self) -> &str {
        match self {
            Node::Text(n) => &n.id,
            Node::File(n) => &n.id,
            Node::Link(n) => &n.id,
            Node::Group(n) => &n.id,
        }
    }
}
```

**Convenções:**

- `snake_case` para funções e variáveis, `CamelCase` para tipos, `SCREAMING_SNAKE_CASE` para constantes
- Módulos pequenos e focados; 1 ficheiro = 1 responsabilidade
- Prefere `?` a `unwrap()`/`expect()` excepto em testes ou invariantes provados
- Doc comments (`///`) em todos os tipos/funções `pub`
- Sem comentários explicativos do *o quê* — só do *porquê não-óbvio*
- Imports agrupados: `std` → externos → `crate::`
- Sem `unsafe` em v0

---

## 6. Testing Strategy

- **Framework:** `cargo test` builtin + fixtures `.canvas` em `tests/fixtures/`
- **Unit tests:** inline em `#[cfg(test)] mod tests` dentro de cada módulo de `canvas/`, `layout/`, `render/`
- **Integration tests:** em `tests/`, carregam fixtures e exercitam parsing + transform
- **Snapshot tests:** `insta` para output renderizado (buffer ratatui → string)
- **Cobertura:** alvo informal 70%+ em `canvas/` e `layout/`; render é mais difícil de cobrir, foca em snapshots
- **CI:** `cargo fmt --check && cargo clippy -D warnings && cargo test` em todos os PRs

Cada PR deve incluir teste novo ou snapshot que falhe sem a alteração.

---

## 7. Boundaries

**Always do:**
- Correr `cargo fmt` + `cargo clippy -D warnings` + `cargo test` antes de cada commit
- Validar JSON Canvas contra a spec 1.0 ao carregar; falhar com erro claro se inválido
- Tratar o `.canvas` como **read-only** no v0 (nunca escrever no ficheiro)
- Respeitar `NO_COLOR` env var
- Suportar canvases até ~1000 nodes sem bloquear o event loop

**Ask first:**
- Adicionar dependências novas ao `Cargo.toml`
- Mudar a versão da spec JSON Canvas suportada
- Alterar o keymap default
- Introduzir `unsafe`
- Tocar em CI ou release config

**Never do:**
- Escrever no ficheiro `.canvas` aberto (v0 é viewer puro)
- Fazer chamadas de rede (link nodes mostram URL, não fazem fetch)
- Commitar segredos, tokens, ou paths absolutos do dev
- Fazer panic no event loop — sempre log + recover
- Bloquear o render com I/O síncrono (file watch corre em thread separada)

---

## 8. Success Criteria

v0 está done quando:

- [ ] `jcv ./file.canvas` abre um canvas real exportado do Obsidian e renderiza todos os 4 tipos de node (text, file, link, group) + edges
- [ ] Navegação com `hjkl` / setas move o viewport; `+`/`-` ajustam zoom; `q` sai limpo
- [ ] Live reload: editar e gravar o ficheiro no Obsidian (ou outro editor) actualiza a vista em <500 ms
- [ ] Canvas com 100 nodes renderiza em <100 ms (release build)
- [ ] Erros de parse mostram mensagem clara apontando linha/path do JSON
- [ ] `jcv --help` documenta todas as flags e o keymap
- [ ] CI verde: `fmt`, `clippy -D warnings`, `cargo test`, build em macOS + Linux
- [ ] README com instalação (`cargo install jcv`) e GIF/screencast a demonstrar uso
- [ ] Versão `0.1.0` publicada (crates.io ou GitHub release com binários)

---

## 9. Layout / Rendering Approach

**Estratégia escolhida:** fidelidade espacial + viewport scrollável.

- O canvas tem coordenadas em pixels (x, y, width, height por node)
- Definimos um factor de escala `cells_per_px` (default a calibrar — provavelmente ~1 célula horizontal = 8 px, 1 vertical = 16 px, dado aspect ratio típico de células)
- O viewport tem offset `(vx, vy)` em células e dimensão = tamanho do terminal
- Render: para cada node, calcula bounding box em células, intersecta com viewport, desenha se visível
- Edges: linhas em ASCII/Unicode box-drawing entre nodes; routing simples (Manhattan) no v0; melhorar depois se necessário

**Subset suportado v0:** spec JSON Canvas 1.0 completa para os 4 tipos de node + edges com labels, cores e sides.
- `text` → renderiza markdown como texto plain (sem render markdown rico no v0)
- `file` → mostra o path + ícone/badge
- `link` → mostra URL (clicável via OSC 8 onde suportado)
- `group` → caixa contendo outros nodes

---

## 10. Roadmap pós-v0

- **v0.1:** edição básica no terminal (mover nodes, editar texto, criar/apagar edges) + escrita atómica no `.canvas`
- **v0.2:** export PNG (render headless reaproveitando o layout engine, via `tiny-skia` ou similar)
- **v0.3:** app nativa Tauri partilhando o core (`canvas/` + `layout/` viram crate reutilizável)
- **v1.0:** depois de feedback real

---

## 11. Open Questions

Decisões não-bloqueantes para começar a implementar — resolver antes da release:

- [ ] **Plataformas suportadas:** macOS + Linux apenas, ou Windows também? (afecta CI matrix)
- [ ] **Distribuição:** `cargo install`? Homebrew tap? GitHub releases com binários pré-built?
- [ ] **Licença:** MIT, Apache-2.0, dual?
- [ ] **Render markdown rico em text nodes:** v0 = plain text; vale a pena `pulldown-cmark` + render TUI no v0.1?
- [ ] **Mouse support:** crossterm suporta; vale a pena no v0 ou só keyboard-driven?
- [ ] **Config file:** `~/.config/jcv/config.toml` para keymap/cores ou hardcoded no v0?
- [ ] **Telemetria de adopção:** como medir os "GitHub adoption signals" do brief sem violar privacidade? (provavelmente: só observar stars/issues/PRs)

---

## 12. Risks (recapitulados do brief)

| Risco | Mitigação aplicada nesta spec |
|---|---|
| Escopo grande demais para <1 mês | v0 corta edit + PNG + app nativa |
| Renderizar canvases complexos legíveis em TTY | Fidelidade espacial + viewport scroll; subset visual fica óptimo se autor desenhou bem no Obsidian |
| Adopção baixa | README com GIF, lançamento em HN/r/commandline/Obsidian discord (fora desta spec) |
| Spec JSON Canvas evoluir | Pin à versão 1.0 (constante em `canvas/spec_version.rs`); documentar compatibilidade no README |

---

## 13. Verification (gating para sair desta fase)

- [x] Cobre os 6 core areas do template (objective, tech stack, commands, structure, style, testing, boundaries)
- [ ] Humano (Henrique) reviu e aprovou
- [x] Success criteria são específicos e testáveis
- [x] Boundaries (Always / Ask first / Never) definidas
- [x] Spec guardada no repo (`SPEC.md`)

Quando o checkbox de revisão estiver marcado, avançar para `agent-skills:planning-and-task-breakdown` para gerar `PLAN.md` + lista de tasks.
