Adoraríamos a sua ajuda para tornar o Roadmap DSA ainda melhor! É um projeto gratuito e open source, feito pela comunidade Craft & Code Club, para a comunidade: o código e o conteúdo estão todos aqui, e qualquer pessoa pode ler, estudar, adaptar e propor mudança. Este guia explica como contribuir com o mínimo de atrito.
Ficou com dúvida em qualquer ponto? Chama a gente no Discord, é o jeito mais rápido de destravar.
- Código de Conduta
- Como rodar o projeto
- Adicionar ou completar um tópico
- Adicionar um visualizador
- Passos gerais (fork e PR)
- Commits e Pull Requests
- Testes
- CI e Pull Requests de forks
- Licença
Leia e siga o nosso Código de Conduta.
Requer Node.js 22+.
npm install
npm run dev # http://localhost:3000
npm run build # gera o site estático em ./out (SSG)
npm run serve # serve o ./out localmente
npm test # testes de navegação (Playwright)Stack: Next.js 16 (App Router) + React 19, export estático (output: "export"),
conteúdo em MDX. As partes interativas (visualizadores, checkboxes de
progresso) são ilhas client; o resto é estático.
O conteúdo mora em content/, na raiz do projeto (irmão de src/, que guarda
só o código de estrutura). Os nomes dos campos são em inglês; os valores
exibidos ficam em português.
Um tópico é uma pasta com dois arquivos, e ele não pertence a lugar nenhum: quem monta sequência são os roadmaps, que o citam pelo slug.
-
O dado — crie
content/topicos/<slug>/index.ts. Campos úteis:youtube(id do vídeo),article(link do artigo no blog),extraVideos(mais vídeos),viz(visualizador). Os problemas e as referências vão nopratica, que é um export à parte no mesmo arquivo.import type { Pratica, Topic } from "@/content/tipos"; export const topico: Topic = { slug: "kadane", name: "Kadane", group: "Arrays e Strings", level: "Médio", status: "soon", youtube: "VIDEO_ID", description: "Maior soma contígua, o clássico.", }; export const pratica: Pratica = { problems: [{ id: "lc-53", name: "Maximum Subarray", number: "53", source: "LeetCode", level: "Médio", url: "https://leetcode.com/…" }], references: [{ title: "Kadane's Algorithm", source: "GeeksforGeeks", url: "https://…" }], };
O
groupé o assunto do tópico, não um endereço. Escreva-o igual ao dos outros tópicos do mesmo assunto: duas grafias viram duas seções em/topicos/, com o mesmo título e a mesma âncora.O selo "NOVO" do menu lateral é uma tag manual:
isNew: true. Como não tem data, ele não sai sozinho — marque o tópico que você está publicando e tire a marca dos anteriores no mesmo PR. -
Os registros — o tópico só existe no site depois de registrado, porque a lista é escrita à mão (o índice é importado por código de cliente, e cliente não tem
fs). São dois lugares: o import nomeado + a entrada emMODULOS, emcontent/topicos/index.ts; e o import dapratica, emcontent/topicos/pratica.ts. O testetests/roadmaps.spec.tscompara a pasta com os registros nos dois sentidos e reprova se faltar. -
Artigo — para virar
status: "ready", criecontent/topicos/<slug>/artigo.mdx. Dentro do MDX já dá para usar, sem importar:<Callout>,<Colunas>,<Cartao>e os visualizadores (vermdx-components.tsx).Sempre declare a linguagem na cerca do bloco de código (
```python). O destaque de sintaxe é gerado no build (Shiki), não no navegador, e é a cerca que também põe o selo discreto de linguagem no canto do bloco. Cerca sem linguagem fica sem cor e sem selo: é o certo para diagrama em ASCII. -
Ligue o artigo — o import do
.mdxemcontent/topicos/artigos.ts, osumarionoindex.tsdo tópico estatus: "ready". Osumarioalimenta o índice "Nesta página" e precisa repetir os títulosh2do artigo no texto exato:export const sumario = ["O problema", "A ideia, em uma frase", "Por que funciona"];
-
Cite num roadmap, se o tópico fizer parte de algum percurso: em
content/roadmaps/<slug>.ts, no grupo certo,{ topic: "kadane" }. Pode citar em quantos roadmaps quiser — a página é a mesma, o que muda é a ordem em volta. Tópico que ninguém cita continua publicado, em/topicos/<slug>/, e aparece no índice completo.
Os tipos ficam em src/content/tipos.ts: na raiz, content/ é só conteúdo.
Sem travessão: na copy do site, use pontuação simples (vírgula, ponto, dois
pontos), nunca o caractere —.
Os visualizadores ficam em content/visualizers/. O padrão vive em
SlidingWindowVisualizer.tsx e TwoPointersVisualizer.tsx: um gerador puro
de passos + a mesma casca de UI (células, código sincronizado, painel de
variáveis, controles e o botão Expandir). Para uma técnica nova:
- Copie o componente, troque
gerarPassose a constanteCODIGO. - Exponha-o em
mdx-components.tsx. - Use no
.mdxdo tópico:<MeuVisualizador />.
A casca — como a peça se adapta à altura da tela, o que fica parado e o que
rola no modo expandido, e o que o teclado faz ali dentro — não se escreve à mão:
ela vem do hook useVisualizer (src/lib/visualizer.tsx), com VizHeader e
VizFooter. O contrato está em
content/visualizers/README.md, com o uso, as
opções e as armadilhas já medidas. Leia antes de mexer nela.
Idioma do código: identificador em inglês, tela em português. Variáveis, tipos, campos e props em inglês; tudo que o aluno lê em português. Comentário em português quando explicar melhor, e o nome do componente como fizer sentido.
Repare que a fronteira não é o arquivo, é a string: o trecho de código que
aparece na tela do visualizador, os rótulos das variáveis e as notas do
passo a passo são conteúdo didático em português, mesmo morando dentro do
.tsx. Renomear em lote traduz a aula junto — já produziu "O array precisa
estar sorted" aqui. O procedimento para conferir isso está no §0 do contrato.
- Veja se já existe uma issue aberta sobre o assunto.
- Abra uma issue para discutir mudanças maiores.
- Faça um fork do repositório.
- Crie sua branch:
git checkout -b feat/minha-melhoria. - Faça commits atômicos (veja Conventional Commits abaixo).
git push origin feat/minha-melhoria.- Abra um Pull Request.
- ✅ FAÇA commits atômicos, para facilitar a revisão.
- ✅ FAÇA PRs pequenos e focados.
- ✅ FAÇA commits no padrão Conventional Commits.
- ❌ EVITE quebrar o build da integração contínua.
Usamos Conventional Commits. Um
workflow de CI (commitlint) valida as mensagens em cada Pull Request, então
vale seguir o padrão desde o primeiro commit.
Formato:
<tipo>(<escopo opcional>): <resumo no imperativo>
<corpo opcional: explica o porquê da mudança, não o como>
<rodapé opcional: BREAKING CHANGE, referência a issue>
Tipos:
| Tipo | Quando usar |
|---|---|
feat |
recurso novo para quem usa (tópico, visualizador, página) |
fix |
correção de bug |
docs |
só documentação (README, CONTRIBUTING, comentários) |
style |
formatação, sem mudar comportamento (espaços, ponto e vírgula) |
refactor |
muda o código sem corrigir bug nem adicionar recurso |
perf |
melhora de desempenho |
test |
adiciona ou ajusta testes |
ci |
pipelines, GitHub Actions, deploy |
build |
build, dependências, config do bundler |
chore |
manutenção que não se encaixa nas de cima |
revert |
reverte um commit anterior |
Escopos sugeridos (a área tocada): roadmap, topics, viz, nav, ui,
apoie, home, seo, test, deps, ci. O escopo é opcional; use quando
ajuda a localizar a mudança.
Regras:
- Resumo no imperativo e em minúsculas: "adiciona", não "adicionado" nem "Adiciona".
- Sem ponto final no resumo. Até ~72 caracteres.
- Em português (inglês só para os nomes técnicos consagrados, como no resto do site).
- Um commit, uma mudança. Commits atômicos facilitam a revisão e o revert.
- Mudança incompatível: use
feat!:(o!) ou um rodapéBREAKING CHANGE: <descrição>.
Exemplos:
feat(roadmap): adiciona tópico de Kadane
fix(viz): corrige overflow do painel de código no mobile
refactor(apoie): puxa apoiadores da APOIA.se no build
docs: melhora o guia de contribuição
ci: valida mensagens de commit com commitlint
Para conferir sua última mensagem localmente, sem instalar nada:
npx --yes --package @commitlint/cli --package @commitlint/config-conventional \
commitlint --from HEAD~1- ✅ FAÇA
npm run buildpassar (o site inteiro precisa compilar). - ✅ FAÇA
npm testpassar (navegação, links e âncoras). - ✅ CONSIDERE adicionar um teste quando o PR resolve um bug ou adiciona algo navegável (nav, links, âncoras do índice).
- ℹ️ A suíte sobe um servidor estático servindo o
./out, na porta 3000 por padrão (PORTmuda). Se a porta estiver ocupada (umnpm run devesquecido, outra suíte rodando), o Playwright falha dizendo isso em vez de testar o que está lá — rode com outra:PORT=3101 npm test.
Por segurança, os pipelines que usam segredos (deploy no Cloudflare Pages) não rodam em PRs vindos de forks. Além disso, o repositório exige aprovação de um admin para rodar os workflows de PRs de colaboradores externos (configuração de Actions do repositório). Um mantenedor vai revisar e liberar a execução.
O repositório tem duas licenças, e a sua contribuição entra sob a que corresponde ao que você escreveu:
- Código → MIT. Componentes, hooks, testes, scripts, configuração, e toda a maquinaria dos visualizadores.
- Conteúdo → CC BY-NC-SA 4.0. Artigos MDX, descrições de tópico, e o material didático que mora dentro dos visualizadores: as notas de passo, o código exibido na tela, os rótulos que explicam.
Ao abrir um PR, você concorda com isso para as duas partes — a maioria dos PRs daqui mexe nas duas ao mesmo tempo, porque um visualizador é as duas coisas no mesmo arquivo.
A fronteira não é por diretório. Ela atravessa arquivos individuais, e está definida com exemplos na seção "Onde passa a fronteira" do LICENSE. A pergunta que decide: se esta linha sumisse, quebraria o programa (código) ou a aula (conteúdo)?
Na prática, é o que garante as duas coisas de uma vez: o código é open source de verdade, e o conteúdo intelectual segue gratuito para quem quer aprender, sem virar produto de ninguém.
Você continua sendo autor do que escreveu. A licença é permissão para o projeto e para quem o usa, não transferência de copyright.