Inkreply
Um caderno para Apple Pencil em que nada vira texto digitado. Cada nota tem um Caderno pautado e um Quadro infinito, e um agente lê o que eu escrevi e responde na própria página, com a letra dele.
Eu estudo escrevendo à mão no iPad: exercício de algoritmo, rascunho de arquitetura, conta. O Inkreply é o caderno que eu fiz para isso. Cada nota tem duas páginas, um Caderno pautado e um Quadro infinito, e um agente lê o que está escrito e responde ali mesmo, com tinta, numa letra que é dele.
Por que eu fiz
Todo app de notas com IA que eu testei faz a mesma coisa: transforma a escrita em texto e conversa num chat ao lado. A página vira um formulário. Eu queria o contrário. A minha letra fica como eu escrevi, com o garrancho e a seta torta, e a resposta entra na página como se alguém tivesse pegado a caneta. Tinta continua tinta, inclusive a do agente.
Como funciona
- Tinta portátil. O desenho do PencilKit só abre em plataformas da Apple, então cada página também é salva num formato JSON meu, versionado: cada traço com ferramenta, cor, transformação e os pontos de controle com tempo, pressão, azimute e altitude. O iPad, o app web em Preact e o servidor leem o mesmo arquivo, e um teste decodifica uma amostra fixa para o formato não mudar sem querer.
- A letra do agente. O agente escreve com as letras da fonte Hershey script, uma fonte de traço único, e uma mão simulada por cima: um tremor lento, pressão que sobe quando a caneta encosta e some quando ela levanta, um pouco de inclinação e de variação de tamanho por letra, a linha de base que deriva. As letras minúsculas se ligam numa palavra só, e o pingo do i e o corte do t vêm depois, como na mão de verdade. No iPad isso chega como traço comum do PencilKit: dá para laçar, mover, apagar e desfazer, igual à minha tinta.
- Diagramas desenhados à mão. Caixas, elipses, losangos e setas são objetos editáveis, no estilo do Excalidraw. O web desenha com o rough.js, e no iPad eu portei o gerador do rough.js para Swift, com o mesmo gerador pseudoaleatório e cada número sorteado na mesma ordem. Um teste compara os caminhos do Swift com os do JavaScript até 1e-9, então uma caixa tem o mesmo tremido em todo aparelho.
- Sincronização na Cloudflare. Um Worker com D1 e R2 guarda biblioteca, páginas, textos e elementos. Cada aparelho envia o que mudou por uma fila que sobrevive a ficar offline e pergunta o que há de novo; o último a escrever ganha, por item.
O que estou fazendo agora
Hoje os aparelhos perguntam a cada cinco segundos. Estou trocando isso por um Durable Object que segura o WebSocket de cada cliente e, depois de cada escrita, manda só um cutucão com o número da versão. O frame não leva dado nenhum: quem recebe busca as mudanças pelo mesmo caminho de sempre, então o WebSocket nunca vira uma segunda fonte da verdade, e com a API de hibernação o objeto nem fica acordado entre uma escrita e outra.
O app roda no iPad, no navegador e no desktop Linux, e estão em andamento apps nativos para Android (Kotlin, Compose e Jetpack Ink) e Windows (C#, WinUI 3 e Win2D). Está em acesso antecipado: a lista de espera fica no site.