# Marionette App — Overhaul Plan V2 > **Author**: Claude Code (analysis session, 2026-02-20) > **Purpose**: Critical assessment of the V1 improvement plan, a testability-first strategy, and a refined roadmap. This document is meant to be reviewed and annotated by the human before any coding begins. > **How to use this document**: Read each section. Where you see **[Q-XX]** markers, those are questions for you. Answer inline or in a separate section — then we can proceed to implementation with full clarity. --- ## Table of Contents 1. [Executive Summary](#1-executive-summary) 2. [Assessment of V1 Proposals](#2-assessment-of-v1-proposals) 3. [Testing Strategy](#3-testing-strategy) 4. [Revised Roadmap](#4-revised-roadmap) 5. [Questions for Remi](#5-questions-for-remi) 6. [Architectural Decisions](#6-architectural-decisions) --- ## 1. Executive Summary After deeply reading the entire codebase (~1850 lines of Python backend, ~1350 lines of vanilla JS frontend, the reachy_mini SDK, the ReachyMiniApp framework, the existing test patterns in minimate, and the SDK's own test suite), here is my high-level assessment: ### What the V1 plan gets right - **Problem identification is excellent.** Every bug and UX issue described is real and verifiable in the code. - **Priority ordering is sensible.** P0 bugs before P1 UX before P2 performance is correct. - **The guiding principle** ("stability and cross-platform compatibility over polish") is exactly right. ### What the V1 plan gets wrong or is missing - **Solutions are over-specified in some areas and under-specified in others.** For example, P0-1 describes the bug in detail but the "one source of truth" fix is hand-wavy. Meanwhile P1-1 goes into very specific UI copy ("Move Reachy Mini's head with your hands...") which should be a design decision, not a spec. - **No testing strategy.** The "Testing Strategy" section at the bottom is a wishlist, not a plan. There are no tests, no test infrastructure, and no plan for how to build them. - **No dependency ordering between items.** Some P1 items depend on P0 fixes (e.g., the countdown UX redesign in P1-1 should be done AFTER P0-1 is fixed, not independently). - **No architecture refactoring plan.** The current code is a 1850-line monolith with tight coupling between HTTP handlers, state machine, recording logic, and HF integration. Any significant feature work will be painful without first making the code more testable. - **Cross-browser testing is mentioned but not planned.** "Use Playwright" is not a plan — we need to know exactly what to mock, what to test, and how to set up the infrastructure. ### The biggest gap: Testability The marionette app currently has **zero tests**. The code is structured as a single monolithic class (`Marionette`) that inherits from `ReachyMiniApp` and does everything: HTTP routing, state management, robot control, file I/O, and HF integration. This makes isolated testing extremely difficult. Before we can iterate on features with confidence, we need: 1. A way to run the backend without a robot (the SDK supports this via `sim=True` + `no_media`) 2. A way to test the frontend without a backend (mock the `/api/state` responses) 3. A way to test the full stack in a browser (Playwright + simulated backend) 4. Backend unit tests for the state machine and recording logic **My recommendation: Start with a "Phase 0" that makes the codebase testable, then use tests as exit criteria for every subsequent phase.** --- ## 2. Assessment of V1 Proposals ### P0-1: Progress bar broken during recording — AGREE, but solution needs refinement **V1 diagnosis is correct.** The root cause is the dual timing system: the backend sends absolute Unix timestamps, the frontend uses `performance.now()` for animation, and the 1500ms poll gap creates a race condition during the countdown→recording transition. The `localRecordingActive` bridge (`main.js:787-797`) is a band-aid that doesn't fully work. **V1's "one source of truth" suggestion is the right direction but incomplete.** Here's what I'd actually do: The backend should send: ```json { "phase": "countdown" | "recording" | "idle" | ..., "phase_start_at": , "phase_end_at": } ``` The frontend should compute progress purely as: ```js const elapsed = (Date.now() / 1000) - state.phase_start_at; const total = state.phase_end_at - state.phase_start_at; const ratio = Math.min(1, Math.max(0, elapsed / total)); ``` No `timingState`, no `localRecordingActive`, no `queuedRecordingDuration`. Just one formula. **Key concern**: Clock skew between the robot (which generates the timestamps) and the browser. If the robot is a wireless CM4, its clock may be slightly off. We should use the **first poll response** to compute a clock offset: `offset = server_time - Date.now()/1000`, then apply it. Or better: the backend sends `server_time` in every `/api/state` response so the frontend can self-calibrate. **Testability**: This can be fully tested with Playwright by mocking `/api/state` responses with known timestamps and verifying the progress bar width converges to the expected value. No robot needed. ### P0-2: Audio upload duration validation — AGREE, simple fix **V1 is correct.** The fix is: 1. Change `step="0.5"` to `step="any"` in `index.html` 2. Backend already accepts any float `gt=0.5, le=300.0` — no backend change needed 3. Consider raising `le=300.0` to `le=600.0` for longer audio files **Testability**: Unit test the backend validation. Playwright test for the form submission with various durations (0.7, 5.2, 201.7). **Assessment: This is a 5-minute fix.** Do it first as a warm-up and to establish the testing pattern. ### P0-3: Recording continues past duration — NEEDS INVESTIGATION **V1 says this is "likely related to P0-1"** but I'm not convinced. Looking at `_capture_motion()` (`main.py:901-965`), the loop terminates when `elapsed >= duration` where `elapsed = time.perf_counter() - start`. This is a local monotonic clock comparison and should be reliable. **My hypothesis**: The symptom ("recording continues") is actually a **frontend display bug**, not a backend bug. The backend may have already finished recording, but the frontend's broken progress bar (P0-1) makes it look like recording is still happening. The "Stop Recording" button staying visible is because the next poll hasn't arrived yet. **The "saved move shows as 0 or 1 second" symptom** points to a different bug: `RecordedMove.duration` is computed as `len(trajectory) * dt` where `dt = (timestamps[-1] - timestamps[0]) / len(timestamps)`. If the recording was very short or had timing issues, this formula could produce wrong values. Specifically, if `timestamps[0]` is not 0.0 (which it shouldn't be — `_capture_motion` starts at 0.0), or if there are very few frames, the division could be off. **Recommendation**: Fix P0-1 first, then retest. If the symptom persists, add logging to `_capture_motion()` to capture the actual number of frames and timestamps, then reproduce with a Lite robot connected. **Testability**: The recording loop can be tested by running the full app with `sim=True` daemon and verifying that a 5-second recording produces ~500 frames with timestamps spanning ~5 seconds. ### P0-4: Movement replay amplitude smaller than recorded — PARTIALLY AGREE **V1's analysis is good** but the most likely cause is simpler than listed: the robot's internal PID controller cannot track fast movements, causing undershoot. This is a **hardware/firmware limitation**, not a software bug. **Checking the code**: The active motion model in the real `dataset_registry.json` is `lead_compensation`, not `no_model`. The `LeadCompensationModel` shifts frames forward by 2 to pre-empt controller lag. This is actually an attempt to fix P0-4! The question is whether it works well enough. **My assessment**: - This is NOT a software bug to fix. It's a fundamental characteristic of the robot's servo control. - The `lead_compensation` model is the right idea but may need tuning (the 2-frame lead may not be enough for fast movements). - We should **document this limitation** clearly in the app ("Tip: Slow movements replay more accurately than fast ones"). - We should NOT promise to "fix" this — it's misleading. **[Q-01]**: Have you verified that `lead_compensation` is intentionally active? The `dataset_registry.json` has `"motion_model": "lead_compensation"` and `"motion_models": true`. Is this your choice, or did it get set accidentally? -> It's on purpose, but has not be tuned a lot. I'm fine with a single value (let's keep it simple, even if fast motions are a bit less accurate, it's ok), not sure if this has been well tuned. **[Q-02]**: For the user who reported small amplitude playback — do you know if they were making fast or slow movements? And were their PID parameters at defaults? -> If there is no obvious bug found during the investigation, let's just assume he had tuned his PID values too low since this has only been reported once. ### P1-1: Redesign recording section — AGREE IN PRINCIPLE, but needs design first **The problems identified are real:** - "dataset" jargon confuses non-technical users - Too many options upfront overwhelms newcomers - Countdown/recording phases are visually indistinguishable **But the V1 plan is mixing problem description with solution design.** Specific UI copy, layout details, and "big countdown numbers" are design decisions that should be prototyped and tested, not spec'd in a text document. **My recommendation**: Do this as a dedicated design pass using the `frontend-design` skill. The input should be: "Simplify the recording form to show only Name + Duration + Record button by default, with advanced options collapsible. Make countdown phase dramatically visually different from recording phase." Let the design agent produce options, then pick one. **Dependency**: P0-1 must be fixed first (the timing system). Otherwise the new countdown display will inherit the same broken timing. ### P1-2 through P1-5: Small UX tweaks — AGREE, bundle together These are all small, low-risk changes: - Remove description field - Rename "Move label" → "Name" - Move audio source to settings - Hide spinner arrows **Bundle these into a single commit** after P1-1 is done. Each one takes under 5 minutes. ### P1-6: Settings panel — AGREE, but scope carefully The idea of a gear icon opening a settings panel is good. But the V1 plan lists too many things in settings: duration, audio source, startup voice, dataset root, HF login, experimental features. That's 6+ categories in one panel. **My recommendation**: Start with just 3 settings: 1. Audio source (mic / upload / none) 2. Dataset root folder 3. HF login status Duration stays in the main form (it changes per recording). Startup voice and experimental features can be added later. ### P1-7: Community datasets display — AGREE, low priority The current display is ugly but functional. This is cosmetic and can wait until after P0-P1 core work. ### P1-8: Rename moves — AGREE, but it's harder than it looks Renaming involves: renaming `.json` + `.wav` files, updating the registry's `uploaded_move_ids`, handling collision with existing names, and re-rendering the move list. It's a full feature, not a quick fix. **My recommendation**: Defer to P5. It's nice-to-have but not critical. ### P2-1/P2-2: Non-blocking startup — AGREE, important The blocking `time.sleep()` during startup is a real UX problem. The fix is straightforward: run the startup animation sequence in a background thread. **However**, this needs careful handling because the `run()` method is the main job loop. If we move the startup sequence to a background thread, we need to ensure the job loop doesn't start processing commands until the startup is complete. **My recommended approach**: 1. Add a `_starting_up` flag 2. Start `run()` immediately with the job loop 3. The startup animation runs in a background thread (or just inline at the top of the loop) 4. While `_starting_up` is True, the `/api/state` endpoint returns `mode: "starting_up"` and all mutating endpoints return 503 5. The frontend shows a nice loading state during `starting_up` **Testability**: Can be tested by starting the app and measuring time-to-first-successful-state-poll. ### P2-3: Multiple startup voices — DEFER This is a nice feature but requires recording new audio files and it's not critical for the overhaul. **Defer until after stability work is complete.** ### P3-1 through P3-3: HF integration — AGREE, important The current HF flow (manual CLI login + manual username typing) is a major UX problem for non-technical users. **Assessment of the proposed approach**: - `huggingface_hub.whoami()` for auto-detection: **Correct approach** - Device flow / OAuth for in-app login: **Look at what the reachy_mini dashboard does first** - Remove manual username field: **Yes, absolutely** **[Q-03]**: Does the reachy_mini dashboard already have an HF login flow implemented? If so, where is that code? (I found a reference to it in the improvement plan but didn't find it in the codebase.) **Testability**: The `whoami()` call can be unit tested with a mock. The login flow needs manual testing once. ### P4-1: Separate recordings from downloads — AGREE, critical UX issue Recording into a downloaded dataset is a real problem. The V1 plan suggests several approaches. **My recommendation**: The simplest approach that works: 1. Add an `origin` field to `DatasetEntry`: `"local"` or `"downloaded"` 2. When a user tries to record while a downloaded dataset is active, show a dialog: "This is a downloaded dataset. Create a new local dataset for your recordings?" with a one-click button to create + switch 3. Downloaded datasets show a download icon; local datasets show a folder icon This is minimal, clear, and doesn't require restructuring the data model. --> Isn't it simpler to just separate the current folder for record and replay? ### P4-2: Selective upload — ALREADY WORKS The V1 plan says "this works" and wants cosmetic improvements. **Defer cosmetic work. The feature is functional.** ### P4-3: Cross-platform paths — ALREADY WORKS The current defaults are reasonable. No changes needed. ### P5-1: YouTube audio — DEFER Requires `yt-dlp` dependency, legal considerations, and significant feature work. **Not part of the overhaul.** ### P6-1: Remove denoise — AGREE The denoise feature doesn't work well and adds complexity. Remove it from the UI and the backend code. Keep the file format compatibility (don't break existing `.denoised.wav` files). **[Q-04]**: The improvement plan mentions the microphone's automatic gain control (AGC) causing issues with loud motor noise. You described a manual fix (`PP_AGCONOFF --values 0`). Should we add a setting in Marionette to send this command to the robot? Or is this too dangerous/niche? --> I'm not sure can find a solid technical way of doing this? But if we can, this is not dangerous. If we can't, at least give the command somewhere so that advanced users can do it. ### P6-2: Motion models — KEEP HIDDEN The `lead_compensation` model is potentially useful for P0-4 but needs proper evaluation. **Keep it behind the experimental toggle. Ensure `no_model` is the default for new installations.** The current `dataset_registry.json` has `lead_compensation` active — this may confuse debugging of P0-4. --- ## 3. Testing Strategy This is the most important section of this document. The goal: **make it possible for Claude Code to autonomously test changes with minimal human intervention.** ### 3.1 Testing Tiers I propose 4 tiers of testing, from fastest (no hardware) to most thorough (full hardware): #### Tier 1: Backend Unit Tests (no hardware, no daemon, runs in <5 seconds) **What**: Test the Marionette class's pure logic in isolation — state machine transitions, data validation, file I/O, slug generation, registry persistence. **How**: Extract testable logic from the monolithic `Marionette` class into standalone functions or small classes. Test with pytest + FastAPI TestClient (following the `minimate` pattern). **What we need to refactor first**: The `Marionette.__init__()` currently: - Checks for daemon on localhost (network call) - Creates a FastAPI app (needs static files to exist) - Loads dataset registry (needs filesystem) To make this testable, we need a `create_app()` factory function (like minimate has) that accepts injected dependencies: ```python def create_marionette_app( registry_path: Path | None = None, dataset_root: Path | None = None, ) -> tuple[FastAPI, Marionette]: """Create a testable Marionette instance and its FastAPI app.""" ``` **Concrete tests for Tier 1** (estimated: 15-20 tests): - `test_slugify()` — various inputs including unicode, empty string, special chars - `test_recording_payload_validation()` — duration bounds, label length, audio ID - `test_state_machine_transitions()` — idle→queued→countdown→recording→idle - `test_state_machine_reject_busy()` — can't record while playing - `test_dataset_registry_persistence()` — save, reload, verify - `test_dataset_creation()` — name collision handling, slugification - `test_dataset_selection()` — switch active dataset, verify recordings refresh - `test_recording_save_format()` — verify JSON structure matches RecordedMove expectations - `test_upload_audio_wav()` — upload a WAV file, verify temp storage - `test_upload_audio_mp3()` — upload MP3, verify conversion to WAV - `test_move_deletion()` — delete files from disk, verify removal from list - `test_state_endpoint()` — verify /api/state response shape - `test_record_endpoint_accepts()` — verify /api/record returns 200 with valid payload - `test_record_endpoint_rejects_busy()` — verify 409 when already recording - `test_hf_status_not_logged_in()` — verify behavior without HF token - `test_dataset_root_change()` — verify path validation and migration **[Q-05]**: Are you comfortable with us refactoring `Marionette.__init__()` to support dependency injection? This is a prerequisite for unit testing. The change would be backwards-compatible — the default behavior stays the same — but it adds a `create_marionette_app()` factory function. --> Yes #### Tier 2: Backend Integration Tests (with simulated daemon, runs in <30 seconds) **What**: Test the full recording/playback flow with a simulated robot. The daemon runs in `sim=True, headless=True` mode (no physics, no hardware), and the Marionette class connects to it normally. **How**: Start a daemon in-process, create a `ReachyMini(media_backend="no_media")` instance, run the Marionette `run()` method in a thread, and exercise the API endpoints. **Pattern** (adapted from `test_daemon.py` and `test_app.py`): ```python @pytest.fixture async def marionette_with_sim(): daemon = Daemon() await daemon.start(sim=True, headless=True, wake_up_on_start=False) app, marionette = create_marionette_app(registry_path=tmp_path / "registry.json") # Start marionette.run() in a background thread # ... yield TestClient(app) marionette.stop() await daemon.stop(goto_sleep_on_stop=False) ``` **Concrete tests for Tier 2** (estimated: 8-10 tests): - `test_full_recording_flow()` — record 2 seconds, verify file saved with ~200 frames - `test_recording_duration_accuracy()` — record 5 seconds, verify timestamps span 4.9-5.1s - `test_recording_cancellation()` — start recording, cancel after 1s, verify partial save or clean abort - `test_playback_flow()` — record a move, play it back, verify mode transitions - `test_playback_cancellation()` — play a move, cancel mid-playback - `test_concurrent_request_rejection()` — start recording, try to play simultaneously, verify 409 - `test_startup_sequence()` — verify app transitions through starting_up → idle without blocking - `test_recording_with_uploaded_audio()` — upload a WAV, record with it, verify WAV copied **Key limitation**: With `media_backend="no_media"`, we can't test actual audio recording. The `start_recording()` and `get_audio_sample()` calls will either no-op or fail. We need to handle this gracefully in tests — either mock the media manager or skip audio-specific assertions. **[Q-06]**: When running with `media_backend="no_media"`, what happens when `_capture_motion()` calls `reachy_mini.media.start_recording()`? Does it silently no-op, or does it raise an error? This determines whether we need to modify the recording logic to handle no-media mode. -> That is for you to investigate/test. Assess if this tier 2 mode is useful enough or if it should be fused with tier 4. I'm fine with both. #### Tier 3: Frontend Browser Tests (Playwright, no robot, runs in <60 seconds) **What**: Test the entire frontend UI using Playwright with a mocked backend (intercepted HTTP responses). **How**: Use Playwright's `page.route()` to intercept all `/api/*` calls and return controlled responses. The frontend runs in a real browser (Chromium, Firefox, WebKit) against a static file server. **Concrete tests** (estimated: 12-15 tests): - `test_page_loads()` — verify title, main sections visible - `test_idle_state_display()` — mock idle state, verify "Ready" message, buttons enabled - `test_countdown_progress_bar()` — mock countdown state, verify bar decreases over 3 seconds - `test_recording_progress_bar()` — mock recording state, verify bar decreases over duration - `test_countdown_to_recording_transition()` — mock state sequence, verify smooth visual transition - `test_recording_stops_at_duration()` — verify bar reaches 0% and mode changes to idle - `test_form_validation_duration()` — enter invalid durations, verify form rejects/accepts - `test_audio_upload_duration_autofill()` — mock upload response, verify duration field set - `test_move_list_rendering()` — mock state with moves, verify list items - `test_dataset_selector()` — mock state with datasets, verify dropdown - `test_mode_pill_updates()` — verify pill shows correct state text - `test_cross_browser()` — run all above in Chromium + Firefox + WebKit **Key advantage**: These tests are **completely independent of hardware**. They test the frontend logic in isolation. They can run in CI on any machine. **Setup required**: - `npm init` in the marionette project (or a top-level test directory) - Install `@playwright/test` - A small test server to serve the static files (or use Playwright's built-in server) - Mock response fixtures (JSON files with realistic `/api/state` payloads) **[Q-07]**: Are you comfortable adding a `package.json` and Playwright as a dev dependency? It would only be used for testing, not shipped with the app. Alternatively, we could use `playwright` via pip (`pip install playwright`) to keep everything Python. --> Yes. #### Tier 4: Full Hardware Tests (with robot connected, manual + automated, runs in minutes) **What**: Test with a real Reachy Mini (Lite or Wireless) connected. **How**: This is the only tier that requires human involvement. The human connects the robot, starts the daemon, then runs the test suite. **Concrete tests** (estimated: 6-8 tests): - `test_recording_with_real_robot()` — record 3 seconds, verify frames captured with real FK data - `test_playback_with_real_robot()` — play back a recording, verify motors move - `test_audio_recording()` — record with microphone, verify WAV file has audio data (not silence) - `test_startup_audio()` — verify intro sound plays - `test_motor_enable_disable()` — verify compliant mode after recording **How to minimize human involvement**: - The test script starts, prints "Please connect a Reachy Mini and press Enter", waits for input - Once confirmed, it runs all Tier 4 tests automatically - Results are saved to a JSON report file - Human only needs to visually verify: "Did the robot actually move during playback?" (Y/N prompt) **[Q-08]**: For the Lite plugged into your laptop — does the daemon start automatically, or do you need to start it manually? If manually, should we include daemon start in the test script? -> For the lite the dameon start is manual, for the wireless it's automatic but the user still has to "turn on" the robot on the dashboard. Let's keep this manual, let's just prompt the user to "start the daemon on his/her physical robot and turn on on the dashboard if it's a wireless and press enter for the physical tests to run" ### 3.2 Cross-Platform Testing Strategy **The reality**: You have one Linux machine. Testing on Windows and macOS requires either: 1. Other physical machines (do you have access to any?) 2. VMs (slow, complex to set up with USB passthrough for robot) 3. CI with cross-platform runners (GitHub Actions has macOS and Windows, but no robot) **My recommended approach**: | Tier | Linux | macOS | Windows | |------|-------|-------|---------| | Tier 1 (unit) | Local + CI | CI | CI | | Tier 2 (integration) | Local + CI | CI (no robot, sim only) | CI (no robot, sim only) | | Tier 3 (Playwright) | Local + CI | CI | CI | | Tier 4 (hardware) | Local (Lite) | Manual when available | Manual when available | **For cross-platform CI**: Set up GitHub Actions with a matrix of `[ubuntu-latest, macos-latest, windows-latest]` running Tiers 1-3. This catches platform-specific bugs (path separators, file permissions, etc.) automatically. **[Q-09]**: Do you have a GitHub repository for this monorepo? Is CI an option? If not, we can still run Tiers 1-3 locally — it just means cross-platform testing is manual. No CI for this repo yet. Let's keep it fully manual, I'll ask coworkers to test this on Windows and Mac (make sure everything is cross platform with a nice documenation on how to run the tests!) **[Q-10]**: Do you have access to a Mac or Windows machine for occasional manual testing? If so, how often — weekly, monthly, or only at release time? cf previous answer. A bit of a heavy setup but OK for big changes like this oe ### 3.3 Cross-Browser Testing Playwright supports Chromium, Firefox, and WebKit (Safari engine) on all platforms. This means: - **Chrome testing**: Covered by Playwright's Chromium browser - **Firefox testing**: Covered by Playwright's Firefox browser - **Safari testing**: Covered by Playwright's WebKit browser (not perfect parity with real Safari, but catches most issues) - **Edge testing**: Edge is Chromium-based, so Chromium tests cover it **Key browser differences to test for** (based on code analysis): 1. `backdrop-filter` CSS — works in all modern browsers now 2. `performance.now()` resolution — may differ between browsers 3. HTML5 form validation behavior (the `step` attribute) — Firefox vs Chrome show different error messages 4. `Date.now()` vs server timestamps — clock precision varies 5. File drag-and-drop — different `dataTransfer` behavior between browsers **Recommendation**: Run Tier 3 Playwright tests in all 3 browsers by default. It's nearly free (Playwright downloads browser binaries automatically). --- ## 4. Revised Roadmap Based on the assessment above, here is the order of work I recommend: ### Phase 0: Make the codebase testable (1-2 sessions) **Goal**: Refactor just enough to enable testing. No new features, no bug fixes yet. 1. **Create `create_marionette_app()` factory function** — extract from `Marionette.__init__()`, accept injected paths 2. **Add a `conftest.py` for marionette** with fixtures: temp registry, temp dataset dir, TestClient 3. **Write 5-10 Tier 1 tests** — prove the testing pattern works 4. **Set up Playwright** — install, write 2-3 basic Tier 3 tests (page loads, idle state) 5. **Write 1-2 Tier 2 tests** — prove the sim daemon pattern works **Exit criteria**: `pytest marionette/tests/` passes with >10 tests. `npx playwright test` passes with >3 tests. ### Phase 1: Fix critical bugs (P0) (1-2 sessions) **Use Ralph Loop for each bug.** 1. **P0-2: Audio upload duration** — 5-minute fix + test - Exit criteria: Playwright test uploads audio with duration 5.2s, 12.5s, 201.7s — all succeed 2. **P0-1: Progress bar timing** — Implement the `phase_start_at`/`phase_end_at` approach - Exit criteria: Playwright test verifies progress bar ratio is within 5% of expected value at 1s intervals 3. **P0-3: Re-evaluate after P0-1** — May already be fixed - Exit criteria: Tier 2 integration test verifies recording produces correct frame count for given duration 4. **P0-4: Document amplitude limitation** — Not a code fix, just documentation + verify `no_model` is default - Exit criteria: Verified `no_model` is default; added tooltip/hint about amplitude ### Phase 2: UX Overhaul (P1) (2-3 sessions) **Use feature-dev skill for the design, then Ralph Loop for implementation.** 1. **Simplify recording form** — Name + Duration + Record button. Audio source in collapsible settings. 2. **Redesign countdown/recording visuals** — Big countdown numbers for prep phase, progress bar for recording 3. **Bundle small UX tweaks** — Remove description, rename label, hide spinners 4. **Settings panel** — Gear icon, collapsible panel with audio source + dataset root + HF status 5. **Remove denoise feature** — Clean removal from UI and backend ### Phase 3: Backend improvements (P2 + P3) (1-2 sessions) 1. **Non-blocking startup** — Background thread for startup animation, `starting_up` mode 2. **HF auto-login detection** — `whoami()` endpoint, remove manual username field 3. **HF login flow** — Copy from dashboard if available, or implement device flow ### Phase 4: Dataset management (P4) (1 session) 1. **Tag datasets as local/downloaded** — `origin` field in `DatasetEntry` 2. **Prevent recording into downloaded datasets** — Warning dialog + auto-create local ### Phase 5: Polish and future work (ongoing) - Community datasets display cleanup - Move renaming - YouTube audio extraction - Multiple startup voices --- ## 5. Questions for Remi All questions collected from throughout this document: ### Architecture & Testing **[Q-05]**: Are you comfortable with refactoring `Marionette.__init__()` to support a `create_marionette_app()` factory function for dependency injection? This is the foundation of our testing strategy. The app's runtime behavior stays the same — we just add an alternative construction path for tests. **[Q-06]**: When running with `media_backend="no_media"`, what happens when code calls `reachy_mini.media.start_recording()`? Does it silently no-op, or raise? This determines if we need to add a guard in the recording path for test mode. **[Q-07]**: For Playwright tests — do you prefer installing it via `pip install playwright` (keeps everything Python) or via `npm` (standard Playwright approach, more tooling support)? ### Infrastructure **[Q-08]**: For the Lite robot on your laptop — does the daemon start automatically when you plug it in, or do you start it manually? This determines how we structure Tier 4 hardware tests. **[Q-09]**: Is this monorepo on GitHub? Can we set up CI (GitHub Actions) for automated cross-platform testing? **[Q-10]**: Do you have access to Mac or Windows machines for occasional manual testing? ### Feature-specific **[Q-01]**: Is `lead_compensation` intentionally active in your `dataset_registry.json`? Should `no_model` be the default for new installations? **[Q-02]**: For the amplitude bug (P0-4) — do you know if the reporter was making fast or slow movements? Were PID parameters at defaults? **[Q-03]**: Does the reachy_mini dashboard already have an HF login flow? If so, where is that code? **[Q-04]**: The mic AGC setting (`PP_AGCONOFF --values 0`) — should we add a UI toggle for this in Marionette, or just document the manual procedure? ### Scope & Priorities **[Q-11]**: Do you agree with the "Phase 0: testability first" approach? I know it means a delay before visible bug fixes, but it means every subsequent fix can be verified automatically. --> Yes **[Q-12]**: The V1 plan mentions "fun/joke startup voice" and "full explanation voice" — do you have these audio files already, or would they need to be recorded? (If they need recording, this is blocked on voice talent and should be deferred.) --> I'll get them soon, just make it easy to add new ones. We have 2 "moments", the first wav, then head movement, then optional second wav. I want to be able to change both of them and make both of them optional. **[Q-13]**: What's your timeline for this overhaul? Is this a sprint (complete in 1-2 weeks) or a marathon (steady progress over months)? --> You will work tiredlessly with as many agents as possible to get this done. **[Q-14]**: How do you want to handle the coding sessions? Options: - **A)** One session per phase — I plan everything, you review, then a fresh Claude Code session implements it - **B)** Continuous — I do analysis + coding in one long session with frequent check-ins - **C)** Analysis-only sessions + separate implementation sessions — you review the plan, then start a new session with "implement Phase 0 from OVERHAUL_PLAN_V2.md" --> B, unless we think a fresh session is needed. In any case, all changes/thougts should be recorded in files so that a new agent can take over if needed. I recommend **C** because it lets you review and adjust between phases, and each implementation session can be focused. --- ## 6. Architectural Decisions These are decisions I've already made based on the codebase analysis. They're documented here for transparency — object if you disagree. ### AD-1: Keep vanilla JS frontend (no framework) The V1 plan and the current codebase use vanilla JS. I'm not proposing to switch to React/Vue/etc. Reasons: - The app is small enough that vanilla JS works - Adding a framework would require a build step, which complicates deployment - The minimate app uses React/Vite, but that's a more complex UI (chess board, analysis) - For Marionette's form-based UI, vanilla JS is appropriate **However**, I may introduce a small utility for DOM updates to replace the "rebuild innerHTML every 1500ms" pattern. Something like a simple `patch(element, newHTML)` that diffs and updates in place. This prevents the interaction state destruction bug (P0-related). ### AD-2: Use `performance.now()` for all client-side timing Currently the code mixes `Date.now()` (wall clock, affected by NTP adjustments) and `performance.now()` (monotonic). I'll standardize on `performance.now()` for all animation and timing, using a single clock offset computed from the first server response. ### AD-3: Keep the 1500ms polling interval The V1 plan doesn't suggest WebSocket, and I agree. Polling at 1500ms is sufficient for this app's needs. The progress bar animation runs locally at 60fps regardless of poll interval — it just recalibrates when a new poll response arrives. **One improvement**: During active operations (countdown, recording, playing), temporarily increase poll frequency to 500ms. This reduces the maximum desync between frontend and backend. --> Can you explain what this polling does? 1500ms seems super long for robotics stuff no? ### AD-4: Backend state machine as the single source of truth The frontend should be a pure renderer of backend state. No `localRecordingActive`, no `queuedRecordingDuration`, no client-side state transitions. If the frontend needs to know the recording started, it reads `phase_start_at` from the backend. Period. ### AD-5: Test infrastructure lives in `marionette/tests/` ``` marionette/ marionette/ main.py motion_models.py static/ assets/ tests/ conftest.py test_api.py # Tier 1 unit tests test_integration.py # Tier 2 integration tests e2e/ test_ui.spec.ts # Tier 3 Playwright tests (or .py if using Python Playwright) fixtures/ idle_state.json recording_state.json ... pyproject.toml # add [tool.pytest.ini_options] and dev dependencies ``` ### AD-6: No new dependencies for the app itself Testing tools (pytest, playwright) are dev-only dependencies. The production app should not gain new runtime dependencies. This means: - No `yt-dlp` (P5-1 deferred) - No frontend framework - No additional Python packages --- *End of document. Please review and answer the [Q-XX] questions. Once we align on answers, we can start Phase 0.*