Keychain, Kuma e tour interativo — sete frentes hardware-free num dia
Tuner agora guarda token no Keychain do OS, smoke:prod publica heartbeat no Uptime Kuma, app mobile ganhou tela Connect, dashboard sim ficou com 5 telas, /sobre virou tour visual. Mais um dia sem hardware com leverage real.
O hardware Fase 0 ainda em trânsito. Dia inteiro de leverage hardware-free — sete commits, sete frentes, todas amarrando coisas que estavam soltas.
Tuner desktop — token no Keychain do OS
Até agora, o cloudState do Tuner guardava apiBase + deviceId + token
em localStorage. Funciona, mas em produção (Tauri shell) localStorage
é o WebView local — não criptografado, fácil de extrair com qualquer
ferramenta de inspeção de perfil. Apps profissionais usam Keychain
(macOS) / Credential Manager (Windows) / Secret Service (Linux).
Adicionei a crate keyring = "3" no
backend Rust e expus três comandos Tauri: secret_get, secret_set,
secret_delete. O frontend tem um wrapper único (secretGet/Set/Delete)
que detecta o runtime via __TAURI_INTERNALS__ e roteia
automaticamente:
- Em produção (Tauri shell) — vai pro keychain nativo do OS.
- Em vite dev (browser) — fallback transparente pra
localStorage. - Em vitest (node) — mesmo fallback, sem precisar mockar.
A loja Svelte continua inicializando síncrono pelo LS (rune Svelte
exige sync), e logo após boot o App.svelte dispara
hydrateSecretsFromKeychain() num $effect que substitui os valores
em memória pelos do keychain quando ele tem coisa nova.
Empacotei o trade-off em ADR-013.
A crate keyring ganhou em cima de tauri-plugin-stronghold (que
exige password do usuário no boot — UX ruim pra ferramenta) e
keytar (descontinuado em 2024).
Garage — heartbeat no Uptime Kuma
O pnpm smoke:prod rodando diariamente em GitHub Actions tinha um
problema sutil: se quebrasse de madrugada, o sinal era um email do
GitHub. Email do GitHub vai pra spam. Resultado: descobre que a API
caiu quando alguém abre o painel de Actions.
Mas a vps-pomatti já roda Uptime Kuma servindo 5+ apps próprios
(Frigoo, Lumê, etc.). É o canal que eu já consulto no celular pra ver
estado do ecossistema. Então o smoke virou push monitor: depois de
rodar os 5 checks, ele faz GET ${UPTIME_KUMA_PUSH_URL}?status=up|down&msg=…&ping=…
com status do conjunto, mensagem one-liner com nomes dos checks que
falharam, e ping médio em ms.
Detalhe importante: falha pra alcançar o Kuma é não-fatal. Se o Kuma cair ou o cron rodar sem internet, o smoke ainda exit-0 com 5/5 verde. A verdade do produto não depende da saúde do monitor.
Documentado em ADR-014 com as alternativas que descartei (Datadog Synthetic, Better Stack — US$ 30-100/mês quando já tenho Kuma free; Prometheus — over-engineering pra escala atual).
Pistonix Ride — tela Connect
O ConnectionRepository (ADR-012) tinha 4 testes verdes mas nenhuma
tela usava ainda. Agora tem: nova tela Connect com círculo grande
de estado (160×160), cor mudando por estado da máquina (idle cinza /
connecting+connected laranja Pistonix / streaming verde / lost+error
vermelho), spinner durante connecting, botão Conectar/Desconectar/Cancelar
contextual ao estado.
O main.dart agora monta um MockConnectionRepository(telemetry: MockTelemetryRepository()) e auto-conecta no boot — o app abre direto
em “streaming” depois de ~500ms de handshake mock. Quando BLE for
real, troca uma linha no boot (MockConnectionRepository → BleConnectionRepository)
e nada mais muda.
Três widget tests novos cobrem render-idle, transição on-tap e o
caminho de erro do BLE stub (que joga UnimplementedError no connect()
até Phase 5+).
Dashboard sim — Settings + Datalog
O simulador SDL2 do dashboard tinha 3 telas (home, race, diagnostic). Adicionei 2: Settings (slider de brilho 10-100%, switches mock km/h↔mph e noite↔dia, status ”● pareado” do Garage) e Datalog (lista mockada de 5 sessões com bike/duração/tamanho/sync indicator).
Tecla S toggla settings, tecla D toggla datalog. Cada toggle hide
todas as outras telas — overlay mutex explícito, sem stack de modais.
TAB e R continuam toglando diagnostic e race respectivamente.
MOCK_ENTRIES[] é um array static no datalog.c; quando o cloud
client real entrar (Phase 5+), substitui pelo payload do
GET /telemetry.
Web /sobre — tour interativo
O /sobre ganhou seção “Como funciona — Cinco peças que conversam”:
5 cards numerados (01-05) descrevendo Forge (ECU) → Dash (display) →
Ride (app) → Garage (cloud) → Tuner (desktop), conectados por setas
horizontais que mostram o protocolo de cada link (CAN bus 500kbps,
BLE GATT, HTTPS, USB · HTTPS).
Hover anima translateX(4px) + border vermelho, gradiente nas linhas
horizontais entre cards, seta apontando pra baixo no fim. Build
completo em 75s, sem regressão de Lighthouse.
ADR-012 — ConnectionRepository pattern
Documentei o pattern do ConnectionRepository (que extende
TelemetryRepository em vez de viver paralelo) em ADR-012. Justifica
o trade-off contra duas alternativas que rejeitei: estados de
transporte dentro do TelemetryRepository (acopla coisas que não
pertencem juntas) e bus global de eventos (singleton implícito que
re-introduz o acoplamento que ADR-010 tinha rejeitado).
STATE.md
Sincronizado com a batch — entrada 2026-05-06 batch (hardware-free, continuação) lista os 4 commits da janela e atualiza totais de
cobertura (200 ECU + 67 integration cloud + 22 Rust+8 vitest tuner +
11 Ride).
O que NÃO fez parte desta sessão
- Mobile BLE de verdade — depende de hardware Dash existir.
- Telemetry binary upload no Garage — schema decidido, handler ainda é stub.
- DTC details modal no dashboard sim — entrou na próxima fila.
Mas os pontos soltos do dia anterior (token em LS, smoke sem broadcast, ConnectionRepository sem UI consumindo, dashboard com 3 telas mas sem settings/datalog) ficaram todos amarrados. Um dia hardware-free com leverage real — não é trabalho que aparece no Lighthouse, mas é a diferença entre “abre a app e funciona” e “abre a app e funciona, e é fácil de operar”.