Skip to content

Repository files navigation

🌕 Fases da Lua

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á.

Executar

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ção

ES 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.

O que parece × o que é

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).

Arquitetura

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

Fluxo de dados

  1. Evento input do slider → ControleDeslizanteDOM normaliza 0–100 para 0–1
  2. AtualizarLua.executar(posicao)CalculadoraDaLua.calcular() produz EstadoDaLua { fracaoIluminada, crescente, diaDoCiclo }
  3. ServicoDeFases.nomear(lua) resolve o nome puro ("Quarto Crescente")
  4. O renderizador ativo (gradiente ou canvas) traduz a geometriaDaFase() do núcleo, escolhe o glifo pelo hemisfério e atualiza #nome
  5. AnunciadorAria.anunciar() expõe o nome em aria-valuetext

A arquitetura faz previsões — e elas são testáveis

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

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

A história como currículo

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.

About

Um slider, uma lua — e tudo que importa em engenharia de software dentro deles.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages