feat(identity): rename user-visible identity to voculinux with one-time legacy import

The fork takes its own name everywhere a user can see it: binaries
(voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus
namespace (com.voculinux.Voculinux, no alias for the old name),
config/data/cache dirs, single-instance lock, IBus engine identity,
tray icons, resource prefixes, VOCULINUX_* environment names, and the
update-checker user agent. The internal Python module stays vocalinux
and the distribution metadata keeps the upstream URLs as provenance.

On first run a previous Vocalinux install's config.json, custom
dictionary, and portal restore token are copied once into the new
locations (new config always wins, no merge, no write-back); models
re-resolve through the pinned-checksum download path. An offline
naming-sweep test proves no old-name strings remain on user-visible
surfaces outside the allowlist.

Refs #18
This commit is contained in:
soubarnak
2026-11-06 01:29:39 +05:30
parent c565e6d946
commit ea7be5f111
251 changed files with 2449 additions and 1991 deletions
+27 -27
View File
@@ -91,10 +91,10 @@ def test_terms_are_live_reloaded_and_scanner_friendly(tmp_path: Path, monkeypatc
"""The UTF-8 line file accepts comments and applies an external replacement."""
manager = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
terms_file = tmp_path / TERMS_FILENAME
terms_file.write_text("# scanner comment\nVocaLinux\nvocalinux\nSupabase\n", encoding="utf-8")
terms_file.write_text("# scanner comment\nVoculinux\nvoculinux\nSupabase\n", encoding="utf-8")
assert manager.get_terms() == ["VocaLinux", "Supabase"]
assert manager.build_initial_prompt() == "VocaLinux Supabase"
assert manager.get_terms() == ["Voculinux", "Supabase"]
assert manager.build_initial_prompt() == "Voculinux Supabase"
terms_file.write_text("PyGObject\n", encoding="utf-8")
assert manager.build_initial_prompt() == "PyGObject"
@@ -105,8 +105,8 @@ def test_terms_save_is_atomic_shape_and_transient_override_is_not_written(
) -> None:
"""UI saves standard terms safely while CLI terms remain session-only."""
manager = manager_at(tmp_path, monkeypatch)
assert manager.save_terms(["VocaLinux", "vocalinux", "# ignored", " PyGObject "])
assert (tmp_path / TERMS_FILENAME).read_text(encoding="utf-8") == "VocaLinux\nPyGObject\n"
assert manager.save_terms(["Voculinux", "voculinux", "# ignored", " PyGObject "])
assert (tmp_path / TERMS_FILENAME).read_text(encoding="utf-8") == "Voculinux\nPyGObject\n"
override = tmp_path / "override.txt"
override.write_text("Override\n", encoding="utf-8")
@@ -122,15 +122,15 @@ def test_incremental_term_edits_preserve_comments_and_blank_lines(
"""GTK add/remove operations retain unrelated scanner-managed source lines."""
manager = manager_at(tmp_path, monkeypatch)
terms_file = tmp_path / TERMS_FILENAME
terms_file.write_text("# scanner note\n\nVocaLinux\n# retain me\nPyGObject\n", encoding="utf-8")
terms_file.write_text("# scanner note\n\nVoculinux\n# retain me\nPyGObject\n", encoding="utf-8")
assert manager.add_term("Supabase")
assert terms_file.read_text(encoding="utf-8") == (
"# scanner note\n\nVocaLinux\n# retain me\nPyGObject\nSupabase\n"
"# scanner note\n\nVoculinux\n# retain me\nPyGObject\nSupabase\n"
)
assert manager.remove_term("PyGObject")
assert terms_file.read_text(encoding="utf-8") == (
"# scanner note\n\nVocaLinux\n# retain me\nSupabase\n"
"# scanner note\n\nVoculinux\n# retain me\nSupabase\n"
)
@@ -278,7 +278,7 @@ def test_pr_767_dictionary_configuration_keys_and_contract_are_preserved(
) -> None:
"""Existing enabled, file_path, and max_words settings remain effective."""
configured_path = tmp_path / TERMS_FILENAME
configured_path.write_text("VocaLinux\nPyGObject\n", encoding="utf-8")
configured_path.write_text("Voculinux\nPyGObject\n", encoding="utf-8")
config = FakeConfig(
{
"dictionary": {
@@ -294,7 +294,7 @@ def test_pr_767_dictionary_configuration_keys_and_contract_are_preserved(
assert Path(DEFAULT_TERMS_PATH).name == TERMS_FILENAME
assert manager.terms_enabled()
assert manager.terms_path() == configured_path
assert manager.build_initial_prompt() == "VocaLinux"
assert manager.build_initial_prompt() == "Voculinux"
def test_default_terms_path_follows_xdg_config_home(tmp_path: Path, monkeypatch) -> None:
@@ -306,10 +306,10 @@ def test_default_terms_path_follows_xdg_config_home(tmp_path: Path, monkeypatch)
assert manager.corrections_path() == tmp_path / CORRECTIONS_FILENAME
def test_legacy_vocalinux_terms_root_keeps_corrections_at_config_dir(
def test_legacy_voculinux_terms_root_keeps_corrections_at_config_dir(
tmp_path: Path, monkeypatch
) -> None:
"""Persisted ~/.config/vocalinux terms do not move the corrections file."""
"""Legacy ~/.config/vocalinux terms do not move the corrections file."""
monkeypatch.setattr("vocalinux.custom_dictionary.config_dir", lambda: str(tmp_path))
legacy_corrections = Path.home() / ".config" / "vocalinux" / CORRECTIONS_FILENAME
expanded_legacy = str(Path.home() / ".config" / "vocalinux" / TERMS_FILENAME)
@@ -434,7 +434,7 @@ def test_corrections_fallback_survives_copy_failure(tmp_path: Path, monkeypatch)
def test_corrections_stay_at_config_dir_with_arbitrary_terms_parent(
tmp_path: Path, monkeypatch
) -> None:
"""Corrections ignore the terms file location outside a vocalinux directory."""
"""Corrections ignore the terms file location outside a voculinux directory."""
xdg_dir = tmp_path / "xdg-config"
xdg_dir.mkdir()
monkeypatch.setattr("vocalinux.custom_dictionary.config_dir", lambda: str(xdg_dir))
@@ -568,7 +568,7 @@ def test_invalid_or_unreadable_configured_terms_path_is_not_persisted(
config = FakeConfig({"dictionary": {"file_path": previous_path}})
manager = manager_at(tmp_path, monkeypatch, config)
assert not manager.set_terms_path("~vocalinux-user-does-not-exist/dictionary.txt")
assert not manager.set_terms_path("~voculinux-user-does-not-exist/dictionary.txt")
assert config.get("dictionary", "file_path") == previous_path
assert not manager.set_terms_path(str(tmp_path))
@@ -581,7 +581,7 @@ def test_invalid_saved_terms_path_is_safe_to_read_and_report(tmp_path: Path, mon
{
"dictionary": {
"enabled": True,
"file_path": "~vocalinux-user-does-not-exist/dictionary.txt",
"file_path": "~voculinux-user-does-not-exist/dictionary.txt",
}
}
)
@@ -609,7 +609,7 @@ def test_terms_path_save_failure_preserves_the_previous_configured_path(
def test_invalid_transient_terms_path_does_not_crash(tmp_path: Path, monkeypatch) -> None:
"""An unresolved CLI tilde path disables only its session's terms safely."""
manager_at(tmp_path, monkeypatch)
manager = CustomDictionaryManager(FakeConfig(), "~vocalinux-user-does-not-exist/dictionary.txt")
manager = CustomDictionaryManager(FakeConfig(), "~voculinux-user-does-not-exist/dictionary.txt")
assert manager.terms_enabled()
assert manager.terms_path() is None
@@ -638,7 +638,7 @@ def test_file_edits_preserve_existing_permissions(tmp_path: Path, monkeypatch) -
manager = manager_at(tmp_path, monkeypatch)
terms_file = tmp_path / TERMS_FILENAME
terms_file.write_text("VocaLinux\n", encoding="utf-8")
terms_file.write_text("Voculinux\n", encoding="utf-8")
terms_file.chmod(0o600)
assert manager.save_terms(["Supabase"])
assert stat.S_IMODE(terms_file.stat().st_mode) == 0o600
@@ -652,7 +652,7 @@ def test_file_edits_preserve_existing_permissions(tmp_path: Path, monkeypatch) -
def test_dictionary_file_argument_is_available_for_a_session_override(monkeypatch) -> None:
"""The retained CLI option accepts a terms file without changing config."""
monkeypatch.setattr("sys.argv", ["vocalinux", "--dictionary-file", "/tmp/terms.txt"])
monkeypatch.setattr("sys.argv", ["voculinux", "--dictionary-file", "/tmp/terms.txt"])
assert parse_arguments().dictionary_file == "/tmp/terms.txt"
@@ -961,7 +961,7 @@ def test_invalid_terms_file_is_ignored_and_explained(tmp_path: Path, monkeypatch
def test_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypatch) -> None:
"""Whisper gets the current file contents as initial_prompt at transcription time."""
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
manager = recognition_manager("whisper", dictionary)
manager.model = MagicMock()
manager.model.transcribe.return_value = {"text": "ok"}
@@ -970,13 +970,13 @@ def test_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypat
with patch.dict("sys.modules", {"numpy": mock_numpy(), "torch": MagicMock()}):
assert manager._transcribe_with_whisper([b"\x00\x00"]) == "ok"
assert manager.model.transcribe.call_args.kwargs["initial_prompt"] == "VocaLinux"
assert manager.model.transcribe.call_args.kwargs["initial_prompt"] == "Voculinux"
def test_faster_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, monkeypatch) -> None:
"""Faster Whisper receives the same live vocabulary bias as OpenAI Whisper."""
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
manager = object.__new__(SpeechRecognitionManager)
manager.dictionary_manager = dictionary
manager._faster_whisper_engine = MagicMock()
@@ -985,7 +985,7 @@ def test_faster_whisper_receives_the_live_custom_terms_prompt(tmp_path: Path, mo
assert manager._transcribe_with_faster_whisper([b"\x00\x00"]) == "ok"
manager._faster_whisper_engine.transcribe.assert_called_once_with(
[b"\x00\x00"], language="auto", initial_prompt="VocaLinux"
[b"\x00\x00"], language="auto", initial_prompt="Voculinux"
)
@@ -994,7 +994,7 @@ def test_whispercpp_composes_and_clears_live_terms_prompt(tmp_path: Path, monkey
config = FakeConfig({"dictionary": {"enabled": True}})
dictionary = manager_at(tmp_path, monkeypatch, config)
terms_file = tmp_path / TERMS_FILENAME
terms_file.write_text("VocaLinux\n", encoding="utf-8")
terms_file.write_text("Voculinux\n", encoding="utf-8")
manager = recognition_manager("whisper_cpp", dictionary, advanced_prompt="Explicit context")
manager._model_lock = threading.Lock()
manager.model = MagicMock()
@@ -1008,7 +1008,7 @@ def test_whispercpp_composes_and_clears_live_terms_prompt(tmp_path: Path, monkey
assert manager._transcribe_with_whispercpp([b"\x00\x00"]) == "ok"
prompts = [call.kwargs["initial_prompt"] for call in manager.model.transcribe.call_args_list]
assert prompts == ["Explicit context VocaLinux", "Explicit context", ""]
assert prompts == ["Explicit context Voculinux", "Explicit context", ""]
def test_vosk_warns_once_for_bias_while_corrections_remain_available(
@@ -1016,7 +1016,7 @@ def test_vosk_warns_once_for_bias_while_corrections_remain_available(
) -> None:
"""VOSK rejects vocabulary bias only; its transcript can still be corrected."""
dictionary = manager_at(tmp_path, monkeypatch, FakeConfig({"dictionary": {"enabled": True}}))
(tmp_path / TERMS_FILENAME).write_text("VocaLinux\n", encoding="utf-8")
(tmp_path / TERMS_FILENAME).write_text("Voculinux\n", encoding="utf-8")
manager = recognition_manager("vosk", dictionary)
manager._get_vosk_model_path = MagicMock(return_value="/models/vosk")
vosk = MagicMock()
@@ -1041,11 +1041,11 @@ def test_whispercpp_prompt_composes_advanced_and_terms() -> None:
recognition_manager = object.__new__(SpeechRecognitionManager)
recognition_manager.whispercpp_initial_prompt = "Write product names accurately."
recognition_manager.dictionary_manager = type(
"Dictionary", (), {"build_initial_prompt": lambda self: "VocaLinux"}
"Dictionary", (), {"build_initial_prompt": lambda self: "Voculinux"}
)()
assert (
recognition_manager._get_whispercpp_prompt() == "Write product names accurately. VocaLinux"
recognition_manager._get_whispercpp_prompt() == "Write product names accurately. Voculinux"
)
recognition_manager.dictionary_manager = type(
"Dictionary", (), {"build_initial_prompt": lambda self: None}