← Blog

A arquitetura invertida

Como um commit do React se torna uma chamada console.log no DevTools.

Adam

Em um framework DOM, os componentes se tornam nós DOM que o navegador organiza e pinta na página. No Text.js, os componentes se tornam células, e cada quadro de células alterado se torna uma chamada a console.log no DevTools. Chamamos isso de arquitetura invertida.

O pipeline

React ─▶ reconciler ─▶ Yoga ─▶ CellBuffer ─▶ row-hash gate ─▶ RLE ─▶ console.log

Cada etapa existe porque o console é uma tela incomum. Ele usa fonte monoespaçada e é orientado a linhas, recebe estilos com %c, e o DevTools decide quando repintá-lo.

Reconciliador

@textjs/react é um reconciliador do React 19 ([email protected]) com dois elementos hospedeiros, tx-box e tx-text, além de instâncias de texto para filhos do tipo string. Tudo o que você importa, de <Box> a <Markdown>, é construído sobre eles. Os commits são agendados em requestAnimationFrame, e um pressionamento de tecla ou uma colagem dispara uma renderização síncrona na mesma tarefa.

Layout

Yoga, compilado para WebAssembly, organiza a árvore com pointScaleFactor = 1, para que cada caixa se alinhe a uma célula inteira. O texto é medido com Intl.Segmenter e tabelas de largura de caracteres do leste asiático, de modo que 你好 ocupa quatro células e 🚀 ocupa duas. Se uma edição não puder alterar o tamanho de um nó de texto, Yoga não é consultado novamente.

CellBuffer

O quadro é uma grade de arrays tipados alocados uma única vez: caracteres em um Uint32Array, cores de primeiro plano e fundo compactadas como 0xRRGGBBAA, atributos como uma máscara de bits Uint16Array. A pintura escreve nesses arrays e rastreia quais linhas foram tocadas.

O filtro

Cada linha tem um hash FNV-1a de 32 bits em cache. Apenas as linhas escritas desde o último quadro têm o hash recalculado. Se nenhuma linha mudou, o driver retorna antes de construir uma string e não faz nenhuma chamada ao console. Um indicador giratório em uma aba oculta não custa nada porque não está avançando.

Codificação por comprimento de sequência

As linhas alteradas são percorridas novamente e agrupadas em sequências de estilo idêntico, e cada sequência se torna um span %c. As sequências continuam através das quebras de linha. As strings CSS são internadas e referenciadas por IDs inteiros de estilo, de modo que o caminho crítico nunca constrói a mesma string duas vezes. Um painel de 100×32 tem 3.200 células e 149 spans.

A string de formato também protege contra dois comportamentos do DevTools. Um % literal é escrito como %%. Um word joiner é inserido depois de ://, dentro de www. e em path:line, para que o DevTools não transforme seu texto em um link e o repinte com suas próprias cores.

Uma chamada

Cada quadro alterado é uma única chamada:

console.log("%c\u200b%c\n%c▼ OpenCode Lite%c …", "margin-top:100vh;", "", "color:#fafafa;…", …);

Os quadros são anexados, cadenciados pelo limitador de 50 ms do DevTools e trocados dentro de uma única pintura. Turbolog: eliminando o flash cobre essa parte.

Entrada

O console não pode receber entradas do teclado, então a página as recebe. Um <textarea> oculto coleta teclas, colagens e composição por IME, e eventos wheel rolam os componentes ScrollBox. No textjs.org, as páginas Playground e OpenCode são uma grande área de foco. Abra o console e clique na página para digitar.

O que a página vê

A página não pode ler o tamanho do console, apenas o da própria janela, então o Text.js o infere: um painel acoplado reduz innerWidth ou innerHeight enquanto outer* permanece igual, e cada hipótese de acoplamento é calculada para os níveis de zoom do Chrome. O melhor ajuste vence. As constantes (56 px de espaçamento interno horizontal, 104 px de elementos da interface do console, 14 px de distância entre linhas) foram medidas no frontend real do DevTools.