Commit graph

28 commits

Author SHA1 Message Date
PontualTech / Karlão
cd4aa9c753 feat(ble): botão Copiar log + Limpar + painel sempre aberto v1.10.6
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Karlão mandou log antigo achando que era novo (não dava pra distinguir
log v1.10.4 de v1.10.5 sem ler timestamps). Adicionado botão 📋 Copiar
log que copia texto puro pro clipboard com header 'Shivao vX.Y.Z · log
diagnóstico' — fica óbvio qual versão está rodando.

Mudanças:
- <details> agora abre por padrão (open attribute)
- Botão 📋 Copiar log (navigator.clipboard + fallback textarea)
- Botão 🗑 Limpar pra zerar histórico antes de novo teste
- Painel max-height 200→300px + font-family mono
- Toast confirma cópia OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 07:30:56 -03:00
PontualTech / Karlão
2fca191676 fix(ble): bmsManualRead reconecta GATT antes do probe v1.10.5
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Bug v1.10.4: clicar 🔄 Re-ler gerava 'getServices erro: Bluetooth LE
not initialized' porque Android desconecta GATT em background pra
economizar bateria, mas state.btDevices ainda mostra 'conectado'.

Fix: bmsManualRead agora faz 3 passos sequenciais com diagnóstico:
1. ensureBleNativeReady() — garante plugin inicializado
2. ble.connect({deviceId, timeout:15000}) — reconecta GATT (silent
   se 'already connected')
3. bmsProbeAndAttach() — probe completo

Cada passo emite log próprio: "Plugin init OK", "GATT reconectado"
ou "GATT já conectado", "🔍 Enumerando characteristics..."

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 07:18:44 -03:00
PontualTech / Karlão
840f0b0dc5 fix(ble): remove requestMtu/requestConnectionPriority — crash em plugin v6.x v1.10.4
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Bug crítico v1.10.3: app crashava ao parear ou clicar Re-ler.
Causa: chamadas a ble.requestConnectionPriority() e ble.requestMtu()
não existem no @capacitor-community/bluetooth-le v6.1.0 (foram
adicionadas em v7+). Sem o método, o plugin lança exception nativa
não-tratada que escapa do try/catch JS e derruba o WebView Capacitor.

Fix:
- Remove requestMtu + requestConnectionPriority
- getServices() chamado UMA vez (não no loop por vendor)
- Filtra services por prefixo vendor (ff00, fff0, ffe0, 0203)
- Lista todos chars descobertos com properties no diagnóstico
- Loga "getServices retornou N services" pra confirmar que enumeração rodou

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 06:59:35 -03:00
PontualTech / Karlão
bba53e4548 feat(bms): versão visível no diagnóstico + MTU bump + connection HIGH v1.10.3
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Karlão reportou log v1.10.1 quando deveria ser v1.10.2 — usuário não
sabia se atualização chegou. Adicionado:

- "Shivao v1.10.X" mostrado no card BMS (status line)
- Primeira linha do log: "📦 Shivao v1.10.3 · Probe iniciado"
- requestConnectionPriority=HIGH antes do probe (alguns BMS exigem)
- requestMtu(247) — Xiaoxiang BMS oficiais às vezes ignoram comandos
  enviados em MTU baixo (23 default), exigem 247 pra responder

Próximo passo: usuário atualiza pra v1.10.3, primeira linha do log
confirma versão. Se ainda zero RX, partiremos pra CCCD descriptor manual.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:04:59 -03:00
PontualTech / Karlão
ca3dd4d7b2 feat(bms): probe automático de protocolo BMS (JBD/JK/Daly) v1.10.2
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Bug v1.10.1: BMS bat2 do Karlão expõe service ff00 mas zero RX no log
após enviar comando JBD 0x03. Significa BMS usa firmware proprietário
não-JBD.

Implementação probe automático:
- Enumera characteristics de cada vendor service (ff00, fff0, ffe0, 0203)
- Lista UUIDs + propriedades (notify/indicate/write/wnr/read) no diagnóstico
- Auto-detecta notify char (com property notify ou indicate)
- Auto-detecta write char (com property write ou writeWithoutResponse)
- Salva config em dev.bmsService/Notify/WriteChar pra reuso
- Subscribe na notify char + listener com hex dump dos chunks RX
- Tenta 4 protocolos sequencialmente (espera 2.5s entre cada):
  1. JBD-0x03 (DD A5 03 00 FF FD 77)
  2. JK-getInfo (AA 55 90 EB 96 00 ... — 20 bytes)
  3. Daly-getInfo (A5 80 90 08 00 00 ... — 13 bytes)
  4. JBD writeWithoutResponse fallback
- Detecta resposta por byte de início (0xDD=JBD, 0xAA=JK, 0xA5=Daly)
- Salva bmsProtocol no device pra usar no poll periódico
- Stubs JK/Daly handlers (parsers específicos virão se BMS responder)

Botão Re-ler agora re-roda probe completo (não só re-envia mesmo comando).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:24:00 -03:00
PontualTech / Karlão
578793d097 feat(bms): dashboard visual + RX log bytes + writeWithoutResponse fallback v1.10.1
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
3 problemas atacados após teste de Karlão:

1) BMS conectou mas não respondeu o comando 0x03:
   - Log mostra "← RX X bytes: hex" pra cada notification recebida
   - Listener registrado ANTES de startNotifications (fix de race condition)
   - Wait 500ms entre subscribe e primeiro write (alguns BMS precisam wake)
   - Após 5s sem resposta, tenta writeWithoutResponse automaticamente
   - Botão 🔄 Re-ler manual no card pra forçar query

2) Karlão pediu "monitor visual humano":
   - Modal full-screen " Monitor da Bateria" com:
     * Círculo SoC grande SVG (ring chart 160x160) com cor por nível
     * Status flow grande:  CARREGANDO / ↓ DESCARGA / — REPOUSO
     * Tempo restante calculado (descarga = remainCap/current)
     * Tempo até cheia (carga = (totalCap-remainCap)/current)
     * 4 cards: Tensão · Corrente · Potência · Capacidade
     * Linha info: ciclos · temperaturas · firmware version
     * Grid de células coloridas por health (vermelho <3.0V, verde >3.6V)
     * Auto-refresh 10s enquanto modal aberto
   - Botão 📊 Monitor no card BMS abre dashboard

3) Estado de erro mais claro:
   - Dashboard mostra "Aguardando dados..." se b.voltage ainda não chegou
   - Diagnóstico log destaca chunks RX em azul

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:06:44 -03:00
PontualTech / Karlão
8f3870412d feat(bms): parser JBD/LLT Power BMS — voltagem, corrente, SOC, células, temps v1.10.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Identificado pelo diagnóstico: BMS do Karlão (bat2) usa protocolo JBD
(Jiabaida) — service ff00, notify ff01, write ff02. Padrão de mercado
para BMS chineses (Overkill Solar, Hankzor, JBD oficial, LLT Power,
Xiaoxiang) — cobre ~80% dos BMS BLE de lítio.

Implementação:
- Auto-detect: ao parear, se device tem service ff00 → ativa parser JBD
- bmsAttachJBD() subscribe na char ff01 (notify) + envia comando 0x03
- Comando: DD A5 03 00 FF FD 77 (Read Basic Info)
- Reassembly de chunks BLE (max 20 bytes/chunk) até receber 0x77 (end)
- Parser decodifica: voltage (uint16/100), current (int16/100, signed),
  remaining/total capacity (Ah), cycle count, protection bitfield,
  SoC (%), FET status, cell count, temperatures (kelvin*10 → °C)
- Re-poll a cada 30s pra atualizar dados em tempo real
- Auto-sync lastBattery com BMS soc pra card resumido

UI expandida:
- Card BMS com 3 stats grandes: TENSÃO (V) · CORRENTE (A) · POTÊNCIA (W)
- Cor dinâmica: verde se carregando (current>0), amarelo se descarregando
- Linha extra: status flow + capacidade (remain/total Ah) + ciclos + temps
- Block de células individuais (4S/8S/16S detectado automaticamente)
- Border-left do card colorido conforme estado de fluxo

Protocolo de referência: gitlab.com/bms-tools/bms-tools

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:56:48 -03:00
PontualTech / Karlão
5dd3362469 feat(ble): diagnóstico verboso pra debugar pareamento BLE v1.9.2
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Karlão reportou: "localiza mas não pareia" (picker abre, seleciona
device, mas conexão falha silenciosa). Sem ver onde trava, impossível fix.

Adicionado:
- setBleDiag() exibe cada step com timestamp + cor (info/ok/warn/err)
- Painel <details> expansível "📋 Diagnóstico" no card BLE
- Logs em cada operação: backend, init, picker, connect, getServices,
  battery read, notifications, device info
- Timeout do connect aumentado: 15s → 30s (BMS podem demorar)
- getServices() lista UUIDs descobertos no device — descobre se BMS
  expõe Battery Service padrão ou só protocolo proprietário
- Mensagens explícitas de erro em cada catch (e.message ou errorMessage)

Próximo passo: Karlão testa, abre painel diagnóstico, me passa screenshot
ou copia o log. Daí descubro exatamente onde trava (timeout, sem service,
permissão negada, etc).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:39:41 -03:00
PontualTech / Karlão
52ee668879 fix(ble): plugin nativo @capacitor-community/bluetooth-le pra APK Android v1.9.1
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Bug v1.9.0: APK Android mostrava "Web Bluetooth não suportado" porque
Android System WebView desabilita Web Bluetooth API por padrão (segurança).

Fix: instala plugin @capacitor-community/bluetooth-le@^6.1.0 (compatível
com Capacitor 6) que expõe API nativa Android/iOS. JS detecta backend:
- Capacitor (APK): usa window.Capacitor.Plugins.BluetoothLe
- Browser web: usa navigator.bluetooth (Chrome PC continua funcionando)

Mudanças:
- mobile/package.json: nova dep @capacitor-community/bluetooth-le ^6.1.0
- AndroidManifest.xml: BLUETOOTH_SCAN (neverForLocation), BLUETOOTH_CONNECT,
  BLUETOOTH/BLUETOOTH_ADMIN (Android ≤30), uses-feature bluetooth_le
- bleBackend() detecta runtime, ensureBleNativeReady() inicializa plugin
- pairBluetoothDevice + connectAndRead + reconnect + remove abstraem backend
- UUIDs em formato 128-bit (compatível com ambos)
- parseDataView helper: plugin envia value como base64, web envia DataView

iOS: plugin suporta nativamente — quando build iOS for feito, funciona.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:24:50 -03:00
PontualTech / Karlão
a6a35c6d6f feat(ble): Web Bluetooth (Battery Service genérico) + slot Raymarine NMEA gateway v1.9.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Bluetooth & Acessórios (aba Mais):
- Pareamento Web Bluetooth API (acceptAllDevices)
- Lê Battery Service padrão (UUID 0x180F) + characteristic 0x2A19
- Subscribe pra notificações em tempo real (battery_level changes)
- Lê Device Info Service (manufacturer + model)
- Lista persistente de devices pareados (state.btDevices)
- Reconexão via navigator.bluetooth.getDevices() (Chrome ≥85)
- Status visual: 🪫/🔋 + cor por nível (verde >50, amarelo 20-50, vermelho <20)
- Cleanup ao remover device (disconnect GATT + remove do state)

Raymarine Gateway (slot, parser em v1.10):
- Card config com IP + porta TCP/UDP do gateway NMEA 2000→WiFi
- Sugere Yacht Devices YDWG-02 / Actisense W2K-1
- Salva em state.nmeaGateway pra parser futuro
- Sem gateway físico ainda, só persiste config

Limitações documentadas no UI:
- iOS Safari não suporta Web Bluetooth (precisa @capacitor/community/bluetooth-le em v1.10)
- Reconexão automática varia por device (Web Bluetooth não persiste connections)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 16:07:37 -03:00
PontualTech / Karlão
0921d98ef3 feat(charts): cartas náuticas OpenSeaMap (grátis) + slot Navionics + export OpenCPN v1.8.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Cartas náuticas nos mapas (rastreio, fundeio, zonas, viagens):
- OpenSeaMap como overlay padrão grátis (sondas, faróis, bóias, marcas)
- Slot Navionics ativável via chave (após aprovação Garmin)
  - Dynamic load do JNC.Leaflet.NavionicsOverlay quando chave preenchida
- Layer switcher no canto direito do mapa: OSM Padrão / Satélite Esri,
  overlay OpenSeaMap / Navionics
- Helper addMapLayers() centraliza configuração — substituiu 5 usages
  manuais de L.tileLayer espalhados (tracking, trip view, anchor,
  anchor history, zone editor)

Settings (Mais → Cartas Náuticas):
- Dropdown provedor: OpenSeaMap/Navionics/só OSM
- Campo chave Navionics (password) com link pro form Garmin
- Status visual do provedor ativo

Integração OpenCPN (Mais → Exportar para OpenCPN):
- Botão gera GPX consolidado de todo o diário:
  - Tracks: cada viagem com pontos GPS sequenciais
  - Waypoints: cada fundeio histórico com símbolo Anchor
  - Routes: cada zona (forbidden/attention) como polígono fechado
  - Aproximação círculo→polígono 16 pontos pra zonas circulares
- Compatível com OpenCPN, Garmin, Raymarine, B&G, qualquer plotter
  GPX-compliant
- Download direto via Blob URL, sem servidor

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:34:24 -03:00
PontualTech / Karlão
f8e92f3c58 fix(auth): cloudConfigured() reconhece login Google/email (sem token) v1.7.1
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Bug crítico: após login Google ou email, o app pedia pra logar
DE NOVO toda vez que abria/fechava. E sync nunca iniciava.

Causa: cloudConfigured() exigia state.cloud.token, mas no login
Google/email a auth fica em state.auth.accessToken (JWT), não em
state.cloud.token (que é o BOAT_TOKEN avançado).

Resultado: cloudConfigured() retornava false → welcome screen
sempre aparecia, rtConnect() nunca rodava, sync zero.

Fix:
- cloudConfigured() agora retorna true se tem state.auth.accessToken
  OU state.cloud.token (qualquer um dos dois)
- maybeShowWelcome() reescrito pra checar autenticação real
- Botão "Usar sem login (modo offline)" mais visível na welcome
  screen pra dar saída clara

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 09:13:37 -03:00
PontualTech / Karlão
c7994167be feat(ui): redesign Marine Pro Dark — bottom nav + dark navy + Inter v1.7.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Reskin completo baseado em pesquisa de Navionics/Windy/PredictWind/Garmin
ActiveCaptain. Mata o feel "magazine editorial vintage" e adota padrões
mobile-app modernos.

Mudanças visuais (CSS overlay v3 sem alterar HTML/JS de business):
- Paleta dark navy (#0d2538) + cyan accent (#06b6d4) + reservado red pra alarme
- Inter (sans-serif) substitui Fraunces (italic editorial)
- Tabular nums em todas as métricas (lat/lon/depth/speed)
- Cards modernos: border-radius 14px + shadows sutis + bg dark
- Header 50% mais compacto (sem compass mark, avatar maior + accent cyan)
- FAB reposicionado acima da bottom nav, gradient cyan
- Modais: bottom sheet no mobile com top corners rounded
- Form fields dark com focus glow cyan
- Buttons com border-radius modernos, primary = cyan filled

Novos componentes:
- Bottom navigation: 5 tabs com line icons (Início/Travessias/Pendências/
  Zonas/Mais), backdrop-filter blur, badge vermelho em pendências overdue
- Safety status bar (sticky abaixo do header): GPS dot + Anchor watch +
  Bateria. Pulsa amarelo se warn, vermelho se danger
- switchPanel() unifica top tabs (legacy) + bottom nav

Service worker bumped pra invalidar cache antigo automaticamente.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 09:00:29 -03:00
PontualTech / Karlão
b57ba0da37 fix(auth): persiste session_id pra OAuth sobreviver app-kill v1.6.2
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Bug: ao clicar "Entrar com Google" no APK Capacitor, app abria Chrome,
user logava OK ("Logado, volte pro app"), mas ao voltar pro app o login
não completava — ficava em loop pedindo pra logar de novo.

Causa: Android matava o WebView do app quando ele ia pra background
(usuario indo pro Chrome). Ao reabrir o app, _googleAuthPolling interval
estava perdido e o session_id (em variável JS) também.

Fix: persiste session_id em localStorage com timestamp. Adiciona
resumePollingIfPending() chamado em:
- Bootstrap (sempre, 500ms após init)
- visibilitychange visible (volta do background)

Também faz uma chamada imediata de poll antes de iniciar interval —
caso os tokens já estejam prontos quando o app reabre.

TTL de 10min no localStorage (mesmo TTL do Map no servidor) — após
isso considera expirado e limpa.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:30:32 -03:00
PontualTech / Karlão
24f6df3da7 fix(auth): login Google funciona em apps Capacitor (redirect+polling) v1.6.1
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Problema: Google Sign-In popup (GSI) não funciona em WebView nativo do
Capacitor. FedCM bloqueia, popup não abre, ou retorna erro silenciosamente.

Solução: detectar Capacitor (window.Capacitor) ou WebView (UA com 'wv') e
usar OAuth redirect tradicional + polling em vez do popup GSI.

Backend (server/src/index.js):
- GET /api/auth/google/start — gera URL OAuth com state contendo
  session_id + flow:'login'. App chama isso e abre URL no browser externo.
- /api/google/callback adaptado — quando state.flow=='login', cria/loga
  user por email do Google, gera JWT, armazena em pendingGoogleSessions
  (Map em memória, TTL 10min) por session_id, mostra HTML "logado, volte
  pro app".
- GET /api/auth/google/poll?session=xxx — app faz polling 2s. Retorna
  204 se ainda esperando, 200 com tokens (one-shot, deleta após).

Frontend (app/diario-bordo.html):
- Detecta Capacitor/WebView, força fluxo redirect+polling
- Browser web: tenta GSI popup primeiro, fallback redirect se prompt
  for bloqueado (FedCM/popup blocker)
- window.open abre Custom Tabs no Android (ou nova aba no PC)
- Timeout de 4min (120 tries × 2s) pro polling

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:20:39 -03:00
PontualTech / Karlão
b48afaa84f feat(welcome): tela de boas-vindas com login Google/Email + URL hardcoded v1.6.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
UX simplificada drasticamente — usuário não precisa mais saber URL/token:
- Tela de boas-vindas full-screen quando não logado
- 3 botões grandes: Google, Email, Servidor próprio (avançado)
- URL hardcoded https://shivao.pontualtech.work como padrão
- Auto-conecta WebSocket + Google Calendar status após login
- Pull inicial automático pra puxar dados existentes da conta

Backend (server/src/index.js):
- Endpoint POST /api/auth/google: recebe credential (Google ID token),
  valida via tokeninfo do Google, confere aud == GOOGLE_CLIENT_ID,
  cria user automático com email do Google se não existe,
  retorna JWT access+refresh tokens
- Reusa GOOGLE_CLIENT_ID/SECRET já configurados no Coolify

Frontend (app/diario-bordo.html):
- Modal welcome com Google Sign-In via @google/gsi/client (script async)
- Tabs Login/Signup pro fluxo email
- Form avançado pra power users self-hosters
- Skip pra modo offline
- Once dismissed, fica oculto (localStorage flag)

Service Worker bumped pra v1.6.0 (invalida cache antigo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:00:54 -03:00
PontualTech / Karlão
ae09a5cce0 feat(gcal): integração Google Agenda bidirecional (graceful-disabled se sem env)
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Backend (server/src/google-calendar.js + endpoints):
- OAuth 2.0 authorization-code flow completo
- Endpoints: /api/google/{status,auth-url,callback,disconnect,sync-pending,pull}
- Auto-refresh de access_token quando expira (com retry 401→refresh→retry)
- Token storage em google_connections (better-sqlite3)
- syncToken pra delta sync eficiente do Google
- Pendência↔evento: / no summary, dueDate→start.date all-day,
  shivaoPendingId/shivaoCompleted em extendedProperties.private
- Graceful disable: 503 + flag isEnabled() se env vars não setadas

Frontend (Arquivo › Google Agenda):
- Card só aparece quando feature ativa no servidor
- Connect: abre OAuth em nova aba + polling 3s pra detectar sucesso
- Auto-sync na criação/edição/deleção de pendência (se conectada + tem dueDate)
- Botão "Sincronizar todas pendências" + "Buscar mudanças do Google"
- Pull automático ao abrir aba Pendências (se passou >2min do último)
- Pendências criadas direto no Google viram pendências locais

Pra ativar em produção, adicionar no Coolify shivao-cloud:
  GOOGLE_CLIENT_ID=...apps.googleusercontent.com
  GOOGLE_CLIENT_SECRET=...
  GOOGLE_REDIRECT_URI=https://shivao.pontualtech.work/api/google/callback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 06:56:56 -03:00
PontualTech / Karlão
21b91b3522 feat(sync): WebSocket realtime + auto-push/pull entre PC e celular v1.5.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Backend (server/src/realtime.js):
- WebSocket server em /ws via lib `ws`
- Auth por JWT ou BOAT_TOKEN (mesmo middleware do REST)
- Broadcast de notificações state:changed por user (skip device origem)
- Heartbeat ping/pong + cleanup de conexões mortas
- Presença: avisa todos os devices do user quantos estão online
- POST /api/data agora dispara broadcast pra outros devices em tempo real

Frontend (app/diario-bordo.html):
- Cliente WS com reconnect exponencial (1s→2s→5s→15s→30s→60s)
- deviceId persistente em localStorage (gerado no primeiro boot)
- Heartbeat 25s pra manter NAT/proxy abertos
- Auto-push debounced 2.5s no saveState (acumula edições rápidas)
- Auto-pull debounced 300ms no recebimento de state:changed
- Reconnect ao voltar pro foreground + ao recuperar conexão
- Indicador visual no header: 🟢 online · 🟡 syncing · 🔴 offline ·  disabled · ⚠️ erro

Echo prevention em 3 camadas:
1) Server skip por originDeviceId (header X-Device-Id)
2) Cliente ignora notif do próprio device
3) Guard temporal: pull rejeita se updated_at < lastPushAt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 06:51:35 -03:00
PontualTech / Karlão
5833efcc48 feat(fleet): foto da embarcação + horímetro + cadastro + matrícula v1.4.1
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
Novos campos por embarcação:
- Foto (capture câmera ou galeria, resize automático max 1280px JPEG q=0.85)
- Horímetro inicial do motor
- Data de cadastro (defaulta hoje em novas)
- Matrícula / TIE (Capitania)
- Notas livres

UI:
- Preview circular no editor com botões câmera/galeria/remover
- Avatar circular no header (foto se houver, ícone do tipo senão)
- Avatar 44x44 na lista da frota
- Foto guardada no IndexedDB (mesma store das mídias de viagem/manutenção)
- Lifecycle pareado: remover barco apaga foto, trocar foto apaga antiga
- /apk redirect aponta pra v1.4.1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 06:37:10 -03:00
PontualTech / Karlão
7ccaa18bfa feat(fleet): sistema multi-embarcação + calculadora de fundeio v1.4.0
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
— Frota: gerencia múltiplas embarcações (veleiro/motor/cata/RIB/outro)
— Selector de barco ativo no header, modal de gerência completo
— Campos por barco: nome, tipo, modelo, comprimento, boca, calado, amarra, ano
— Toggle global de unidades (metros/pés) com conversão em todos displays
— Calculadora de fundeio: scope ratio, raio de giro, raio sugerido p/ alarme
— Dicas adaptativas por vento (auto-fetch Windy/OpenMeteo) + tipo de embarcação
— Migration automática state.boat → state.boats[] (compat retroativa)
— Storage interno sempre em metros (ISO), conversão só no boundary

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 06:21:29 -03:00
PontualTech / Karlão
6e340cc733 feat(ui): refresh visual premium — design system v2 com depth, microinterações, polish
Some checks are pending
Build Android (APK + AAB) / build-android (push) Waiting to run
OVERLAY no <style> existente — mantém TODA a estrutura/funcionalidade,
moderniza profundamente o visual mantendo identidade marítima.

DESIGN SYSTEM
- Variables novas: spacing (s1-s8), radius (sm/md/lg/xl/pill), shadow scale (sh-1 a sh-5 + glow), transitions
- Surfaces elevated com backdrop-filter blur
- Border tokens (subtle/strong)

VISUAL UPGRADES
- Background limpo elegante (sem ruído pergaminho)
- Header gradient sutil + glow latão na compass-mark
- Tabs como pill nav modern com indicador animado e hover
- Cards com depth real (sombras multi-layer)
- GPS/Anchor card gradient diagonal com inner glow
- Buttons com lift hover, gradients sutis, focus ring
- Fields com border focus + glow latão
- Modal com blur backdrop + entrance polida + radius arredondado
- FAB com lift hover e scale
- Stats grid com cards individuais hovers
- Empty states elegantes
- Status pills arredondados
- Sensor widget com glassmorphism premium
- Auth box com glass effect

MICROINTERAÇÕES
- Todos hover transitions em 200ms cubic-bezier
- Lift de buttons (translateY -1px)
- Compass mark rotaciona 15deg no hover
- Cards levantam com box-shadow
- Media thumbs scale up com z-index

ACESSIBILIDADE
- Focus rings visíveis em tudo (outline 2px brass)
- prefers-reduced-motion honrado
- Cursor pointer garantido em interativos
- Scrollbars sutis customizadas

Sincronizado em app/ + server/public/. Sem mudança JS.
2026-04-27 20:39:23 -03:00
PontualTech / Karlão
7a523b8873 feat(mobile): scaffold Capacitor pra Android Play Store + adapter nativo
ESTRUTURA NOVA: mobile/ + scripts/sync-html.mjs

- mobile/package.json: Capacitor 6 + plugins (geolocation, local-notifications, network, preferences, status-bar)
- mobile/capacitor.config.json: appId br.com.pontualtech.shivao, allowNavigation pra OSM/Windy/CDNs
- mobile/.gitignore: protege keystore (NUNCA commitar chaves privadas)
- mobile/README.md: setup completo (JDK + Android Studio + keystore + build APK/AAB + Play Store submission + iOS futuro + troubleshooting)
- scripts/sync-html.mjs: copia app/diario-bordo.html → server/public + mobile/www (1 fonte da verdade)

ADAPTER NATIVO no HTML (sincronizado app/ + server/public/):
- isNative() / nativePlatform() detecta Capacitor
- nativeWatchPosition() usa Capacitor.Geolocation (background-capable) com fallback navigator.geolocation
- nativeNotify() usa Capacitor.LocalNotifications com fallback toast
- initServiceWorker() pula registro no Capacitor (WebView nativo já tem cache próprio)

NÃO INCLUI (ainda):
- Build local: precisa JDK 17 + Android Studio (~3GB) — instruções no README
- Keystore: gerar 1 vez via keytool (script no README)
- AAB pra Play Store: comandos no README
- Conta Google Play Developer: $25 1× pelo dono

Próximo passo manual: instalar JDK + Android Studio, rodar 'npm install && npx cap add android'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:02:34 -03:00
PontualTech / Karlão
ca9de52ae1 feat(billing): integração Asaas — checkout PIX/Cartão/Boleto + webhook + UI upgrade
BACKEND
- Nova tabela payments (user_id, asaas_payment_id, plan, cycle, value, billing_type, status, ...)
- Coluna users.asaas_customer_id (cache pra reaproveitar customer entre payments)
- server/src/billing.js: cliente Asaas v3 com getOrCreateCustomer, createPayment, getPixQrCode, status mapping
- Endpoint POST /api/billing/checkout — cria cobrança + retorna URL/QR PIX
- Endpoint GET /api/billing/payment/:id — verifica status, faz reconciliação se webhook falhou
- Endpoint POST /api/billing/asaas-webhook — ativa licença em RECEIVED/CONFIRMED, revoga em REFUNDED
- Endpoint GET /api/billing/payments — histórico do user
- 503 se ASAAS_API_KEY não configurado (graceful degradation)
- Webhook valida ASAAS_WEBHOOK_TOKEN (shared secret) se setado

FRONTEND (sincronizado app/ + server/public/)
- openUpgradeModal() — modal dinâmico com seleção plano (Pro/Captain) + ciclo (mensal/anual) + tipo (PIX/Cartão/Boleto)
- _doCheckout() — chama backend, exibe QR Code PIX OU link invoice
- checkPaymentStatus() — verifica e ativa licença quando pago

ENV VARS NECESSÁRIAS NO COOLIFY (próximo passo manual):
- ASAAS_API_KEY=$aact_prod_... (chave Asaas que Karlão já usa em outros projetos)
- ASAAS_API_URL=https://api.asaas.com/v3 (default)
- ASAAS_WEBHOOK_TOKEN=whsec_... (gere um valor aleatório, configure no painel Asaas → Integrações)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:55:08 -03:00
PontualTech / Karlão
85b60a800c feat(saas): multi-tenant com login/cadastro + JWT + planos free/pro/captain
BACKEND
- bcryptjs + jsonwebtoken adicionados (JS puro, sem build nativo)
- Schema users + licenses, migration adiciona user_id em todas tabelas (state, media, anchor_session, alarm_log, shares, audit_log)
- User default id=1 (karlao@outlook.com) com plano captain — preserva uso pessoal pré-multi-tenant
- Endpoints /api/auth/{signup,login,refresh,me} + /api/license
- Middleware requireAuth aceita JWT OU BOAT_TOKEN (fallback legado mapeia ao user 1)
- TODAS rotas autenticadas atualizadas pra usar req.user.id (state, media, anchor, share, alarm, audit)
- Dead-man switch agora itera todos anchor_sessions ativos (multi-user)
- 3 planos definidos em auth.js: free (Âncora), pro (R$19/mês), captain (R$39/mês)

FRONTEND
- state.auth + state.license persistidos em localStorage
- cloudFetch usa JWT preferencialmente, fallback BOAT_TOKEN; auto-refresh em 401
- Nova seção 'Conta' no painel Arquivo: tabs Entrar/Cadastrar + status de plano + Logout + botão upgrade
- Sincronizado em app/ e server/public/

Backward-compat 100% preservada: app legado com BOAT_TOKEN continua funcionando como user default.
Próximo: webhook Asaas pra ativar licenças após pagamento PIX.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:37:15 -03:00
PontualTech / Karlão
d1a2401048 feat(offline+sensors): Service Worker, bússola, barômetro, pré-cache de mapa
Implementa requisitos pra uso em áreas remotas:

OFFLINE REAL (Service Worker em server/public/sw.js)
- Pré-cache de shell (HTML, manifest, icon, Leaflet, fontes Google)
- Cache-first pra map tiles OSM (offline em alto-mar com tiles já visitados)
- Network-first pra Windy/Open-Meteo (com fallback ao cache)
- /api/* passa direto (não interferir em sync, heartbeat, auth)
- Skip-waiting + claim pra ativar imediatamente após install

SENSORES (sensor widget flutuante canto superior direito)
- Bússola via DeviceOrientationEvent (suporta iOS webkitCompassHeading + Android alpha)
- iOS: pede permission via gesture do usuário (botão 'Ativar bússola')
- Barômetro via Generic Sensor API (Android com sensor real, fallback gracioso)
- Tendência de pressão (subindo/caindo/estável) baseada em janela móvel
- Indicador de online/offline sempre visível

PRÉ-CACHE DE MAPA
- Botão 'Pré-cachear mapa' baixa tiles ~50km de raio (zooms 8-13, ~200 tiles)
- Comunicação page→SW via MessageChannel
- Limit 6 conexões paralelas (respeitando OSM tile policy)

DOCUMENTAÇÃO TERMÔMETRO: API web não tem termômetro de ambiente.
Solução: usar dado da Windy (já implementado) + cache offline via SW.

Sincronizado em app/ e server/public/ — single-file HTML preservado.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:55:39 -03:00
PontualTech / Karlão
36ea8f006b feat(pwa): manifest.json + ícone SVG marítimo pra instalar como app
- Endpoint Express /manifest.json com name/short_name/icons/theme
- /icon.svg vetorial (veleiro estilizado em paleta marítima)
- Links manifest + apple-touch-icon nos 2 HTMLs (app/ e server/public/)

Habilita:
- Android Chrome: 'Add to Home Screen' com ícone bonito
- iOS Safari: ícone na tela inicial
- PWABuilder: pode gerar APK Android sideload-ready

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 14:35:01 -03:00
PontualTech / Karlão
78c6de538a feat(reliability): vigia reconnect (Wake Lock release + GPS retry exponencial)
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>
2026-04-27 13:28:09 -03:00
PontualTech / Karlão
5b02feae50 chore: initial commit + security hardening (4 runs squad shivao-melhoria)
Importação inicial do projeto Shivão (Diário de Bordo do veleiro) em estado
pronto pra produção, já incluindo as 5 mudanças de hardening implementadas
pela squad shivao-melhoria em 2026-04-27.

Mudanças de hardening (HANDOFF.md seção " Pronto"):

1. **Rate limiting nos 3 endpoints públicos de share**
   - express-rate-limit ^8.4.1, 60 req/min/IP
   - server/src/index.js linhas 38, 262, 271, 279

2. **Tamanho de upload reduzido pra 50MB** (era 200MB)
   - multer linha 84

3. **Validação Zod nos endpoints autenticados** (POST /api/data e /zones)
   - novo arquivo server/src/schemas/index.js
   - middleware validate() retorna 400 com top 5 issues

4. **Audit log de ações sensíveis**
   - nova tabela audit_log + funções db.audit() e db.recentAudit()
   - 6 endpoints instrumentados (state_set, media_insert/delete, share_create/revoke/zones_update)
   - novo endpoint GET /api/audit (autenticado)

5. **Catch silencioso de webhook em zona PROIBIDA tratado**
   - app/diario-bordo.html + server/public/index.html linha 3280
   - agora loga erro + exibe toast ao usuário

Status final do HANDOFF:
- 🔴 Críticos restantes: 1 (CORS — decisão consciente single-tenant, não-acionável)
- 🟡 Importantes: 4 itens (testes, vigia reconnect, refator frontend, demais catches)
- 🟢 Bom-ter: 5 itens

Stack: Node 20 ESM + Express + better-sqlite3 + Docker (Coolify)
Single-tenant pessoal · single-file frontend HTML · offline-first

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:24:08 -03:00