Skip to content

UX redesign: motion modes, volume consistency, mode naming, chattiness rate-limit #53

Description

@dderuntz

Why this exists

Real user feedback in docs/FEEDBACK.md surfaced four overlapping issues with how the duck behaves and how its modes/controls are described. They're not independent — fixing them piecemeal would land contradictory UX. This issue tracks a comprehensive redesign, with the option to ship it in phases.

Permissions remain the core feature and must never be silenced, regardless of any new chattiness or motion controls.


1. Motion / motor control — port Bambu's taper model

The complaint: The motor moves multiple times a minute. It's loud in a home office. Users want a way to keep the duck visually present but quieter.

The Bambu duck already solved this. See bambu/firmware/main/servo.c next_hop_window() — three modes, all driven by time since last user interaction:

Mode Behavior
ALWAYS Alert cadence during waking hours, dormant during quiet hours
TAPER (default) Alert (0–2 min): 4–15s hops. Settling (2–10 min): 30–60s hops. Drowsy (10 min–1 hr): 25–60 min between hops. Dormant (1 hr+): one hop / ~12 hr
INTERACTION_ONLY Alert for 2 min after any interaction, dormant otherwise

Proposal for the Mac/Claude Code duck: port this same model (or a near-mirror) to the firmware shared between products, exposed as three named user-facing modes:

  • Active (= ALWAYS)
  • Chill (= TAPER) — default
  • Zen (= INTERACTION_ONLY or off entirely if motor is unwelcome)

Bambu's "quiet hours" concept is also worth bringing across (nighttime suppression).


2. Volume consistency

The complaint: "It'll quack or something pretty loudly and then proceed to speak to me in an indoor voice."

The reaction TTS respects the user volume setting; chirps / quacks / hardware tones may not. Need an audit:

  • Every audio output (TTS, melody, chirps, hardware piezo, serial-audio path) MUST honor the same user-set volume
  • Identify each path:
    • SpeechService TTS (say or serial-streamed Opus)
    • MelodyEngine (Jeopardy thinking melody)
    • Any chirp / squelch / notification beep
    • The hardware speaker's max-volume firmware path
  • Add a single shared outputVolume(for: AudioKind) resolver, or normalize at the synth level

This is closer to a bug than a design decision — the user already has volume control; some sound emitters just don't read it.


3. Mode names + descriptions

The complaint: "Opinions, permissions, voice" — opinions and voice are duck outputs, permissions is the user's vocal input. Two different actors smuggled into one list. Plus "Permissions Only (No-Mic)" is contradictory wording (permissions implies voice, no-mic implies no voice).

User-proposed rewording (from FEEDBACK.md, lightly adjusted):

Mode Description
Companion Ducky speaks opinions & alerts you to permission requests. You approve via voice.
Permissions Only Ducky alerts you to permission requests. You approve via voice.
Companion (No-Mic) Ducky speaks opinions & alerts you to permission requests. You approve with clicks.
Silent (?) Ducky watches but doesn't speak. (Need to decide if this exists.)

Each description states (1) what the duck does, (2) how the user responds. Separates actor from action explicitly.

Also unclear: whether "No-Mic" means the duck's mic is off (so wake-word + voice approval are off) or something else. Mic state is conflated with mode. Likely needs its own toggle (see #4).


4. Mic on/off as a separate setting

The complaint: User wanted to silence the mic specifically (privacy, hot mic concerns) but didn't want to lose companion features. They gravitated to "No Mic" as the closest mode.

Proposal: make mic on/off a global setting, independent of mode. Like a system-wide mute, only for the mic:

  • Default ON for new users (so wake-word + voice approval work)
  • When OFF, modes that previously implied "uses mic" silently fall back to "click to approve"
  • Surface as a single visible toggle (status-bar icon? checkmark item?)

Combined with #3, modes become purely about what the duck does, not about how the user responds. The mic toggle controls the response channel separately.


5. Chattiness rate-limit — "comments per hour"

New idea (Danny): users should be able to tune the duck's verbosity. A "max comments per hour" knob, or a discrete slider (Quiet / Normal / Chatty), would let users dial back without going all-or-nothing.

Implementation sketch:

  • Token-bucket rate limiter at the eval-reaction layer (NOT the permission layer)
  • When over budget, the duck stays silent on a given eval — score still records, animation can still play
  • Permission prompts always pass through — they're the core feature and a missed one is a bug, not a feature
  • "Bypass for important reactions"? Maybe — e.g. very-negative ambition / very-high risk reactions always speak. Open question.

This composes with #1 (motion mode) cleanly: a user can have Chill motion + Quiet voice for a calm office, or Active motion + Chatty voice for solo coding. Two independent axes.


Suggested phasing

If this issue is too big to land in one PR, suggested order:

  • Phase 1: rename modes + fix descriptions (Device expressions #3, low risk, immediate UX win)
  • Phase 2: volume audit (Phase 2 hw #2, mechanical, no design debate)
  • Phase 3: mic toggle as independent setting (Hot plug #4, depends on Phase 1)
  • Phase 4: motion modes ported from Bambu (Phase 1 #1, real firmware/widget work)
  • Phase 5: chattiness rate-limit (Plugin #5, the meaty one — needs design loop)

Out of scope (for this issue)

  • The duck's personality / wording of reactions (separate concern)
  • Removing the duck UI/menu — only changes within the existing menu surfaces
  • Mobile companion app (no such app exists yet)
  • Cloud-only / network-dependent settings

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions