# Contrato · Número

> Segue o modelo de `contratos/ruler.md`. Implementado em
> `app/src/componentes/SourcedNumber/SourcedNumber.tsx` (era `Numero` até
> 23/set/2026, props traduzidas pela regra de Idioma — ver `CHANGELOG.md`
> v0.55.0; os quatro valores de origem ficaram em português de propósito). **Não é extração** — não existia
> componente, só a classe utilitária `.num`
> (`font-variant-numeric:tabular-nums`) aplicada 110 vezes no protótipo, sem
> carregar origem nenhuma. Especificação original em `HANDOFF.md` §4.3.

Existe por causa da Regra 1 do `AGENTS.md` ("nenhum número órfão"). É o
componente que torna a regra **código que reprova**, não prosa que alguém
lembra de seguir.

## Anatomia

| Parte | Papel | Obrigatória |
|---|---|---|
| `value` | o número | sim |
| `origin` | de onde ele vem — quatro chaves fechadas | **sim** |
| `source` / `formula` / `definedBy` | o detalhe da origem | **sim, um dos três, conforme `origin`** |
| `prefix` | texto antes do número, com espaço | não |
| `suffix` | texto depois do número, com espaço | não |

## Props

| Prop | Tipo | Default | Observação |
|---|---|---|---|
| `value` | `number` | **nenhum** | quebra em dev se `NaN` |
| `origin` | `'publico'` \| `'ref'` \| `'premissa'` \| `'derivado'` | **nenhum** | **Obrigatória.** As mesmas 4 chaves de `MEM_FONTE` no protótipo |
| `source` | `string` | — | exigida quando `origin` é `publico` ou `ref` |
| `formula` | `string` | — | exigida quando `origin` é `derivado` — a fórmula, com as parcelas |
| `definedBy` | `string` | — | exigida quando `origin` é `premissa` |
| `prefix` | `string` | — | "R$ 12.500", nunca "12.500 R$" |
| `suffix` | `string` | — | "227 mi" |
| `decimals` | `number` | `0` | casas decimais — `0` é a convenção da função `so()` do protótipo, `1` é a de `umaCasa()` |

### Por que `origin` não tem default, e por que é união discriminada

Mesma lógica de `peso` em Botão: se existisse um default, o defeito que este
componente existe para impedir voltaria — um número na tela sem ninguém saber
de onde veio. `origin` é uma união discriminada em TypeScript
(`ComPublico | ComRef | ComPremissa | ComDerivado`), então o **editor cobra o
campo certo no ponto de chamada**: escrever `origin="ref"` sem `source` é erro
de tipo, não só de convenção. Verificado durante a construção: `tsc` reprova
`<SourcedNumber value={1} origin="ref" />` faltando `source`, e reprova `origin`
ausente também.

O throw em desenvolvimento (`import.meta.env.DEV`) é a rede de segurança de
runtime, para quando o tipo é burlado — props espalhadas de uma fonte
dinâmica, `as` explícito. Mesma dupla camada de Botão (`icone`/`rotulo`) e
Badge (`clicavel`/`removivel`).

### Por que as quatro chaves são exatamente as do `MEM_FONTE`

`prototipo/index.html` já tem:

```js
const MEM_FONTE = {
  publico: 'dado público',
  premissa: 'sua premissa',
  ref: 'referência Dataland',
  derivado: 'derivado',
}
```

`handoff/componentes.md` é explícito: "use essas mesmas quatro chaves, não
invente sinônimo". `ORIGIN_LABEL` em `lib/origin.ts` repete os mesmos quatro
textos, byte a byte — quem lê a memória de cálculo e quem lê o código veem a
mesma palavra.

### Achado construindo, e por que `unidade` virou `prefix`/`suffix`

Os dois exemplos de `HANDOFF.md` §4.3 usam uma única prop `unidade`, sempre
como **sufixo**:

```tsx
<Numero valor={227} unidade="mi" origem="derivado" conta="..." />
<Numero valor={12500} unidade="R$/m²" origem="ref" fonte="..." />
```

Mas a própria memória de cálculo do protótipo **nunca** escreve assim: a
função `rs(x)` (`prototipo/index.html`) formata dinheiro como
`'R$ '+so(x)+' mi'` — R$ é **sempre prefixo**, em toda ocorrência de valor
monetário no arquivo (6 linhas de `memBlocos()` confirmam). Um
`unidade="R$/m²"` renderizado literalmente como sufixo daria
`"12.500 R$/m²"`, que não é como o produto real escreve dinheiro em lugar
nenhum.

Por isso `SourcedNumber` tem `prefix` e `suffix` como duas props separadas, em vez
de uma `unidade` só. Mapeamento dos exemplos do HANDOFF:

| HANDOFF.md | Implementado |
|---|---|
| `unidade="mi"` | `suffix="mi"` |
| `unidade="R$/m²"` | `prefix="R$" suffix="/m²"` |

Mesma prática já usada em `contratos/badge.md` (contagem de tom corrigida) e
`contratos/button.md`: quando a especificação e o código real do produto
divergem, o código real manda — e a divergência fica documentada aqui, não
silenciada.

## O `title` é o único mecanismo de "discreto e alcançável", por agora

`HANDOFF.md` pede que "a origem apareça na interface de forma discreta e
alcançável (o padrão que já existe no protótipo é a memória de cálculo,
`MEM_FONTE`)". A memória de cálculo é um **modal completo**, fora do escopo
desta entrega (Regra 8 da HANDOFF proíbe migrar essa lógica agora).

Para este componente, "discreto e alcançável" é o atributo `title` nativo do
HTML — passar o mouse em qualquer `<SourcedNumber>` mostra `"referência Dataland ·
41 comparáveis no raio de 1 km"`, por exemplo. **Isto não é acessível por
teclado nem por toque**, e é uma limitação real, não uma escolha definitiva:
implementar um popover próprio violaria a Regra 5 (não reinventar
comportamento de peça pronta — a tooltip com link do próprio protótipo já
custou três rodadas de retrabalho), e o componente `Popover` do design system
ainda não existe. Ver Pendências.

## Acessibilidade

- Elemento é `<span>` — número é conteúdo, não controle interativo.
- `source` / `formula` / `definedBy`: o que não corresponde a `origin` **não
  vaza para o DOM** como atributo inválido. Testado explicitamente.
- `title` funciona com leitor de tela via hover/foco do navegador, mas o
  `<span>` em si não é focável — ver limitação acima.

## Fora do contrato

- **Formatação de moeda completa** (símbolo, milhares, decimais e a regra de
  quando abreviar para "mi"/"mil") não é responsabilidade deste componente.
  `SourcedNumber` formata o número puro em pt-BR e deixa `prefix`/`suffix`/`decimals`
  para o consumidor compor.
- **A memória de cálculo** (o modal que lista todos os números de um cenário
  com origem) não é este componente. `SourcedNumber` é a peça atômica; agregar
  várias delas numa auditoria é responsabilidade de uma tela, não da peça.
- **Escala de input** (o eixo de um controle, tipo régua) não usa `SourcedNumber` —
  é a quinta categoria da Regra 1, explicitamente não-dado.

## Pendências deste componente

- **Sem alcançabilidade por teclado/toque para o detalhe de origem.**
  Bloqueado por `Popover` não existir ainda. Quando existir, `SourcedNumber` deveria
  oferecer uma variante que abre popover em vez de depender só de `title`.
- **`prefix`/`suffix` divergem do `unidade` de `HANDOFF.md`.** Documentado
  acima, não é silencioso, mas é uma mudança de API em relação ao que foi
  negociado — vale confirmar com o time de dev na próxima revisão.
- ~~Nenhuma página de documentação própria foi criada nesta entrega.~~
  **Resolvido.** Número é um dos 7 da Fase 1 — `NumeroDoc.tsx` existe desde
  27/ago/2026, página em `/componentes/numero`. A frase ficou parada no
  contrato depois que a página já existia; achado na auditoria de
  21/set/2026.
- **Não agrega com a memória de cálculo real do protótipo.** Um `<SourcedNumber>`
  usado numa tela nova não aparece automaticamente na memória de cálculo
  daquele cenário — são dois sistemas paralelos por enquanto.
