# Criar um molde para a Cadernita

Este documento é a especificação completa de um molde, e serve a dois usos:

- **um agente o busca sozinho** em `https://cadernita.com/criar-moldes.md`,
  quando alguém pede "crie um molde de X para o cadernita.com". Quem pede não
  precisa ter o código, nem entregar documento nenhum;
- **ou uma pessoa o cola inteiro** num assistente, seguido da descrição do molde.

Ele traz tudo que o validador exige — e o validador é rigoroso, então um molde
escrito de memória quase sempre é recusado.

---

## Primeiro passo: leia três moldes de fábrica

**Antes de desenhar qualquer coisa, abra
[`https://cadernita.com/moldes.md`](https://cadernita.com/moldes.md), escolha
três moldes e leia as fichas deles.** Não é leitura opcional, e não é para
depois: é o passo que decide a forma do que você vai escrever.

O motivo é este. Este documento diz o que o validador **recusa**, e nada mais —
seguir só ele leva sempre à forma mais barata que passa, uma linha curta em cima
e um campo de texto embaixo, com os moldes do pacote se distinguindo apenas pelo
`placeholder`. Os trinta e três moldes de fábrica da Cadernita são o contrário
disso: uma semana em grade, um bitmap escrito à mão, duas vozes que se alternam,
alternativas lado a lado, uma foto composta. Cada ficha traz o código inteiro do
molde e o comentário de projeto de quem o fez.

Escolha assim: **dois pelo parecido** — os moldes cujo uso mais se aproxima do
que vão te pedir — e **um pelo estranho**, que não tenha nada a ver, para ver do
que um molde é capaz.

E não se inspire nos pacotes que estiverem no `inbox/` de alguém: foram escritos
por outro agente que também não tinha o que ler, e copiar de lá multiplica a
mesma forma pobre.

---

## O que você vai produzir

Você vai escrever um **pacote de moldes da Cadernita**: um único arquivo JSON.

A Cadernita é um caderno digital. Uma *nota* nasce de um *molde* — um pequeno
formulário com aparência e comportamento próprios. O molde é a caneta; a nota é
o que fica escrito. O núcleo do aplicativo **nunca interpreta o conteúdo**: ele
só sabe que todo valor editável é um campo de formulário com um `name` estável.

Responda **somente com o JSON**, sem cercas de código e sem explicação em volta.

## O formato do arquivo

```json
{
  "format": "cadernita-package",
  "version": 1,
  "id": "meu-pacote",
  "name": "nome legível do pacote",
  "revision": 1,
  "molds": [
    {
      "id": "meu-molde",
      "name": "nome curto que aparece no seletor",
      "hint": "uma linha dizendo para que serve",
      "paper": "#eef0e6",
      "ink": "#26231f",
      "size": "content",
      "html": "…",
      "css": "…",
      "js": "…"
    }
  ],
  "palettes": [
    {
      "id": "minha-paleta",
      "name": "nome da paleta",
      "hint": "quando usar esta paleta",
      "entryStageId": "inicio",
      "stages": [
        {
          "id": "inicio",
          "templateId": "meu-molde",
          "afterCommit": "inicio",
          "onEscape": "segundo"
        },
        {
          "id": "segundo",
          "templateId": "meu-outro-molde",
          "afterCommit": "segundo",
          "onEscape": "inicio"
        }
      ]
    }
  ]
}
```

Regras do envelope:

- todo `id` casa com `^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$` — minúsculas,
  números e hífen
- `revision` é inteiro positivo — veja "corrigir é uma revisão nova" adiante
- `paper` e `ink` são cores CSS simples (`#rrggbb`); são o fundo e a tinta da nota
- `size` é um de `full`, `attached`, `content`, `grows` (explicados adiante)
- `html`, `css` e `js` vão como **strings JSON** — escape as aspas e use `\n`
- cada fonte tem no máximo 240.000 caracteres; o pacote inteiro, 1.000.000 bytes
- em `stages`, `templateId` é o **id simples** do molde (`"meu-molde"`), e não
  o id prefixado — a Cadernita monta o `local:pacote:molde` sozinha ao instalar
- `afterCommit` é a etapa que vem depois de guardar, e `onEscape` é a que vem ao
  apertar Esc — veja a regra padrão logo abaixo, porque errá-la deixa moldes
  inalcançáveis

Campos opcionais do molde:

- `"attachesToPrevious": true` — a nota cola visualmente na anterior
- `"remembers"` e `"rememberAfterCommit"` — veja "a memória do molde" adiante

## Como ligar as etapas — a regra padrão

**Siga esta regra salvo se pedirem outra coisa.** Controlar o fluxo à mão é
recurso avançado, e quase toda paleta quer só isto:

- **uma etapa por molde** da paleta;
- **`afterCommit` aponta para a própria etapa** — guardar uma nota deixa você no
  mesmo molde, pronto para escrever outra igual;
- **`onEscape` aponta para a etapa seguinte**, e a última volta para a primeira.

`Esc` é o que troca de molde, e o anel é o que faz isso funcionar. Com quatro
moldes:

```text
     ┌──────────────────────────────────────────┐
     ▼                                          │
  molde-a ──esc──► molde-b ──esc──► molde-c ──esc┘
     ▲ │
     └─┘ guardar (afterCommit)
```

**Um molde sem etapa não aparece na paleta.** Ele ainda pode ser alcançado pela
paleta "todos", que lista o catálogo inteiro, mas a paleta que você escreveu não
o mostra — e é o erro mais fácil de cometer, porque o validador não o recusa.

Uma paleta de **uma etapa só** é um caso legítimo, mas saiba o que ela significa:
`Esc` não tem para onde ir e deixa de trocar de molde. Se quiser um molde
avulso, aponte o `onEscape` dele para uma etapa de outro molde do pacote.

## A nota mostra tudo — a regra que mais importa

**Uma nota guardada tem que caber inteira na tela, sem rolagem por dentro.**

Não é gosto. A nota guardada é desenhada pelo **mesmo molde**, e ela não rola:
para não roubar a rolagem da fita, a nota guardada ignora o ponteiro. Então
qualquer coisa que sobre de uma barra de rolagem interna fica **invisível para
sempre** — o texto está lá, e ninguém consegue lê-lo.

Além disso a fita já é uma rolagem. Um molde que rola cria um segundo scroll
aninhado no primeiro, e no celular esse gesto vira uma disputa entre os dois.

Na prática: prefira o molde crescer a limitar a altura. Não escreva
`max-height`, `overflow: auto` nem `overflow: scroll` num campo de escrita.

## Os quatro tamanhos

O molde declara quanto espaço ocupa, e **uma única região cede** — a que levar a
classe `mold__yield`. Ponha essa classe no campo que absorve a sobra, quase
sempre o `textarea` principal; sem ela o molde não sabe onde ceder.

- **`grows`** — cresce com o texto e empurra a nota junto. **É o certo para
  qualquer molde com prosa, e é o que você quer na dúvida.** Dos 33 moldes de
  fábrica da Cadernita, 18 são `grows`.
- `content` — a altura é a do conteúdo. Para molde de campos **curtos e
  limitados**: um título, uma escolha, uma data. Só 4 moldes de fábrica o usam.
- `full` — ocupa a faixa inteira de escrita. Para **instrumento de composição
  fixa**: uma foto, uma grade de semana, um quadro, uma busca.
- `attached` — para continuação, junto com `attachesToPrevious`.

## A memória do molde

**Todo molde já guarda rascunho, sem você declarar nada.** Sair de um molde,
escrever noutro e voltar devolve o que estava escrito. Não use `remembers` para
isso: já é de graça.

O que `remembers` controla é o **depois de guardar**:

- sem ele, guardar limpa o molde e você começa a próxima nota em branco;
- `"remembers": true` — os valores **sobrevivem ao guardar** e continuam ali;
- `"rememberAfterCommit": ["materia", "professor"]` — só esses campos sobrevivem,
  e o resto limpa. É o que faz um molde de aula manter a matéria e esquecer o
  apontamento.

E `contexto.onMemoryChange()` serve para quando **o seu JavaScript** muda valores
por conta própria: avise, e o rascunho acompanha. Digitação não precisa — ela já
é observada.

### A pegadinha

Um molde que declara `remembers` **não recebe `contexto.onCommit`**, e um que não
declara **não recebe `onMemoryChange`**. São exclusivos. Se o seu molde precisa
pedir para guardar a partir do JavaScript, ele não pode declarar `remembers`.

## Ver a fita

O molde pode olhar as notas que estão à vista. Não vem pelo `contexto`: vem pelo
próprio documento, como uma lista escondida — a mesma ideia do `data-export`, um
molde que precisa ver o caderno o vê **olhando a tela**.

```js
const marcas = [...document.querySelectorAll('#cadernita-ribbon article')];
for (const marca of marcas) {
  marca.dataset.noteId;        // identidade da nota
  marca.dataset.noteAt;        // quando foi escrita, ISO
  marca.dataset.noteTemplate;  // de qual molde nasceu
  marca.dataset.noteTags;      // as tags, separadas por espaço
  marca.dataset.noteSize;      // quanto foi escrito, em caracteres
}
```

Três coisas que precisam ficar claras:

**É metadado, nunca o conteúdo.** O núcleo da Cadernita não interpreta o que está
escrito nas notas, e um molde também não recebe. Ainda dá para fazer bastante:
contar notas de um molde, medir ritmo pelas datas, somar o tamanho escrito,
agrupar por tag.

**É o que está à vista**, não o caderno inteiro — o recorte de tags atual. Trocar
de caderno muda a lista.

**Ela se atualiza sozinha** enquanto o molde está aberto: guardar uma nota mexe
na lista na hora. Mas o seu JavaScript roda uma vez só — para acompanhar, observe
a lista, senão o número congela no que era quando o molde abriu:

```js
const fita = document.getElementById('cadernita-ribbon');
const olho = new MutationObserver(contar);
olho.observe(fita, { childList: true });
return () => olho.disconnect();
```

Já uma nota **guardada** recebe a fita de quando ela foi escrita e não muda mais
— é registro, não painel. Por isso vale sair cedo em `contexto.locked`.

## O HTML

O molde roda dentro de um `<iframe sandbox="allow-scripts">` com CSP severa. A
Cadernita já adiciona `class="mold mold--<size>"` no `<form>` e desliga o
preenchimento automático — você não precisa fazer isso.

**Obrigatório:**

- exatamente **um** `<form>`, e ele é a raiz do molde
- todo `<input>`, `<textarea>` e `<select>` editável precisa de `name` estável —
  é por ele que o valor é lido e restaurado
- para texto de uma linha use `<textarea rows="1" wrap="off">`, **nunca**
  `<input type="text">` (nem `email`, `password`, `search`, `tel`, `url`,
  `number`) — o preenchimento automático do navegador atrapalha a escrita

**Proibido, e o validador recusa:**

- `<script>`, `<iframe>`, `<object>`, `<embed>`, `<base>`, `<meta>`, `<link>`
- eventos inline (`onclick=`, `oninput=`, …) — registre no JavaScript
- `action=` no formulário
- qualquer `src`/`href` apontando para `http:`, `https:`, `//` ou `javascript:`

Extras úteis:

- `data-autofocus` no campo que deve receber o foco ao abrir
- `data-export="canal"` num elemento marca o texto dele como saída exportável

## O CSS

Vale só para dentro do molde.

**Proibido:** `@import`; `url(http…)` ou `url(//…)`; seletores `html`, `body` ou
`:root`; `position: fixed`. Se usar `animation:`, ofereça alternativa em
`@media (prefers-reduced-motion: reduce)` — sem isso sai um aviso.

Não declare fontes externas: nada é baixado da rede.

## O JavaScript

Um módulo ES que **exporta como default** a função de montagem:

```js
export default function montar(form, contexto) {
  // ... aqui você registra comportamento
  return () => { /* limpeza opcional */ };
}
```

O `contexto` traz:

- `contexto.values` — os valores já restaurados, como objeto
- `contexto.locked` — `true` quando é uma nota guardada, não um molde ativo.
  **Sempre confira**: nota guardada não deve reagir a nada.
- `contexto.onCommit()` — pede à Cadernita para guardar a nota
- `contexto.onMemoryChange()` — avisa que a memória do molde mudou
- `contexto.assets.keep(file)` → `Promise<id>` — guarda uma imagem
- `contexto.assets.read(id)` → `Promise<Blob>` — lê uma imagem guardada

Você **não precisa** ler nem gravar os campos: a Cadernita coleta sozinha todo
`[name]` dentro do formulário, e restaura sozinha ao reabrir. Escreva JavaScript
só quando houver comportamento de verdade.

### A regra que mais reprova: a propriedade do DOM

O validador **prova, lendo o código, que toda escrita no DOM tem alvo dentro do
seu próprio molde.** Só existem duas origens de propriedade:

1. o parâmetro `form`
2. `document.createElement(...)`

Descer a partir delas preserva a propriedade (`form.querySelector('…')` continua
sendo seu). **Subir a perde**: `.parentNode`, `.parentElement`, `.ownerDocument`,
`.offsetParent`, `.host`, `.getRootNode()` e `.closest()` devolvem algo que você
pode ler, mas não alterar.

```js
// certo
const campo = form.querySelector('[name="texto"]');
campo.value = '';
const item = document.createElement('li');
form.querySelector('ul').append(item);

// recusado
document.querySelector('.note').textContent = 'x';   // não é seu
campo.parentNode.append(item);                        // subiu, perdeu
```

### Globais proibidos

Nenhum destes pode aparecer: `fetch`, `XMLHttpRequest`, `WebSocket`,
`EventSource`, `sendBeacon` (*moldes funcionam sem rede*); `localStorage`,
`sessionStorage`, `indexedDB` (*use `contexto.assets`*); `window`, `self`,
`globalThis`, `parent`, `top`, `opener`, `frames`, `location`, `history`,
`navigator`; `eval`, `Function`, `Proxy`, `Reflect`, `WebAssembly`; `Worker`,
`SharedWorker`, `BroadcastChannel`.

Também não use os protótipos do DOM (`Node`, `Element`, `HTMLElement`,
`Document`, `Range`, `Selection`, …) — eles escondem de onde veio o elemento.

`document` pode ser lido, e serve essencialmente para `createElement` e
`addEventListener`.

### Teclado

`Escape` e `Enter` segurado já são tratados pela Cadernita. Nunca impeça a
propagação de `Enter` — ele é o gesto de guardar, e sequestrá-lo quebra o
aplicativo inteiro. Se um comando seu só existe como tecla, dê um par tocável
para quem está no celular.

## Exemplo completo, que passa no validador

Dois moldes ligados em anel: um guarda a passagem e de onde ela veio, o outro
anota o que você pensou dela. `Esc` alterna entre os dois; guardar mantém você
no mesmo. Os dois são `grows`, porque os dois têm prosa.

```json
{
  "format": "cadernita-package",
  "version": 1,
  "id": "citacoes",
  "name": "citações",
  "revision": 1,
  "molds": [
    {
      "id": "trecho",
      "name": "trecho",
      "hint": "guarda uma passagem e de onde ela veio",
      "paper": "#f2ece0",
      "ink": "#2b2721",
      "size": "grows",
      "html": "<form>\n  <textarea class=\"mold__yield\" name=\"trecho\" data-autofocus placeholder=\"a passagem\"></textarea>\n  <div class=\"rodape\">\n    <textarea name=\"fonte\" rows=\"1\" wrap=\"off\" placeholder=\"de onde veio\"></textarea>\n    <button type=\"button\" data-acao=\"marcar\">marcar</button>\n  </div>\n  <p class=\"marca\" data-export=\"citacao\"></p>\n</form>",
      "css": ".rodape { display: flex; gap: 8px; align-items: center; }\n.rodape textarea { flex: 1 1 auto; }\n.marca { margin: 6px 0 0; font-size: 12px; opacity: 0.7; }",
      "js": "export default function montar(form, contexto) {\n  if (contexto.locked) return;\n  const trecho = form.querySelector('[name=\"trecho\"]');\n  const fonte = form.querySelector('[name=\"fonte\"]');\n  const marca = form.querySelector('.marca');\n  const botao = form.querySelector('[data-acao=\"marcar\"]');\n  const atualizar = () => {\n    const texto = trecho.value.trim();\n    marca.textContent = texto ? texto.slice(0, 60) + ' — ' + fonte.value.trim() : '';\n  };\n  botao.addEventListener('click', atualizar);\n  return () => botao.removeEventListener('click', atualizar);\n}"
    },
    {
      "id": "comentario",
      "name": "comentário",
      "hint": "o que você pensou sobre o trecho anterior",
      "paper": "#e8ecef",
      "ink": "#24282b",
      "size": "grows",
      "attachesToPrevious": true,
      "html": "<form>\n  <textarea class=\"mold__yield\" name=\"comentario\" rows=\"2\" data-autofocus placeholder=\"o que isto te disse\"></textarea>\n</form>",
      "css": "textarea { width: 100%; }",
      "js": ""
    }
  ],
  "palettes": [
    {
      "id": "citando",
      "name": "citando",
      "hint": "passagens e de onde vieram",
      "entryStageId": "trecho",
      "stages": [
        {
          "id": "trecho",
          "templateId": "trecho",
          "afterCommit": "trecho",
          "onEscape": "comentario"
        },
        {
          "id": "comentario",
          "templateId": "comentario",
          "afterCommit": "comentario",
          "onEscape": "trecho"
        }
      ]
    }
  ]
}
```

## Valide antes de entregar

O validador da Cadernita responde por HTTP, e é **o mesmo** que roda dentro do
aplicativo — o que passa aqui passa lá:

```sh
curl -X POST https://cadernita.com/validar \
  -H 'Content-Type: application/json' \
  --data-binary @meu-molde.json
```

A resposta é `{ "ok": ..., "diagnostics": [...] }`, e cada diagnóstico traz
`code`, `path`, `line` e `column`. Corrija e valide de novo até `ok: true`. Nada
é guardado ali.

## Exemplo: memória e fita em uso

Dois moldes. O **sessão** guarda a matéria entre uma nota e outra, e esquece o
tópico — é `remembers` com `rememberAfterCommit`. O **ritmo** conta o que está à
vista e se atualiza quando você guarda uma nota, observando a lista escondida.

Repare no `js` do ritmo: ele **lê** de `document` e **escreve** só no que veio de
`form`. É a regra de propriedade em uso.

```json
{
  "format": "cadernita-package",
  "version": 1,
  "id": "estudo",
  "name": "estudo",
  "revision": 1,
  "molds": [
    {
      "id": "sessao",
      "name": "sessão",
      "hint": "abre um estudo e guarda a matéria entre as notas",
      "paper": "#eceff0",
      "ink": "#22262b",
      "size": "grows",
      "remembers": true,
      "rememberAfterCommit": [
        "materia"
      ],
      "html": "<form>\n  <textarea class=\"mold__line\" name=\"materia\" rows=\"1\" wrap=\"off\" data-autofocus placeholder=\"a matéria\"></textarea>\n  <p class=\"selo\"></p>\n  <textarea class=\"mold__yield\" name=\"topico\" placeholder=\"o que foi visto\"></textarea>\n</form>",
      "css": ".selo { margin: 4px 0; font-size: 11px; opacity: 0.6; }\ntextarea { width: 100%; }",
      "js": "export default function montar(form, contexto) {\n  if (contexto.locked) return;\n  const materia = form.querySelector('[name=\"materia\"]');\n  const topico = form.querySelector('[name=\"topico\"]');\n  const selo = form.querySelector('.selo');\n  const mostrar = () => {\n    selo.textContent = materia.value.trim() ? 'continuando ' + materia.value.trim() : 'nova matéria';\n  };\n  mostrar();\n  materia.addEventListener('input', mostrar);\n  topico.addEventListener('input', mostrar);\n  return () => materia.removeEventListener('input', mostrar);\n}"
    },
    {
      "id": "ritmo",
      "name": "ritmo",
      "hint": "quanto já foi escrito no que está à vista",
      "paper": "#f0eee7",
      "ink": "#26231f",
      "size": "content",
      "html": "<form>\n  <textarea class=\"mold__line\" name=\"resumo\" rows=\"1\" wrap=\"off\" readonly></textarea>\n  <textarea class=\"mold__line\" name=\"nota\" rows=\"1\" wrap=\"off\" data-autofocus placeholder=\"o que isso te diz\"></textarea>\n</form>",
      "css": "textarea { width: 100%; }\n[readonly] { opacity: 0.65; }",
      "js": "export default function montar(form, contexto) {\n  const resumo = form.querySelector('[name=\"resumo\"]');\n  const fita = document.getElementById('cadernita-ribbon');\n\n  const contar = () => {\n    const marcas = [...document.querySelectorAll('#cadernita-ribbon article')];\n    const letras = marcas.reduce((total, marca) => total + Number(marca.dataset.noteSize || 0), 0);\n    resumo.value = marcas.length + ' notas à vista, ' + letras + ' letras escritas';\n  };\n\n  contar();\n  /* Nota guardada é registro: mostra o que contou quando foi escrita. */\n  if (contexto.locked) return;\n\n  /* A fita muda enquanto o molde está aberto; sem observá-la, o número congela. */\n  const olho = new MutationObserver(contar);\n  olho.observe(fita, { childList: true });\n  return () => olho.disconnect();\n}"
    }
  ],
  "palettes": [
    {
      "id": "estudando",
      "name": "estudando",
      "hint": "matéria, e o ritmo do que já foi escrito",
      "entryStageId": "sessao",
      "stages": [
        {
          "id": "sessao",
          "templateId": "sessao",
          "afterCommit": "sessao",
          "onEscape": "ritmo"
        },
        {
          "id": "ritmo",
          "templateId": "ritmo",
          "afterCommit": "ritmo",
          "onEscape": "sessao"
        }
      ]
    }
  ]
}
```

## Corrigir é uma revisão nova

Se este pacote **já foi instalado alguma vez**, mudar qualquer coisa nele exige
subir o `revision`. Mesmo `id`, número maior.

Não é burocracia: o número nomeia um conteúdo exato, e é isso que permite à
pessoa desfazer a atualização e voltar exatamente ao que ela tinha. Por isso a
Cadernita **recusa** um arquivo que traga a mesma revisão com conteúdo diferente
— ela diria "a revisão 1 já está instalada com outro conteúdo; suba `revision`
para 2".

Corrigir é o caso mais comum ao escrever molde com uma IA, e é o mais fácil de
esquecer. Ao entregar uma correção, confira o número antes de mais nada.

## Onde deixar o arquivo

Salve com extensão `.json`. Depois, qualquer um destes caminhos serve:

**Se a pessoa tem uma pasta conectada à Cadernita** — grave dentro de `inbox/`,
ali na raiz da pasta. É a caixa de entrada: a Cadernita a lê e mostra a proposta
no painel de paletas, para a pessoa aceitar. A pasta `inbox/` é criada sozinha,
e qualquer `.json` dentro dela é considerado.

**Se não tem, ou não se sabe** — deixe o arquivo onde a pessoa ache, e diga a
ela para, em [cadernita.com](https://cadernita.com), fazer um destes:

- **arrastar** o arquivo para cima da página
- **copiar** o arquivo e apertar `Ctrl+V` na página
- abrir o painel de dados e usar **abrir caderno recebido**

Este segundo caminho funciona em qualquer aparelho, inclusive celular.

Se o pacote tiver erro, a Cadernita mostra o diagnóstico com linha e coluna. Leve
a mensagem de volta ao assistente e peça a correção — ela é precisa.

---

**Agora descreva o molde que você quer.** Diga para que serve, quais campos tem,
que aparência deve ter e o que acontece ao escrever. Quanto mais concreto o uso
real, melhor o molde.
