diff --git a/app/diario-bordo.html b/app/diario-bordo.html
index 7b9ede4..3dacbfc 100644
--- a/app/diario-bordo.html
+++ b/app/diario-bordo.html
@@ -3749,6 +3749,16 @@ async function updateStorageInfo(){
(async()=>{
// Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente)
try{
+ // Detecta crash BLE da sessão anterior via breadcrumb
+ try{
+ const lastStep=localStorage.getItem('shivao_ble_last_step');
+ if(lastStep){
+ setTimeout(()=>{
+ try{alert('⚠ Crash detectado na sessão anterior!\nÚltima ação BLE: '+lastStep+'\n\nMe envie esta mensagem.')}catch{}
+ },2000);
+ localStorage.removeItem('shivao_ble_last_step');
+ }
+ }catch{}
await openDB();
try{loadState()}catch(e){console.error('[boot] loadState',e);try{localStorage.removeItem(STORAGE_KEY)}catch{}/* corrupt state — reset */}
// Migration defensiva: limpa entries inválidas em state.btDevices
@@ -5885,6 +5895,12 @@ function setBleDiag(msg,type){
console.log('[ble]',msg);
}
+// Breadcrumb persistente: grava ANTES de cada chamada nativa pra sobreviver crash do WebView
+function bleCrumb(step){
+ try{localStorage.setItem('shivao_ble_last_step',step+' @ '+new Date().toISOString())}catch{}
+}
+function bleCrumbClear(){try{localStorage.removeItem('shivao_ble_last_step')}catch{}}
+
async function pairBluetoothDevice(){
const backend=bleBackend();
setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
@@ -5892,8 +5908,10 @@ async function pairBluetoothDevice(){
try{
let deviceId,deviceName;
if(backend==='capacitor'){
+ bleCrumb('ensureBleNativeReady');
setBleDiag('Inicializando plugin nativo...');
await ensureBleNativeReady();
+ bleCrumb('requestDevice');
setBleDiag('Plugin OK · abrindo picker...');
const ble=window.Capacitor.Plugins.BluetoothLe;
const result=await ble.requestDevice({
@@ -5901,9 +5919,10 @@ async function pairBluetoothDevice(){
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
allowDuplicates:false,
});
- if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return}
+ if(!result?.deviceId){bleCrumbClear();setBleDiag('Picker cancelado','warn');return}
deviceId=result.deviceId;
deviceName=result.name||'Dispositivo BLE';
+ bleCrumb('selected:'+deviceName);
setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
}else{
setBleDiag('Abrindo picker do navegador...');
@@ -5963,6 +5982,7 @@ async function connectAndRead(deviceId,deviceName){
if(backend==='capacitor'){
const ble=window.Capacitor.Plugins.BluetoothLe;
try{
+ bleCrumb('ble.connect');
await ble.connect({deviceId,timeout:30000});
setBleDiag('GATT conectado','ok');
}catch(e){
@@ -5972,13 +5992,12 @@ async function connectAndRead(deviceId,deviceName){
const conn=_bleConnections.get(deviceId)||{};
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
_bleConnections.set(deviceId,conn);
- // Discover all services pra diagnóstico + auto-detect protocols
try{
+ bleCrumb('ble.getServices');
const r=await ble.getServices({deviceId});
const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8);
setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info');
info.services=svcs;
- // Auto-detect: service ff00 = JBD/LLT Power BMS
const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00'));
if(hasJbd){
setBleDiag('🔋 JBD BMS protocol detectado!','ok');
@@ -6461,7 +6480,7 @@ async function removeBluetoothDevice(id){
renderBluetoothCard();
}
-const APP_VERSION='1.10.17';
+const APP_VERSION='1.10.18';
function renderBluetoothCard(){
const el=document.getElementById('bt-list');
const supportEl=document.getElementById('bt-support');
diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle
index b42bb9e..fcafd1d 100644
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "br.com.pontualtech.shivao"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 30
- versionName "1.10.17"
+ versionCode 31
+ versionName "1.10.18"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/mobile/package.json b/mobile/package.json
index 8beb1e8..647b9d7 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -1,6 +1,6 @@
{
"name": "shivao-mobile",
- "version": "1.10.17",
+ "version": "1.10.18",
"description": "Shivao app nativo (Capacitor wrapper Android/iOS)",
"main": "index.js",
"type": "module",
diff --git a/server/public/index.html b/server/public/index.html
index 7b9ede4..3dacbfc 100644
--- a/server/public/index.html
+++ b/server/public/index.html
@@ -3749,6 +3749,16 @@ async function updateStorageInfo(){
(async()=>{
// Wrapper try/catch pra capturar crash no boot (Capacitor WebView pode fechar silenciosamente)
try{
+ // Detecta crash BLE da sessão anterior via breadcrumb
+ try{
+ const lastStep=localStorage.getItem('shivao_ble_last_step');
+ if(lastStep){
+ setTimeout(()=>{
+ try{alert('⚠ Crash detectado na sessão anterior!\nÚltima ação BLE: '+lastStep+'\n\nMe envie esta mensagem.')}catch{}
+ },2000);
+ localStorage.removeItem('shivao_ble_last_step');
+ }
+ }catch{}
await openDB();
try{loadState()}catch(e){console.error('[boot] loadState',e);try{localStorage.removeItem(STORAGE_KEY)}catch{}/* corrupt state — reset */}
// Migration defensiva: limpa entries inválidas em state.btDevices
@@ -5885,6 +5895,12 @@ function setBleDiag(msg,type){
console.log('[ble]',msg);
}
+// Breadcrumb persistente: grava ANTES de cada chamada nativa pra sobreviver crash do WebView
+function bleCrumb(step){
+ try{localStorage.setItem('shivao_ble_last_step',step+' @ '+new Date().toISOString())}catch{}
+}
+function bleCrumbClear(){try{localStorage.removeItem('shivao_ble_last_step')}catch{}}
+
async function pairBluetoothDevice(){
const backend=bleBackend();
setBleDiag('Backend: '+(backend||'NENHUM'),backend?'info':'err');
@@ -5892,8 +5908,10 @@ async function pairBluetoothDevice(){
try{
let deviceId,deviceName;
if(backend==='capacitor'){
+ bleCrumb('ensureBleNativeReady');
setBleDiag('Inicializando plugin nativo...');
await ensureBleNativeReady();
+ bleCrumb('requestDevice');
setBleDiag('Plugin OK · abrindo picker...');
const ble=window.Capacitor.Plugins.BluetoothLe;
const result=await ble.requestDevice({
@@ -5901,9 +5919,10 @@ async function pairBluetoothDevice(){
optionalServices:[BLE_BATTERY_SERVICE,BLE_DEVICE_INFO],
allowDuplicates:false,
});
- if(!result?.deviceId){setBleDiag('Picker cancelado','warn');return}
+ if(!result?.deviceId){bleCrumbClear();setBleDiag('Picker cancelado','warn');return}
deviceId=result.deviceId;
deviceName=result.name||'Dispositivo BLE';
+ bleCrumb('selected:'+deviceName);
setBleDiag('Selecionado: '+deviceName+' ('+deviceId+')','ok');
}else{
setBleDiag('Abrindo picker do navegador...');
@@ -5963,6 +5982,7 @@ async function connectAndRead(deviceId,deviceName){
if(backend==='capacitor'){
const ble=window.Capacitor.Plugins.BluetoothLe;
try{
+ bleCrumb('ble.connect');
await ble.connect({deviceId,timeout:30000});
setBleDiag('GATT conectado','ok');
}catch(e){
@@ -5972,13 +5992,12 @@ async function connectAndRead(deviceId,deviceName){
const conn=_bleConnections.get(deviceId)||{};
conn.backend='capacitor';conn.deviceId=deviceId;conn.connected=true;
_bleConnections.set(deviceId,conn);
- // Discover all services pra diagnóstico + auto-detect protocols
try{
+ bleCrumb('ble.getServices');
const r=await ble.getServices({deviceId});
const svcs=(r.services||r||[]).map(s=>s.uuid||s).slice(0,8);
setBleDiag('Serviços encontrados: '+svcs.length+' ('+svcs.map(u=>u.slice(4,8)).join(', ')+')','info');
info.services=svcs;
- // Auto-detect: service ff00 = JBD/LLT Power BMS
const hasJbd=svcs.some(u=>u.toLowerCase().startsWith('0000ff00'));
if(hasJbd){
setBleDiag('🔋 JBD BMS protocol detectado!','ok');
@@ -6461,7 +6480,7 @@ async function removeBluetoothDevice(id){
renderBluetoothCard();
}
-const APP_VERSION='1.10.17';
+const APP_VERSION='1.10.18';
function renderBluetoothCard(){
const el=document.getElementById('bt-list');
const supportEl=document.getElementById('bt-support');
diff --git a/server/public/sw.js b/server/public/sw.js
index f661c19..8d8919d 100644
--- a/server/public/sw.js
+++ b/server/public/sw.js
@@ -1,7 +1,7 @@
// Shivao Service Worker — offline real
// Estratégia: shell precachado, tiles cache-first, windy network-first, /api passa direto.
// Versão usada nos cache names — bumpa essa string pra invalidar caches antigos em deploys.
-const VERSION = 'shivao-v1.10.17';
+const VERSION = 'shivao-v1.10.18';
const SHELL_CACHE = `shivao-shell-${VERSION}`;
const TILES_CACHE = 'shivao-tiles-v1'; // separado pra não invalidar tiles em update do shell
const WINDY_CACHE = `shivao-windy-${VERSION}`;
diff --git a/server/src/index.js b/server/src/index.js
index 9aa5c01..fff6380 100644
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -413,7 +413,7 @@ app.get('/.well-known/assetlinks.json', (req, res) => {
});
// Atalho: /apk redireciona pra última APK release no Forgejo
-const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.10.17/Shivao-v1.10.17.apk';
+const LATEST_APK_URL = 'https://git.pontualtech.work/karlao/shivao-projeto/releases/download/v1.10.18/Shivao-v1.10.18.apk';
app.get('/apk', (req, res) => res.redirect(302, LATEST_APK_URL));
// Página A4 imprimível com QR Code + instruções (cola no barco/marina)