diff --git a/CHANGELOG.md b/CHANGELOG.md
index 441ccc4..fa6c8d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
Formato baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/); versões seguem SemVer.
+## [0.3.1] - 2026-10-18
+
+### Corrigido
+- Abrir uma sessão salva falhava por inteiro quando a origem (por exemplo `/dev/sdb`) não estava acessível, e a mensagem de erro era vaga. Agora a sessão sempre abre: dá para navegar, exportar e gerar relatório, e o botão "Escolher a origem…" reconecta o disco ou uma imagem para voltar a pré-visualizar e recuperar.
+- Mensagens claras quando o arquivo de sessão não pode ser lido (por exemplo, criado como administrador em `/root`) ou não é uma sessão válida.
+- Teste automático garante que sessões gravadas pela v0.1.0 continuam abrindo.
+
## [0.3.0] - 2026-10-18
### Adicionado
diff --git a/Cargo.lock b/Cargo.lock
index 48edef1..25bf418 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -317,7 +317,7 @@ dependencies = [
[[package]]
name = "guara-cli"
-version = "0.3.0"
+version = "0.3.1"
dependencies = [
"clap",
"ctrlc",
@@ -329,7 +329,7 @@ dependencies = [
[[package]]
name = "guara-core"
-version = "0.3.0"
+version = "0.3.1"
dependencies = [
"flate2",
"image",
diff --git a/Cargo.toml b/Cargo.toml
index b32f758..982cbe1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/guara-core", "crates/guara-cli"]
[workspace.package]
-version = "0.3.0"
+version = "0.3.1"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/lucasrguerra/guara"
diff --git a/apps/gui/src-tauri/Cargo.toml b/apps/gui/src-tauri/Cargo.toml
index 3b38d2a..95450de 100644
--- a/apps/gui/src-tauri/Cargo.toml
+++ b/apps/gui/src-tauri/Cargo.toml
@@ -3,7 +3,7 @@
[package]
name = "guara-gui"
description = "Guará: interface gráfica de recuperação de arquivos"
-version = "0.3.0"
+version = "0.3.1"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/lucasrguerra/guara"
diff --git a/apps/gui/src-tauri/tauri.conf.json b/apps/gui/src-tauri/tauri.conf.json
index 39a0e99..055a9d5 100644
--- a/apps/gui/src-tauri/tauri.conf.json
+++ b/apps/gui/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Guara",
- "version": "0.3.0",
+ "version": "0.3.1",
"identifier": "dev.guara.app",
"build": {
"frontendDist": "../ui"
diff --git a/apps/gui/ui/app.js b/apps/gui/ui/app.js
index 102cf3f..6ccaa65 100644
--- a/apps/gui/ui/app.js
+++ b/apps/gui/ui/app.js
@@ -210,7 +210,7 @@ $('#loadSessionBtn').onclick = async () => {
const s = { job, target: st.target, label: 'Sessão: ' + st.target.split(/[\\/]/).pop() };
S.sessions.unshift(s);
openJob(s);
- } catch (e) { toast(e.message); }
+ } catch (e) { info({ title: 'Não foi possível abrir a sessão', html: `
${esc(e.message)}
` }); }
};
// ------------------------------------------------------------------ início
@@ -447,7 +447,8 @@ function paintStatus() {
const enr = st.enriching ? `Lendo datas e metadados das fotos… ${st.enriching.done.toLocaleString('pt-BR')}/${st.enriching.total.toLocaleString('pt-BR')}` : '';
const buttons = running ? ''
: st.status === 'paused' ? '' : '';
- const tools = !running && st.found ? '' : '';
+ const relink = st.source === false ? '' : '';
+ const tools = relink + (!running && st.found ? '' : '');
if (running || st.status === 'paused') {
setHTML($('#progressArea'), `
${esc(p.phase || 'Iniciando')}${pct != null ? ` · ${pct}%` : ''}
${buttons}
@@ -473,7 +474,12 @@ async function onProgressClick(e) {
if (act === 'pause') { await rpc('scan_pause', { job: S.job }); toast('Pausando…'); }
else if (act === 'stop') await rpc('scan_stop', { job: S.job });
else if (act === 'resume') { await rpc('scan_resume', { job: S.job }); stopPoll(); S.poll = setInterval(tick, 500); tick(); }
- else if (act === 'csv') {
+ else if (act === 'relink') {
+ const path = await ask({ title: 'Escolher a origem', text: `Esta sessão foi feita sobre
${esc(S.status?.target || '')}. Informe o dispositivo ou a imagem do mesmo disco para pré-visualizar e recuperar.`, value: S.status?.target || '', ok: 'Usar esta origem' });
+ if (path) {
+ try { await rpc('session_relink', { job: S.job, path }); toast('Origem reconectada'); const cur = { job: S.job, target: S.status.target, label: S.sessions.find((x) => x.job === S.job)?.label || 'Sessão' }; openJob(cur); } catch (err) { info({ title: 'Não foi possível usar essa origem', html: `
${esc(err.message)}
` }); }
+ }
+ } else if (act === 'csv') {
const path = await ask({ title: 'Exportar para CSV', text: 'Grava os arquivos da vista atual (com os filtros aplicados). O arquivo abre direto no Excel.', value: (S.defaults.home || '') + (S.defaults.sep || '/') + 'guara-achados.csv', ok: 'Exportar' });
if (path) { try { const r = await rpc('export_csv', { job: S.job, path, query: EX?.query() }); toast(`${r.rows.toLocaleString('pt-BR')} linhas gravadas`); } catch (err) { toast(err.message); } }
} else if (act === 'report') {
diff --git a/apps/gui/ui/explorer.js b/apps/gui/ui/explorer.js
index d84af74..3f2adce 100644
--- a/apps/gui/ui/explorer.js
+++ b/apps/gui/ui/explorer.js
@@ -279,7 +279,10 @@ export function createExplorer(deps) {
if (img.tagName !== 'IMG' || !img.dataset.th) return;
const wrap = img.closest('.xp-thumb, .xp-lth');
img.remove();
- wrap?.insertAdjacentHTML('afterbegin', `
${I.warn}?
`);
+ const offline = E.status && E.status.source === false;
+ wrap?.insertAdjacentHTML('afterbegin', offline
+ ? '
🖼IMG
'
+ : `
${I.warn}?
`);
}, true);
// ------------------------------------------------------------ seleção
diff --git a/crates/guara-core/src/service.rs b/crates/guara-core/src/service.rs
index 5767207..43f7d5f 100644
--- a/crates/guara-core/src/service.rs
+++ b/crates/guara-core/src/service.rs
@@ -63,7 +63,7 @@ pub struct Job {
opts: ScanOptions,
cancel: AtomicBool,
stop_requested: AtomicBool,
- src: Option
>,
+ src: Mutex