Pequeno o suficiente para caber inteiro na cabeça; denso o suficiente para que tudo que importa em software esteja dentro dele.
Um slider, uma lua, um nome de fase. Parece trivial — e essa impressão é o ponto de partida: complexidade de superfície e profundidade de problema são eixos independentes. Este repositório é um laboratório de engenharia de software em JavaScript puro, zero dependências, onde as mesmas forças que atuam em sistemas grandes — acoplamento, fronteiras, ponto flutuante, concorrência de intenção, acessibilidade, modos de falha — aparecem isoladas, visíveis e testadas. O projeto cresceu por critério, e a promessa precisa hoje é outra: estratificado, não pequeno — cabe na cabeça uma camada por vez, com mapa: o domínio sozinho é uma aula completa; depois as portas; depois um adaptador.
O vocabulário canônico do projeto — termos, invariantes e convenções de nomenclatura — vive no GLOSSARIO.md; os nomes do código derivam de lá.
npm start # servidor estático em node:http — abra http://localhost:8000
npm test # 54 testes com node:test — sem navegador, sem bibliotecas
npm run grafo # regenera o mapa da aplicação e audita as fronteiras
# da arquitetura (requer Python + graphifyy)
npm run afirmacoes # audita as afirmações do projeto sobre si mesmo:
# idioma, contagem de testes, hashes, composiçãoES modules não carregam via file://; por isso o servidor. Na página: arraste o
controle; [ e ] saltam entre fases; R volta à lua cheia; ▶ anima o ciclo
(~12 s por mês sinódico); o hemisfério espelha o lado iluminado (padrão Sul,
como se vê do Brasil); o seletor Sombra alterna entre o canvas
(terminador exato — o padrão, escolhido de olho) e o gradiente CSS
(aproximação suave).
Abra o DevTools: a aplicação narra sua própria composição no console — camadas conectadas, tabela das 8 fases, cada transição de fase e cada decisão dos adaptadores (pausa por arrasto, adoção de saltos, troca de hemisfério). Se os módulos falharem, o diagnóstico aparece na página e no console.
| Parece | É |
|---|---|
| Uma fórmula de uma linha | Modelagem de domínio — e física. O valor bruto do controle (0–100) e o estado da lua (EstadoDaLua: fração, lado, dia) são coisas diferentes — um único ponto (CalculadoraDaLua) interpreta o cru. E a fração segue a física real, (1 − cos(2πv)) / 2: muda devagar perto da lua nova e cheia, rápido nos quartos — a primeira versão usava uma onda triangular, e a troca custou uma linha no domínio. |
| Oito nomes de fase | Fronteiras sob IEEE 754. Math.cos(π/2) não é zero — é ~6e-17 — então os quartos produzem fração 0.49999999999999994. Sem tolerância no domínio e arredondamento na apresentação, fases e geometria oscilariam. Um teste varre os 101 valores possíveis do slider contra a tabela histórica como oráculo de regressão — foi ele que provou que a troca de fórmula não moveu nenhuma fronteira. |
| Uma sombra bonita | Geometria aplicada. Crescente e gibosa exigem estratégias de gradiente opostas (elipse de luz no limbo iluminado × elipse de sombra no limbo escuro), trocando na metade do ciclo. O bug original — extremos ~69% iluminados — vive como teste de regressão. |
| Um toggle Sul/Norte | Correção que depende do observador. A crescente ilumina lados opostos conforme o hemisfério — requisito invisível até perguntar quem olha. A feature ainda expôs um defeito latente de camadas: os emojis (apresentação) moravam no domínio e contradiziam a lua espelhada. |
| Um botão ▶ | Dois atores, um estado. Relógio e usuário dirigem a mesma posição: arrastar pausa, saltos de teclado realocam a animação. O requestAnimationFrame é injetado — o tempo roda em Node com um agendador falso. |
| Um rótulo de texto | Interface dupla. A página tem duas UIs: a visual e a anunciada (aria-valuetext com o nome puro, sem glifo verbalizado — canal único: o rótulo visível é aria-hidden, porque um segundo canal falado verbalizaria o emoji e duplicaria cada anúncio; prefers-reduced-motion oculta a animação). Mantê-las coerentes é o mesmo problema de manter dois clientes de uma API. |
| Um seletor Gradiente/Canvas | Declarativo × imperativo sobre um núcleo comum. A mesma geometria — decidida uma única vez em geometriaDaFase() — vira string de radial-gradient num renderizador e caminhos de ctx.ellipse() no outro. Paridade por construção: os tradutores não têm como divergir na decisão. E o flip mostra aproximação × exatidão: o canvas desenha o terminador real (b = r·(1 − 2·largura)). |
| Uma página estática | Modos de falha. Módulos falham em silêncio via file://: a página nasce com conteúdo estático coerente e um aviso, revelado por CSS após 2 s, transforma falha muda em diagnóstico acionável (npm start). |
Hexagonal (Ports & Adapters). Dependências apontam sempre para dentro; o domínio
não conhece HTML, CSS ou DOM. Um conceito por arquivo, uma pasta por camada,
testes espelhando src/:
index.html · estilo.css apresentação estática (na raiz — o Pages serve dela)
src/
principal.js raiz de composição — só instancia e conecta
configuracao.js painel de ajustes (folha; o domínio nunca lê)
dominio/ núcleo puro: estado-da-lua, calculadora-da-lua,
servico-de-fases
portas/ contratos (com JSDoc): porta-renderizador-da-lua,
porta-entrada-do-ciclo, porta-anunciador-de-fase
aplicacao/ atualizar-lua (zero imports — tudo injetado)
adaptadores/ um por responsabilidade: núcleo geométrico,
renderizadores (gradiente, canvas, alternável),
controle deslizante, anunciadores (aria,
console, composto), teclado, animação,
controles da animação, seletores (hemisfério,
renderizador)
servidor.js servidor de desenvolvimento (fora da arquitetura)
ferramentas/ bancada de dev: gerar-grafo-da-aplicacao (npm run grafo)
testes/ espelho de src/ + auxiliares/ (dublês compartilhados)
| Camada | Pasta | Responsabilidade |
|---|---|---|
| Domínio | src/dominio/ |
EstadoDaLua (value object), CalculadoraDaLua (posição do ciclo → estado), ServicoDeFases (estado → nome puro da fase; o emoji é decisão da apresentação) |
| Portas | src/portas/ |
PortaRenderizadorDaLua e PortaAnunciadorDeFase (saída — o que o caso de uso consome), PortaEntradaDoCiclo (entrada — o que os dirigentes dirigem); nomeadas pelo papel, não pelo widget |
| Aplicação | src/aplicacao/ |
AtualizarLua — orquestra cálculo, nome, renderização e anúncio; zero imports, tudo por injeção |
| Adaptadores | src/adaptadores/ |
geometriaDaFase/rotuloDaFase (núcleo geométrico: a decisão visual, uma vez), RenderizadorDaLuaDOM (→ gradiente CSS) e RenderizadorCanvasDaLua (→ terminador exato) sob o RenderizadorAlternavel (strategy), ControleDeslizanteDOM, AnunciadorAria/AnunciadorConsole/AnunciadorComposto, AtalhosDeTeclado e AnimacaoDoCiclo (dirigem a porta, não o adaptador concreto), ControlesDaAnimacao, SeletorDeHemisferio e SeletorDeRenderizador (UI dos controles secundários) |
| Composição | src/principal.js |
Instancia e conecta tudo; nenhuma outra camada usa new entre camadas |
- Evento
inputdo slider →ControleDeslizanteDOMnormaliza0–100para0–1 AtualizarLua.executar(posicao)→CalculadoraDaLua.calcular()produzEstadoDaLua { fracaoIluminada, crescente, diaDoCiclo }ServicoDeFases.nomear(lua)resolve o nome puro ("Quarto Crescente")- O renderizador ativo (gradiente ou canvas) traduz a
geometriaDaFase()do núcleo, escolhe o glifo pelo hemisfério e atualiza#nome AnunciadorAria.anunciar()expõe o nome emaria-valuetext
O valor do hexagonal não é estético; é falsificável. Este projeto fez quatro previsões e as cumpriu: hemisfério, animação, narrativa de console e a coexistência gradiente/canvas — features de naturezas bem diferentes — entraram cada uma como adaptadores novos + conexão, com diff zero em domínio, portas e aplicação. E a suíte inteira roda sem navegador e sem jsdom: o caso de uso é testado com dublês de três linhas, a geometria CSS com elementos falsos, o tempo com um agendador falso. Quando a arquitetura é real, os testes ficam baratos; quando é decorativa, o mock vira arqueologia.
testes/dominio/ matemática do ciclo, dia sinódico, fronteiras das 8 fases,
varredura de equivalência dos 101 valores do slider
testes/portas/ contratos exigem implementação (métodos lançam erro)
testes/aplicacao/ orquestração com dublês (a prova da hexagonalidade)
testes/adaptadores/ núcleo geométrico como dado (decisão testada uma vez),
tradução para gradiente e para canvas (fumaças), strategy
alternável, glifos por hemisfério, atalhos, animação com
relógio falso, controles de UI, regressão do bug original
(~69% iluminado nos extremos)
testes/auxiliares/ dublês compartilhados das portas e do DOM
O git deste repositório registra o argumento central — e as lições estão destiladas, commit a commit, no APRENDIZADOS.md: cada commit removeu um problema real de uma base que "já funcionava" — duplicação semântica no domínio, fronteira instável por ponto flutuante, convenção de hemisfério errada para o público, apresentação vazada no domínio, contrato de porta misturando dois papéis, lógica sem teste na raiz de composição. Nenhum desses defeitos era visível na tela. Todos estavam lá.
É isso que este projeto quer demonstrar: a distância entre "funciona na demo" e "é correto, testável e evolui sem medo" — a distância que constitui a engenharia de software — não depende do tamanho do projeto.