Run 6 da squad shivao-melhoria — HANDOFF item 5 (Reconexão da vigia)
parcialmente resolvido sem Service Worker (decisão conservadora,
SW mal feito quebraria offline-first).
4 fixes em app/diario-bordo.html + server/public/index.html (sincronizados):
1. requestWakeLock (tracking, linha 1345) + requestAnchorWakeLock (anchor, 1831)
- Adicionado wakeLock.addEventListener('release', ...) com auto-reacquire em 1s
- Garante que sistema soltando o Wake Lock (background tab, low battery)
não deixa a tela apagar permanentemente enquanto vigia/rastreio ativos
2. startGPS (tracking, linha 1347) + startAnchorGPS (anchor, 1901)
- Retry exponencial: 1s, 2s, 4s, 8s, 16s, 30s (max)
- PERMISSION_DENIED é fatal sem retry (com mensagem clara ao dono)
- Outros erros (POSITION_UNAVAILABLE, TIMEOUT) → retry com backoff
- Counter resetado a cada GPS update bem-sucedido (recuperação completa)
Combinado com visibilitychange listener existente (linha 1824) que re-acquire
Wake Lock quando tab volta foreground, cobre o cenário completo de:
- Tab em background no Android Chrome (GPS pausa, sistema solta Wake Lock)
- Tela apagada (Wake Lock soltada, GPS continua se permitido)
- Erro transitório de GPS (perda de sinal, recupera sozinho)
- App reaberto com vigia em andamento (loadAnchorState chama startAnchorGPS
que agora tem retry built-in)
Service Worker real (push notifications + cache de tiles offline) fica
pra iteração futura com spec própria.
HANDOFF.md item 5 marcado como "parcialmente resolvido".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.1 KiB
HANDOFF — Estado do Projeto
Documento para a equipe que vai continuar o desenvolvimento.
✅ O que está pronto e funcionando
Frontend (app/diario-bordo.html)
Arquivo único HTML standalone (~200 KB). Pode ser:
- Aberto direto no navegador
- Servido pelo backend (já é o caso — está em
server/public/index.html) - "Instalado" no Android via "Adicionar à tela inicial"
Funcionalidades verificadas em desenvolvimento:
| Recurso | Status | Observações |
|---|---|---|
| Persistência local (localStorage + IndexedDB) | ✅ | Robusto |
| Travessias / Reparos / Pendências CRUD | ✅ | Pronto |
| GPS + rastreio + mapa | ✅ | Leaflet + OSM tiles |
| Vigia de fundeio + alarme | ✅ | Web Audio + Vibration + Wake Lock |
| Swing circle + auto-recentro | ✅ | Implementado mas pouco testado em campo |
| Geofencing | ✅ | Apenas círculos (polígonos = futuro) |
| Mídia (foto/áudio/vídeo) | ✅ | IndexedDB, com viewer e download |
| Importar GPX | ✅ | Trk e Rte points |
| Exportar GPX | ✅ | Por travessia |
| Sync com nuvem | ✅ | Pull, push, auto-sync |
| Webhooks diretos (Telegram, Discord) | ✅ | Sem servidor |
| Compartilhamento de posição ao vivo | ✅ | Requer servidor |
| Checklists customizáveis | ✅ | 5 templates pré-cadastrados |
| Windy Point Forecast API | ✅ | Premium key + fallback Open-Meteo |
| Modo economia de energia | ✅ | Battery API (Chrome only) |
Backend (server/)
Express + SQLite + Docker, deployável em Coolify ou qualquer VPS.
| Recurso | Status |
|---|---|
| Auth via Bearer Token | ✅ |
| Endpoints de sync | ✅ |
| Upload/download de mídia | ✅ |
| Vigia + dead-man switch | ✅ |
| Notificações fan-out (6 canais) | ✅ |
| Compartilhamento público com mapa | ✅ |
| Migração de schema | ✅ Automática |
| Health check | ✅ |
| Cleanup de shares expirados | ✅ Diário |
✅ Pronto (recém-implementado)
- Audit log de ações sensíveis (
server/src/db.js+ 6 endpoints instrumentados +GET /api/audit)- Nova tabela
audit_log (id, ts, action, entity, entity_id, summary, ip)com índice emts DESC - Funções
db.audit(action, entity, entityId, summary, ip)edb.recentAudit(limit) - Eventos rastreados:
state_set,media_insert,media_delete,share_create,share_revoke,share_zones_update - Endpoint
GET /api/audit?limit=N(autenticado, max 500) pra consulta - Implementado pelo squad
shivao-melhoriaem 2026-04-27 (run 2026-04-27-131311 +audit step)
- Nova tabela
- Validação Zod nos endpoints autenticados (
server/src/schemas/index.js+ middleware nos endpoints)POST /api/data: schema permissivoz.object({data: z.record(z.string(), z.unknown())})— aceita qualquer estrutura de objeto, rejeita payload semdataou não-objetoPOST /api/share/:token/zones: schema com até 100 zonas, cada uma com lat/lng/radius validados- 400 com top 5 issues do Zod em caso de falha
- Implementado pelo squad
shivao-melhoriaem 2026-04-27 (run 2026-04-27-131311 +zod step)
- Tamanho de upload reduzido pra 50 MB (
server/src/index.jslinha 83)- Multer agora retorna 413 (Payload Too Large) acima de 50MB
- Implementado pelo squad
shivao-melhoriaem 2026-04-27 (run 2026-04-27-131311)
- Rate limiting nos 3 endpoints públicos de share (
/api/share/:token/info,/api/share/:token/positions,/share/:token)- 60 req/min/IP via
express-rate-limitv8 — cobre auto-refresh 15s da tripulação com margem 15× - Aplicado em
server/src/index.jslinhas 38 (limiter), 262, 271, 279 (middleware) - Implementado pelo squad
shivao-melhoriaem 2026-04-27 (run 2026-04-27-124638)
- 60 req/min/IP via
⚠️ O que precisa ser feito antes de produção
🔴 Crítico (segurança)
- CORS atualmente permissivo (
*) — apropriado para single-tenant pessoal, mas se for evoluir para multi-usuário, precisa rever- Status: decisão consciente de manter
*enquanto for single-tenant. Item documentado, não-acionável agora.
- Status: decisão consciente de manter
🟡 Importante (qualidade)
-
Testes automatizados — projeto não tem testes
- Backend: vitest ou jest para os endpoints
- Frontend: playwright para fluxos críticos (especialmente alarme e GPS)
-
Tratamento de erros no frontend (parcialmente resolvido)
- ✅ Catch silencioso de
dispatchWebhooksem zona PROIBIDA (linha 3280) agora loga + toast (run 2026-04-27-131311 +catch step) - Restantes: catches em fetches Windy/Open-Meteo (linhas 2712, 2733) — fallbacks intencionais, mantidos conscientemente
- Restantes: catches
}catch(e){}em Wake Lock e tracking state — best effort em iOS antigo, mantidos - Pendente futura: indicador visual de "última sync falhou" no painel cloud
- ✅ Catch silencioso de
-
Reconexão da vigia (parcialmente resolvido)
- ✅ Wake Lock auto-reacquire:
wakeLock.addEventListener('release', ...)em tracking + anchor (run 2026-04-27-131311 +reconnect step) - ✅ GPS retry exponencial (1s→2s→4s→8s→16s→30s) com
PERMISSION_DENIEDfatal sem retry, em tracking + anchor - ✅
visibilitychangelistener (já existia) re-acquire Wake Lock quando tab volta foreground - Pendente: Service Worker real (para push notifications + offline tile cache) — exige spec própria, deixar pra futuro
- ✅ Wake Lock auto-reacquire:
-
Refatoração do frontend
- HTML monolítico tem ~3500 linhas
- Considerar quebrar em módulos (mas mantendo single-file deployment)
- Avaliar migrar para Vite + Vue/Svelte mantendo build single-file
-
TypeScript no backend
- Hoje JavaScript puro com JSDoc
- Migração para TS deixaria mais robusto
🟢 Bom ter
-
Service Worker / PWA real
- Hoje usa apenas meta tags + "Adicionar à tela inicial"
- Service Worker permitiria offline real, push notifications
-
Backup automático para S3/Backblaze
- Hoje SQLite + filesystem em volume Coolify
- Schedule de backup off-site
-
Multi-usuário / multi-barco
- Hoje single-tenant (um BOAT_TOKEN)
- Para escalar: introduzir users + boats + permissions
-
Histórico de tracks de cada vigia de fundeio
- Hoje só salva resumo (max drift, alarms)
- Salvar também a poligonal de movimento
-
i18n
- Hoje hardcoded em português
- Estrutura permite extrair strings facilmente
🐛 Bugs/limitações conhecidas
-
iOS: Battery API não disponível → modo "auto" sempre cai em normal. Não é problema crítico, só não economiza bateria automaticamente.
-
iOS: Wake Lock funciona em Safari 16.4+. Em versões anteriores, a tela apaga e o GPS pode parar.
-
iOS: Modo silencioso pode silenciar o som do alarme. Vibração continua funcionando.
-
Android: Quando o navegador é fechado completamente, o GPS pára (limitação do browser). O dead-man switch do servidor cobre isso.
-
Modal de tracking abre como modal mas seria melhor full-screen real. Atualmente usa CSS hack com height:100vh.
-
Geofencing só suporta círculos, não polígonos. Para áreas irregulares (canal estreito, baía com formato complexo) é uma limitação.
-
Map tiles vêm de tile.openstreetmap.org sem cache. Em viagem com dados móveis caros, pode consumir muito. Considerar cache ou tiles próprios (MapTiler, Mapbox).
-
Não há sincronização incremental — sempre envia/baixa o estado todo. Para usuários com muito histórico, fica lento. Sugerimos versionar por entidade (CRDT ou last-write-wins por id+ts).
-
A chave Windy fica em localStorage — se sincronizada via cloud, trafega entre dispositivos do dono (OK, mas vale documentar).
-
Sem HTTPS local em desenvolvimento — a API do Geolocation só funciona em HTTPS ou localhost. Devs precisam usar localhost.
📋 Decisões técnicas relevantes
Detalhes em ARCHITECTURE.md, mas em resumo:
- SQLite + filesystem em vez de Postgres + S3 → simplicidade, single-tenant, fácil backup
- Single-file HTML → instalável no celular sem app store, deploy simples
- Bearer Token simples em vez de OAuth → uso pessoal
- Leaflet + OSM em vez de Google Maps/Mapbox → grátis, sem chaves
- Web Audio API para alarme em vez de mp3 estático → mais alto e confiável
- Open-Meteo como fallback → app funciona sem nenhuma chave de API
- Windy como provedor premium → o dono já tem assinatura
🚀 Para começar
Para colocar em produção:
- Ver DEPLOY.md — passo-a-passo do Coolify
- Configurar canais de notificação (mínimo: Telegram via .env)
- Criar bot do Telegram + obter chat_id (instruções em DEPLOY.md)
- Definir
BOAT_TOKEN(32 chars aleatórios) - Volume
/datapersistente - Deploy
Para desenvolver localmente:
- Ver CONTRIBUTING.md
cp .env.example .enve preenchernpm installnpm start- Acessar
http://localhost:3000
Para o time do dono passar a chave Windy:
- A chave premium é configurada no app (não no servidor) via Aba Arquivo → Meteorologia · Windy Point Forecast
- Não cole no código nem em variáveis de ambiente
📞 Contato
Projeto pessoal do dono do veleiro Shivao. Equipe de devs deve coordenar diretamente com ele para credenciais de produção, domínios e orçamento.