The post below is in Portuguese. We will translate critical announcements as the project gains an English-speaking audience.
Keychain, Kuma, and an interactive tour — seven hardware-free fronts in a day
Tuner now keeps tokens in the OS keychain, smoke:prod publishes a heartbeat to Uptime Kuma, the mobile app got a Connect screen, the dashboard sim hit 5 screens, /sobre became a visual tour. Another day without hardware with real leverage.
Phase 0 hardware still in transit. Full day of hardware-free leverage — seven commits, seven fronts, all tying together pieces that were loose.
Tuner desktop — token in the OS keychain
Until now, Tuner’s cloudState kept apiBase + deviceId + token in
localStorage. Works, but in production (Tauri shell) localStorage
is the local WebView — unencrypted, easy to extract with any profile
inspection tool. Pro apps use Keychain (macOS) / Credential Manager
(Windows) / Secret Service (Linux).
I added the keyring = "3" crate
to the Rust backend and exposed three Tauri commands: secret_get,
secret_set, secret_delete. The frontend has a single wrapper
(secretGet/Set/Delete) that detects the runtime via
__TAURI_INTERNALS__ and routes automatically:
- In production (Tauri shell) — goes to the OS native keychain.
- In vite dev (browser) — transparent fallback to
localStorage. - In vitest (node) — same fallback, no mocking needed.
The Svelte store still initializes synchronously from LS (Svelte runes
require sync), and right after boot App.svelte fires
hydrateSecretsFromKeychain() in a $effect that replaces the
in-memory values with the keychain ones whenever it has something
fresher.
I packaged the trade-off in
ADR-013.
The keyring crate beat tauri-plugin-stronghold (which requires a
user password on boot — bad UX for a tool) and keytar (deprecated
in 2024).
Garage — heartbeat on Uptime Kuma
pnpm smoke:prod running daily on GitHub Actions had a subtle
problem: if it broke at dawn, the signal was a GitHub email. GitHub
emails go to spam. Result: you find out the API is down when someone
opens the Actions panel.
But vps-pomatti already runs Uptime Kuma serving 5+ owner-managed
apps (Frigoo, Lumê, etc.). It’s the channel I already check on my
phone for ecosystem state. So smoke became a push monitor: after
running the 5 checks, it does
GET ${UPTIME_KUMA_PUSH_URL}?status=up|down&msg=…&ping=… with the
overall set status, a one-liner with the names of the failing checks,
and the average ping in ms.
Important detail: failing to reach Kuma is non-fatal. If Kuma is down or the cron runs without internet, smoke still exits 0 with 5/5 green. Product truth doesn’t depend on monitor health.
Documented in ADR-014 with the alternatives I rejected (Datadog Synthetic, Better Stack — US$ 30-100/month when I already have Kuma free; Prometheus — over-engineering for current scale).
Pistonix Ride — Connect screen
ConnectionRepository (ADR-012) had 4 green tests but no screen used
it yet. Now it does: a new Connect screen with a big state circle
(160×160), color shifting per state-machine state (idle gray /
connecting+connected Pistonix orange / streaming green /
lost+error red), spinner during connecting, Connect/Disconnect/Cancel
button contextual to state.
main.dart now mounts a MockConnectionRepository(telemetry: MockTelemetryRepository()) and auto-connects on boot — the app opens
straight into “streaming” after ~500ms of mock handshake. When BLE
is real, swap one line at boot
(MockConnectionRepository → BleConnectionRepository) and nothing
else changes.
Three new widget tests cover render-idle, on-tap transition, and the
error path of the BLE stub (which throws UnimplementedError in
connect() until Phase 5+).
Dashboard sim — Settings + Datalog
The SDL2 dashboard simulator had 3 screens (home, race, diagnostic). Added 2: Settings (10-100% brightness slider, mock km/h↔mph and day↔night switches, ”● paired” Garage status) and Datalog (mock list of 5 sessions with bike / duration / size / sync indicator).
S key toggles settings, D toggles datalog. Each toggle hides all
other screens — explicit overlay mutex, no modal stack. TAB and R
still toggle diagnostic and race respectively.
MOCK_ENTRIES[] is a static array in datalog.c; when the real
cloud client lands (Phase 5+), it gets replaced by the
GET /telemetry payload.
Web /sobre — interactive tour
/sobre got a “How it works — Five pieces talking to each other”
section: 5 numbered cards (01-05) describing Forge (ECU) → Dash
(display) → Ride (app) → Garage (cloud) → Tuner (desktop), connected
by horizontal arrows that show each link’s protocol (CAN bus 500kbps,
BLE GATT, HTTPS, USB · HTTPS).
Hover animates translateX(4px) + red border, gradient on the
horizontal lines between cards, arrow pointing down at the end. Full
build in 75s, no Lighthouse regression.
ADR-012 — ConnectionRepository pattern
Documented the ConnectionRepository pattern (which extends
TelemetryRepository instead of living parallel to it) in ADR-012.
Justifies the trade-off against two alternatives I rejected:
transport states inside TelemetryRepository (couples things that
don’t belong together) and a global event bus (implicit singleton
that re-introduces the coupling ADR-010 had rejected).
STATE.md
Synced with the batch — 2026-05-06 batch (hardware-free, continuação) entry lists the 4 commits in the window and updates
coverage totals (200 ECU + 67 cloud integration + 22 Rust + 8 vitest
Tuner + 11 Ride).
What was NOT part of this session
- Real mobile BLE — depends on the Dash hardware existing.
- Telemetry binary upload in Garage — schema decided, handler still a stub.
- DTC details modal in the dashboard sim — went into the next queue.
But the loose ends from the previous day (token in LS, smoke without broadcast, ConnectionRepository with no UI consuming it, dashboard with 3 screens but no settings/datalog) all got tied up. A hardware-free day with real leverage — work that doesn’t show up in Lighthouse, but it’s the difference between “the app opens and works” and “the app opens and works and is easy to operate.”