Release v3.1.0#332
Conversation
The native logger backend rejects with prefix-coded strings (device unreachable:/device hung:/protocol error:/unsupported:/Wi-Fi join was declined/no logger connected) but every download flow rendered the raw string verbatim. Add a pure classifier (lib/loggers/errors.ts) that maps prefixes — plus web NotAllowedError/SecurityError — to typed categories with per-stage recovery actions, and a shared ErrorPanel that shows a translated headline with the matching Retry/Rescan/Reconnect button, tucking the raw message behind a collapsed "technical details" line. Android BLE permission denials (reported under device unreachable:) get their own "Bluetooth permission needed" guidance; a declined Wi-Fi join re-drives the OS picker; Alfano's desktop `unsupported:` rejection renders as an informational not-available panel instead of an error. Download-stage failures retry the same file while the link is alive and only claim the file was saved when the auto-save actually succeeded. Also migrates the last hard-coded English out of the web Fledgling flow and the shared download panels into the logger namespace, with hand-seeded translations for all six languages (no ANTHROPIC_API_KEY here for the seeder — same stop-gap as the profiles/datalogger strings). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHEmsdQeeMQ4pjF8BKDV8P
The native shell had no firmware path at all — OTA only worked on the web build over Web Bluetooth. Add the native flow end-to-end while reusing the entire transport-agnostic dfu/ layer (manifest fetch, variant pick, version evaluate, CRC-verified download): - lib/ble/dfu/firmwareImage.ts: extract the download+CRC-verify step (acquireFirmwareImage) out of useFirmwareUpdate so web and native share it; the web hook now calls it with no behavior change. - doveslogger/ipc.ts: loggerUpdateFirmware(image, onProgress) invoking logger_update_firmware with a Channel for upload progress. - doveslogger/firmwareInfo.ts: pure extractor deriving version/model/ variant from the connect handshake's LoggerDeviceInfo.fields (candidate key probing — exact names still aligning with the LapWing side) plus the optional cap.fw_update capability flag. - useNativeFirmwareUpdate + NativeFirmwarePanel: a Firmware button on the connected file-list screen drives check -> confirm (with an explicit variant pick when the device didn't report one) -> download -> upload -> "device is restarting" -> back to the scan screen. Preview builds bypass the version compare like the web flow. Availability is runtime-detected, not flag-gated: an unsupported:/unknown- command rejection renders a "not available in this app version" panel and latches for the session, so the UI degrades gracefully until the backend command ships. A link drop after the full image was received is treated as the documented flash-and-reboot success, not an error. Settings/tracks/ battery stay web-only (supportsDeviceDetails is untouched). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHEmsdQeeMQ4pjF8BKDV8P
Record the design in docs/plans/0008 (including the open items to align with the LapWing backend: firmware command byte transport, device-info field keys, unknown-command wording, MyChron SSID prefix, Alfano format), write the DovesLogger-native and Alfano sections docs/android.md was missing, point docs/ble.md at the shared dfu layer's native path, refresh the CLAUDE.md architecture map, and start the [3.1.0] unreleased changelog heading with the error-UX and native-firmware entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHEmsdQeeMQ4pjF8BKDV8P
…y-u2t8bm plan 0008: logger error UX + native Fledgling firmware update
Cloud-synced logs can now be shared behind an opaque /s/{token} URL that
never reveals the filename: the anon-readable shared_sessions table has no
filename column at all (the owner-side token<->filename mapping rides the
log's private sync_records index row as data.share), and the raw blob is
copied into a public shared-sessions bucket at {userId}/{token}. Anyone with
the link opens the full session in the plan-0005 read-only viewer — the
frozen course geometry travels with the share, so laps and sectors time
precisely even on personal custom courses (which also auto-queue to the
community submissions pipeline, never blocking the link).
- profiles.share_sessions_default: new uploads auto-publish when on
(future uploads only); per-session share modal overrides both ways, with
an optedOut marker so an unshared file never resurrects
- share/unshare UI: Link2 icon on synced file-browser rows (new FileRow
mount) and in the Cloud Logs panel; default toggle in the Account panel
- viewer: cloud-gated /s/:token route self-loads through Index (reload-safe,
works cold for anonymous visitors); Exit returns by source
- driver profile page gains a public "Shared sessions" section
- quota: shared copies count toward the pooled byte budget (new
enforce_share_quota trigger; usage/trim functions redefined) and
trim_expired_logs/deleteCloudFile/account deletion all retire shares
- fixed: uploadBlob clobbered the index row's data on re-upload, which
would have wiped share state (now read-merges; regression-tested)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKZefCzbyk4ZZPzckLbJvk
…xvjbo plan 0009: shareable public session links for cloud-synced logs
…hide Vehicle tab Post-merge feedback on the shared-session viewer: - opening a share now auto-selects the fastest lap (was whole-session view) - the Overlays menu is enabled in read-only (shares and leaderboards) so viewers can compare read-only laps against their own local sessions - weather panels (map + pro-mode InfoBox) work in read-only again; the per-session weather cache and station-metadata write-back are skipped so the synthetic "shared"/"leaderboard" names never key a cache entry - the pro-mode InfoBox Vehicle tab is hidden in read-only (missed in the original pass — no vehicle/setup saving on read-only views) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QKZefCzbyk4ZZPzckLbJvk
|
Updates to Preview Branch (BETA) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
lapwing | 7606c2f | Commit Preview URL Branch Preview URL |
Jul 18 2026, 02:24 AM |
Coverage SummaryLines: 57.51% (6570/11424) · Statements: 56.61% · Functions: 55.23% · Branches: 53.51% Per-file coverage
|
…xvjbo plan 0009: read-only viewer polish — fastest lap, overlays, weather, hide Vehicle tab
Sim plan Phase 4 vendoring: the real DovesDataLogger firmware compiled
to WebAssembly, committed here so the viewer only ever runs a
deliberately-pinned build (no CDN, no CORS, same-origin fetch).
- public/sim/birdseye-sim.mjs stable public wrapper (API contract v1)
- public/sim/birdseye-sim-core.mjs Emscripten factory (internal)
- public/sim/birdseye-sim-core.wasm the firmware, 207 KB
- public/sim/version.json provenance: firmware sha ef62d0f,
DovesLapTimer sha, display-lib versions
- public/sim/test.html standalone harness (boot + dovex replay)
- public/_headers /sim/* always revalidates (not hashed)
Built from DovesDataLogger BETA (PR #96) with emsdk 3.1.61; node smoke
test green; renders byte-identically to the firmware repo's native
golden fixtures. The /simulator page consuming this lands in follow-up
work (sim plan Phases 5-7).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft Vendor BirdsEye firmware simulator wasm build (public/sim/)
…ase A) The real DovesDataLogger firmware (vendored wasm, public/sim/) booted in the browser, replaying the bundled OKC sample session — the same file the firmware repo's CI oracle proves reproduces the hardware's 13 lap times to the millisecond. - lib/sim/simClient: typed loader for /sim/birdseye-sim.mjs (API contract v1; async reset() = true fresh boot via re-instantiation). - lib/sim/simPlayback (PURE, Vitest-covered): sample→injectPvt mapping on canonical channel ids, tick planning (every row injected at its own timestamp, stepMillis between — one injection per step batch, per the contract), no-fix boot pre-roll, scrub planning (backward = reset + headless fast-replay). - hooks/useSimPlayback: rAF loop, play/pause/1-10x/seek, frame-hash- gated canvas blits, ~10 Hz state publishing; paused keeps the firmware clock breathing so menus stay interactive. - components/sim/SimDevicePanel: 128x64 canvas at integer scales (4/6/8x, pixelated) or TRUE SIZE (55x27.5 mm via CSS mm, default ON for first-time visitors with a why-so-small tooltip); bezel; three buttons (pointer + arrow/Enter keys) through the real firmware debounce. - pages/Simulator (public, lazy, offline-first): demo session via ensureSampleFile's IDB cache + the standard datalogParser (no second dovex parser), transport bar, live firmware-state chips, provenance footer from getVersion(). - i18n: new 'simulator' namespace across all 7 languages; SW precache glob gains mjs so the sim works offline. Verified end-to-end in Chromium against the production build: boot page renders on canvas, Select navigates the real menu, playback runs, scrub-to-end fast-replays the full 15-min session to exactly 13 laps, rewind (reset + replay) works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft plan 0010: /simulator — the firmware running in the browser (sim panel + playback engine)
Map above the device panel, one clock for everything: - components/sim/SimMap.tsx: lean Leaflet map — session path polyline, the detected course's start/finish line (loadTracks + autoDetectCourse, the same offline track DB the whole app uses), dark/satellite tile toggle, and the shared positionArrowMarker cursor (created once, moved per tick). - lib/sim/simPlayback: positionIndexAt (pure, binary search, tested) — the map cursor index is computed from the SAME playback cursor the firmware consumes, so the marker, the lap counter on the simulated display, and the scrubber cannot disagree at any speed (the spec's Phase-6 done-criterion, by construction). - Session picker: bundled sample by name + 'Upload .dovex' — user logs are parsed in-memory with the standard datalogParser (no second parser, nothing saved to the file manager) and swap the running sim to a fresh boot, paused. - useSimPlayback: a session swap now starts paused at the fresh boot. - i18n: picker/map keys across all 7 languages. Verified end-to-end in Chromium against the production build: map draws path + start/finish, no cursor during pre-roll, scrub-to-end lands on 13 laps with the marker present, and the marker moves with live playback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
- SimMap: z-0 + isolate on the map wrapper. Leaflet's internal panes carry z-indexes in the hundreds; without a contained stacking context they floated over the sticky SiteHeader (z-30) once the page scrolled. - Remove 'Upload .dovex' from the simulator (owner call: not taking on the support surface of arbitrary logs here). The page shows the bundled sample session only; the adoptSession seam keeps a future add-back small. Upload i18n keys dropped from all 7 locales; CHANGELOG + plan 0010 updated to match. Verified in Chromium: with the page scrolled, elementFromPoint inside the header band resolves to the header (not the map), and no Upload button renders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft plan 0010: simulator map + synced playback cursor + session picker (Phase B)
…fix) - Re-vendor public/sim/ from DovesDataLogger 97b2248 (PR #97): the firmware-side fix for the dead center button in race mode (the SIM invert-display carve-out is gone — Select now does the hardware's speed-aware pace/best page jump) plus the new getBootFrames() API. - Boot intro: useSimPlayback queues the boot keyframes captured by init() (splash -> boot page, virtual timestamps) and plays them through the canvas sink at their real pace before live stepping starts. The frame sink now carries raw 1024-byte framebuffers so boot frames and live frames share one blit path. - data-sim-page attribute on the state chips (invisible) so e2e drives can assert the firmware's current page. Verified in Chromium against the production build: the boot animation visibly plays on load, and pressing Select mid-session switches the firmware page (tach 6 -> best-lap 9; pace 8 when moving fast) instead of doing nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft plan 0010: real boot sequence on load + center-button fix (re-vendored sim)
Landing: the 'Build your own logger' tile now opens a dialog instead of deep-linking straight to GitHub — the Fledgling photo (same asset as the download picker), a short pitch, the GitHub build-guide link, and a prominent 'See how it works!' CTA that launches /simulator. Simulator: a collapsible guide at the top of the page (open by default) replaces guesswork — booting (skip with any button, or auto-advance ~3 s after GPS lock), outer-buttons/center-button controls, replaying a session (auto race mode -> track search -> course detection), ending it (auto or via END RACE), reviewing the temporarily-saved session via the Review menu, a reference table for all nine race-mode pages, and the Bluetooth/USB not-simulated note. All strings in the landing/simulator namespaces across the 7 languages. Verified in Chromium: modal opens with image + links, CTA navigates to /simulator, guide sections and the page reference render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft plan 0010: "Build your own logger" dialog + simulator how-to guide
…ssary Update UX: a new build detected while the user sits idle on the home page now applies itself — "Updating…" progress toast, automatic reboot onto the new version, confirmation toast on the fresh load — instead of the silent eventually-flashing refresh. The persistent "Update ready" toast remains for every other case (session loaded, any non-home route), so active work is never yanked. Policy is the pure, tested decideUpdateAction(); a sessionStorage per-commit guard prevents reload loops when an apply fails to stick. Index.tsx publishes session-loaded state via the new appActivity host flag. Simulator guide: added a "Menu & system pages" glossary (boot splash, GPS status, main menu, race, review, transfer, camera, SD format, warnings, charging) across all 7 locales, with a note on which pages need real hardware. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft Auto-update on idle home page + simulator system-pages glossary (plan 0010)
…nsport A short description card under the playback controls walking through the firmware's actual behavior: boot on button press or RPM, nearest-track lookup with all courses loaded into memory, automatic race-mode entry on RPM, and the waypoint-drop course-detection pass (distance match + multiple line crossings) that selects the course and registers an accurate first lap. All 7 locales. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft Simulator "what the device is doing" explainer below the transport (plan 0010)
Set package.json version 3.0.0 -> 3.1.0 to match the topmost CHANGELOG heading and dated [3.1.0] to 2026-07-17. Coach plugin was already on the production npm package (@perchwerks/eye-in-the-sky 0.5.0) on BETA, so no flip was needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VTBXGG2rNGSSqeozPyfjxy
Fixes the four confirmed, non-blocking findings from the BETA->main release-gate review: - PERF-01/COR-01: coalesce simulator scrub seeks (useSimPlayback) via a pendingSeekRef so a backward drag no longer re-boots the wasm + replays once per slider tick and no longer strands the thumb at a stale target; only the released position is rendered. - TEST-01: add publicShare.test.ts (12 tests) for the new anon shared-session read module — both profile-embed shapes, null/missing profile, null dates, not-found and error paths. - DOC-01: add README Features bullets for the /simulator page and shareable session links. Also adds the review report under docs/reviews/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VTBXGG2rNGSSqeozPyfjxy
Beta → Main Release Review — 2026-07-17🚦 Verdict: GO-WITH-FIXES → all confirmed findings fixed in #343 (now effectively GO)No Critical and no release-blocking High. Four confirmed findings (2 Medium, 2 Low), all non-blocking; every one has been addressed on the release-prep branch. Blockers: 0. PR: #332 ( Release contract checklist
Summary
Two headline features drive this release — a public firmware simulator ( Findings (sorted: Critical → Low, then by dimension)[Medium] PERF-01 — Backward scrub re-boots the wasm sim and synchronously replays the whole session from start
[Medium] TEST-01 — New anon-read module
|
Rebuilt the sim from the firmware repo's v3.0.0 release tag (ecc88c3) with emsdk 3.1.61 — same recipe as the firmware sim-build CI wasm job — and vendored the dist into public/sim/. Node smoke test (boot, state, determinism, reset) passed on the artifact before vendoring; verified in Chromium that /simulator boots it and reports the new provenance. DovesLapTimer moves to its current BETA revision (a6add21). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ADME.md The artifact list, exact build commands (emsdk 3.1.61, emcmake configure/build, node smoke test), the configure-time version.json stamping gotcha, the five-file vendor step, and the CI-artifact alternative — previously only encoded in the firmware repo's sim-build workflow. Not precached (md is outside the PWA glob). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BCdGxqExj9ojEQgZj4f6G
…ork-ssd2ft Vendor firmware v3.0.0 wasm simulator build (plan 0010)
…-lr243e Release prep v3.1.0
v3.1.0 — 2026-07-17
Added
/simulator). The real DovesDataLogger firmware — the vendored WebAssembly build — running in the browser on a simulated 128×64 OLED, replaying the bundled Orlando Kart Center sample session: watch it boot, acquire GPS, auto-enter race mode, detect the track and count laps, exactly as the physical device does (the same wasm core reproduces this session's 13 hardware lap times to the millisecond). Three on-screen buttons (plus ← Enter →) drive the real firmware menus, even mid-playback. Includes play/pause, 1–10× speed, a scrubbable timeline (rewinds re-boot the firmware and fast-replay — it's a real device, not a video), an integer-scale zoom picker, and a true-size toggle showing the display at its physical 1.3″ size. Fully offline-capable. (plan 0010, Phase A)public/sim/, groundwork). The real DovesDataLogger firmware compiled to WebAssembly (207 KB), pinned by construction:birdseye-sim.mjs(API contract v1 — seeBirdsEye/sim/API.mdin the firmware repo), the Emscripten core module + wasm,version.jsonbuild provenance (firmware sha, DovesLapTimer sha, display-lib versions), and a standalonesim/test.htmlharness that boots the firmware on a canvas and replays real.dovexfiles. Renders byte-identically to the firmware repo's golden fixtures and reproduces a hardware session's 13 lap times to the exact millisecond. The/simulatorpage consuming it lands in a follow-up (sim plan Phases 5-7)./s/…) that never reveals the file name — anyone with the link (no account needed) opens the full session in the read-only viewer, with precise lap and sector timing even on your personal custom courses (the course geometry travels with the share; custom courses are also auto-submitted to the community track database for review). Share or unshare any session from the link icon in the file browser or the Cloud Logs panel; a new profile setting makes future uploads public by default (private remains the default), and your public sessions appear on your driver profile page. Shared copies count toward cloud storage. Opening a shared session selects its fastest lap automatically.Changed
Full changelog: v3.0.0...v3.1.0