The final proof runs on the real target: the packaged, renamed build installed on Void under niri with Dank Material Shell on RTX 2050-class hardware, worked through the acceptance checklist no container can cover. The tray renders and operates in the shell; the documented keybind toggles dictation; dictation types into Ghostty with the clipboard provably untouched; hold-to-talk works through the in-app backend; the portal probe result on niri is what the spec predicts; the D-Bus state signal tracks a live dictation; and models load at the expected ladder rungs on Vulkan and, where a local toolkit exists, CUDA. Every checklist item lands on this ticket as recorded evidence — what was run, what was seen — including honest failures, which are spun out as follow-up tickets rather than silently absorbed.
Acceptance criteria
Checklist evidence records the exact package version, host, compositor, shell, GPU, and driver under test.
Tray icon and menu work in the shell with no false missing-tray warning; the state signal is observed changing across a live dictation.
The documented keybind toggles dictation start and stop; clipboard contents before and after a Ghostty dictation are shown unchanged.
Hold-to-talk dictates successfully, and the documented double-trigger misconfiguration is either demonstrated or explicitly not reproduced with the configuration noted.
Portal probe outcome on niri is recorded; model rung selection is recorded on Vulkan and on CUDA where a toolkit is present, matching the calibrated thresholds.
Any failing checklist item becomes its own follow-up ticket linked from here; this ticket closes only when every item is passed or spun out.
## Parent
[Spec: Voculinux — Void Linux (glibc) Wayland/niri port of Vocalinux](https://git.bongbetic.com/xavierk/voculinux/issues/16)
## What to build
The final proof runs on the real target: the packaged, renamed build installed on Void under niri with Dank Material Shell on RTX 2050-class hardware, worked through the acceptance checklist no container can cover. The tray renders and operates in the shell; the documented keybind toggles dictation; dictation types into Ghostty with the clipboard provably untouched; hold-to-talk works through the in-app backend; the portal probe result on niri is what the spec predicts; the D-Bus state signal tracks a live dictation; and models load at the expected ladder rungs on Vulkan and, where a local toolkit exists, CUDA. Every checklist item lands on this ticket as recorded evidence — what was run, what was seen — including honest failures, which are spun out as follow-up tickets rather than silently absorbed.
## Acceptance criteria
- [ ] Checklist evidence records the exact package version, host, compositor, shell, GPU, and driver under test.
- [ ] Tray icon and menu work in the shell with no false missing-tray warning; the state signal is observed changing across a live dictation.
- [ ] The documented keybind toggles dictation start and stop; clipboard contents before and after a Ghostty dictation are shown unchanged.
- [ ] Hold-to-talk dictates successfully, and the documented double-trigger misconfiguration is either demonstrated or explicitly not reproduced with the configuration noted.
- [ ] Portal probe outcome on niri is recorded; model rung selection is recorded on Vulkan and on CUDA where a toolkit is present, matching the calibrated thresholds.
- [ ] Any failing checklist item becomes its own follow-up ticket linked from here; this ticket closes only when every item is passed or spun out.
## Blocked by
- [Rename user-visible identity to voculinux with one-time legacy import](https://git.bongbetic.com/xavierk/voculinux/issues/18)
- [Free-memory model ladder, conditional backend order, step-down retry](https://git.bongbetic.com/xavierk/voculinux/issues/20)
- [D-Bus dictation state signal](https://git.bongbetic.com/xavierk/voculinux/issues/21)
- [niri and Dank Material Shell integration pack](https://git.bongbetic.com/xavierk/voculinux/issues/22)
- [Void package smoke gate](https://git.bongbetic.com/xavierk/voculinux/issues/28)
Hardware acceptance #32 — partial evidence run (2026-11-06, NOT closing)
Hardware is present, so this is a genuine partial run, not a full gap:
all probes ran from integration/spec16 @ da97b67 (clean) with the
system python (gi available). Podman never invoked; host untouched
(no config writes, no installs).
Environment (checklist item 1 — recorded)
Package: voculinux 0.18.5 (src/vocalinux/version.py @ da97b67). Not installed: xbps-query -l | grep -i voc empty; only the old vocalinux pip shim remains on PATH.
GPU NVIDIA GeForce RTX 2050 4 GB, driver 595.112.07, CUDA 13.2 runtime; no nvcc (driver-only, no CUDA toolkit); Vulkan proprietary 595.112.7.0 sees GPU0 RTX 2050.
PASS (observed)
Tray watcher probe: busctl --user list shows org.kde.StatusNotifierWatcher owned by DMS (qs pid 2478).
Executed the verbatim _check_status_notifier_watcher body
(tray_indicator.py:696) headless against the live session bus → WATCHER-PROBE: True, i.e. no false missing-tray warning. Per NIRI.md
this closes its question: current probe stands, no widening needed.
(Rendered icon/menu still need the running app — gap below.)
D-Bus state signal mechanics: real VocalinuxDBusService
(dbus_service.py) on the live session bus owned com.voculinux.Voculinux, get_state() starts idle; set_state(listening|processing) → True, repeat → False (dedup),
bogus → False; subscriber observed exactly ['listening','processing']. DBUS-ROUNDTRIP: PASS.
(Tracking across a live dictation still needs the running app — gap.)
Model ladder rung: live MemAvailable 9994 MiB ≥ 1386 threshold → large-v3-turbo-q8_0 (EN and non-EN); next_rung_down chain intact. cuda_toolkit_ready() → False (nvcc absent, compute cap 8.6), preferred_backend_order() → ('vulkan','cpu') — exactly the
driver-without-toolkit rule from model_ladder.py. No live model load
(pywhispercpp not importable here) — gap.
MISMATCH (candidate follow-up ticket, not spun out — owner to triage)
Portal probe on niri: live RemoteDesktopPortal.probe() → True, version 2 (result cached on re-probe), but USER_GUIDE.md
predicts no backend implements RemoteDesktop on niri
("the portal is never selected"). This host runs xdg-desktop-portal-gnome + -gtk under niri, which answers the
version read. Either the doc needs a "GNOME portal installed" caveat
or the probe needs a session-creation check. Suggested follow-up:
verify first-dictation effective rung with the GNOME backend present.
GAP (needs installed packaged build + live session; not run)
voculinux xbps package not built/installed here (xbps-src build
out of scope for this run); resources/niri/voculinux.kdl ships in-tree
but the host ~/.config/niri/config.kdl carries no voculinux bind
(left untouched), so keybind toggle, Ghostty clipboard
before/after, hold-to-talk (in-app Right Alt) and the double-trigger
demonstration, tray render/menu operation, and live-dictation
D-Bus tracking are all unattempted. No failures observed, none spun out.
Unit gates (test_model_ladder.py, dbus/injection tests) not re-run
here (project venv absent); prior per-ticket CI greens stand.
Net
3/6 checklist mechanics verified live; env fully recorded; 1 doc-vs-live
mismatch flagged; live-dictation half awaits the installed build.
Ticket stays open per its own rule.
# Hardware acceptance #32 — partial evidence run (2026-11-06, NOT closing)
Hardware **is** present, so this is a genuine partial run, not a full gap:
all probes ran from `integration/spec16` @ `da97b67` (clean) with the
system python (gi available). Podman never invoked; host untouched
(no config writes, no installs).
## Environment (checklist item 1 — recorded)
- Package: voculinux `0.18.5` (`src/vocalinux/version.py` @ `da97b67`).
**Not installed**: `xbps-query -l | grep -i voc` empty; only the old
`vocalinux` pip shim remains on PATH.
- Host `the acceptance machine`, Void Linux, kernel `7.3.0_1`, 15 GB RAM.
- Compositor `niri 26.04` (wayland), shell DMS (`dms v2.1.0`, `qs` pid 2478),
Ghostty `1.3.1` running (pid 32122).
- GPU `NVIDIA GeForce RTX 2050` 4 GB, driver `595.112.07`, CUDA 13.2 runtime;
**no `nvcc`** (driver-only, no CUDA toolkit); Vulkan proprietary
`595.112.7.0` sees GPU0 RTX 2050.
## PASS (observed)
- **Tray watcher probe**: `busctl --user list` shows
`org.kde.StatusNotifierWatcher` owned by DMS (`qs` pid 2478).
Executed the verbatim `_check_status_notifier_watcher` body
(`tray_indicator.py:696`) headless against the live session bus →
`WATCHER-PROBE: True`, i.e. no false missing-tray warning. Per `NIRI.md`
this closes its question: current probe stands, no widening needed.
(Rendered icon/menu still need the running app — gap below.)
- **D-Bus state signal mechanics**: real `VocalinuxDBusService`
(`dbus_service.py`) on the live session bus owned
`com.voculinux.Voculinux`, `get_state()` starts `idle`;
`set_state(listening|processing)` → True, repeat → False (dedup),
bogus → False; subscriber observed exactly
`['listening','processing']`. `DBUS-ROUNDTRIP: PASS`.
(Tracking *across a live dictation* still needs the running app — gap.)
- **Model ladder rung**: live `MemAvailable` 9994 MiB ≥ 1386 threshold →
`large-v3-turbo-q8_0` (EN and non-EN); `next_rung_down` chain intact.
`cuda_toolkit_ready()` → False (nvcc absent, compute cap 8.6),
`preferred_backend_order()` → `('vulkan','cpu')` — exactly the
driver-without-toolkit rule from `model_ladder.py`. No live model load
(pywhispercpp not importable here) — gap.
## MISMATCH (candidate follow-up ticket, not spun out — owner to triage)
- **Portal probe on niri**: live `RemoteDesktopPortal.probe()` →
`True, version 2` (result cached on re-probe), but `USER_GUIDE.md`
predicts *no* backend implements RemoteDesktop on niri
("the portal is never selected"). This host runs
`xdg-desktop-portal-gnome` + `-gtk` under niri, which answers the
version read. Either the doc needs a "GNOME portal installed" caveat
or the probe needs a session-creation check. Suggested follow-up:
verify first-dictation effective rung with the GNOME backend present.
## GAP (needs installed packaged build + live session; not run)
- `voculinux` xbps package not built/installed here (xbps-src build
out of scope for this run); `resources/niri/voculinux.kdl` ships in-tree
but the host `~/.config/niri/config.kdl` carries no voculinux bind
(left untouched), so keybind toggle, Ghostty clipboard
before/after, hold-to-talk (in-app Right Alt) and the double-trigger
demonstration, tray render/menu operation, and live-dictation
D-Bus tracking are all unattempted. No failures observed, none spun out.
- Unit gates (`test_model_ladder.py`, dbus/injection tests) not re-run
here (project venv absent); prior per-ticket CI greens stand.
## Net
3/6 checklist mechanics verified live; env fully recorded; 1 doc-vs-live
mismatch flagged; live-dictation half awaits the installed build.
Ticket stays open per its own rule.
Package voculinux-0.18.5_1 installed (signed local repo). Deps: python3-pywhispercpp-1.5.0_1 (Vulkan, reports version 0.0.0), python3-PyAudio-0.2.14_1.
Void Linux glibc x86_64, kernel 7.3.0_1, niri 26.04, Dank Material Shell (qs, dms-shell-custom-1.6.2).
GPU: RTX 2050 4096 MiB, driver 595.112.07. iGPU AMD Radeon 660M. CUDA rows use the local venv-cuda build (CUDA 13.2.51, unsupported).
Checklist
#
item
verdict
evidence
1
Tray renders and menu works, no false missing-tray warning
PASS (automated part); visual NEEDS-HUMAN
Watcher org.kde.StatusNotifierWatcher owned by DMS (PID 2478). App item registered in RegisteredStatusNotifierItems. Installed probe returns True. No missing-tray warning in logs. See #22.
2
State signal tracks a live dictation
PASS
busctl --user monitor com.voculinux.Voculinux (hw/logs/state-monitor3.log) shows listening, processing, idle for every dictation (10 wake-loop runs plus earlier runs).
3
Documented keybind toggles start and stop, once per press
Config validation PASS; live press NEEDS-HUMAN
niri validate rc=0 on the example (see #22). Spawn path verified. Finding: the example key Mod+Shift+D is already bound to Move Column Right in ~/.config/niri/dms/binds.kdl:131.
4
Ghostty typing, clipboard hash unchanged
Direct typing (wtype) PASS; app-pipeline Ghostty dictation NEEDS-HUMAN
Own Ghostty window (title voc-hw-sink-*, PID verified as mine), focus checked before and after: wtype put "voc hw typing probe 0123" into its file. Clipboard sha256 (first 16) 792cf4dce8077acf before and after: identical. The app's own Ghostty dictation was not run automated because of the incident below.
5
Hold-to-talk via evdev
Readiness PASS; hold NEEDS-HUMAN
User is in input; /dev/input/event* are root:input mode 660. App log: Using evdev backend for global shortcuts (Wayland). Not started in automated runs (disable_internal_hotkey: true).
6
Double-trigger misconfiguration
NEEDS-HUMAN
Not reproduced in automation; needs a real key press with bind and in-app shortcut on the same key.
7
Portal probe on niri does not select the portal
PASS for the injector; finding for the tool
The injector skips the probe on niri (_is_niri_session()). inject-probe reports first_rung: wtype. But inject-probe (no niri guard) reports portal_available: true: the RemoteDesktop frontend answers version 2 with AvailableDeviceTypes = 0. The spec's "no backend implements RemoteDesktop" is true only in effect. No session was created, so no dialog.
8
Model rungs on Vulkan
PASS
Startup log: free 801 MiB gave startup rung small.en; free 3711 MiB gave startup rung large-v3-turbo-q8_0. Thresholds 1644 and 1358 MiB in model_ladder.py.
9
Model rungs on CUDA
PARTIAL
venv-cuda: preferred_backend_order('cuda') = (cuda, cpu); cuda_toolkit_ready() True without nvcc on PATH; child load log using CUDA0 backend. App-level CUDA startup was not run.
10
Failing items spun out
Done (below)
Caveat on rung evidence: about 8 minutes into the session the user's ollama runner held 2.9 GB of VRAM. It was gone by 22:35.
Incident (honest failure of my test harness, disclosed)
At 22:35:53, 22:36:10, 22:36:31 and 22:36:52, dictation ran with focus on the user's terminal (an unrelated terminal window of the user) instead of my sink. The app types into the window focused when the segment is submitted, and my harness had not re-verified focus before each toggle. Each injection typed the flite sample's transcript: "Vocalinox dictation test. The quick brown fox jumps over the lazy dog. Speech recognition runs on the graphics card." (117 characters). No Enter was sent, so nothing should have run. Please clear the unsent text in that terminal (for example Ctrl+U at the prompt). Injection was halted at 22:37, and all later dictation used the pad (dictate_to_pad).
An earlier run (22:30) typed 86 characters of ambient mic transcript into my own sink. That text is deleted.
Defects to spin out (no code changed)
F1, privacy, high.audio/pipewire.py runs pw-record --raw ... --target <sink node> without stream.capture.sink. In the app's own run and in isolation, the stream linked to the default mic (amd_dmic_highpass) and not the sink monitor. Workaround verified: PIPEWIRE_PROPS='{ stream.capture.sink = true }' in the environment links the monitor.
F2, medium. A configured PortAudio virtual device (pipewire, index 11) is refused as "not a safe input device", and the app falls back to the hardware mic ([8] HD-Audio ALC256 Analog). Observed once.
F3, medium.python3-pywhispercpp-1.5.0_1 reports 0.0.0 (egg-info), so the app disables GPU device selection with "upgrade to 1.5+".
F4, medium. The CPU backend has no AVX/FMA/F16C (no ymm/zmm in libggml-cpu.so). CPU RTF is about 1.3 (small.en) to 1.7 (q8_0) on an AVX2 host.
F5, low. The docs example key Mod+Shift+D collides with DMS Move Column Right. Niri does not report duplicates.
F6, low.inject-probe ignores the niri guard, so it reports portal_available: true on niri.
In your own Ghostty, clear the unsent test text (see the incident above).
Copy resources/niri/voculinux.kdl, change the key if Mod+Shift+D is taken, then add the include line. With External Activation on, press the key once and confirm one toggle.
Start voculinux and confirm the icon in the DMS bar opens its menu, with no "System tray icon may not appear" dialog.
Turn External Activation off. Focus a Ghostty window, note wl-paste -n | sha256sum, hold Right Alt, speak one sentence, release. Confirm the text lands and the hash is unchanged.
Put the same key in both the niri bind and the in-app shortcut, press once, and confirm the double toggle. Then revert.
With free VRAM under 1359 MiB (for example while another GPU user holds memory), start dictation and confirm startup rung small.en in the debug log.
## #32 hardware acceptance run: checklist evidence (2026-11-06, partial, honest)
### Host under test
- Package `voculinux-0.18.5_1` installed (signed local repo). Deps: `python3-pywhispercpp-1.5.0_1` (Vulkan, reports version 0.0.0), `python3-PyAudio-0.2.14_1`.
- Void Linux glibc x86_64, kernel 7.3.0_1, niri 26.04, Dank Material Shell (`qs`, dms-shell-custom-1.6.2).
- GPU: RTX 2050 4096 MiB, driver 595.112.07. iGPU AMD Radeon 660M. CUDA rows use the local venv-cuda build (CUDA 13.2.51, unsupported).
### Checklist
| # | item | verdict | evidence |
|---|---|---|---|
| 1 | Tray renders and menu works, no false missing-tray warning | **PASS** (automated part); visual **NEEDS-HUMAN** | Watcher `org.kde.StatusNotifierWatcher` owned by DMS (PID 2478). App item registered in `RegisteredStatusNotifierItems`. Installed probe returns True. No missing-tray warning in logs. See #22. |
| 2 | State signal tracks a live dictation | **PASS** | `busctl --user monitor com.voculinux.Voculinux` (`hw/logs/state-monitor3.log`) shows `listening`, `processing`, `idle` for every dictation (10 wake-loop runs plus earlier runs). |
| 3 | Documented keybind toggles start and stop, once per press | Config validation **PASS**; live press **NEEDS-HUMAN** | `niri validate` rc=0 on the example (see #22). Spawn path verified. **Finding:** the example key `Mod+Shift+D` is already bound to `Move Column Right` in `~/.config/niri/dms/binds.kdl:131`. |
| 4 | Ghostty typing, clipboard hash unchanged | Direct typing (`wtype`) **PASS**; app-pipeline Ghostty dictation **NEEDS-HUMAN** | Own Ghostty window (title `voc-hw-sink-*`, PID verified as mine), focus checked before and after: `wtype` put "voc hw typing probe 0123" into its file. Clipboard sha256 (first 16) `792cf4dce8077acf` before and after: identical. The app's own Ghostty dictation was not run automated because of the incident below. |
| 5 | Hold-to-talk via evdev | Readiness **PASS**; hold **NEEDS-HUMAN** | User is in `input`; `/dev/input/event*` are `root:input` mode 660. App log: `Using evdev backend for global shortcuts (Wayland)`. Not started in automated runs (`disable_internal_hotkey: true`). |
| 6 | Double-trigger misconfiguration | **NEEDS-HUMAN** | Not reproduced in automation; needs a real key press with bind and in-app shortcut on the same key. |
| 7 | Portal probe on niri does not select the portal | **PASS** for the injector; **finding** for the tool | The injector skips the probe on niri (`_is_niri_session()`). `inject-probe` reports `first_rung: wtype`. But `inject-probe` (no niri guard) reports `portal_available: true`: the RemoteDesktop frontend answers version 2 with `AvailableDeviceTypes = 0`. The spec's "no backend implements RemoteDesktop" is true only in effect. No session was created, so no dialog. |
| 8 | Model rungs on Vulkan | **PASS** | Startup log: free 801 MiB gave `startup rung small.en`; free 3711 MiB gave `startup rung large-v3-turbo-q8_0`. Thresholds 1644 and 1358 MiB in `model_ladder.py`. |
| 9 | Model rungs on CUDA | **PARTIAL** | venv-cuda: `preferred_backend_order('cuda')` = (cuda, cpu); `cuda_toolkit_ready()` True without nvcc on PATH; child load log `using CUDA0 backend`. App-level CUDA startup was not run. |
| 10 | Failing items spun out | Done (below) | |
Caveat on rung evidence: about 8 minutes into the session the user's ollama runner held 2.9 GB of VRAM. It was gone by 22:35.
### Incident (honest failure of my test harness, disclosed)
- At 22:35:53, 22:36:10, 22:36:31 and 22:36:52, dictation ran with focus on the user's terminal (an unrelated terminal window of the user) instead of my sink. The app types into the window focused when the segment is submitted, and my harness had not re-verified focus before each toggle. Each injection typed the flite sample's transcript: "Vocalinox dictation test. The quick brown fox jumps over the lazy dog. Speech recognition runs on the graphics card." (117 characters). No Enter was sent, so nothing should have run. **Please clear the unsent text in that terminal (for example Ctrl+U at the prompt).** Injection was halted at 22:37, and all later dictation used the pad (`dictate_to_pad`).
- An earlier run (22:30) typed 86 characters of ambient mic transcript into my own sink. That text is deleted.
### Defects to spin out (no code changed)
- **F1, privacy, high.** `audio/pipewire.py` runs `pw-record --raw ... --target <sink node>` without `stream.capture.sink`. In the app's own run and in isolation, the stream linked to the default mic (`amd_dmic_highpass`) and not the sink monitor. Workaround verified: `PIPEWIRE_PROPS='{ stream.capture.sink = true }'` in the environment links the monitor.
- **F2, medium.** A configured PortAudio virtual device (`pipewire`, index 11) is refused as "not a safe input device", and the app falls back to the hardware mic (`[8] HD-Audio ALC256 Analog`). Observed once.
- **F3, medium.** `python3-pywhispercpp-1.5.0_1` reports `0.0.0` (egg-info), so the app disables GPU device selection with "upgrade to 1.5+".
- **F4, medium.** The CPU backend has no AVX/FMA/F16C (no `ymm`/`zmm` in `libggml-cpu.so`). CPU RTF is about 1.3 (small.en) to 1.7 (q8_0) on an AVX2 host.
- **F5, low.** The docs example key `Mod+Shift+D` collides with DMS `Move Column Right`. Niri does not report duplicates.
- **F6, low.** `inject-probe` ignores the niri guard, so it reports `portal_available: true` on niri.
### Human-session checklist (one run, roughly 5 minutes)
1. In your own Ghostty, clear the unsent test text (see the incident above).
2. Copy `resources/niri/voculinux.kdl`, change the key if `Mod+Shift+D` is taken, then add the include line. With External Activation on, press the key once and confirm one toggle.
3. Start voculinux and confirm the icon in the DMS bar opens its menu, with no "System tray icon may not appear" dialog.
4. Turn External Activation off. Focus a Ghostty window, note `wl-paste -n | sha256sum`, hold Right Alt, speak one sentence, release. Confirm the text lands and the hash is unchanged.
5. Put the same key in both the niri bind and the in-app shortcut, press once, and confirm the double toggle. Then revert.
6. With free VRAM under 1359 MiB (for example while another GPU user holds memory), start dictation and confirm `startup rung small.en` in the debug log.
Evidence directory (absolute): `scratchpad/hw/` (`results/`, `logs/`).
Human session results (2026-11-07), installed build from main 0c48db5
Build: voculinux-0.18.5_1 + python3-pywhispercpp-1.5.0_1 rebuilt from main 0c48db5. It includes the fixes for #41–#46 and is signed with key fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25. The install smoke passed: pywhispercpp reports 1.5.0, and the CPU variant libggml-cpu-haswell.so is loaded.
Item
Result
Evidence
Tray icon in the DMS bar
PASS
The user confirmed the icon. RegisteredStatusNotifierItems = :1.4889/org/ayatana/NotificationItem/voculinux. The icon appears only after the model load, about 18 s after launch; that is tracked in #50.
niri keybind
PASS
The user confirmed the toggle works. The bind moved to Mod+Alt+V at the user's request: it is unbound in niri and DMS, and niri validate passes.
Hold-to-talk (evdev, Right Alt) into Ghostty
PASS
The user confirmed the dictated text lands.
Clipboard untouched
PASS
A marker was put on the clipboard (sha256 4c566a89…). After a hold-to-talk dictation, the clipboard still held the marker with the same hash.
Double trigger (same key in niri and in-app)
PASS (user-observed)
The user reported that the toggle works with both bound.
Vulkan on the RTX 2050 (rebuilt package)
PASS
ggml_vulkan: 0 = NVIDIA GeForce RTX 2050. small.en RTF 0.017 on the GPU and 0.083 CPU-only (haswell variant); the transcripts are identical.
CUDA rung
Not packaged by design
Void has no CUDA toolkit package. The local CUDA build measured RTF 0.023 against 0.021 for Vulkan (see #19).
The automated items were recorded in the earlier comment. Defects found are tracked in #41–#46 (all fixed) and #50 (open).
## Human session results (2026-11-07), installed build from main 0c48db5
Build: voculinux-0.18.5_1 + python3-pywhispercpp-1.5.0_1 rebuilt from main `0c48db5`. It includes the fixes for #41–#46 and is signed with key fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25. The install smoke passed: pywhispercpp reports 1.5.0, and the CPU variant `libggml-cpu-haswell.so` is loaded.
| Item | Result | Evidence |
|---|---|---|
| Tray icon in the DMS bar | PASS | The user confirmed the icon. `RegisteredStatusNotifierItems` = `:1.4889/org/ayatana/NotificationItem/voculinux`. The icon appears only after the model load, about 18 s after launch; that is tracked in #50. |
| niri keybind | PASS | The user confirmed the toggle works. The bind moved to `Mod+Alt+V` at the user's request: it is unbound in niri and DMS, and `niri validate` passes. |
| Hold-to-talk (evdev, Right Alt) into Ghostty | PASS | The user confirmed the dictated text lands. |
| Clipboard untouched | PASS | A marker was put on the clipboard (sha256 `4c566a89…`). After a hold-to-talk dictation, the clipboard still held the marker with the same hash. |
| Double trigger (same key in niri and in-app) | PASS (user-observed) | The user reported that the toggle works with both bound. |
| Vulkan on the RTX 2050 (rebuilt package) | PASS | `ggml_vulkan: 0 = NVIDIA GeForce RTX 2050`. small.en RTF 0.017 on the GPU and 0.083 CPU-only (haswell variant); the transcripts are identical. |
| CUDA rung | Not packaged by design | Void has no CUDA toolkit package. The local CUDA build measured RTF 0.023 against 0.021 for Vulkan (see #19). |
The automated items were recorded in the earlier comment. Defects found are tracked in #41–#46 (all fixed) and #50 (open).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Parent
Spec: Voculinux — Void Linux (glibc) Wayland/niri port of Vocalinux
What to build
The final proof runs on the real target: the packaged, renamed build installed on Void under niri with Dank Material Shell on RTX 2050-class hardware, worked through the acceptance checklist no container can cover. The tray renders and operates in the shell; the documented keybind toggles dictation; dictation types into Ghostty with the clipboard provably untouched; hold-to-talk works through the in-app backend; the portal probe result on niri is what the spec predicts; the D-Bus state signal tracks a live dictation; and models load at the expected ladder rungs on Vulkan and, where a local toolkit exists, CUDA. Every checklist item lands on this ticket as recorded evidence — what was run, what was seen — including honest failures, which are spun out as follow-up tickets rather than silently absorbed.
Acceptance criteria
Blocked by
Hardware acceptance #32 — partial evidence run (2026-11-06, NOT closing)
Hardware is present, so this is a genuine partial run, not a full gap:
all probes ran from
integration/spec16@da97b67(clean) with thesystem python (gi available). Podman never invoked; host untouched
(no config writes, no installs).
Environment (checklist item 1 — recorded)
0.18.5(src/vocalinux/version.py@da97b67).Not installed:
xbps-query -l | grep -i vocempty; only the oldvocalinuxpip shim remains on PATH.the acceptance machine, Void Linux, kernel7.3.0_1, 15 GB RAM.niri 26.04(wayland), shell DMS (dms v2.1.0,qspid 2478),Ghostty
1.3.1running (pid 32122).NVIDIA GeForce RTX 20504 GB, driver595.112.07, CUDA 13.2 runtime;no
nvcc(driver-only, no CUDA toolkit); Vulkan proprietary595.112.7.0sees GPU0 RTX 2050.PASS (observed)
busctl --user listshowsorg.kde.StatusNotifierWatcherowned by DMS (qspid 2478).Executed the verbatim
_check_status_notifier_watcherbody(
tray_indicator.py:696) headless against the live session bus →WATCHER-PROBE: True, i.e. no false missing-tray warning. PerNIRI.mdthis closes its question: current probe stands, no widening needed.
(Rendered icon/menu still need the running app — gap below.)
VocalinuxDBusService(
dbus_service.py) on the live session bus ownedcom.voculinux.Voculinux,get_state()startsidle;set_state(listening|processing)→ True, repeat → False (dedup),bogus → False; subscriber observed exactly
['listening','processing'].DBUS-ROUNDTRIP: PASS.(Tracking across a live dictation still needs the running app — gap.)
MemAvailable9994 MiB ≥ 1386 threshold →large-v3-turbo-q8_0(EN and non-EN);next_rung_downchain intact.cuda_toolkit_ready()→ False (nvcc absent, compute cap 8.6),preferred_backend_order()→('vulkan','cpu')— exactly thedriver-without-toolkit rule from
model_ladder.py. No live model load(pywhispercpp not importable here) — gap.
MISMATCH (candidate follow-up ticket, not spun out — owner to triage)
RemoteDesktopPortal.probe()→True, version 2(result cached on re-probe), butUSER_GUIDE.mdpredicts no backend implements RemoteDesktop on niri
("the portal is never selected"). This host runs
xdg-desktop-portal-gnome+-gtkunder niri, which answers theversion read. Either the doc needs a "GNOME portal installed" caveat
or the probe needs a session-creation check. Suggested follow-up:
verify first-dictation effective rung with the GNOME backend present.
GAP (needs installed packaged build + live session; not run)
voculinuxxbps package not built/installed here (xbps-src buildout of scope for this run);
resources/niri/voculinux.kdlships in-treebut the host
~/.config/niri/config.kdlcarries no voculinux bind(left untouched), so keybind toggle, Ghostty clipboard
before/after, hold-to-talk (in-app Right Alt) and the double-trigger
demonstration, tray render/menu operation, and live-dictation
D-Bus tracking are all unattempted. No failures observed, none spun out.
test_model_ladder.py, dbus/injection tests) not re-runhere (project venv absent); prior per-ticket CI greens stand.
Net
3/6 checklist mechanics verified live; env fully recorded; 1 doc-vs-live
mismatch flagged; live-dictation half awaits the installed build.
Ticket stays open per its own rule.
#32 hardware acceptance run: checklist evidence (2026-11-06, partial, honest)
Host under test
voculinux-0.18.5_1installed (signed local repo). Deps:python3-pywhispercpp-1.5.0_1(Vulkan, reports version 0.0.0),python3-PyAudio-0.2.14_1.qs, dms-shell-custom-1.6.2).Checklist
org.kde.StatusNotifierWatcherowned by DMS (PID 2478). App item registered inRegisteredStatusNotifierItems. Installed probe returns True. No missing-tray warning in logs. See #22.busctl --user monitor com.voculinux.Voculinux(hw/logs/state-monitor3.log) showslistening,processing,idlefor every dictation (10 wake-loop runs plus earlier runs).niri validaterc=0 on the example (see #22). Spawn path verified. Finding: the example keyMod+Shift+Dis already bound toMove Column Rightin~/.config/niri/dms/binds.kdl:131.wtype) PASS; app-pipeline Ghostty dictation NEEDS-HUMANvoc-hw-sink-*, PID verified as mine), focus checked before and after:wtypeput "voc hw typing probe 0123" into its file. Clipboard sha256 (first 16)792cf4dce8077acfbefore and after: identical. The app's own Ghostty dictation was not run automated because of the incident below.input;/dev/input/event*areroot:inputmode 660. App log:Using evdev backend for global shortcuts (Wayland). Not started in automated runs (disable_internal_hotkey: true)._is_niri_session()).inject-probereportsfirst_rung: wtype. Butinject-probe(no niri guard) reportsportal_available: true: the RemoteDesktop frontend answers version 2 withAvailableDeviceTypes = 0. The spec's "no backend implements RemoteDesktop" is true only in effect. No session was created, so no dialog.startup rung small.en; free 3711 MiB gavestartup rung large-v3-turbo-q8_0. Thresholds 1644 and 1358 MiB inmodel_ladder.py.preferred_backend_order('cuda')= (cuda, cpu);cuda_toolkit_ready()True without nvcc on PATH; child load logusing CUDA0 backend. App-level CUDA startup was not run.Caveat on rung evidence: about 8 minutes into the session the user's ollama runner held 2.9 GB of VRAM. It was gone by 22:35.
Incident (honest failure of my test harness, disclosed)
dictate_to_pad).Defects to spin out (no code changed)
audio/pipewire.pyrunspw-record --raw ... --target <sink node>withoutstream.capture.sink. In the app's own run and in isolation, the stream linked to the default mic (amd_dmic_highpass) and not the sink monitor. Workaround verified:PIPEWIRE_PROPS='{ stream.capture.sink = true }'in the environment links the monitor.pipewire, index 11) is refused as "not a safe input device", and the app falls back to the hardware mic ([8] HD-Audio ALC256 Analog). Observed once.python3-pywhispercpp-1.5.0_1reports0.0.0(egg-info), so the app disables GPU device selection with "upgrade to 1.5+".ymm/zmminlibggml-cpu.so). CPU RTF is about 1.3 (small.en) to 1.7 (q8_0) on an AVX2 host.Mod+Shift+Dcollides with DMSMove Column Right. Niri does not report duplicates.inject-probeignores the niri guard, so it reportsportal_available: trueon niri.Human-session checklist (one run, roughly 5 minutes)
resources/niri/voculinux.kdl, change the key ifMod+Shift+Dis taken, then add the include line. With External Activation on, press the key once and confirm one toggle.wl-paste -n | sha256sum, hold Right Alt, speak one sentence, release. Confirm the text lands and the hash is unchanged.startup rung small.enin the debug log.Evidence directory (absolute):
scratchpad/hw/(results/,logs/).Human session results (2026-11-07), installed build from main
0c48db5Build: voculinux-0.18.5_1 + python3-pywhispercpp-1.5.0_1 rebuilt from main
0c48db5. It includes the fixes for #41–#46 and is signed with key fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25. The install smoke passed: pywhispercpp reports 1.5.0, and the CPU variantlibggml-cpu-haswell.sois loaded.RegisteredStatusNotifierItems=:1.4889/org/ayatana/NotificationItem/voculinux. The icon appears only after the model load, about 18 s after launch; that is tracked in #50.Mod+Alt+Vat the user's request: it is unbound in niri and DMS, andniri validatepasses.4c566a89…). After a hold-to-talk dictation, the clipboard still held the marker with the same hash.ggml_vulkan: 0 = NVIDIA GeForce RTX 2050. small.en RTF 0.017 on the GPU and 0.083 CPU-only (haswell variant); the transcripts are identical.The automated items were recorded in the earlier comment. Defects found are tracked in #41–#46 (all fixed) and #50 (open).
Addendum: the user also confirmed that clicking the tray icon opens the menu in the Dank Material Shell bar.