doxa-maps/plugin-designs/rudder-voice
Doxa Maps e028682dd1 The draft's last line is unreachable when a notice makes the header tall
FIX-RUDDER-PANEL-DRAFT-LAST-LINE-UNREACHABLE-WITH-TALL-HEADER.

.compact .compose carried min-height:120px, so the column refused to shrink and
whatever the header took was pushed past the panel's clip. The draft's own
scrollbar cannot help: the overflow is OUTSIDE the scrollable area, which is why
a wheel over it reaches the end of its content while the last line stays hidden.

Measured in the corner widget with the engine offline, header 237px:
  390x560 — panel leaves 99px, the floor forces 120, overflow 21px.
  375x450 — panel leaves 33px, overflow 87px.
Both reproduced independently, and both are exactly 120 minus the room available,
which is what makes the floor the mechanism rather than a coincidence.

Pre-existing: present on plain development as well as on my earlier branch, so
this is its own defect and not something a previous round introduced.

The floor is removed unconditionally. A viewport-height guard would not work —
what consumes the room is the HEADER, and a notice can make it 237px at any
viewport; an earlier version of this fix guarded above 520px and would have left
the 560px case still broken.

Cut clean onto development rather than carried on the branch it was written on:
that branch also held two hunks that were deliberately withdrawn after an A/B
showed they changed nothing measurable, and landing it would have quietly
reinstated them.
2026-09-29 09:41:48 +00:00
..
index.html Add Rudder (rudder-voice): offline streaming voice-to-text, a desktop-target plugin 2026-09-27 08:23:17 +00:00
index.js Add Rudder (rudder-voice): offline streaming voice-to-text, a desktop-target plugin 2026-09-27 08:23:17 +00:00
README.md Rudder: Robot voice — speak each sentence in a synthetic voice, offline, for videos without your real voice 2026-09-28 14:30:56 +00:00
rudder-voice.meta.json bun run desktop:build <plugin> packages a desktop plugin as an app; check:targets keeps non-web plugins out of the web build 2026-09-28 06:44:14 +00:00
rudder-voice.vue The draft's last line is unreachable when a notice makes the header tall 2026-09-29 09:41:48 +00:00

rudder-voice — Rudder, private voice-to-text (desktop target)

Rudder is a plugin that isn't a map. It shows that the same dx-maps pattern — one folder in plugin-designs/, developed live in the Plugin Studio — can ship a desktop app with its own local backend, not only embeddable maps. It is also a genuinely useful tool for the people who build here: talk to your agents instead of typing, with nothing leaving your computer.

  • Streaming. Words appear in the text box while you speak; talk for as long as you like.

  • Type and talk at once. Type, paste links or code anywhere in the box; everything you say is added to the end. While you're typing, Rudder holds its words and adds them when you pause.

  • CPU only. No graphics card. About 1 GB of memory; keeps up with normal speech on a laptop CPU.

  • Offline, no telemetry. NVIDIA Nemotron 3.5 ASR Streaming (0.6B) run by NVIDIA's open-source NeMo-Speech.cpp (ggml). Traced with strace while transcribing: zero sockets, zero DNS lookups.

  • Microphones like a video-call app. Pick the input, hot-plug updates, a 3-second mic test, and plain-language errors (muted, disconnected, dead Bluetooth mic link — which it tries to recover).

  • Robot voice (optional). Settings → Robot voice: every sentence you say is spoken back in a synthetic voice, so you can record a video that uses dictation without your real voice in it. Offline neural voices (Piper, run by the engine on the CPU) or the browser's own on-device voices; nothing leaves the computer. While the robot talks through loudspeakers, the engine stops listening, so it never transcribes itself; with headphones it keeps listening.

Run it in the Plugin Studio

bash backend/rudder-engine/setup-runtime.sh   # once: runtime + model (verified by SHA-256) + engine build
bun run dev                                   # starts the engine too; open "rudder-voice" in the studio

Linux with PipeWire or PulseAudio (parec, pactl). The engine listens on 127.0.0.1 only.

How it fits together

<rudder-voice> (this folder)  ──HTTP + SSE /api/rudder──►  backend/rudder-engine (Go)
                                                            ├─ parec / pw-record  (mic, open only while recording)
                                                            └─ WebSocket ► nemo-speech serve (127.0.0.1, model stays loaded)

bun run dev starts backend/bin/rudder-engine through internal/backend-dev-plugin.mjs and forwards /api/rudder to it, so the plugin calls the same API in the browser as it will inside the desktop app, where Tauri runs the engine as a sidecar and passes its address in profile-config (apiBase).

rudder-voice.meta.json declares "targets": ["desktop"]: on a plain website there is no local engine, so this plugin is not meant for web embeds.

Settings (environment variables)

Variable Default What it does
RUDDER_PORT 5193 engine HTTP port (loopback)
RUDDER_NEMO_PORT 5194 speech server port (loopback)
RUDDER_MODEL / RUDDER_NEMO_BIN ~/.local/share/rudder/... model and runtime paths; the engine refuses a model that isn't a local file
RUDDER_LANG en-US default language (the model supports ~30)
RUDDER_KEEP_MSBC unset keep Bluetooth headsets on mSBC instead of switching them to the more stable CVSD
RUDDER_BT_RESET unset optional command that fully resets a Bluetooth headset, used when its mic link dies

Not yet

  • Desktop build (desktop:build) with the engine as a Tauri sidecar.
  • A saved word-correction table (PocketBase) for technical terms the model mishears.