Files
voculinux/tests
b71164a214 feat(tray): add recent dictation snippets history menu 🤖🤖🤖 (#487)
* feat(tray): add recent dictation snippets history menu

Add a "Recent Snippets" submenu to the tray indicator that lists recent
dictation sessions. Clicking a snippet copies its full text to the
clipboard for easy re-pasting; a "Clear History" entry empties the list.

Snippets are recorded on recognition, independent of whether text
injection succeeds, so text can be recovered even when injection silently
no-ops (e.g. on some Wayland compositors). One snippet corresponds to one
dictation session (everything said between start and stop); voice commands
are not recorded since they take a separate callback path.

History is kept in memory only (newest-first, capped, never written to
disk) and clears on quit — a deliberate privacy choice for a tool that
sees everything dictated.

Adds a "history" config section (enabled, max_items) and matching
Settings UI under General. Includes unit tests for the new
TranscriptionHistory store.

* fix(history): sanitize saved max_items to keep startup robust

A hand-edited config.json can store a non-numeric or out-of-range
history.max_items. Coerce the value on load (and in TranscriptionHistory
itself) with a positive-int fallback so a malformed preference warns and
falls back to the default instead of aborting startup or crashing the
Settings dialog spin button.

* fix(history): fold late-arriving segments into their originating session

stop_recognition() only waits on the recognition worker for a bounded
time before reporting IDLE, so a slow final segment can still fire its
text callback afterwards — landing in the next session's pending
snippet, or starting a phantom entry of its own.

Track the open session (state transitions, its worker thread, and its
pending segments) under a lock and file each segment by origin: while a
session is open only its own worker's segments accumulate into it; a
segment arriving after finalization extends that session's committed
snippet via the new TranscriptionHistory.extend_latest(), and a segment
from a session that produced nothing forms its own entry instead of
polluting the next one. ERROR now also finalizes the session, so a
partial result is still recorded and the tracker never wedges open.

Covered by new tests driving the callback and state wiring directly:
mid-session LISTENING/PROCESSING transitions, back-to-back sessions,
late callbacks from a lingering worker thread, disabled history, and
invalid saved limits.

* style(ui): type the new history handlers in tray and settings

* fix(history): keep cleared snippets from reappearing via late segments

The recognition worker can still emit a final segment after the manager's
bounded stop wait reported IDLE. Once the user cleared the history, that
late text could not extend the now-empty history and was appended as a
new entry — resurrecting snippets the user explicitly removed.

Give TranscriptionHistory a clear epoch: clear() (and disabling, which
clears) bumps it even when the store is already empty. add() and
extend_latest() accept an expected_epoch and refuse stale epochs
atomically under the store lock, so the check cannot race the clear.

main.py tracks the epoch each session opened under and the epoch of the
most recently ended session. Session commits and every late-segment
write are guarded by them, so text produced before a clear — whether a
session's whole snippet, its straggler segments, or leftovers committed
on the next session's start — cannot re-enter history afterwards.

* test(history): cover clear-epoch rejection and type new signatures

Add regression tests for the cleared-history race: a late segment after
clear() must not reappear, a session ending after a mid-session clear
leaves no snippet, and add/extend_latest refuse a stale expected_epoch
while unguarded writes keep working.

Annotate the new signatures per the repo guide: _boot's extra_settings
and return tuple, every test in the new TestSessionHistoryRecording and
history-menu tray classes, and all methods of the history store tests.

* fix(main): keep post-clear dictation by rebinding the session epoch

* fix(history): drop the boundary segment when a clear lands mid-session

* fix: scope dictation history to post-clear audio via capture-time stamps

The engine now tags each queued audio segment with the monotonic time
capture began and reports it to a new segment_callbacks hook. History
recording drops segments whose capture started before the last clear,
so pre-clear audio that decodes late can no longer resurrect cleared
entries or leak into a later session, while text dictated after a
mid-session clear is kept instead of being discarded by a stale epoch
check.

* fix(settings): keep Test Dictation speech out of transcription history

The mic test swaps text callbacks for its preview, but history recording
runs on segment callbacks, so test speech still landed in Recent
Snippets. The test now saves and clears the segment callback list
alongside the text list — restoring both on success and on the
start-failure path. register/unregister_segment_callback also gain the
missing return annotations and the engine gets symmetric
get/set_segment_callbacks accessors.

* fix(settings): floor test-dictation speech instead of swapping callbacks

Instead of clearing the segment callback list during the mic test —
which would have dropped leftovers from a still-finishing dictation —
the engine exposes test_capture_floor: segments captured at or after
the stamp are test speech and stay out of Recent Snippets, while
earlier-session leftovers continue to file normally.

* refactor(history): narrow change-callback exception surface

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-09-30 00:06:51 -07:00
..