Rodando, editando e mantendo o projeto
Resumo em uma frase: clone, npm install, npm run dev — e antes de qualquer contribuição, leia esta página até o fim, porque ela é a checklist real que toda mudança precisa passar.
Esta página é sobre código. Se você só quer usar o app, veja Como usar. Se quer entender a arquitetura antes, veja Arquitetura e o Mapa do código.
Rodando localmente
git clone https://github.com/murilobarbosa2002/zerohop.git
cd zerohop
npm install
npm run devnpm run dev sobe o electron-vite em modo desenvolvimento: hot-reload no renderer, o processo principal reinicia sozinho quando você edita algo em src/main/.
| Comando | O que faz |
|---|---|
npm run dev | Ambiente de desenvolvimento, com hot-reload. |
npm run build | Gera o bundle de produção em out/ (Vite). É o que o electron-builder empacota depois. |
npm run typecheck | tsc -b --noEmit nos três projetos TypeScript (main, preload/renderer web). Roda no CI a cada push/PR. |
npm run test:e2e | Builda e roda a suíte de ponta a ponta (ver Testes). |
npm run lint:md | Markdownlint em todo .md do repositório (inclusive esta documentação). |
npm run lint | ESLint (regras de qualidade — variável não usada, etc.). Cobre por enquanto só .js/.mjs (ver observação abaixo sobre .ts/.tsx). |
npm run format | Formata o projeto inteiro com Prettier (espaço, aspas, quebra de linha). |
npm run format:check | Confere se está tudo formatado, sem alterar nada — é o que o CI roda. |
npm run dist:local | Gera uma pasta portátil (target: dir) sem passar pelo instalador — só pra testar rápido no Windows/WSL. Não é o caminho de release oficial. |
.ts/.tsxainda não passam pelo ESLint. O projeto usa a versão mais recente do TypeScript (7.x), e a ferramenta que faz o ESLint entender TypeScript (typescript-eslint) ainda não dá suporte a essa versão — nem a build mais recente dela. Enquanto isso, arquivos.ts/.tsxsão formatados pelo Prettier normalmente e continuam com a checagem de tipos de sempre (npm run typecheck), só não passam pelas regras de qualidade do ESLint. Assim quetypescript-eslintpublicar suporte ao TypeScript 7, vale estender oeslint.config.mjspra cobrir esses arquivos também.
Testes
A suíte de ponta a ponta (e2e/) usa Playwright contra o binário real do Electron (node_modules/electron/dist/electron), abrindo múltiplas instâncias do app pra simular pessoas de verdade numa sala. Roda com:
npm run build
ELECTRON_RUN_AS_NODE= xvfb-run -a node e2e/run-all.mjs(xvfb-run só é necessário em Linux sem display gráfico — no Windows/macOS com interface, node e2e/run-all.mjs sozinho já funciona.)
Cada arquivo em e2e/*.spec.mjs é um cenário independente. e2e/helpers/launchApp.mjs centraliza como o Electron é iniciado nos testes (com --use-fake-device-for-media-stream pra simular câmera/microfone sem hardware real, e um --user-data-dir único por instância — cada chamada de launchApp() ganha um perfil isolado, sem compartilhar contacts.json/settings.json/localStorage entre specs ou entre processos do mesmo teste). Toda mudança de comportamento visível precisa de um teste novo ou atualizado — é assim que regressões como "a voz para de tocar ao ocultar a sala" ou "a malha quebra com 3+ pessoas" não voltam a acontecer.
Como adicionar coisas comuns
Um som novo
- Sintetizar o
.wav(tom senoidal/triangular/quadrado/dente-de-serra com envelope de ataque/liberação — os scripts usados historicamente ficam fora do repositório, mas qualquer gerador simples em Python com o módulowavefunciona) e salvar emsrc/renderer/src/assets/sounds/. - Importar o arquivo e exportar uma função
playXSound()emsrc/renderer/src/services/soundEffects.ts, associada a umaSoundCategory(constants/soundEffects.ts). - Chamar
playXSound()no componente/hook onde a ação acontece. - Antes de tudo isso, conferir se a ação já não tem som (procurar em
soundEffects.ts) — nunca reaproveitar um som existente pra uma ação com significado diferente (ver Sistema de sons).
Uma preferência nova (configurável em Configurações)
- Se só o
rendererprecisa saber o valor (a maioria dos casos): siga o padrão deservices/soundEffectsPreference.ts—get/set/subscribesobrelocalStorage, com um hookuseX.tsusandouseSyncExternalStorepor cima. - Se o
maintambém precisa saber (ex: um atalho de teclado, uma flag que muda o comportamento do Chromium): a preferência vai emsettings.json, editandomain/settings.ts, e precisa de um canal de IPC prorendererler/escrever (ver Um canal de IPC novo).
Um canal de IPC novo
- Declarar o nome do canal em
src/shared/ipcChannels.ts. - Registrar o handler (
ipcMain.handlepra requisição/resposta,ipcMain.onpra fire-and-forget) emsrc/main/ipc.ts. - Expor a função correspondente em
src/preload/index.ts. - Declarar a assinatura em
src/renderer/src/global.d.ts(interface Window.api) — esse arquivo é escrito à mão, não é gerado automaticamente a partir dopreload.
Uma dependência nativa nova (.node)
Regra sem exceção: nunca import/require estático no topo de um arquivo do processo principal. Um módulo nativo ausente (biblioteca do sistema faltando, arquitetura incompatível) derruba o processo inteiro na inicialização se for importado de forma estática, mesmo que a feature seja opt-in. Sempre:
async function loadMinhaLibNativa() {
try {
return await import('minha-lib-nativa');
} catch {
return null;
}
}...chamado só de dentro da função que realmente usa a lib, nunca no topo do arquivo. Além disso, a dependência precisa entrar em build.asarUnpack no package.json (senão o .node compilado fica preso dentro do app.asar e não carrega no app empacotado). Exemplos reais no projeto: loopback-capture, node-window-manager.
Processo de release
O instalador Windows nunca é compilado localmente — é gerado do zero por um workflow do GitHub Actions rodando numa máquina Windows real, disparado a cada tag vX.Y.Z publicada.
Checklist completa antes de marcar uma tag:
- Bump de versão em
package.json(version) e noconsole.logdesrc/renderer/src/main.tsx(facilita confirmar qual build está rodando, visível na barra de título/tela de Logs sem precisar de DevTools). - Escrever
docs/releases/vX.Y.Z.md(o formato de todas as versões anteriores é o padrão a seguir) e adicionar uma linha emCHANGELOG.md. - Atualizar a página correspondente em
docs-site/(guia de uso, ou esta seção técnica, dependendo da mudança) — nunca deixar pra depois. npm run typecheck && npm run build && npm run lint && npm run format:check && npm run lint:mdlimpos.npm run test:e2epassando (suíte inteira).npm install --package-lock-onlypra sincronizar opackage-lock.jsoncom a nova versão — precisa bater antes de marcar a tag.- Commit,
git tag vX.Y.Z,git push origin main --tags. - O workflow
.github/workflows/release.ymlbuilda e publica um rascunho de release no GitHub; ele precisa ser revisado e publicado manualmente (ou via automação de quem mantém o repositório) depois que o build terminar com sucesso.
Convenções de código
As regras detalhadas de estilo, arquitetura de componentes React, nomenclatura e formato de commits ficam fora desta documentação pública (são um arquivo de instruções interno do repositório, não pensado pra leitura externa). Os princípios gerais que valem a pena saber como contribuidor externo:
- Zero duplicação de código. A mesma lógica em dois lugares vira função/hook/componente compartilhado.
- TypeScript estrito em todo o projeto, sem
any— tipo genuinamente desconhecido viraunknowne é refinado. - Sem comentários no código. Nomes de variáveis/funções/componentes precisam ser autoexplicativos; se algo precisa de explicação, o código deveria estar mais claro, não ganhar um comentário.
- Toda string visível ao usuário (texto de botão, mensagem de erro, label) vive num arquivo
strings/*.tsdedicado, nunca solta dentro do JSX. services/nunca importa decomponents/ouhooks/— a dependência é sempre uma via,components → hooks → services.