Airplan
Uma aplicação interna de gestão de projetos e portfólio para uma organização de TI corporativa — intake, scoring, planejamento trimestral, roadmap, execução de sprints e relatórios executivos em 31 páginas — construída diretamente sobre o banco de dados que a empresa já usava, de forma que ela lê e grava os mesmos registros que todo mundo usa.

- 31
- Páginas
- 74,486
- Linhas, frontend
- 29
- Módulos puros
- 517
- Testes
Os dados estavam bem. Só faltava a porta de entrada.
A organização de TI já mantinha seu portfólio em um banco de dados relacional: projetos, solicitações de demanda, tarefas, sprints, milestones, equipes, registros de scoring. Nada estava faltando. Mas a única forma de usar isso era abrir tabelas.
Então todo mundo criou seu próprio jeito de contornar isso. A priorização acontecia em uma planilha que era copiada para fora e colada de volta. Solicitantes mandavam e-mail perguntando o que tinha acontecido com o pedido deles, porque não havia onde consultar. A cada trimestre, o trabalho recorrente de run-the-business era redigitado à mão. E as três perguntas que um portfólio existe para responder — quem é o responsável, quando isso será entregue, o que está bloqueando — exigiam quatro tabelas e um palpite.
O Airplan substitui as tabelas por uma superfície de uso. Não é um dashboard em cima de uma cópia dos dados: é uma aplicação real de leitura e escrita dentro do mesmo banco, então existe exatamente um conjunto de registros e nenhuma sincronização para ficar desatualizada.
Uma ferramenta de planejamento, planejada
Dois releases em maio, depois oito semanas silenciosas refazendo a estrutura por baixo — e doze releases nos dezenove dias que se seguiram. A barra longa não é um vazio no trabalho; é o trabalho que fez o resto acontecer rápido.
Cinco problemas, cinco decisões
- 01
A grade se movia enquanto você pontuava
A priorização reordenava a cada tecla digitada, então a linha que você estava avaliando escapava de debaixo do cursor. As pessoas passaram a pontuar em uma planilha.
Fix:O ranking passou a ser explícito. As notas salvam instantaneamente; a ordem fica congelada durante a sessão. Um controle
Refresh rankingse marca como atualizado quando as notas atuais indicam uma ordem diferente, e você reordena quando estiver pronto. - 02
As solicitações caíam em um vazio
Uma nova solicitação era uma linha sem estado de revisão. Ninguém conseguia diferenciar uma solicitação não lida de uma já triada, e a pessoa que a registrou não tinha nada para consultar.
Fix:Uma fila de intake e uma página do solicitante. As solicitações recebidas se dividem em precisa de revisão e revisada, com registro de quem e quando. Uma página separada mostra a cada solicitante todos os projetos que ele pediu, com etapa e tempo de espera.
- 03
O trabalho recorrente era redigitado a cada trimestre
Cada fluxo de run-the-business precisava de um projeto novo por trimestre, com uma dezena de campos obrigatórios, e cada tarefa não concluída precisava ser movida à mão.
Fix:O trimestre se renova automaticamente. Os projetos do próximo trimestre são criados 15 dias antes do atual terminar, já totalmente preenchidos, e cada tarefa ainda aberta segue seu fluxo através da virada.
- 04
"Esse milestone está pronto para ser lançado?"
As dependências de um milestone eram uma sequência de nomes de tarefas separados por vírgula. Isso dizia quais tarefas estavam vinculadas e nada sobre se alguma delas estava concluída.
Fix:Dependências como uma tabela de verdade. Status, responsável e data de entrega por dependência, ordenável e filtrável — e qualquer uma delas abre em um painel lateral sem sair do milestone.
- 05
Cada lista criou seus próprios chips de filtro
Cada página adicionava chips até a barra de ferramentas ficar sem espaço, e nenhuma página filtrava do mesmo jeito que a outra.
Fix:Um único botão de Filtro, em todo lugar. Um único botão abre um painel com todas as facetas daquela página. A barra de ferramentas mantém espaço para busca e ações, e o botão assume a cor de destaque sempre que algum filtro está aplicado.
As decisões que valem a pena mostrar
Ranking é uma decisão, não um efeito colateral
Uma linha editada de forma que deixa de atender ao filtro ativo permanece no lugar, com uma nota explicando o motivo, até você navegar para outro lugar. Um registro que desaparece no instante em que você o toca é a forma mais rápida de fazer as pessoas desconfiarem de uma ferramenta.
Toda faceta atrás de um único controle
Em todas as 31 páginas, com is / is not em cada uma. O botão exibe uma contagem e
assume a cor de destaque quando algo está aplicado, então uma lista inesperadamente
curta se explica por si só.
Um componente, três superfícies de milestone
Preview, editor e criação compartilham esse componente, então não conseguem se distanciar um do outro. Abrir uma dependência desliza um painel sobre o milestone em vez de navegar para outra página, e esse painel usa o mesmo formulário de tarefa compartilhado em todo o resto.
Todo número abre as linhas por trás dele
Um KPI que você não consegue investigar é um número para discutir; um KPI que abre sua própria lista de registros — ordenável, pesquisável, editável no local — encerra a discussão no mesmo clique.
"Aberto" tem exatamente uma definição
Tudo, exceto concluído e não será feito — e a mesma função que decide o que a virada de trimestre carrega também produz as contagens de itens abertos mostradas em cada linha de fluxo. O número na tela é uma promessa sobre o que vai acontecer, não um cálculo separado que pode discordar.
Usável como outra pessoa
Uma allow-list em duas camadas permite que um administrador autorizado visualize o produto inteiro através das permissões de outra pessoa, e um único hook fornece a identidade efetiva para toda a árvore — assim as regras de acesso ficam testáveis simplesmente ao percorrê-las.
Como foi construído
O Airplan funciona como uma extensão de interface personalizada: uma aplicação React 19 carregada dentro da plataforma do banco de dados, mantendo referências de tabelas em tempo real em vez de um conjunto de dados copiado. Não há backend separado e não há ETL. Isso garante correção em tempo real e custa cada rede de segurança que um servidor teria oferecido — então a maior parte da engenharia foi dedicada a recolocar essas redes no lugar.
// Páginas — 31 arquivos
- Um arquivo por página, cada um se inscrevendo apenas nas tabelas que precisa
- Uma divisão wrapper → interno: o wrapper resolve as tabelas e é responsável pelo estado de carregamento, o componente interno recebe referências garantidamente não nulas
- As tabelas são resolvidas por id, não por nome, então renomear algo no banco de dados não derruba a aplicação
// Superfície compartilhada — design system
- Tokens, primitivos e todo comportamento transversal em um só lugar
- Paletas gêmeas claro/escuro, painéis laterais redimensionáveis que lembram a largura escolhida por cada pessoa
- Tabelas que rolam para os lados em vez de espremer colunas até virarem tiras finas
- Popovers ancorados, um editor de texto rico, um painel de notas em thread, e um único controle de filtro
// Módulos de domínio — 29 arquivos, zero dependências
- Toda a lógica que vale a pena discutir vive em módulos ES simples, sem React e sem SDK da plataforma
- Ordenação por trimestre fiscal, a virada trimestral, grafos de dependência e detecção de ciclos, árvores de planejamento, aplicação de templates, adaptadores de filtro, posicionamento de popovers, conversão de markdown
- Dados entram, dados saem — essa única fronteira é o que torna tudo testável
// Acesso seguro — a realidade do iframe
- Toda leitura de campo passa por um accessor protegido, e um error boundary envolve a raiz
- Uma tabela ausente é renderizada como ausente, não como zero
- Um schema que sofreu drift nunca pode reportar um portfólio vazio como se estivesse saudável
Como ele continua bem construído
517 testes — lógica pura, testada de forma simples
Como os módulos de domínio não importam nada, a suíte roda no test runner nativo da linguagem, sem framework, sem bundler e sem navegador. Aproximadamente 7.900 linhas de testes para 8.900 linhas de lógica pura.
Contratos — testes que leem o código-fonte
Regras de interface que não podem ser testadas por teste unitário são verificadas contra o texto-fonte. Um contrato enumera cada popover ancorado pelo nome e verifica a contagem — então um novo dropdown que seria cortado por um contêiner de rolagem quebra o build em vez de ser lançado como "esse campo não tem opções."
Lint delta — falha apenas em problemas novos
Uma baseline registrada dos achados existentes permite que o build falhe apenas por lint novo, enquanto uma base de código de 74.000 linhas adota regras estritas de forma incremental. O rigor chega sem uma limpeza de uma vez só que ninguém tem tempo de fazer.
Parse gate — sintaxe não chega à produção
Todo arquivo-fonte é transformado antes do release para capturar erros de sintaxe logo no início. Em um ambiente sem type checker, um parse gate é o piso de segurança mais barato possível.
Release — nada é resolvido pela rede
As dependências são commitadas como um arquivo revisado e a ferramenta de publicação fica fixada em uma versão, então um release não instala nada que ainda não tenha sido revisado. Um manifesto lista as evidências que um release precisa carregar: referência de aprovação, revisão do código-fonte, notas, e uma versão em produção verificada.
Docs — documentação como dado
A ajuda dentro do app é um conjunto de dados estruturado, renderizado por um pequeno block renderer, aberto a partir de um lançador em cada página. Atualizá-la é parte do lançamento, não uma tarefa posterior — então a ajuda nunca descreve o produto do mês passado.
Três coisas que eu levaria para o próximo projeto
- 01
Coloque a fronteira onde os testes precisam dela
Puxar cada regra para módulos sem dependências foi a decisão de maior impacto no projeto — não porque era elegante, mas porque transformou uma aplicação iframe intestável em 517 testes rápidos.
- 02
Uma regra de interface só é real se algo a impõe
O bug que fazia um picker parecer vazio já tinha sido corrigido uma vez em outro picker. Ele voltou porque o guard enumerava quatro componentes pelo nome, e um quinto foi escrito. Contratos precisam contar, não listar.
- 03
Consistência é uma feature que as pessoas sentem
Um controle de filtro, um formulário de tarefa, uma definição de "aberto." Em cada lugar onde isso foi permitido divergir, alguém eventualmente encontrou a costura — e parou de confiar no número na tela.
Construído com
Stack
- React 19
- Airtable Blocks SDK
- Plain ES modules
- node:test
Linha do tempo
v0.1.0 maio de 2026 → v0.7.0 agosto de 2026 · 14 releases · projetado e construído sozinho. As telas de interface no estudo de caso original são recriações desenhadas com projetos e pessoas inventados.