# Guia de Desenvolvimento — 18/10/2025 Este documento consolida o estado atual do ambiente de desenvolvimento, descreve como rodar lint/test/build localmente (e no CI) e registra erros recorrentes com as respectivas soluções. ## Resumo rápido - **Node/PNPM**: Node 20.9+ (alinhado ao requisito do Next 15) + pnpm 9 (habilite via `corepack enable && corepack prepare pnpm@9 --activate`). - **Next.js 15.5.5**: Projeto voltou para a versão estável (`next@15.5.5`) com Turbopack como bundler padrão e whitelist de domínios garantida pelo middleware. - **Lint/Test/Build**: `pnpm lint`, `pnpm test`, `pnpm build`. O script de testes usa `vitest --run --passWithNoTests`, eliminando o modo watch interativo. - **Banco DEV**: SQLite em `prisma/prisma/db.dev.sqlite`. Defina `DATABASE_URL="file:./prisma/db.dev.sqlite"` ao chamar CLI do Prisma. - **Desktop (Tauri)**: fonte em `apps/desktop`. Usa Radix tabs + componentes shadcn-like, integra com os endpoints `/api/machines/*` e suporta atualização automática via GitHub Releases. - **CI**: Workflow `Quality Checks` roda lint/test/build para pushes e PRs na `main`, além do pipeline de deploy existente. ## Banco de dados (Prisma) 1. Gere/atualize o schema local: ```bash DATABASE_URL="file:./prisma/db.dev.sqlite" pnpm exec prisma db push DATABASE_URL="file:./prisma/db.dev.sqlite" pnpm prisma:generate DATABASE_URL="file:./prisma/db.dev.sqlite" pnpm auth:seed ``` 2. Rode o app Next.js: ```bash pnpm dev ``` 3. Credenciais padrão (seed): `admin@sistema.dev / admin123`. 4. Herdou dados antigos? Execute `node scripts/remove-legacy-demo-users.mjs` para limpar contas demo legadas. > **Por quê inline?** Evitamos declarar `DATABASE_URL` em `prisma/.env` porque o Prisma lê também o `.env` da raiz (produção). O override inline garante isolamento do banco DEV. ## Next.js 15 (estável) - Mantemos o projeto em `next@15.5.5`, priorizando estabilidade enquanto acompanhamos as novidades do 16. - **React 18.2**: voltamos para a versão suportada oficialmente pelo Next 15. Evite APIs exclusivas do React 19 (`use(...)`, `useActionState`, etc.). - **Turbopack** segue como bundler padrão, sem flags experimentais adicionais. - **Whitelist de hosts**: o release estável 15.5 não aceita `server.allowedHosts` (vide [`invalid-next-config`](https://nextjs.org/docs/messages/invalid-next-config)), portanto bloqueamos domínios exclusivamente via `middleware.ts`. ### Editor rich text (TipTap) — menções de ticket - Menções (`ticketMention`) agora têm prioridade maior (`priority: 1000`) e um Link seguro (`SafeLinkExtension`) foi introduzido para ignorar ``. Isso evita que o `Link` do StarterKit capture as âncoras na hidratação, garantindo que as menções continuem como nodes (não como marks) durante a edição. - O mesmo helper `normalizeTicketMentionHtml` é aplicado ao carregar/atualizar conteúdo no editor, dentro dos fluxos de comentários e no Convex. Esse helper reescreve qualquer HTML legado (`#123•Assunto`) no formato de chip completo (datasets, spans, dot). - Resultado: o chip mantém layout e comportamento ao editar (Backspace/Delete removem o node inteiro, node view continua ativo) sem exigir reload. - Se precisar adicionar novos comportamentos, importe `SafeLinkExtension` e mantenha a ordem `[TicketMentionExtension, StarterKit (link:false), SafeLinkExtension, Placeholder]` para que o parser continue estável. ## Comandos de qualidade - `pnpm lint`: executa ESLint (flat config) sobre os arquivos do projeto. - `pnpm test`: Vitest em modo não interativo (`--run --passWithNoTests`). Use `pnpm test -- --watch` somente quando quiser rodar em watch localmente. Inclui testes de API (rotas `/api/machines/*`) e utilitários de SMTP. - `pnpm build`: `next build --turbopack`. - `pnpm prisma:generate`: necessário antes do build quando o client Prisma muda. ### Automação no CI Arquivo: `.github/workflows/quality-checks.yml` Etapas: 1. Instala dependências (`pnpm install --frozen-lockfile`). 2. `pnpm prisma:generate`. 3. `pnpm lint`. 4. `pnpm test`. 5. `pnpm build`. O workflow dispara em todo `push`/`pull_request` para `main` e fornece feedback imediato sem depender do pipeline de deploy. ## Testes rápidos via curl (Convites & acessos) 1. Rode `pnpm dev` e autentique-se em `http://localhost:3000/login` usando `admin@sistema.dev / admin123`. 2. Copie o valor do cookie `BETTER_AUTH_SESSION` e exporte no shell: `export COOKIE="BETTER_AUTH_SESSION="`. ### Usuários ```bash # Listar usuários com acesso web curl -s http://localhost:3000/api/admin/users \ -H "Cookie: $COOKIE" \ -H "Accept: application/json" | jq '.users | map({ id, email, role })' # remova o pipe se não tiver jq # Criar usuário gestor (ajuste o e-mail se necessário) NEW_EMAIL="api.teste.$(date +%s)@sistema.dev" curl -s -X POST http://localhost:3000/api/admin/users \ -H "Cookie: $COOKIE" \ -H "Content-Type: application/json" \ -d "{\"name\":\"Usuário via curl\",\"email\":\"$NEW_EMAIL\",\"role\":\"manager\",\"tenantId\":\"tenant-atlas\"}" \ | tee /tmp/user-created.json # Remover o usuário recém-criado USER_ID=$(jq -r '.user.id' /tmp/user-created.json) curl -i -X DELETE http://localhost:3000/api/admin/users/$USER_ID \ -H "Cookie: $COOKIE" ``` > Os exemplos acima utilizam `jq` para facilitar a leitura. Se não estiver disponível, remova os pipes e leia o JSON bruto. ### Convites ```bash # Criar convite válido por 7 dias INVITE_EMAIL="convite.$(date +%s)@sistema.dev" curl -s -X POST http://localhost:3000/api/admin/invites \ -H "Cookie: $COOKIE" \ -H "Content-Type: application/json" \ -d "{\"email\":\"$INVITE_EMAIL\",\"name\":\"Convite via curl\",\"role\":\"collaborator\",\"tenantId\":\"tenant-atlas\",\"expiresInDays\":7}" \ | tee /tmp/invite-created.json # Revogar convite pendente INVITE_ID=$(jq -r '.invite.id' /tmp/invite-created.json) curl -i -X PATCH http://localhost:3000/api/admin/invites/$INVITE_ID \ -H "Cookie: $COOKIE" \ -H "Content-Type: application/json" \ -d '{"reason":"Revogado via curl"}' # Reativar (até 7 dias após a revogação) curl -i -X PATCH http://localhost:3000/api/admin/invites/$INVITE_ID \ -H "Cookie: $COOKIE" \ -H "Content-Type: application/json" \ -d '{"action":"reactivate"}' ``` > Dica: ao receber `409` na criação de convite, há outro convite pendente/aceito para o mesmo e-mail. Revogue ou remova o usuário antes. ## Desktop (Tauri) - Tabs Radix + estilos shadcn: `apps/desktop/src/components/ui/tabs.tsx`. - Painel principal: `apps/desktop/src/main.tsx` — abas Resumo/Inventário/Diagnóstico/Configurações, envio manual de inventário, seleção de persona (colaborador/gestor) e vínculo com usuário. - Coleta/hardware: `apps/desktop/src-tauri/src/agent.rs`. - Variáveis de build: - `VITE_APP_URL` (URL Web). - `VITE_API_BASE_URL` (API). ### Build local ```bash corepack enable && corepack prepare pnpm@9 --activate pnpm -C apps/desktop install VITE_APP_URL=http://localhost:3000 \ VITE_API_BASE_URL=http://localhost:3000 \ pnpm -C apps/desktop tauri build ``` Artefatos: `apps/desktop/src-tauri/target/release/bundle/`. #### Ícone do instalador (NSIS) - O Windows espera que `apps/desktop/src-tauri/icons/icon.ico` contenha sprites em 16, 24, 32, 48, 64, 128 e 256 px, todos com fundo transparente. Sem esses tamanhos o Explorer gera uma miniatura reduzida com bordas acinzentadas. - Para atualizar o ícone a partir do `icon-512.png`, execute: ```bash cd apps/desktop/src-tauri python3 - <<'PY' from PIL import Image img = Image.open("icons/icon-512.png") img.save("icons/icon.ico", sizes=[(16,16),(24,24),(32,32),(48,48),(64,64),(128,128),(256,256)]) PY ``` - Depois de regerar `icon.ico`, faça o commit e rode novamente `pnpm -C apps/desktop tauri build` para empacotar o instalador com o ícone correto. ### Atualizações OTA 1. Gere chaves (`pnpm tauri signer generate`). 2. Defina `TAURI_SIGNING_PRIVATE_KEY` (+ password) no ambiente de build. 3. Publique os pacotes e um `latest.json` em release GitHub. 4. O app verifica ao iniciar e pelo botão “Verificar atualizações”. ## Erros recorrentes e soluções | Sintoma | Causa | Correção | | --- | --- | --- | | `ERR_PNPM_OUTDATED_LOCKFILE` no pipeline | Dependências do desktop alteradas sem atualizar `pnpm-lock.yaml` | Rodar `pnpm install` na raiz e commitar o lockfile. | | Prisma falha com `P2021` / tabelas Better Auth inexistentes | CLI leu `.env` da raiz (produção) | Usar `DATABASE_URL="file:./prisma/db.dev.sqlite"` nos comandos. | | Vitest trava em modo watch | Script `pnpm test` sem `--run` e CI detecta TTY | Ajustado para `vitest --run --passWithNoTests`. Localmente, use `pnpm test -- --watch` se quiser. | | Desktop não encontra updater | Falta `latest.json` ou assinatura inválida | Publicar release com `*.sig` e `latest.json` apontando para os pacotes corretos. | ## Cronômetro dos tickets - A UI e o backend agora compartilham um relógio real alinhado via `serverNow`. Toda resposta de `tickets.workSummary`, listagens de tickets e mutations `startWork/pauseWork` envia `serverNow` (epoch UTC). - O frontend (`ticket-summary-header` e tabela) calcula um deslocamento (`offset = Date.now() - serverNow`) e projeta `Date.now()` para a linha do tempo do servidor usando `toServerTimestamp`. Isso elimina drift quando o navegador está adiantado/atrasado. - Durante o `startWork`, se a mutation retornar `status = already_started` sem `startedAt`, a UI usa `getServerNow()` como fallback, e assim que o Convex reenviar a sessão ativa, o reconciliador (`reconcileLocalSessionStart`) substitui o valor local. - O tempo em execução exibido “ao vivo” torna-se idêntico ao acumulado após pausar, com tolerância de ±1 s por conta do tick do cronômetro. Ao depurar inconsistências, verifique se `serverNow` está chegando (painel de rede) e se o offset em `ticket-timer.utils.ts` está sendo calibrado. ## Referências úteis - **Deploy (Swarm)**: veja `docs/DEPLOY-RUNBOOK.md`. - **Plano do agente desktop / heartbeat**: `docs/plano-app-desktop-maquinas.md`. - **Histórico de incidentes**: `docs/historico-agente-desktop-2025-10-10.md`. > Última revisão: 18/10/2025. Atualize este guia sempre que o fluxo de DEV ou automações mudarem. - **Next.js 16 (beta)**: comportamento sujeito a mudanças. Antes de subir para stable, acompanhe o changelog oficial (quebra: `revalidateTag` com segundo argumento, params assíncronos, etc.). Já estamos compatíveis com os breaking changes atuais.