* feat(aur): add vocalinux-bin PKGBUILD shipping the AppImage
Closes#817
* fix(aur): hard-dep injection tools, pin real digests, gate the -bin package
* fix(aur): pin both-arch -bin digests and build the binpkg as builder
updpkgsums in the publish action only regenerates the x86_64 sum array, leaving aarch64 stale after every tag; release.yml now pins both AppImage digests itself and publishes with updpkgsums off. The AUR gate also created $WORKDIR/binpkg as root after handing WORKDIR to builder, so makepkg could not write there. AUR.md now matches the published -bin package.
* fix(ci): wait for aarch64 AppImage before publishing vocalinux-bin
publish-aur only needed build-and-release, so on a tag the digest-pinning
step could curl the aarch64 AppImage before build-appimage-arm64 attached
it — a 404 that failed the job between the source and -bin publishes.
Split the -bin bump/pin/publish steps into publish-aur-bin, which needs
[publish-aur, build-appimage-arm64]: the source package still ships as
early as before while the binary package only runs once both AppImages
exist on the release.
* fix(ci): ship -bin local sources and wait for the aarch64 asset on AUR publish
Split the -bin bump/pin/publish into publish-aur-bin needing
[publish-aur, build-appimage-arm64] so the digest-pinning step can no
longer curl a 404 for the not-yet-attached aarch64 AppImage mid-release,
and publish with asset_dir so the PKGBUILD's local sources
(vocalinux.desktop, vocalinux.svg, LICENSE) reach the AUR repo instead
of a lone PKGBUILD users cannot build.
* style(tests): apply black formatting to aur packaging test
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* chore(release): add snap-promote workflow for gated stable releases
Issue #783 option 2 (safer): v* tags keep auto-publishing the snap to
edge + candidate in release.yml, and latest/stable is promoted by a
manual snap-promote.yml dispatch after candidate QA.
The dispatch resolves the revision on latest/candidate and refuses to
promote unless its version matches the tag, so a typo or stale candidate
fails the run instead of shipping the wrong revision. Declaring the
snap-stable environment with required reviewers adds an approval gate
on top of the dispatch.
Fixes#783
* fix(ci): serialize snap promotions and re-verify candidate
awk used the gawk-only \S which mawk reads literally, so the latest-track candidate row never matched. The promote step also trusted a revision resolved minutes earlier, letting an overlapping promotion downgrade stable. Serializes dispatches and re-resolves the candidate right before release.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(flatpak): publish a signed self-hosted OSTree remote on releases (#785)
release.yml gains publish-flatpak-remote: it imports the per-arch
.flatpak bundles into an OSTree repo, signs commits and the summary
with a provisioned GPG key, regenerates static deltas and the
vocahq.flatpakrepo/index page, and pushes to VocaHQ/vocalinux-flatpak
served by GitHub Pages — the Homebrew-tap analogue, so installs
auto-update via flatpak update instead of per-release downloads.
The job is secret-gated like publish-aur (FLATPAK_GPG_PRIVATE_KEY +
FLATPAK_REPO_TOKEN) and skips itself while unconfigured. Prior repo
objects are pulled back in from gh-pages so clients get incremental
deltas. The manifest now declares branch: stable and the flatpakrepo
declares DefaultBranch=stable + RuntimeRepo for the Flathub GNOME
runtime.
Docs: new docs/FLATPAK_REMOTE.md (one-time setup, rotation,
verification); README, docs/INSTALL.md, packaging/flatpak/README.md
and the release notes now lead with the remote and keep release
bundles as the no-auto-update fallback. tests/test_release_integrity.py
pins the job's shape.
Closes#785
* fix(flatpak-remote): publish a .flatpakref and gate the remote to stable tags
- needs build-and-release so the remote cannot lead the GitHub Release
- stable tags only: prereleases must not reach `flatpak update`
- probe gh-pages before the empty-repo fallback: a transient checkout
failure no longer discards remote history
- publish com.vocalinux.Vocalinux.flatpakref (RuntimeRepo lives there, not
on .flatpakrepo) plus vocahq.gpg, so clean installs resolve the GNOME
runtime and key rotation works via remote-modify --gpg-import
- gate the release-notes remote instructions on the publish actually
running; the bundle path stays primary otherwise
- document rotation via remote-modify --gpg-import / remote-delete
--force and the explicit Flathub setup step
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(release): attach the snap to GitHub Releases
v0.17.0 packed the snap, then Store review of uinput failed the job, so
the file never reached the tag. Build and attach first. A Store hold is
a warning, not a failed release. SHA256SUMS covers the .snap.
snap-backfill.yml rebuilds an existing tag, appends the snap to the
manifest, and attests only that file.
* fix(release): keep snap tags out of the shell and fail real Store errors
Pass the tag through the environment like verify-release.yml. Treat only
an allow-installation review as a successful Store publish. snap-backfill
runs verify_release.py after it rewrites SHA256SUMS.
* style(test): format snap-backfill shell-injection guard with black
The builder action restore-keys is `flatpak-builder-${arch}`. Our key
was `flatpak-builder-${hash}`, which the action stores as
`flatpak-builder-${hash}-x86_64`. That does not start with
`flatpak-builder-x86_64`, so a packaging hash change (a lock refresh,
or merging one) discarded the whole .flatpak-builder cache and
recompiled numpy and pywhispercpp from source, ~18 min instead of ~4.
Put ${{ matrix.arch }} in the key first so the fallback matches. Then
flatpak-builder skips unchanged modules and only rebuilds what the
hash change actually touched.
PRs no longer watch src/**. python-test already covers those, and the
expensive compile does not. push to main still does, so a sandbox
install break fails the default branch.
* feat(release): attach Flatpak bundles to v* GitHub Releases
On each version tag, build x86_64 and aarch64 .flatpak assets with the
same builder pins as flatpak.yml, upload them as workflow artifacts, and
attach Vocalinux-${VERSION}-{arch}.flatpak next to the AppImages.
SHA256SUMS now checksums *.flatpak as well.
The Flathub builder image has no GitHub CLI, so attach is a follow-up
job on ubuntu-latest using gh release upload --clobber. Snap jobs are
unchanged.
Fixes#784
* fix(ci): pin privileged Flatpak builder to an immutable digest
The gnome-50 tag is mutable; both release Flatpak jobs and flatpak.yml
now pull the image by OCI index digest. The integrity test requires
@sha256 rather than the tag alone.
* feat(snap): add Snap packaging recipe and store publish strategy
Add a strict-confinement snapcraft.yaml aligned with the current app
version and Flatpak sandbox lessons (mic, desktop, network models,
xdotool/xsel injection). Document human-only Snap Store steps and mark
install docs as not published yet.
Refs #48
* fix(snap): adopt-info so craftctl can set version from version.py
snapcraft pack failed: craftctl set version requires adopt-info on the part.
* fix(snap): stop staging GTK/GLib over the gnome platform
Prepending snap-staged GI typelibs and lib paths caused a segfault on
from gi.repository import Gtk. Use platform AppIndicator/GTK and only
stage PortAudio plus xdotool/clipboard helpers.
* docs(snap): note platform GTK path to avoid GI segfault
* fix(snap): route ALSA through Pulse so Settings does not abort
PortAudio enumerates ALSA devices when Settings opens. Missing
/usr/share/alsa/alsa.conf inside the snap triggered a PortAudio assert.
Ship an asound.conf that uses the pulse plugin, stage libasound2-plugins,
and bind ALSA paths via layout.
* fix(snap): keep ALSA conf under snap/local for snapcraft
Move asound.conf out of snap/alsa so snapcraft stops warning about
non-snapcraft paths in the snap directory.
* fix(snap): request raw-input for global keyboard shortcuts
Strict confinement blocks /proc/bus/input and /dev/input, so Wayland
evdev hotkeys never start. Declare the raw-input plug (manual connect)
and print snap-specific connect instructions instead of host input-group
hints.
* fix(hotkeys): discover keyboards via evdev when /proc is blocked
Snap raw-input grants /dev/input/event* but still denies
/proc/bus/input/devices, so find_keyboard_devices() returned empty
and hotkeys never started. Fall back to evdev.list_devices() when
/proc cannot be read.
* fix(snap): restore UI sounds and prefer wtype for Wayland inject
Stage paplay (pulseaudio-utils) and wtype; teach audio_feedback about
pw-play/canberra. xdotool-only injection often hits the wrong window on
native Wayland even when it reports success.
* fix(snap): sync 0.16.2/AGPL metadata and AppImage-safe audio env
Align snapcraft fallback version and store metadata with current main
(VocaHQ URLs, AGPL-3.0), keep docs pointing at #48 on VocaHQ, pass
host_env to pw-play/canberra paths, and document ordered Snap Store
human deploy steps.
* test(snap): harden audio host_env unit test against suite pollution
Patch os.path.exists like sibling audio_feedback tests instead of relying
on a real tempfile path that can collide with other suite mocks.
* style(snap): black-format new audio and snap packaging tests
* docs(snap): treat Store listing as live; edge→candidate→stable refresh
Name vocalinux is already on snapcraft.io (edge @ 0.14.0-beta rev 6).
Document upload/release without re-register, add snap-store-refresh.sh
helper, and point INSTALL/README/DISTRO at the live listing.
* fix(snap): move to core24 and close evdev capability FDs
core22 only ships Python 3.10, which fails requires-python >=3.11, so the
snap base is now core24 (Python 3.12) with the gnome-46 platform and the
Ubuntu 24.04 libasound2t64 package. Also close InputDevice after failed
capability scans and annotate the proc parser with TextIO.
* Snap: align store listing metadata and icon
Match snapcraft.yaml summary, description, license, website, and store
icon to the live Snap Store listing.
* Snap: trim packaging docs, script, and unused SVG
Drop store refresh wrapper, long packaging README, duplicate snap/gui SVG, and speculative pw-play/canberra paths; keep recipe and confinement fixes.
* ci: publish snap to edge and candidate on release tags
* Snap: set grade stable for candidate channel CI
* Snap: skip pip PyGObject; use gnome extension GI
* chore(release): build once, and publish sha256sums and provenance (epic #701, phase 5)
* chore(ci): pin every third-party action by commit sha (epic #701, phase 5)
* test(ci): anchor the dependabot guard on the ecosystem it must cover
* build(appimage): pin the interpreter, packages and tooling
* ci(appimage): build inside a pinned ubuntu 22.04 image
* test(appimage): guard the pins and the base image
* docs: state the AppImage glibc floor
* fix(appimage): bundle libibus with its typelib
* ci(appimage): let the container run the isolated GI smoke test
* fix(appimage): keep the bundle's libraries out of host binaries
* fix(appimage): bundle libgpg-error with the libgcrypt that needs it
* docs(agents): require host_env() for spawned binaries
* ci: run the AppImage jobs when its other inputs change
* fix(appimage): check every bundled typelib, not just the seeded ones
* fix(ibus): keep the bundle when respawning our own interpreter
* fix(injection): pass host_env() to setxkbmap and notify-send
* test(ibus): wait for the IBus warmup thread before asserting
* chore(build): migrate justfile tooling to uv
All recipes now run through 'uv run' against .venv, with a shared
DEV_EXTRAS variable (dev + vad) because uv syncs the environment
exactly and would otherwise uninstall the dev tools between
recipes. 'deps' installs dev+vad; new 'deps-all' covers every
extra. 'build' switches to 'uv build'; 'version' uses --no-sync.
Bump [tool.uv] required-version to >=0.12,<0.13 for uv 0.12.x, and
raise the mypy python_version target to 3.12: mypy 1.19 in the lock
cannot target 3.9 and numpy 2.x stubs need a 3.12 target to parse
(mypy is not run in CI or pre-commit; this only affects 'just
typecheck').
* fix(tests): isolate test runs from real XDG state; fix Arch model-path crash
Local test runs failed en masse (185 failures) while CI stayed
green: tests picked up the developer's real ~/.local/share/vocalinux
models via the default XDG paths and switched IBus/keyboard code
paths through the desktop session env (XDG_CURRENT_DESKTOP/
XDG_SESSION_TYPE on GNOME/Wayland, issue #478 branches). conftest
now points XDG base dirs at a throwaway session-wide directory and
clears session variables at import time, mirroring a fresh CI
runner (paths like MODELS_DIR freeze at first import during
collection, so fixture-time isolation is too late).
Also guard the Arch branch of _get_system_model_paths(): it removed
/usr/local/share/vocalinux/models unconditionally and crashed with
ValueError on Arch hosts when a custom XDG_DATA_DIRS never added
that path. Add regression tests, and make the lock-file test assert
the actual $XDG_DATA_HOME/vocalinux contract instead of the
hardcoded ~/.local/share default.
* fix(tests): keep the IBus engine double a plain class
Python 3.14's unittest.mock re-instantiates a user subclass when it builds
child mocks (klass(name=...)). VocalinuxEngine inherits from whatever
IBus.Engine resolves to, and the test module bound that to the MagicMock
class itself, so every engine instantiation raised
TypeError: VocalinuxEngine.__init__() got an unexpected keyword argument 'name'
on 3.14 while passing on 3.13. Replace it with a plain stand-in class that
still hands out per-instance MagicMocks for the IBus API surface the engine
touches.
The same pattern remains in test_ibus_engine.py, test_ibus_engine_utils.py,
test_ibus_active_engine_guard.py and test_ibus_xkb_wayland.py; those never
instantiate the engine, so they are unaffected for now.
* fix(install): build the venv from the system Python
install.sh looked up a bare `python3` from PATH. Started from an activated
virtualenv — a shell left in uv's .venv after `just deps` — it built venv/
from that interpreter, where distro PyGObject is invisible even with
--system-site-packages, and the run aborted with
Distro PyGObject (python3-gi / python3-gobject) is not importable in the venv.
A mismatched venv/ then survived every retry, because the installer reuses
whatever venv it finds.
- drop an inherited VIRTUAL_ENV (and its bin from PATH) before any
interpreter lookup
- select the interpreter explicitly: $SYSTEM_PYTHON (default
/usr/bin/python3) or PATH python3, preferring one that can import gi,
and create the venv with it instead of `python3`
- recreate venv/ when it was built by a different interpreter
- name the interpreter in the PyGObject guard's error
Documents the two environments (uv's .venv for tooling, venv/ for the
installed app) in CONTRIBUTING, the justfile header and AGENTS.md, and adds
the mismatch symptom to the INSTALL troubleshooting section.
* chore(python): raise the supported floor to 3.11
requires-python promised 3.9 while the CI matrix ran 3.9/3.10/3.11/3.13,
the classifiers advertised up to 3.14, mypy targeted 3.12 and install.sh
enforced 3.9 — five different answers to "which Python does this support".
Settle on 3.11 everywhere: pyproject (requires-python, classifiers, black
targets), install.sh's MIN_VERSION, check-system-deps.sh, the distro package
map, the CI matrix (now 3.11-3.14, matching the classifiers), the release and
nightly workflows, and the docs.
mypy now targets the floor instead of 3.12, so type checking covers what we
promise; numpy 2.x ships stubs in 3.12 syntax that a 3.11 target cannot
parse, so its imports are skipped. (mypy has 71 pre-existing errors either
way; no CI job gates on it.)
uv.lock and the requirements/* exports are regenerated — dropping 3.9/3.10
removes most of the version-conditional resolution branches.
tests/test_python_version_policy.py pins the invariant: install.sh's floor,
requires-python, the classifiers and the CI matrix must agree.
* ci: run lint and tests from uv.lock
The lock file and the hash-pinned exports were not enforced anywhere: CI
still built a venv by hand and installed unpinned `PyGObject` plus
`-e ".[dev,vad]"`, so the tool versions it checked against had nothing to do
with the ones `just lint` uses locally.
Both Python jobs now use astral-sh/setup-uv and run through `uv sync
--locked` / `uv run --locked`, which also fails the build when uv.lock drifts
from pyproject.toml.
Partially covers phase 5 of #701 (tests install from the lock, lock freshness
gate); trusted publishing, artifact checksums and action SHA pinning are
still open there.
* ci: lint from a dedicated dependency group
The lint job failed on this branch: `uv run --extra dev` syncs the whole
project, so it tried to build pyaudio from source on a runner that installs
no system dependencies.
src/pyaudio/device_api.c:9:10: fatal error: portaudio.h: No such file or directory
Move black/isort/flake8 out of the `dev` extra into a `lint` dependency
group. CI runs `uv run --locked --only-group lint`, which installs the three
pure-Python tools and skips the project entirely — no portaudio or
girepository headers needed. `just deps` and DEV_EXTRAS request the group
alongside the dev extras, so the local environment still carries test and
lint tooling at once and `just lint` matches CI version for version.
pre-commit is unaffected: its hooks pin their own tool versions upstream.
* fix(install): never configure an engine the venv cannot import
vosk became an optional extra in #705, but the installer still writes
`"engine": "vosk"` into config.json on the whisper.cpp fallback path even
when installing the extra only produced a warning. config.json also survives
reinstalls — every writer guards on "file does not exist" — so an engine
picked by an earlier attempt outlives the venv that supported it. Either way
Vocalinux dies at startup:
ModuleNotFoundError: No module named 'vosk'
- the fallback now writes the vosk config only when `import vosk` actually
succeeds, and otherwise leaves the engine configuration alone
- a new verify_configured_engine() runs near the end of the install: if
config.json selects vosk and the venv cannot import it, it says so and
prints both fixes instead of leaving a broken install behind
* feat(models): verify every model download against a pinned digest
Pins a sha256/md5 per downloadable speech model in
src/vocalinux/utils/model_checksums.txt and verifies against it in both
install.sh and the runtime downloaders, before the file is moved into place.
Verification fails closed. Covers phase 2.5 of #701; see the PR for details.
* docs(agents): rebase AGENTS.md onto the #727 rewrite
* fix(install): hash models that are already on disk instead of trusting them
* refactor(models): one download path per engine, and fix Whisper's
* docs(install): drop distros that cannot meet the 3.11 floor
* docs: retire the distros and Python versions the 3.11 floor rules out
* fix(install): abort instead of warning when Python is below the floor
* fix(install): fall back to the system Python, not the one on PATH
* fix(install): match an existing venv by installation, not by X.Y
* fix(install): repair a config naming an engine the venv cannot import
* fix(models): pin VOSK by the sha256 of bytes we fetched, not upstream's md5
* fix(justfile): request the dev set in every recipe that syncs
* test(version): scan the docs and website trees for a stale Python floor
* docs: correct the stale model-checksum and distro claims
* fix(models): stamp the VOSK tree the app extracts, as the installer does
* fix(install): replace an unverified VOSK model only once its successor is ready
* docs(install): align the Fedora floor with the rest of the tree
* test(install): drop an f-string with no placeholders
* test(models): stub the downloader probe the VOSK installer test relies on
* docs: drop the long dashes from two model-checksum comments
* fix(install): gate on the interpreter, not on the distro release label
* fix(justfile): export the lint group into requirements/dev.txt
* fix(models): unpack the VOSK tree into staging and stamp it before the swap
* fix(whisper): adopt a checkpoint an earlier release stored under the catalog name
* docs(settings): state what a stray Whisper copy really means for deletion
* fix(models): hash a whisper.cpp model found on disk before it reaches ctypes
* docs(contributing): stop telling readers to uninstall their linters
* fix(install): stop warning off distros the project documents as supported
* docs(distros): file the supported derivatives under supported
Stable minor since v0.15.0. Bumps version to 0.16.0 across app, web, AUR, and Flatpak AppStream, and refreshes docs, changelog, and SECURITY.
Tag v0.16.0 after this lands on main.
* fix(whisper): use bundled GPU libs and skip software Vulkan devices
AppImage 0.15.0 shipped a CPU-only pywhispercpp wheel, so the Settings
GPU picker and host vulkaninfo never moved inference off the CPU. Rebuild
pywhispercpp with Vulkan in the AppImage, log the libraries that actually
load, skip llvmpipe in auto-select, and document how to verify.
Fixes#604
* fix(appimage): pin gcc/g++ for Vulkan pywhispercpp rebuild
Hosts that default cc/c++ to clang fail CMAKE's C++ test because clang
does not search gcc's libstdc++ path. Default the rebuild to gcc/g++.
Repo links, install one-liners, and release checks now use
github.com/VocaHQ/vocalinux. The site mentions VocaHQ without a redesign.
The Codeberg mirror, personal email, and X handle are unchanged.
* fix(appimage): ship transitive GI typelibs for non-Debian hosts
The 0.15.0 AppImage pruned girepository down to a partial allowlist and
dropped xlib/Dbusmenu/GModule. On Ubuntu the missing typelibs fell back
to /usr/lib/x86_64-linux-gnu/girepository-1.0; on openSUSE/Fedora that
path does not exist, so Gtk failed to import and startup reported
missing GTK3/AppIndicator (#585).
Keep linuxdeploy's typelib extras, seed the full transitive set, bundle
AppIndicator/Notify shared libs, and smoke-test GI imports with host
typelibs hidden.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* style(test): format appimage packaging regression test
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(ci): avoid libappindicator/ayatana apt conflict
Ubuntu packages libappindicator3-1 and libayatana-appindicator3-1
conflict. Rely on gir1.2-* to pull a single implementation and keep
libdbusmenu/libnotify explicit for bundling.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(ci): install gir1.2-dbusmenu-glib for AppImage typelib seed
Dbusmenu-0.4 is a required transitive typelib for AppIndicator; without
the GIR package the build fails the required-typelib check.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(appimage): require only one AppIndicator typelib variant
Runtime accepts AppIndicator3 or AyatanaAppIndicator3. Treat them as
alternates in the AppImage typelib check so single-stack build hosts
do not fail require_all.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(appimage): prefer Ayatana after rebase onto main
Align AppImage typelib seeding and GI smoke order with main's
Ayatana-first tray preference from #621/#638.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(appimage): accept lowercase AyatanaAppindicator3 typelib
Match tray_indicator.py's three-step fallback so AppImage builds and
GI smoke succeed on hosts that only ship AyatanaAppindicator3-0.1.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(main): accept lowercase AyatanaAppindicator3 in dep check
Align check_dependencies with tray_indicator.py and the AppImage GI
smoke path so a lowercase-only typelib host does not fail at startup.
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
Fix ESLint errors for hash install links, anonymous default-export
warnings, and the ineffective next.config headers() that warned under
static export. Correct softwareVersion sync to match unquoted JS keys,
and bump Actions to Node-24 runtimes so deprecation notices stop.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(ui): AppImage smoke fixes for tray icons, labels, and identity
Replace placeholder mic tray SVGs with opaque branded icons, show
whisper.cpp instead of Whisper_cpp, set GLib/AppIndicator identity to
Vocalinux (not main.py), and give About dialog buttons enough padding
so labels are not clipped under Adwaita.
* fix(packaging): harden AppImage build for Wayland and tray deps
Pin the build interpreter, install onnxruntime into the bundle, set
desktop Exec=AppRun + StartupWMClass, prune typelibs to an allowlist
after linuxdeploy, and stop forcing GDK_BACKEND=x11 on Wayland
(VOCALINUX_FORCE_X11=1 remains as an escape hatch). CI AppImage jobs
also pull Ayatana AppIndicator and librsvg SVG loader packages.
* fix(packaging): bundle full stdlib from base_prefix in AppImage
When build.sh runs under a venv, sys.prefix only has site-packages, so
the AppImage was missing encodings and crashed on start. Copy the
interpreter and stdlib from sys.base_prefix, force pip
--ignore-installed into AppDir, set Gdk program class to Vocalinux, and
tone down the muted tray slash so small StatusNotifier icons stay slate.
* fix(tray): use absolute icon paths so stale hicolor icons lose
XFCE StatusNotifier resolves IconName through the system theme and can
ignore IconThemePath. A leftover ~/.local/share/icons placeholder
(vocalinux-microphone-off.svg filled #e74c3c) was winning over the
AppImage's bundled icons. Outside Flatpak, pass absolute SVG paths to
set_icon_full / new_with_path instead of bare theme names.
* feat(packaging): add self-contained AppImage build
Bundles the wheel plus PyGObject/GTK3/AppIndicator/IBus typelibs into a
relocatable x86_64 AppImage via linuxdeploy + linuxdeploy-plugin-gtk, so
users get a zero-install, no-sandbox-negotiation alternative alongside
AUR/PyPI. Wired into nightly and tagged releases, plus a smoke-test job
in the PR pipeline since AppImage/ELF bundling can't be verified on a
macOS dev machine.
* feat(packaging): add aarch64 AppImage builds
Parameterizes build.sh on uname -m instead of hardcoding x86_64 (both
linuxdeploy and appimagetool publish aarch64 continuous builds, and
pywhispercpp ships manylinux_aarch64 wheels, so no from-source compile
needed). Adds an arch matrix to the PR smoke-test job, and a second
job in nightly.yml/release.yml that builds on GitHub's free
ubuntu-24.04-arm hosted runner and attaches the aarch64 AppImage to
the same release the x86_64 job already created.
* feat(packaging): add AUR release package and CI publish
Ship packaging/aur/vocalinux for yay installs of tagged releases, wire
AUR publish into the release workflow (skipped until secrets are set),
and document one-time maintainer setup.
Fixes#116
* refactor(packaging): slim AUR PR to PKGBUILD + CI
Drop systemd unit, install script, website edits, and long docs.
Keep release PKGBUILD, minimal publish job, and short AUR setup notes.
* feat(web): overhaul SEO structure and landing pages
Add distro-specific install pages and an engine comparison hub to capture high-intent organic queries. Centralize metadata and structured data handling to improve crawl consistency, internal linking, and rich-result eligibility.
* feat(web): add subpage navigation and visual polish
Make SEO landing pages easier to discover and navigate by adding persistent subpage header/footer links to home and key routes. Refresh install and comparison pages with colorful icon-driven sections while expanding homepage footer links for stronger internal linking.
* fix(ci): make web version sync robust to schema location
CI assumed softwareVersion lived in layout.tsx, but SEO refactors moved SoftwareApplication JSON-LD to page.tsx. Update workflow sync steps to patch whichever file currently contains softwareVersion so website build and release jobs keep working.
* chore(web): remove SEO audit artifact
Drop the internal SEO audit markdown from the PR so only production website and workflow changes are included.
Changed install commands to download install.sh directly from the release
tag instead of from main branch with --tag parameter. This ensures:
1. The install script version matches the release being installed
2. No version mismatch between script and code
3. Simpler, cleaner install commands
Changes:
- install.sh: Updated error message to use v0.6.0-beta URL
- README.md: All install commands now use v0.6.0-beta/install.sh
- docs/INSTALL.md: Updated quick install command
- docs/UPDATE.md: Updated update command examples
- docs/RELEASE_PROCESS.md: Updated example command
- src/vocalinux/main.py: Updated error message install command
- tests/distro-testing-checklist.md: Updated download URL
- web/src/app/page.tsx: Now uses dynamic tag in URL (not main)
- .github/workflows/release.yml: Release notes use tag-based URL
Before:
curl .../main/install.sh | bash -s -- --tag=v0.6.0-beta
After:
curl .../v0.6.0-beta/install.sh | bash
Fixes version consistency between install script and installed code.
The website displays the software version from source code in its
JSON-LD structured data. Previously, when version bumps occurred
(changing only src/vocalinux/version.py), the website was not rebuilt
or deployed because the path filter only detected 'python' changes,
not 'web' changes.
Changes:
- unified-pipeline.yml: Deploy website when either web OR python files
change (for version updates)
- unified-pipeline.yml: Add version sync step to copy version from
src/vocalinux/version.py to web/src/app/layout.tsx during build
- release.yml: Always deploy website as part of release workflow
This ensures the website's displayed version stays in sync with the
actual package version.
The v0.3.0-alpha release was created before the --tag feature was
added to install.sh, so installing from that tag fails with "Unknown
option: --tag=v0.3.0-alpha".
Solution: Always download install.sh from main (which has --tag support)
and pass the release tag as a parameter. This ensures users get a working
installer that can install the specific stable release.
Changes:
- web/src/app/page.tsx: Download from main instead of ${latestRelease}
- README.md: Update install commands to download from main
- docs/INSTALL.md: Update install command
- .github/workflows/release.yml: Update release notes
* fix: Use latest release tag for install commands instead of main branch
The web install commands previously pointed to install.sh from the main branch,
which could be unstable/untested code. Now they dynamically fetch the latest
release tag from GitHub API and use that for the install script URLs.
Changes:
- Add getInstallCommands() function that takes a release tag
- Add latestRelease state that fetches from GitHub releases API
- Update all install commands to use the fetched release tag
- Fallback to 'main' if GitHub API fails
* docs: Update README and docs to use latest release tag
Also update GitHub release workflow to use the release tag dynamically.
Changes:
- README.md: Update install commands to use v0.3.0-alpha tag
- docs/INSTALL.md: Update install command to use v0.3.0-alpha tag
- .github/workflows/release.yml: Use ${{ github.ref_name }} for install URL
- Add note pointing to GitHub Releases for latest version
---------
Co-authored-by: Claude Opus <noreply@anthropic.com>
- Added publish-pypi job to release workflow
- Builds, verifies, and uploads package to PyPI after GitHub release
- Uses PYPI_API_TOKEN secret for authentication
- Added id-token permission for trusted publishing support
- Package verification with twine check before upload
* feat: prepare for v0.2.0-alpha release
* feat: prepare for v0.2.0-alpha release
* chore: remove CHANGELOG.md, use GitHub Releases instead
* refactor: use existing About dialog instead of Settings section
* style: enhance About dialog UI readability
* style: fix formatting with black and isort
* feat: show VAD and silence timeout settings for Whisper engine
- Renamed 'VOSK Settings' to 'Recognition Settings'
- VAD Sensitivity and Silence Timeout now apply to both engines
- Added tooltips explaining each setting
- VOSK-specific model info only shown when VOSK selected
* fix: resolve overlapping UI elements in settings dialog
* fix: save VAD and silence timeout settings for both engines
Previously these settings were only saved when VOSK was selected.
Now they persist correctly for Whisper as well.
* fix: show download progress when selecting undownloaded model
Previously when auto-applying settings, models would download silently
in the background without showing the progress dialog. Now:
- Download progress dialog appears for undownloaded models
- Model icons update correctly after download completes
* feat: add download progress, speed, ETA and cancel button
- Show real download progress percentage for VOSK models
- Display download speed (MB/s) and estimated time remaining
- Add Cancel button to abort downloads
- Update status during extraction phase
- Whisper downloads still pulse (uses internal downloader)
* fix: defer model download until needed, don't block startup
- App now starts instantly even if models aren't downloaded
- Models are downloaded on-demand when selected in Settings
- If user tries to dictate without a model, plays error sound
- Added model_ready property to check if engine is usable
* fix: improve first-run UX without downloaded models
- Settings dialog now defaults to smallest available model, not saved config
- Only show saved model if it's actually downloaded
- Add desktop notification when trying to dictate without a model
- Prevents auto-downloading large models on first launch
* feat: add download progress tracking for Whisper models
- Implement custom Whisper model download with progress/speed/ETA
- Uses OpenAI's CDN URLs directly instead of whisper.load_model()
- Progress callback works the same as VOSK downloads
- Supports cancellation during download
* fix: enable download progress callback for Whisper models
Progress callback was only being set for VOSK downloads, not Whisper.
Now both engines show real-time progress, speed, and ETA.
* fix: update tests for defer_download and config manager mocking
- test_main: Mock ConfigManager to prevent saved config from overriding test values
- test_settings_dialog: Mock ModelDownloadDialog to prevent GTK init errors
- test_speech_recognition: Use proper MockVoskModule class instead of MagicMock, pass defer_download=False for download test
- test_whisper_support: Pass defer_download=False to trigger model loading in test
* fix: resolve test failures for CI
- test_main: Fix ConfigManager patch path to use source module
- test_settings_dialog: Mock _is_vosk_model_downloaded and _is_whisper_model_downloaded to skip download flow, add missing dialog attributes
- test_speech_recognition: Replace recognizer with properly configured mock that returns valid JSON, fixing test isolation issues when running all tests together
* fix: mock _download_whisper_model in whisper test to avoid requests exception handling issue
* fix: add gir1.2-girepository-2.0 dep for PyGObject on Ubuntu 24.04
- Add missing girepository-2.0 package for newer Ubuntu runners
- Use --no-build-isolation for PyGObject to use system packages
* fix: skip PyGObject pip install, use system python3-gi package
- Use --no-deps for editable install to skip PyGObject
- Install other deps explicitly without PyGObject
- Add python3-gi-cairo for complete GTK support
- Removes problematic girepository-2.0 build requirement
* fix: install PyGObject via pip with proper build deps
- Remove python3-gi/python3-gi-cairo (doesn't work with setup-python)
- Install PyGObject from pip after installing build dependencies
- Simplify venv setup without --system-site-packages hack
* Fix GObject
* fix: set GI_TYPELIB_PATH for pip-installed PyGObject to find AppIndicator3
* Add GitHub Actions CI/CD workflows and status badges
* Fix CI workflow dependency installation
* Fix CI workflow: remove pip caching and add explicit requirements.txt
* Fix CI workflow: add system dependencies for PyGObject and pycairo
* Refactor code for improved readability and consistency
- Cleaned up whitespace and formatting in audio_feedback.py, config_manager.py, keyboard_shortcuts.py, tray_indicator.py, run_basic_tests.py, and test_basic.py.
- Enhanced the structure of the code by aligning comments and ensuring consistent indentation.
- Updated function definitions and calls to maintain a uniform style across the codebase.
- Improved logging messages for better clarity during debugging.
- Ensured all files end with a newline for better compatibility with various tools.
* Refactor imports for consistency and clarity across multiple modules
* Fix test imports by setting PYTHONPATH and adding conftest.py
* Update CI workflow to use Python 3.12 and ensure proper import path in tests
* Fix CI: Add GObject Introspection development dependencies
* Update CI: Use virtual environment like local setup
* Fix CI
* Update README and setup.py: Change Python version to 3.8 and correct author email
* Restructure tests for better organization and maintainability
* Fix formatting
* Update CI workflow to remove redundant test step
* Fix CI tests by properly mocking VOSK model loading
* Update CI and Tests
* Simplify