Repository: espanso/espanso (language: rust)

Issue:
Remove the `migrate` module
### Description

We do not need `espanso-migrate` anymore, and there are a bunch of places in the old config or format is called.

It would be good to remove all of them

### Motivation

To ease the app and reduce unused dependencies

### Alternatives

Just continue to keep espanso migrate, though is unclear if we will need that in the future, given we don't plan any strong refactorings

### Have you tried building it? Or would you like it?

- [X] I'd be willing to contribute this feature to Espanso myself.


Discussion / hints:

Fix patch:
diff --git a/.devcontainer/features/x11-dependencies/install.sh b/.devcontainer/features/x11-dependencies/install.sh
index a55f9ae57..7aec5cb29 100644
--- a/.devcontainer/features/x11-dependencies/install.sh
+++ b/.devcontainer/features/x11-dependencies/install.sh
@@ -4,4 +4,4 @@ echo "Activating feature 'espanso_x11'"
 
 # Devcontainer install scripts always run as root, therefore we don't need to use "sudo".
 apt update -y
-apt install -y --no-install-recommends libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.2-dev
\ No newline at end of file
+apt install -y --no-install-recommends libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.*-dev
diff --git a/.github/scripts/Dockerfile b/.github/scripts/Dockerfile
index 7ec1bfa38..ac1754dbb 100644
--- a/.github/scripts/Dockerfile
+++ b/.github/scripts/Dockerfile
@@ -2,7 +2,7 @@ FROM ubuntu:24.04
 
 RUN apt-get update \
     && apt-get install -y \
-    libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.2-dev \
+    libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.*-dev \
     libssl-dev \
     libxdo-dev \
     wget git file build-essential pkg-config
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f65ecdde9..c41683eee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,7 @@ env:
 jobs:
   build:
     strategy:
-      fail-fast: false
+      fail-fast: true
       matrix:
         os: [windows-latest, macos-latest, ubuntu-latest]
 
@@ -36,7 +36,7 @@ jobs:
         if: ${{ runner.os == 'Linux' }}
         run: |
           sudo apt-get update
-          sudo apt-get install -y libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.0-gtk3-dev
+          sudo apt-get install -y libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.*-dev
       - name: Install rust-script and cargo-make
         run: |
           cargo install rust-script --version "0.7.0"
@@ -78,7 +78,7 @@ jobs:
         if: ${{ runner.os == 'Linux' }}
         run: |
           sudo apt-get update
-          sudo apt-get install -y libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.0-gtk3-dev
+          sudo apt-get install -y libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.*-dev
       - name: Install rust-script and cargo-make
         run: |
           cargo install rust-script --version "0.7.0"
@@ -98,7 +98,7 @@ jobs:
       - name: Install Linux dependencies
         run: |
           sudo apt-get update
-          sudo apt-get install -y libxkbcommon-dev libwxgtk3.0-gtk3-dev libdbus-1-dev
+          sudo apt-get install -y libxkbcommon-dev libwxgtk3.*-dev libdbus-1-dev
       - name: Install rust-script and cargo-make
         run: |
           cargo install rust-script --version "0.7.0"
@@ -122,7 +122,7 @@ jobs:
       - name: Install Linux dependencies
         run: |
           sudo apt-get update
-          sudo apt-get install -y libxkbcommon-dev libwxgtk3.0-gtk3-dev libdbus-1-dev
+          sudo apt-get install -y libxkbcommon-dev libwxgtk3.*-dev libdbus-1-dev
       - name: Install rust-script and cargo-make
         run: |
           cargo install rust-script --version "0.7.0"
diff --git a/Cargo.lock b/Cargo.lock
index 0beb9c6f6..e3d3e8eba 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,15 +17,6 @@ dependencies = [
  "memchr",
 ]
 
-[[package]]
-name = "ansi_term"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
-dependencies = [
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "anyhow"
 version = "1.0.38"
@@ -455,16 +446,6 @@ dependencies = [
  "memchr",
 ]
 
-[[package]]
-name = "ctor"
-version = "0.1.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
-dependencies = [
- "quote 1.0.35",
- "syn 1.0.67",
-]
-
 [[package]]
 name = "cty"
 version = "0.2.2"
@@ -512,12 +493,6 @@ dependencies = [
  "zeroize",
 ]
 
-[[package]]
-name = "diff"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
-
 [[package]]
 name = "difference"
 version = "2.0.0"
@@ -702,7 +677,6 @@ dependencies = [
  "espanso-kvs",
  "espanso-mac-utils",
  "espanso-match",
- "espanso-migrate",
  "espanso-modulo",
  "espanso-package",
  "espanso-path",
@@ -865,25 +839,6 @@ dependencies = [
  "unicase",
 ]
 
-[[package]]
-name = "espanso-migrate"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "fs_extra",
- "glob",
- "include_dir",
- "lazy_static",
- "path-slash",
- "pretty_assertions",
- "regex",
- "tempdir",
- "test-case",
- "thiserror",
- "walkdir",
- "yaml-rust 0.4.6",
-]
-
 [[package]]
 name = "espanso-modulo"
 version = "0.1.0"
@@ -1384,30 +1339,6 @@ dependencies = [
  "unicode-normalization",
 ]
 
-[[package]]
-name = "include_dir"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d58bdeb22b1c4691106c084b1063781904c35d0f22eda2a283598968eac61a"
-dependencies = [
- "glob",
- "include_dir_impl",
- "proc-macro-hack",
-]
-
-[[package]]
-name = "include_dir_impl"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327869970574819d24d1dca25c891856144d29159ab797fa9dc725c5c3f57215"
-dependencies = [
- "anyhow",
- "proc-macro-hack",
- "proc-macro2",
- "quote 1.0.35",
- "syn 1.0.67",
-]
-
 [[package]]
 name = "indexmap"
 version = "1.9.3"
@@ -2043,15 +1974,6 @@ version = "6.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
 
-[[package]]
-name = "output_vt100"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
-dependencies = [
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "parking_lot_core"
 version = "0.9.9"
@@ -2065,12 +1987,6 @@ dependencies = [
  "windows-targets 0.48.5",
 ]
 
-[[package]]
-name = "path-slash"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cacbb3c4ff353b534a67fb8d7524d00229da4cb1dc8c79f4db96e375ab5b619"
-
 [[package]]
 name = "percent-encoding"
 version = "2.1.0"
@@ -2214,24 +2130,6 @@ dependencies = [
  "treeline",
 ]
 
-[[package]]
-name = "pretty_assertions"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
-dependencies = [
- "ansi_term",
- "ctor",
- "diff",
- "output_vt100",
-]
-
-[[package]]
-name = "proc-macro-hack"
-version = "0.5.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
-
 [[package]]
 name = "proc-macro2"
 version = "1.0.78"
@@ -2715,7 +2613,7 @@ dependencies = [
  "dtoa",
  "linked-hash-map",
  "serde",
- "yaml-rust 0.4.5",
+ "yaml-rust",
 ]
 
 [[package]]
@@ -2999,19 +2897,6 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
-[[package]]
-name = "test-case"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "956044ef122917dde830c19dec5f76d0670329fde4104836d62ebcb14f4865f1"
-dependencies = [
- "cfg-if 1.0.0",
- "proc-macro2",
- "quote 1.0.35",
- "syn 1.0.67",
- "version_check",
-]
-
 [[package]]
 name = "textwrap"
 version = "0.16.1"
@@ -3918,14 +3803,6 @@ dependencies = [
  "linked-hash-map",
 ]
 
-[[package]]
-name = "yaml-rust"
-version = "0.4.6"
-source = "git+https://github.com/federico-terzi/yaml-rust#454221bebabc93307bbf7aa7f556407dd3027363"
-dependencies = [
- "linked-hash-map",
-]
-
 [[package]]
 name = "zeroize"
 version = "1.3.0"
diff --git a/Cargo.toml b/Cargo.toml
index 0c3655fdf..5ab963fff 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,6 @@ members = [
   "espanso-info",
   "espanso-path",
   "espanso-modulo",
-  "espanso-migrate",
   "espanso-mac-utils",
   "espanso-kvs",
   "espanso-engine",
diff --git a/Compilation.md b/Compilation.md
index f8c93e5c5..2cf63358b 100644
--- a/Compilation.md
+++ b/Compilation.md
@@ -90,7 +90,7 @@ sudo apt install libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3
 If compiling on a version of Ubuntu X11 after 22.04:
 
 ```bash
-sudo apt install libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.2-dev
+sudo apt install libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.*-dev
 ```
 
 ### Compiling for X11
diff --git a/espanso-config/src/config/store.rs b/espanso-config/src/config/store.rs
index a2c37d660..0f38c243d 100644
--- a/espanso-config/src/config/store.rs
+++ b/espanso-config/src/config/store.rs
@@ -126,13 +126,6 @@ impl DefaultConfigStore {
       non_fatal_errors,
     ))
   }
-
-  pub fn from_configs(
-    default: Arc<dyn Config>,
-    customs: Vec<Arc<dyn Config>>,
-  ) -> DefaultConfigStore {
-    Self { default, customs }
-  }
 }
 
 #[cfg(test)]
diff --git a/espanso-config/src/legacy/config.rs b/espanso-config/src/legacy/config.rs
deleted file mode 100644
index 122c84337..000000000
--- a/espanso-config/src/legacy/config.rs
+++ /dev/null
@@ -1,1867 +0,0 @@
-// This file is taken from the old version of espanso, and used to load
-// the "legacy" config format
-
-/*
- * This file is part of espanso.
- *
- * Copyright (C) 2019 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use super::model::{KeyModifier, PasteShortcut};
-use log::error;
-use serde::{Deserialize, Serialize};
-use serde_yaml::Value;
-use std::collections::{HashMap, HashSet};
-use std::error::Error;
-use std::fmt;
-use std::fs::File;
-use std::io::Read;
-use std::path::{Path, PathBuf};
-use std::string::ToString;
-use walkdir::{DirEntry, WalkDir};
-
-pub const DEFAULT_CONFIG_FILE_NAME: &str = "default.yml";
-pub const USER_CONFIGS_FOLDER_NAME: &str = "user";
-
-// Default values for primitives
-fn default_name() -> String {
-  "default".to_owned()
-}
-fn default_parent() -> String {
-  "self".to_owned()
-}
-fn default_filter_title() -> String {
-  String::new()
-}
-fn default_filter_class() -> String {
-  String::new()
-}
-fn default_filter_exec() -> String {
-  String::new()
-}
-fn default_log_level() -> i32 {
-  0
-}
-fn default_conflict_check() -> bool {
-  false
-}
-fn default_ipc_server_port() -> i32 {
-  34982
-}
-fn default_worker_ipc_server_port() -> i32 {
-  34983
-}
-fn default_use_system_agent() -> bool {
-  true
-}
-fn default_config_caching_interval() -> i32 {
-  800
-}
-fn default_word_separators() -> Vec<char> {
-  vec![' ', ',', '.', '?', '!', '\r', '\n', 22u8 as char]
-}
-fn default_toggle_interval() -> u32 {
-  230
-}
-fn default_toggle_key() -> KeyModifier {
-  KeyModifier::ALT
-}
-fn default_preserve_clipboard() -> bool {
-  true
-}
-fn default_passive_match_regex() -> String {
-  "(?P<name>:\\p{L}+)(/(?P<args>.*)/)?".to_owned()
-}
-fn default_passive_arg_delimiter() -> char {
-  '/'
-}
-fn default_passive_arg_escape() -> char {
-  '\\'
-}
-fn default_passive_delay() -> u64 {
-  100
-}
-fn default_passive_key() -> KeyModifier {
-  KeyModifier::OFF
-}
-fn default_enable_passive() -> bool {
-  false
-}
-fn default_enable_active() -> bool {
-  true
-}
-fn default_backspace_limit() -> i32 {
-  3
-}
-fn default_backspace_delay() -> i32 {
-  0
-}
-fn default_inject_delay() -> i32 {
-  0
-}
-fn default_restore_clipboard_delay() -> i32 {
-  300
-}
-fn default_exclude_default_entries() -> bool {
-  false
-}
-fn default_secure_input_watcher_enabled() -> bool {
-  true
-}
-fn default_secure_input_notification() -> bool {
-  true
-}
-fn default_show_notifications() -> bool {
-  true
-}
-fn default_auto_restart() -> bool {
-  true
-}
-fn default_undo_backspace() -> bool {
-  true
-}
-fn default_show_icon() -> bool {
-  true
-}
-fn default_fast_inject() -> bool {
-  true
-}
-fn default_secure_input_watcher_interval() -> i32 {
-  5000
-}
-fn default_matches() -> Vec<Value> {
-  Vec::new()
-}
-fn default_global_vars() -> Vec<Value> {
-  Vec::new()
-}
-fn default_modulo_path() -> Option<String> {
-  None
-}
-fn default_post_inject_delay() -> u64 {
-  100
-}
-fn default_wait_for_modifiers_release() -> bool {
-  false
-}
-fn default_search_trigger() -> Option<String> {
-  Some("jkj".to_string())
-}
-fn default_search_shortcut() -> Option<String> {
-  Some("ALT+SPACE".to_string())
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct LegacyConfig {
-  #[serde(default = "default_name")]
-  pub name: String,
-
-  #[serde(default = "default_parent")]
-  pub parent: String,
-
-  #[serde(default = "default_filter_title")]
-  pub filter_title: String,
-
-  #[serde(default = "default_filter_class")]
-  pub filter_class: String,
-
-  #[serde(default = "default_filter_exec")]
-  pub filter_exec: String,
-
-  #[serde(default = "default_log_level")]
-  pub log_level: i32,
-
-  #[serde(default = "default_conflict_check")]
-  pub conflict_check: bool,
-
-  #[serde(default = "default_ipc_server_port")]
-  pub ipc_server_port: i32,
-
-  #[serde(default = "default_worker_ipc_server_port")]
-  pub worker_ipc_server_port: i32,
-
-  #[serde(default = "default_use_system_agent")]
-  pub use_system_agent: bool,
-
-  #[serde(default = "default_config_caching_interval")]
-  pub config_caching_interval: i32,
-
-  #[serde(default = "default_word_separators")]
-  pub word_separators: Vec<char>,
-
-  #[serde(default = "default_toggle_key")]
-  pub toggle_key: KeyModifier,
-
-  #[serde(default = "default_toggle_interval")]
-  pub toggle_interval: u32,
-
-  #[serde(default = "default_preserve_clipboard")]
-  pub preserve_clipboard: bool,
-
-  #[serde(default = "default_passive_match_regex")]
-  pub passive_match_regex: String,
-
-  #[serde(default = "default_passive_arg_delimiter")]
-  pub passive_arg_delimiter: char,
-
-  #[serde(default = "default_passive_arg_escape")]
-  pub passive_arg_escape: char,
-
-  #[serde(default = "default_passive_key")]
-  pub passive_key: KeyModifier,
-
-  #[serde(default = "default_passive_delay")]
-  pub passive_delay: u64,
-
-  #[serde(default = "default_enable_passive")]
-  pub enable_passive: bool,
-
-  #[serde(default = "default_enable_active")]
-  pub enable_active: bool,
-
-  #[serde(default = "default_undo_backspace")]
-  pub undo_backspace: bool,
-
-  #[serde(default)]
-  pub paste_shortcut: PasteShortcut,
-
-  #[serde(default = "default_backspace_limit")]
-  pub backspace_limit: i32,
-
-  #[serde(default = "default_restore_clipboard_delay")]
-  pub restore_clipboard_delay: i32,
-
-  #[serde(default = "default_secure_input_watcher_enabled")]
-  pub secure_input_watcher_enabled: bool,
-
-  #[serde(default = "default_secure_input_watcher_interval")]
-  pub secure_input_watcher_interval: i32,
-
-  #[serde(default = "default_post_inject_delay")]
-  pub post_inject_delay: u64,
-
-  #[serde(default = "default_secure_input_notification")]
-  pub secure_input_notification: bool,
-
-  #[serde(default)]
-  pub backend: BackendType,
-
-  #[serde(default = "default_exclude_default_entries")]
-  pub exclude_default_entries: bool,
-
-  #[serde(default = "default_show_notifications")]
-  pub show_notifications: bool,
-
-  #[serde(default = "default_show_icon")]
-  pub show_icon: bool,
-
-  #[serde(default = "default_fast_inject")]
-  pub fast_inject: bool,
-
-  #[serde(default = "default_backspace_delay")]
-  pub backspace_delay: i32,
-
-  #[serde(default = "default_inject_delay")]
-  pub inject_delay: i32,
-
-  #[serde(default = "default_auto_restart")]
-  pub auto_restart: bool,
-
-  #[serde(default = "default_matches")]
-  pub matches: Vec<Value>,
-
-  #[serde(default = "default_global_vars")]
-  pub global_vars: Vec<Value>,
-
-  #[serde(default = "default_modulo_path")]
-  pub modulo_path: Option<String>,
-
-  #[serde(default = "default_search_trigger")]
-  pub search_trigger: Option<String>,
-
-  #[serde(default = "default_search_shortcut")]
-  pub search_shortcut: Option<String>,
-
-  #[serde(default = "default_wait_for_modifiers_release")]
-  pub wait_for_modifiers_release: bool,
-}
-
-// Macro used to validate config fields
-#[macro_export]
-macro_rules! validate_field {
-    ($result:expr, $field:expr, $def_value:expr) => {
-        if $field != $def_value {
-            let mut field_name = stringify!($field);
-            if field_name.starts_with("self.") {
-                field_name = &field_name[5..];  // Remove the 'self.' prefix
-            }
-            error!("Validation error, parameter '{}' is reserved and can be only used in the default.yml config file", field_name);
-            $result = false;
-        }
-    };
-}
-
-impl LegacyConfig {
-  /*
-   * Validate the Config instance.
-   * It makes sure that user defined config instances do not define
-   * attributes reserved to the default config.
-   */
-  fn validate_user_defined_config(&self) -> bool {
-    let mut result = true;
-
-    validate_field!(
-      result,
-      self.config_caching_interval,
-      default_config_caching_interval()
-    );
-    validate_field!(result, self.log_level, default_log_level());
-    validate_field!(result, self.conflict_check, default_conflict_check());
-    validate_field!(result, self.toggle_key, default_toggle_key());
-    validate_field!(result, self.toggle_interval, default_toggle_interval());
-    validate_field!(result, self.backspace_limit, default_backspace_limit());
-    validate_field!(result, self.ipc_server_port, default_ipc_server_port());
-    validate_field!(result, self.use_system_agent, default_use_system_agent());
-    validate_field!(
-      result,
-      self.preserve_clipboard,
-      default_preserve_clipboard()
-    );
-    validate_field!(
-      result,
-      self.passive_match_regex,
-      default_passive_match_regex()
-    );
-    validate_field!(
-      result,
-      self.passive_arg_delimiter,
-      default_passive_arg_delimiter()
-    );
-    validate_field!(
-      result,
-      self.passive_arg_escape,
-      default_passive_arg_escape()
-    );
-    validate_field!(result, self.passive_key, default_passive_key());
-    validate_field!(
-      result,
-      self.restore_clipboard_delay,
-      default_restore_clipboard_delay()
-    );
-    validate_field!(
-      result,
-      self.secure_input_watcher_enabled,
-      default_secure_input_watcher_enabled()
-    );
-    validate_field!(
-      result,
-      self.secure_input_watcher_interval,
-      default_secure_input_watcher_interval()
-    );
-    validate_field!(
-      result,
-      self.secure_input_notification,
-      default_secure_input_notification()
-    );
-    validate_field!(
-      result,
-      self.show_notifications,
-      default_show_notifications()
-    );
-    validate_field!(result, self.show_icon, default_show_icon());
-
-    result
-  }
-}
-
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Default)]
-pub enum BackendType {
-  Inject,
-  Clipboard,
-  #[default]
-  Auto,
-}
-
-impl LegacyConfig {
-  fn load_config(path: &Path) -> Result<LegacyConfig, ConfigLoadError> {
-    let file_res = File::open(path);
-    if let Ok(mut file) = file_res {
-      let mut contents = String::new();
-      let res = file.read_to_string(&mut contents);
-
-      if res.is_err() {
-        return Err(ConfigLoadError::UnableToReadFile);
-      }
-
-      let config_res = serde_yaml::from_str(&contents);
-
-      match config_res {
-        Ok(config) => Ok(config),
-        Err(e) => Err(ConfigLoadError::InvalidYAML(path.to_owned(), e.to_string())),
-      }
-    } else {
-      eprintln!("Error: Cannot load file {path:?}");
-      Err(ConfigLoadError::FileNotFound)
-    }
-  }
-
-  fn merge_overwrite(&mut self, new_config: LegacyConfig) {
-    // Merge matches
-    let mut merged_matches = new_config.matches;
-    let mut match_trigger_set = HashSet::new();
-    for m in &merged_matches {
-      match_trigger_set.extend(triggers_for_match(m));
-    }
-    let parent_matches: Vec<Value> = self
-      .matches
-      .iter()
-      .filter(|&m| {
-        !triggers_for_match(m)
-          .iter()
-          .any(|trigger| match_trigger_set.contains(trigger))
-      })
-      .cloned()
-      .collect();
-
-    merged_matches.extend(parent_matches);
-    self.matches = merged_matches;
-
-    // Merge global variables
-    let mut merged_global_vars = new_config.global_vars;
-    let mut vars_name_set = HashSet::new();
-    for m in &merged_global_vars {
-      vars_name_set.insert(name_for_global_var(m));
-    }
-    let parent_vars: Vec<Value> = self
-      .global_vars
-      .iter()
-      .filter(|&m| !vars_name_set.contains(&name_for_global_var(m)))
-      .cloned()
-      .collect();
-
-    merged_global_vars.extend(parent_vars);
-    self.global_vars = merged_global_vars;
-  }
-
-  fn merge_no_overwrite(&mut self, default: &LegacyConfig) {
-    // Merge matches
-    let mut match_trigger_set = HashSet::new();
-    self.matches.iter().for_each(|m| {
-      match_trigger_set.extend(triggers_for_match(m));
-    });
-    let default_matches: Vec<Value> = default
-      .matches
-      .iter()
-      .filter(|&m| {
-        !triggers_for_match(m)
-          .iter()
-          .any(|trigger| match_trigger_set.contains(trigger))
-      })
-      .cloned()
-      .collect();
-
-    self.matches.extend(default_matches);
-
-    // Merge global variables
-    let mut vars_name_set = HashSet::new();
-    self.global_vars.iter().for_each(|m| {
-      vars_name_set.insert(name_for_global_var(m));
-    });
-    let default_vars: Vec<Value> = default
-      .global_vars
-      .iter()
-      .filter(|&m| !vars_name_set.contains(&name_for_global_var(m)))
-      .cloned()
-      .collect();
-
-    self.global_vars.extend(default_vars);
-  }
-}
-
-fn triggers_for_match(m: &Value) -> Vec<String> {
-  if let Some(triggers) = m.get("triggers").and_then(|v| v.as_sequence()) {
-    triggers
-      .iter()
-      .filter_map(|v| v.as_str().map(ToString::to_string))
-      .collect()
-  } else if let Some(trigger) = m.get("trigger").and_then(|v| v.as_str()) {
-    vec![trigger.to_string()]
-  } else {
-    vec![]
-  }
-}
-
-fn name_for_global_var(v: &Value) -> String {
-  v.get("name")
-    .and_then(|v| v.as_str())
-    .expect("global var is missing name field")
-    .to_string()
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct LegacyConfigSet {
-  pub default: LegacyConfig,
-  pub specific: Vec<LegacyConfig>,
-}
-
-impl LegacyConfigSet {
-  pub fn load(config_dir: &Path, package_dir: &Path) -> Result<LegacyConfigSet, ConfigLoadError> {
-    if !config_dir.is_dir() {
-      return Err(ConfigLoadError::InvalidConfigDirectory);
-    }
-
-    // Load default configuration
-    let default_file = config_dir.join(DEFAULT_CONFIG_FILE_NAME);
-    let default = LegacyConfig::load_config(default_file.as_path())?;
-
-    // Analyze which config files have to be loaded
-
-    let mut target_files = Vec::new();
-
-    let specific_dir = config_dir.join(USER_CONFIGS_FOLDER_NAME);
-    if specific_dir.exists() {
-      let dir_entry = WalkDir::new(specific_dir);
-      target_files.extend(dir_entry);
-    }
-
-    let package_files = if package_dir.exists() {
-      let dir_entry = WalkDir::new(package_dir);
-      dir_entry.into_iter().collect()
-    } else {
-      Vec::new()
-    };
-
-    // Load the user defined config files
-
-    let mut name_set = HashSet::new();
-    let mut children_map: HashMap<String, Vec<LegacyConfig>> = HashMap::new();
-    let mut package_map: HashMap<String, Vec<LegacyConfig>> = HashMap::new();
-    let mut root_configs = vec![default];
-
-    let mut file_loader = |entry: walkdir::Result<DirEntry>,
-                           dest_map: &mut HashMap<String, Vec<LegacyConfig>>|
-     -> Result<(), ConfigLoadError> {
-      match entry {
-        Ok(entry) => {
-          let path = entry.path();
-
-          // Skip non-yaml config files
-          if path
-            .extension()
-            .unwrap_or_default()
-            .to_str()
-            .unwrap_or_default()
-            != "yml"
-          {
-            return Ok(());
-          }
-
-          // Skip hidden files
-          if path
-            .file_name()
-            .unwrap_or_default()
-            .to_str()
-            .unwrap_or_default()
-            .starts_with('.')
-          {
-            return Ok(());
-          }
-
-          let mut config = LegacyConfig::load_config(path)?;
-
-          // Make sure the config does not contain reserved fields
-          if !config.validate_user_defined_config() {
-            return Err(ConfigLoadError::InvalidParameter(path.to_owned()));
-          }
-
-          // No name specified, defaulting to the path name
-          if config.name == "default" {
-            path
-              .to_str()
-              .unwrap_or_default()
-              .clone_into(&mut config.name);
-          }
-
-          if name_set.contains(&config.name) {
-            return Err(ConfigLoadError::NameDuplicate(path.to_owned()));
-          }
-
-          name_set.insert(config.name.clone());
-
-          if config.parent == "self" {
-            // No parent, root config
-            root_configs.push(config);
-          } else {
-            // Children config
-            let children_vec = dest_map.entry(config.parent.clone()).or_default();
-            children_vec.push(config);
-          }
-        }
-        Err(e) => {
-          eprintln!("Warning: Unable to read config file: {e}");
-        }
-      }
-
-      Ok(())
-    };
-
-    // Load the default and user specific configs
-    for entry in target_files {
-      file_loader(entry, &mut children_map)?;
-    }
-
-    // Load the package related configs
-    for entry in package_files {
-      file_loader(entry, &mut package_map)?;
-    }
-
-    // Merge the children config files
-    let mut configs_without_packages = Vec::new();
-    for root_config in root_configs {
-      let config = LegacyConfigSet::reduce_configs(root_config, &children_map, true);
-      configs_without_packages.push(config);
-    }
-
-    // Merge package files
-    // Note: we need two different steps as the packages have a lower priority
-    //       than configs.
-    let mut configs = Vec::new();
-    for root_config in configs_without_packages {
-      let config = LegacyConfigSet::reduce_configs(root_config, &package_map, false);
-      configs.push(config);
-    }
-
-    // Separate default from specific
-    let default = configs.first().unwrap().clone();
-    let mut specific = configs[1..].to_vec();
-
-    // Add default entries to specific configs when needed
-    for config in &mut specific {
-      if !config.exclude_default_entries {
-        config.merge_no_overwrite(&default);
-      }
-    }
-
-    // Check if some triggers are conflicting with each other
-    // For more information, see: https://github.com/espanso/espanso/issues/135
-    if default.conflict_check {
-      let has_conflicts = Self::has_conflicts(&default, &specific);
-      if has_conflicts {
-        eprintln!("Warning: some triggers had conflicts and may not behave as intended");
-        eprintln!("To turn off this check, add \"conflict_check: false\" in the configuration");
-      }
-    }
-
-    Ok(LegacyConfigSet { default, specific })
-  }
-
-  fn reduce_configs(
-    target: LegacyConfig,
-    children_map: &HashMap<String, Vec<LegacyConfig>>,
-    higher_priority: bool,
-  ) -> LegacyConfig {
-    if children_map.contains_key(&target.name) {
-      let mut target = target;
-      for children in children_map.get(&target.name).unwrap() {
-        let children = Self::reduce_configs(children.clone(), children_map, higher_priority);
-        if higher_priority {
-          target.merge_overwrite(children);
-        } else {
-          target.merge_no_overwrite(&children);
-        }
-      }
-      target
-    } else {
-      target
-    }
-  }
-
-  fn has_conflicts(default: &LegacyConfig, specific: &[LegacyConfig]) -> bool {
-    let mut sorted_triggers: Vec<String> = default
-      .matches
-      .iter()
-      .flat_map(triggers_for_match)
-      .collect();
-    sorted_triggers.sort();
-
-    let mut has_conflicts = Self::list_has_conflicts(&sorted_triggers);
-
-    for s in specific {
-      let mut specific_triggers: Vec<String> =
-        s.matches.iter().flat_map(triggers_for_match).collect();
-      specific_triggers.sort();
-      has_conflicts |= Self::list_has_conflicts(&specific_triggers);
-    }
-
-    has_conflicts
-  }
-
-  fn list_has_conflicts(sorted_list: &[String]) -> bool {
-    if sorted_list.len() <= 1 {
-      return false;
-    }
-
-    let mut has_conflicts = false;
-
-    for (i, item) in sorted_list.iter().skip(1).enumerate() {
-      let previous = &sorted_list[i];
-      if item.starts_with(previous) {
-        has_conflicts = true;
-        eprintln!(
-          "Warning: trigger '{item}' is conflicting with '{previous}' and may not behave as intended"
-        );
-      }
-    }
-
-    has_conflicts
-  }
-}
-
-// Error handling
-#[allow(dead_code)]
-// `UnableToCreateDefaultConfig` error is constructed when we need to implemented
-#[derive(Debug, PartialEq, Eq)]
-pub(crate) enum ConfigLoadError {
-  FileNotFound,
-  UnableToReadFile,
-  InvalidYAML(PathBuf, String),
-  InvalidConfigDirectory,
-  InvalidParameter(PathBuf),
-  NameDuplicate(PathBuf),
-  UnableToCreateDefaultConfig,
-}
-
-impl fmt::Display for ConfigLoadError {
-  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-    match self {
-            ConfigLoadError::FileNotFound =>  write!(f, "File not found"),
-            ConfigLoadError::UnableToReadFile =>  write!(f, "Unable to read config file"),
-            ConfigLoadError::InvalidYAML(path, e) => write!(f, "Error parsing YAML file '{}', invalid syntax: {}", path.to_str().unwrap_or_default(), e),
-            ConfigLoadError::InvalidConfigDirectory =>  write!(f, "Invalid config directory"),
-            ConfigLoadError::InvalidParameter(path) =>  write!(f, "Invalid parameter in '{}', use of reserved parameters in used defined configs is not permitted", path.to_str().unwrap_or_default()),
-            ConfigLoadError::NameDuplicate(path) =>  write!(f, "Found duplicate 'name' in '{}', please use different names", path.to_str().unwrap_or_default()),
-            ConfigLoadError::UnableToCreateDefaultConfig =>  write!(f, "Could not generate default config file"),
-        }
-  }
-}
-
-impl Error for ConfigLoadError {
-  fn description(&self) -> &str {
-    match self {
-      ConfigLoadError::FileNotFound => "File not found",
-      ConfigLoadError::UnableToReadFile => "Unable to read config file",
-      ConfigLoadError::InvalidYAML(_, _) => "Error parsing YAML file, invalid syntax",
-      ConfigLoadError::InvalidConfigDirectory => "Invalid config directory",
-      ConfigLoadError::InvalidParameter(_) => {
-        "Invalid parameter, use of reserved parameters in user defined configs is not permitted"
-      }
-      ConfigLoadError::NameDuplicate(_) => {
-        "Found duplicate 'name' in some configurations, please use different names"
-      }
-      ConfigLoadError::UnableToCreateDefaultConfig => "Could not generate default config file",
-    }
-  }
-}
-
-#[cfg(test)]
-mod tests {
-  use super::*;
-  use std::fs;
-  use std::fs::create_dir_all;
-  use std::io::Write;
-  use tempfile::{NamedTempFile, TempDir};
-
-  const DEFAULT_CONFIG_FILE_CONTENT: &str = include_str!("res/test/default.yml");
-  const TEST_WORKING_CONFIG_FILE: &str = include_str!("res/test/working_config.yml");
-  const TEST_CONFIG_FILE_WITH_BAD_YAML: &str = include_str!("res/test/config_with_bad_yaml.yml");
-
-  // Test Configs
-
-  fn replace_for_match(m: &Value) -> String {
-    m.get("replace")
-      .and_then(|v| v.as_str())
-      .expect("match is missing replace field")
-      .to_string()
-  }
-
-  fn create_tmp_file(string: &str) -> NamedTempFile {
-    let file = NamedTempFile::new().unwrap();
-    file.as_file().write_all(string.as_bytes()).unwrap();
-    file
-  }
-
-  #[test]
-  fn test_config_file_not_found() {
-    let config = LegacyConfig::load_config(Path::new("invalid/path"));
-    assert!(config.is_err());
-    assert_eq!(config.unwrap_err(), ConfigLoadError::FileNotFound);
-  }
-
-  #[test]
-  fn test_config_file_with_bad_yaml_syntax() {
-    let broken_config_file = create_tmp_file(TEST_CONFIG_FILE_WITH_BAD_YAML);
-    let config = LegacyConfig::load_config(broken_config_file.path());
-    match config {
-      Ok(_) => unreachable!(),
-      Err(e) => match e {
-        ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, broken_config_file.path().to_owned()),
-        _ => unreachable!(),
-      },
-    }
-  }
-
-  #[test]
-  fn test_validate_field_macro() {
-    let mut result = true;
-
-    validate_field!(result, 3, 3);
-    assert!(result);
-
-    validate_field!(result, 10, 3);
-    assert!(!result);
-
-    validate_field!(result, 3, 3);
-    assert!(!result);
-  }
-
-  #[test]
-  fn test_user_defined_config_does_not_have_reserved_fields() {
-    let working_config_file = create_tmp_file(
-      r"
-
-        backend: Clipboard
-
-        ",
-    );
-    let config = LegacyConfig::load_config(working_config_file.path());
-    assert!(config.unwrap().validate_user_defined_config());
-  }
-
-  #[test]
-  fn test_user_defined_config_has_reserved_fields_config_caching_interval() {
-    let working_config_file = create_tmp_file(
-      r"
-
-        # This should not happen in an app-specific config
-        config_caching_interval: 100
-
-        ",
-    );
-    let config = LegacyConfig::load_config(working_config_file.path());
-    assert!(!config.unwrap().validate_user_defined_config());
-  }
-
-  #[test]
-  fn test_user_defined_config_has_reserved_fields_toggle_key() {
-    let working_config_file = create_tmp_file(
-      r"
-
-        # This should not happen in an app-specific config
-        toggle_key: CTRL
-
-        ",
-    );
-    let config = LegacyConfig::load_config(working_config_file.path());
-    assert!(!config.unwrap().validate_user_defined_config());
-  }
-
-  #[test]
-  fn test_user_defined_config_has_reserved_fields_toggle_interval() {
-    let working_config_file = create_tmp_file(
-      r"
-
-        # This should not happen in an app-specific config
-        toggle_interval: 1000
-
-        ",
-    );
-    let config = LegacyConfig::load_config(working_config_file.path());
-    assert!(!config.unwrap().validate_user_defined_config());
-  }
-
-  #[test]
-  fn test_user_defined_config_has_reserved_fields_backspace_limit() {
-    let working_config_file = create_tmp_file(
-      r"
-
-        # This should not happen in an app-specific config
-        backspace_limit: 10
-
-        ",
-    );
-    let config = LegacyConfig::load_config(working_config_file.path());
-    assert!(!config.unwrap().validate_user_defined_config());
-  }
-
-  #[test]
-  fn test_config_loaded_correctly() {
-    let working_config_file = create_tmp_file(TEST_WORKING_CONFIG_FILE);
-    let config = LegacyConfig::load_config(working_config_file.path());
-    assert!(config.is_ok());
-  }
-
-  // Test ConfigSet
-
-  pub fn create_temp_espanso_directories() -> (TempDir, TempDir) {
-    create_temp_espanso_directories_with_default_content(DEFAULT_CONFIG_FILE_CONTENT)
-  }
-
-  pub fn create_temp_espanso_directories_with_default_content(
-    default_content: &str,
-  ) -> (TempDir, TempDir) {
-    let data_dir = TempDir::new().expect("unable to create data directory");
-    let package_dir = TempDir::new().expect("unable to create package directory");
-
-    let default_path = data_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
-    fs::write(default_path, default_content).unwrap();
-
-    (data_dir, package_dir)
-  }
-
-  pub fn create_temp_file_in_dir(tmp_dir: &Path, name: &str, content: &str) -> PathBuf {
-    let user_defined_path = tmp_dir.join(name);
-    let user_defined_path_copy = user_defined_path.clone();
-    fs::write(user_defined_path, content).unwrap();
-
-    user_defined_path_copy
-  }
-
-  pub fn create_user_config_file(tmp_dir: &Path, name: &str, content: &str) -> PathBuf {
-    let user_config_dir = tmp_dir.join(USER_CONFIGS_FOLDER_NAME);
-    if !user_config_dir.exists() {
-      create_dir_all(&user_config_dir).unwrap();
-    }
-
-    create_temp_file_in_dir(&user_config_dir, name, content)
-  }
-
-  pub fn create_package_file(
-    package_data_dir: &Path,
-    package_name: &str,
-    filename: &str,
-    content: &str,
-  ) -> PathBuf {
-    let package_dir = package_data_dir.join(package_name);
-    if !package_dir.exists() {
-      create_dir_all(&package_dir).unwrap();
-    }
-
-    create_temp_file_in_dir(&package_dir, filename, content)
-  }
-
-  #[test]
-  fn test_config_set_default_content_should_work_correctly() {
-    let (data_dir, package_dir) = create_temp_espanso_directories();
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
-    assert!(config_set.is_ok());
-  }
-
-  #[test]
-  fn test_config_set_load_fail_bad_directory() {
-    let config_set = LegacyConfigSet::load(Path::new("invalid/path"), Path::new("invalid/path"));
-    assert!(config_set.is_err());
-    assert_eq!(
-      config_set.unwrap_err(),
-      ConfigLoadError::InvalidConfigDirectory
-    );
-  }
-
-  #[test]
-  fn test_config_set_missing_default_file() {
-    let data_dir = TempDir::new().expect("unable to create temp directory");
-    let package_dir = TempDir::new().expect("unable to create package directory");
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
-    assert!(config_set.is_err());
-    assert_eq!(config_set.unwrap_err(), ConfigLoadError::FileNotFound);
-  }
-
-  #[test]
-  fn test_config_set_invalid_yaml_syntax() {
-    let (data_dir, package_dir) =
-      create_temp_espanso_directories_with_default_content(TEST_CONFIG_FILE_WITH_BAD_YAML);
-    let default_path = data_dir.path().join(DEFAULT_CONFIG_FILE_NAME);
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
-    match config_set {
-      Ok(_) => unreachable!(),
-      Err(e) => match e {
-        ConfigLoadError::InvalidYAML(p, _) => assert_eq!(p, default_path),
-        _ => unreachable!(),
-      },
-    }
-  }
-
-  #[test]
-  fn test_config_set_specific_file_with_reserved_fields() {
-    let (data_dir, package_dir) = create_temp_espanso_directories();
-
-    let user_defined_path = create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r"
-        config_caching_interval: 10000
-        ",
-    );
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
-    assert!(config_set.is_err());
-    assert_eq!(
-      config_set.unwrap_err(),
-      ConfigLoadError::InvalidParameter(user_defined_path)
-    );
-  }
-
-  #[test]
-  fn test_config_set_specific_file_missing_name_auto_generated() {
-    let (data_dir, package_dir) = create_temp_espanso_directories();
-
-    let user_defined_path = create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r"
-        backend: Clipboard
-        ",
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
-    assert!(config_set.is_ok());
-    assert_eq!(
-      config_set.unwrap().specific[0].name,
-      user_defined_path.to_str().unwrap_or_default()
-    );
-  }
-
-  #[test]
-  fn test_config_set_specific_file_duplicate_name() {
-    let (data_dir, package_dir) = create_temp_espanso_directories();
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r"
-        name: specific1
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific2.yml",
-      r"
-        name: specific1
-        ",
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path());
-    assert!(config_set.is_err());
-    assert!(matches!(
-      &config_set.unwrap_err(),
-      &ConfigLoadError::NameDuplicate(_)
-    ));
-  }
-
-  #[test]
-  fn test_user_defined_config_set_merge_with_parent_matches() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-        matches:
-            - trigger: ":lol"
-              replace: "LOL"
-            - trigger: ":yess"
-              replace: "Bob"
-        "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific1.yml",
-      r#"
-        name: specific1
-
-        matches:
-            - trigger: "hello"
-              replace: "newstring"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.default.matches.len(), 2);
-    assert_eq!(config_set.specific[0].matches.len(), 3);
-
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|x| triggers_for_match(x)[0] == "hello"));
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|x| triggers_for_match(x)[0] == ":lol"));
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|x| triggers_for_match(x)[0] == ":yess"));
-  }
-
-  #[test]
-  fn test_user_defined_config_set_merge_with_parent_matches_child_priority() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-        matches:
-            - trigger: ":lol"
-              replace: "LOL"
-            - trigger: ":yess"
-              replace: "Bob"
-        "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific2.yml",
-      r#"
-        name: specific1
-
-        matches:
-            - trigger: ":lol"
-              replace: "newstring"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.default.matches.len(), 2);
-    assert_eq!(config_set.specific[0].matches.len(), 2);
-
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|x| { triggers_for_match(x)[0] == ":lol" && replace_for_match(x) == "newstring" }));
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|x| triggers_for_match(x)[0] == ":yess"));
-  }
-
-  #[test]
-  fn test_user_defined_config_set_exclude_merge_with_parent_matches() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-        matches:
-            - trigger: ":lol"
-              replace: "LOL"
-            - trigger: ":yess"
-              replace: "Bob"
-        "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific2.yml",
-      r#"
-        name: specific1
-
-        exclude_default_entries: true
-
-        matches:
-            - trigger: "hello"
-              replace: "newstring"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.default.matches.len(), 2);
-    assert_eq!(config_set.specific[0].matches.len(), 1);
-
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|x| { triggers_for_match(x)[0] == "hello" && replace_for_match(x) == "newstring" }));
-  }
-
-  #[test]
-  fn test_only_yaml_files_are_loaded_from_config() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-            matches:
-                - trigger: ":lol"
-                  replace: "LOL"
-                - trigger: ":yess"
-                  replace: "Bob"
-            "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.zzz",
-      r#"
-        name: specific1
-
-        exclude_default_entries: true
-
-        matches:
-            - trigger: "hello"
-              replace: "newstring"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-  }
-
-  #[test]
-  fn test_hidden_files_are_ignored() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-            matches:
-                - trigger: ":lol"
-                  replace: "LOL"
-                - trigger: ":yess"
-                  replace: "Bob"
-            "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      ".specific.yml",
-      r#"
-        name: specific1
-
-        exclude_default_entries: true
-
-        matches:
-            - trigger: "hello"
-              replace: "newstring"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-  }
-
-  #[test]
-  fn test_config_set_no_parent_configs_works_correctly() {
-    let (data_dir, package_dir) = create_temp_espanso_directories();
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r"
-        name: specific1
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific2.yml",
-      r"
-        name: specific2
-        ",
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 2);
-  }
-
-  #[test]
-  fn test_config_set_default_parent_works_correctly() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        parent: default
-
-        matches:
-            - trigger: "hello"
-              replace: "world"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-    assert_eq!(config_set.default.matches.len(), 2);
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hasta"));
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hello"));
-  }
-
-  #[test]
-  fn test_config_set_no_parent_should_not_merge() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        matches:
-            - trigger: "hello"
-              replace: "world"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 1);
-    assert_eq!(config_set.default.matches.len(), 1);
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hasta"));
-    assert!(!config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hello"));
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hello"));
-  }
-
-  #[test]
-  fn test_config_set_default_nested_parent_works_correctly() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        name: custom1
-        parent: default
-
-        matches:
-            - trigger: "hello"
-              replace: "world"
-        "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific2.yml",
-      r#"
-        parent: custom1
-
-        matches:
-            - trigger: "super"
-              replace: "mario"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-    assert_eq!(config_set.default.matches.len(), 3);
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hasta"));
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hello"));
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "super"));
-  }
-
-  #[test]
-  fn test_config_set_parent_merge_children_priority_should_be_higher() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        parent: default
-
-        matches:
-            - trigger: "hasta"
-              replace: "world"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-    assert_eq!(config_set.default.matches.len(), 1);
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| { triggers_for_match(m)[0] == "hasta" && replace_for_match(m) == "world" }));
-  }
-
-  #[test]
-  fn test_config_set_package_configs_default_merge() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_package_file(
-      package_dir.path(),
-      "package1",
-      "package.yml",
-      r#"
-        parent: default
-
-        matches:
-            - trigger: "harry"
-              replace: "potter"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-    assert_eq!(config_set.default.matches.len(), 2);
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hasta"));
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "harry"));
-  }
-
-  #[test]
-  fn test_config_set_package_configs_lower_priority_than_user() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_package_file(
-      package_dir.path(),
-      "package1",
-      "package.yml",
-      r#"
-        parent: default
-
-        matches:
-            - trigger: "hasta"
-              replace: "potter"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-    assert_eq!(config_set.default.matches.len(), 1);
-    assert_eq!(
-      triggers_for_match(&config_set.default.matches[0])[0],
-      "hasta"
-    );
-    assert_eq!(
-      replace_for_match(&config_set.default.matches[0]),
-      "Hasta la vista"
-    );
-  }
-
-  #[test]
-  fn test_config_set_package_configs_without_merge() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_package_file(
-      package_dir.path(),
-      "package1",
-      "package.yml",
-      r#"
-        matches:
-            - trigger: "harry"
-              replace: "potter"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 1);
-    assert_eq!(config_set.default.matches.len(), 1);
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hasta"));
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "harry"));
-  }
-
-  #[test]
-  fn test_config_set_package_configs_multiple_files() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: hasta
-              replace: Hasta la vista
-        ",
-    );
-
-    create_package_file(
-      package_dir.path(),
-      "package1",
-      "package.yml",
-      r#"
-        name: package1
-
-        matches:
-            - trigger: "harry"
-              replace: "potter"
-        "#,
-    );
-
-    create_package_file(
-      package_dir.path(),
-      "package1",
-      "addon.yml",
-      r#"
-        parent: package1
-
-        matches:
-            - trigger: "ron"
-              replace: "weasley"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 1);
-    assert_eq!(config_set.default.matches.len(), 1);
-    assert!(config_set
-      .default
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "hasta"));
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "harry"));
-    assert!(config_set.specific[0]
-      .matches
-      .iter()
-      .any(|m| triggers_for_match(m)[0] == "ron"));
-  }
-
-  #[test]
-  fn test_list_has_conflict_no_conflict() {
-    assert!(!LegacyConfigSet::list_has_conflicts(&[
-      ":ab".to_owned(),
-      ":bc".to_owned()
-    ]));
-  }
-
-  #[test]
-  fn test_list_has_conflict_conflict() {
-    let mut list = vec!["ac".to_owned(), "ab".to_owned(), "abc".to_owned()];
-    list.sort();
-    assert!(LegacyConfigSet::list_has_conflicts(&list));
-  }
-
-  #[test]
-  fn test_has_conflict_no_conflict() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: ac
-              replace: Hasta la vista
-            - trigger: bc
-              replace: Jon
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        name: specific1
-
-        matches:
-            - trigger: "hello"
-              replace: "world"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert!(!LegacyConfigSet::has_conflicts(
-      &config_set.default,
-      &config_set.specific
-    ),);
-  }
-
-  #[test]
-  fn test_has_conflict_conflict_in_default() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: ac
-              replace: Hasta la vista
-            - trigger: bc
-              replace: Jon
-            - trigger: acb
-              replace: Error
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        name: specific1
-
-        matches:
-            - trigger: "hello"
-              replace: "world"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert!(LegacyConfigSet::has_conflicts(
-      &config_set.default,
-      &config_set.specific
-    ),);
-  }
-
-  #[test]
-  fn test_has_conflict_conflict_in_specific_and_default() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: ac
-              replace: Hasta la vista
-            - trigger: bc
-              replace: Jon
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        name: specific1
-
-        matches:
-            - trigger: "bcd"
-              replace: "Conflict"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert!(LegacyConfigSet::has_conflicts(
-      &config_set.default,
-      &config_set.specific
-    ),);
-  }
-
-  #[test]
-  fn test_has_conflict_no_conflict_in_specific_and_specific() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r"
-        matches:
-            - trigger: ac
-              replace: Hasta la vista
-            - trigger: bc
-              replace: Jon
-        ",
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-        name: specific1
-
-        matches:
-            - trigger: "bad"
-              replace: "Conflict"
-        "#,
-    );
-    create_user_config_file(
-      data_dir.path(),
-      "specific2.yml",
-      r#"
-        name: specific2
-
-        matches:
-            - trigger: "badass"
-              replace: "Conflict"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert!(!LegacyConfigSet::has_conflicts(
-      &config_set.default,
-      &config_set.specific
-    ),);
-  }
-
-  #[test]
-  fn test_config_set_specific_inherits_default_global_vars() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-        global_vars:
-            - name: testvar
-              type: date
-              params:
-                format: "%m"
-        "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-         global_vars:
-            - name: specificvar
-              type: date
-              params:
-                format: "%m"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 1);
-    assert_eq!(config_set.default.global_vars.len(), 1);
-    assert_eq!(config_set.specific[0].global_vars.len(), 2);
-    assert!(config_set.specific[0]
-      .global_vars
-      .iter()
-      .any(|m| name_for_global_var(m) == "testvar"));
-    assert!(config_set.specific[0]
-      .global_vars
-      .iter()
-      .any(|m| name_for_global_var(m) == "specificvar"));
-  }
-
-  #[test]
-  fn test_config_set_default_get_variables_from_specific() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-        global_vars:
-            - name: testvar
-              type: date
-              params:
-                format: "%m"
-        "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-         parent: default
-         global_vars:
-            - name: specificvar
-              type: date
-              params:
-                format: "%m"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 0);
-    assert_eq!(config_set.default.global_vars.len(), 2);
-    assert!(config_set
-      .default
-      .global_vars
-      .iter()
-      .any(|m| name_for_global_var(m) == "testvar"));
-    assert!(config_set
-      .default
-      .global_vars
-      .iter()
-      .any(|m| name_for_global_var(m) == "specificvar"));
-  }
-
-  #[test]
-  fn test_config_set_specific_dont_inherits_default_global_vars_when_exclude_is_on() {
-    let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
-      r#"
-        global_vars:
-            - name: testvar
-              type: date
-              params:
-                format: "%m"
-        "#,
-    );
-
-    create_user_config_file(
-      data_dir.path(),
-      "specific.yml",
-      r#"
-         exclude_default_entries: true
-
-         global_vars:
-            - name: specificvar
-              type: date
-              params:
-                format: "%m"
-        "#,
-    );
-
-    let config_set = LegacyConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
-    assert_eq!(config_set.specific.len(), 1);
-    assert_eq!(config_set.default.global_vars.len(), 1);
-    assert_eq!(config_set.specific[0].global_vars.len(), 1);
-    assert!(config_set.specific[0]
-      .global_vars
-      .iter()
-      .any(|m| name_for_global_var(m) == "specificvar"));
-  }
-}
diff --git a/espanso-config/src/legacy/mod.rs b/espanso-config/src/legacy/mod.rs
deleted file mode 100644
index 01f5923c0..000000000
--- a/espanso-config/src/legacy/mod.rs
+++ /dev/null
@@ -1,722 +0,0 @@
-/*
- * This file is part of espanso.
- *
- * C title: (), class: (), exec: ()opyright (C) 2019-2021 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use anyhow::Result;
-use log::warn;
-use regex::Regex;
-use std::{collections::HashMap, path::Path, sync::Arc};
-
-use self::config::LegacyConfig;
-use crate::matches::{
-  group::loader::yaml::{
-    parse::{YAMLMatch, YAMLVariable},
-    try_convert_into_match, try_convert_into_variable,
-  },
-  MatchEffect,
-};
-use crate::{config::store::DefaultConfigStore, counter::StructId};
-use crate::{
-  config::Config,
-  config::{AppProperties, ConfigStore},
-  counter::next_id,
-  matches::{
-    store::{MatchSet, MatchStore},
-    Match, Variable,
-  },
-};
-use std::convert::TryInto;
-
-mod config;
-mod model;
-
-pub fn load(
-  base_dir: &Path,
-  package_dir: &Path,
-) -> Result<(Box<dyn ConfigStore>, Box<dyn MatchStore>)> {
-  let config_set = config::LegacyConfigSet::load(base_dir, package_dir)?;
-
-  let mut match_deduplicate_map = HashMap::new();
-  let mut var_deduplicate_map = HashMap::new();
-
-  let (default_config, mut default_match_group) = split_config(config_set.default);
-  deduplicate_ids(
-    &mut default_match_group,
-    &mut match_deduplicate_map,
-    &mut var_deduplicate_map,
-  );
-
-  let mut match_groups = HashMap::new();
-  match_groups.insert("default".to_string(), default_match_group);
-
-  let mut custom_configs: Vec<Arc<dyn Config>> = Vec::new();
-  for custom in config_set.specific {
-    let (custom_config, mut custom_match_group) = split_config(custom);
-    deduplicate_ids(
-      &mut custom_match_group,
-      &mut match_deduplicate_map,
-      &mut var_deduplicate_map,
-    );
-
-    match_groups.insert(custom_config.name.clone(), custom_match_group);
-    custom_configs.push(Arc::new(custom_config));
-  }
-
-  let config_store = DefaultConfigStore::from_configs(Arc::new(default_config), custom_configs);
-  let match_store = LegacyMatchStore::new(match_groups);
-
-  Ok((Box::new(config_store), Box::new(match_store)))
-}
-
-fn split_config(config: LegacyConfig) -> (LegacyInteropConfig, LegacyMatchGroup) {
-  let global_vars = config
-    .global_vars
-    .iter()
-    .filter_map(|var| {
-      let var: YAMLVariable = serde_yaml::from_value(var.clone()).ok()?;
-      let (var, warnings) = try_convert_into_variable(var, true).ok()?;
-      for warning in warnings {
-        warn!("{}", warning);
-      }
-      Some(var)
-    })
-    .collect();
-
-  let matches = config
-    .matches
-    .iter()
-    .filter_map(|var| {
-      let m: YAMLMatch = serde_yaml::from_value(var.clone()).ok()?;
-      let (m, warnings) = try_convert_into_match(m, true).ok()?;
-      for warning in warnings {
-        warn!("{}", warning);
-      }
-      Some(m)
-    })
-    .collect();
-
-  let match_group = LegacyMatchGroup {
-    matches,
-    global_vars,
-  };
-
-  let config: LegacyInteropConfig = config.into();
-  (config, match_group)
-}
-
-/// Due to the way the legacy configs are loaded (matches are copied multiple times in the various configs)
-/// we need to deduplicate the ids of those matches (and global vars).
-fn deduplicate_ids(
-  match_group: &mut LegacyMatchGroup,
-  match_map: &mut HashMap<Match, StructId>,
-  var_map: &mut HashMap<Variable, StructId>,
-) {
-  deduplicate_vars(&mut match_group.global_vars, var_map);
-  deduplicate_matches(&mut match_group.matches, match_map, var_map);
-}
-
-fn deduplicate_matches(
-  matches: &mut [Match],
-  match_map: &mut HashMap<Match, StructId>,
-  var_map: &mut HashMap<Variable, StructId>,
-) {
-  for m in matches.iter_mut() {
-    // Deduplicate variables first
-    if let MatchEffect::Text(effect) = &mut m.effect {
-      deduplicate_vars(&mut effect.vars, var_map);
-    }
-
-    let mut m_without_id = m.clone();
-    m_without_id.id = 0;
-    if let Some(id) = match_map.get(&m_without_id) {
-      m.id = *id;
-    } else {
-      match_map.insert(m_without_id, m.id);
-    }
-  }
-}
-
-// TODO: test case of matches with inner variables
-fn deduplicate_vars(vars: &mut [Variable], var_map: &mut HashMap<Variable, StructId>) {
-  for v in vars.iter_mut() {
-    let mut v_without_id = v.clone();
-    v_without_id.id = 0;
-    if let Some(id) = var_map.get(&v_without_id) {
-      v.id = *id;
-    } else {
-      var_map.insert(v_without_id, v.id);
-    }
-  }
-}
-
-struct LegacyInteropConfig {
-  pub name: String,
-  match_paths: Vec<String>,
-
-  id: i32,
-
-  config: LegacyConfig,
-
-  filter_title: Option<Regex>,
-  filter_class: Option<Regex>,
-  filter_exec: Option<Regex>,
-}
-
-impl From<config::LegacyConfig> for LegacyInteropConfig {
-  fn from(config: config::LegacyConfig) -> Self {
-    Self {
-      id: next_id(),
-      config: config.clone(),
-      name: config.name.clone(),
-      match_paths: vec![config.name],
-      filter_title: if config.filter_title.is_empty() {
-        None
-      } else {
-        Regex::new(&config.filter_title).ok()
-      },
-      filter_class: if config.filter_class.is_empty() {
-        None
-      } else {
-        Regex::new(&config.filter_class).ok()
-      },
-      filter_exec: if config.filter_exec.is_empty() {
-        None
-      } else {
-        Regex::new(&config.filter_exec).ok()
-      },
-    }
-  }
-}
-
-impl Config for LegacyInteropConfig {
-  fn id(&self) -> i32 {
-    self.id
-  }
-
-  fn label(&self) -> &str {
-    &self.config.name
-  }
-
-  fn backend(&self) -> crate::config::Backend {
-    match self.config.backend {
-      config::BackendType::Inject => crate::config::Backend::Inject,
-      config::BackendType::Clipboard => crate::config::Backend::Clipboard,
-      config::BackendType::Auto => crate::config::Backend::Auto,
-    }
-  }
-
-  fn auto_restart(&self) -> bool {
-    self.config.auto_restart
-  }
-
-  fn match_paths(&self) -> &[String] {
-    &self.match_paths
-  }
-
-  fn is_match(&self, app: &AppProperties) -> bool {
-    if self.filter_title.is_none() && self.filter_exec.is_none() && self.filter_class.is_none() {
-      return false;
-    }
-
-    let is_title_match = if let Some(title_regex) = self.filter_title.as_ref() {
-      if let Some(title) = app.title {
-        title_regex.is_match(title)
-      } else {
-        false
-      }
-    } else {
-      true
-    };
-
-    let is_exec_match = if let Some(exec_regex) = self.filter_exec.as_ref() {
-      if let Some(exec) = app.exec {
-        exec_regex.is_match(exec)
-      } else {
-        false
-      }
-    } else {
-      true
-    };
-
-    let is_class_match = if let Some(class_regex) = self.filter_class.as_ref() {
-      if let Some(class) = app.class {
-        class_regex.is_match(class)
-      } else {
-        false
-      }
-    } else {
-      true
-    };
-
-    // All the filters that have been specified must be true to define a match
-    is_exec_match && is_title_match && is_class_match
-  }
-
-  fn clipboard_threshold(&self) -> usize {
-    crate::config::default::DEFAULT_CLIPBOARD_THRESHOLD
-  }
-
-  fn pre_paste_delay(&self) -> usize {
-    crate::config::default::DEFAULT_PRE_PASTE_DELAY
-  }
-
-  fn toggle_key(&self) -> Option<crate::config::ToggleKey> {
-    match self.config.toggle_key {
-      model::KeyModifier::CTRL => Some(crate::config::ToggleKey::Ctrl),
-      model::KeyModifier::SHIFT => Some(crate::config::ToggleKey::Shift),
-      model::KeyModifier::ALT => Some(crate::config::ToggleKey::Alt),
-      model::KeyModifier::META => Some(crate::config::ToggleKey::Meta),
-      model::KeyModifier::BACKSPACE => None,
-      model::KeyModifier::OFF => None,
-      model::KeyModifier::LEFT_CTRL => Some(crate::config::ToggleKey::LeftCtrl),
-      model::KeyModifier::RIGHT_CTRL => Some(crate::config::ToggleKey::RightCtrl),
-      model::KeyModifier::LEFT_ALT => Some(crate::config::ToggleKey::LeftAlt),
-      model::KeyModifier::RIGHT_ALT => Some(crate::config::ToggleKey::RightAlt),
-      model::KeyModifier::LEFT_META => Some(crate::config::ToggleKey::LeftMeta),
-      model::KeyModifier::RIGHT_META => Some(crate::config::ToggleKey::RightMeta),
-      model::KeyModifier::LEFT_SHIFT => Some(crate::config::ToggleKey::LeftShift),
-      model::KeyModifier::RIGHT_SHIFT => Some(crate::config::ToggleKey::RightShift),
-      model::KeyModifier::CAPS_LOCK => None,
-    }
-  }
-
-  fn preserve_clipboard(&self) -> bool {
-    self.config.preserve_clipboard
-  }
-
-  fn restore_clipboard_delay(&self) -> usize {
-    self.config.restore_clipboard_delay.try_into().unwrap()
-  }
-
-  fn paste_shortcut_event_delay(&self) -> usize {
-    crate::config::default::DEFAULT_SHORTCUT_EVENT_DELAY
-  }
-
-  fn paste_shortcut(&self) -> Option<String> {
-    match self.config.paste_shortcut {
-      model::PasteShortcut::Default => None,
-      model::PasteShortcut::CtrlV => Some("CTRL+V".to_string()),
-      model::PasteShortcut::CtrlShiftV => Some("CTRL+SHIFT+V".to_string()),
-      model::PasteShortcut::ShiftInsert => Some("SHIFT+INSERT".to_string()),
-      model::PasteShortcut::CtrlAltV => Some("CTRL+ALT+V".to_string()),
-      model::PasteShortcut::MetaV => Some("META+V".to_string()),
-    }
-  }
-
-  fn disable_x11_fast_inject(&self) -> bool {
-    !self.config.fast_inject
-  }
-
-  fn inject_delay(&self) -> Option<usize> {
-    if self.config.inject_delay == 0 {
-      None
-    } else {
-      Some(self.config.inject_delay.try_into().unwrap())
-    }
-  }
-
-  fn key_delay(&self) -> Option<usize> {
-    if self.config.backspace_delay == 0 {
-      None
-    } else {
-      Some(self.config.backspace_delay.try_into().unwrap())
-    }
-  }
-
-  fn word_separators(&self) -> Vec<String> {
-    self
-      .config
-      .word_separators
-      .iter()
-      .map(|c| String::from(*c))
-      .collect()
-  }
-
-  fn backspace_limit(&self) -> usize {
-    self.config.backspace_limit.try_into().unwrap()
-  }
-
-  fn apply_patch(&self) -> bool {
-    true
-  }
-
-  fn keyboard_layout(&self) -> Option<crate::config::RMLVOConfig> {
-    None
-  }
-
-  fn search_trigger(&self) -> Option<String> {
-    self.config.search_trigger.clone()
-  }
-
-  fn search_shortcut(&self) -> Option<String> {
-    self.config.search_shortcut.clone()
-  }
-
-  fn undo_backspace(&self) -> bool {
-    self.config.undo_backspace
-  }
-
-  fn show_icon(&self) -> bool {
-    self.config.show_icon
-  }
-
-  fn show_notifications(&self) -> bool {
-    self.config.show_notifications
-  }
-
-  fn secure_input_notification(&self) -> bool {
-    self.config.secure_input_notification
-  }
-
-  fn enable(&self) -> bool {
-    self.config.enable_active
-  }
-
-  fn post_form_delay(&self) -> usize {
-    crate::config::default::DEFAULT_POST_FORM_DELAY
-  }
-
-  fn max_form_width(&self) -> usize {
-    700
-  }
-
-  fn max_form_height(&self) -> usize {
-    500
-  }
-
-  fn post_search_delay(&self) -> usize {
-    crate::config::default::DEFAULT_POST_SEARCH_DELAY
-  }
-
-  fn emulate_alt_codes(&self) -> bool {
-    false
-  }
-
-  fn win32_exclude_orphan_events(&self) -> bool {
-    true
-  }
-
-  fn evdev_modifier_delay(&self) -> Option<usize> {
-    Some(10)
-  }
-
-  fn win32_keyboard_layout_cache_interval(&self) -> i64 {
-    2000
-  }
-
-  fn x11_use_xclip_backend(&self) -> bool {
-    false
-  }
-
-  fn x11_use_xdotool_backend(&self) -> bool {
-    false
-  }
-}
-
-struct LegacyMatchGroup {
-  matches: Vec<Match>,
-  global_vars: Vec<Variable>,
-}
-
-struct LegacyMatchStore {
-  groups: HashMap<String, LegacyMatchGroup>,
-}
-
-impl LegacyMatchStore {
-  pub fn new(groups: HashMap<String, LegacyMatchGroup>) -> Self {
-    Self { groups }
-  }
-}
-
-impl MatchStore for LegacyMatchStore {
-  fn query(&self, paths: &[String]) -> MatchSet {
-    let group = if paths.is_empty() {
-      None
-    } else {
-      self.groups.get(&paths[0])
-    };
-
-    if let Some(group) = group {
-      MatchSet {
-        matches: group.matches.iter().collect(),
-        global_vars: group.global_vars.iter().collect(),
-      }
-    } else {
-      MatchSet {
-        matches: Vec::new(),
-        global_vars: Vec::new(),
-      }
-    }
-  }
-
-  fn loaded_paths(&self) -> Vec<String> {
-    self.groups.keys().cloned().collect()
-  }
-}
-
-#[cfg(test)]
-mod tests {
-  use super::*;
-  use std::{fs::create_dir_all, path::Path};
-  use tempdir::TempDir;
-
-  pub fn use_test_directory(callback: impl FnOnce(&Path, &Path, &Path)) {
-    let dir = TempDir::new("tempconfig").unwrap();
-    let user_dir = dir.path().join("user");
-    create_dir_all(&user_dir).unwrap();
-
-    let package_dir = TempDir::new("tempconfig").unwrap();
-
-    callback(
-      &dunce::canonicalize(dir.path()).unwrap(),
-      &dunce::canonicalize(&user_dir).unwrap(),
-      &dunce::canonicalize(package_dir.path()).unwrap(),
-    );
-  }
-
-  #[test]
-  fn load_legacy_works_correctly() {
-    use_test_directory(|base, user, packages| {
-      std::fs::write(
-        base.join("default.yml"),
-        r#"
-      backend: Clipboard
-
-      global_vars:
-        - name: var1
-          type: test
-
-      matches:
-        - trigger: "hello"
-          replace: "world"
-      "#,
-      )
-      .unwrap();
-
-      std::fs::write(
-        user.join("specific.yml"),
-        r#"
-      name: specific
-      parent: default
-
-      matches:
-        - trigger: "foo"
-          replace: "bar"
-      "#,
-      )
-      .unwrap();
-
-      std::fs::write(
-        user.join("separate.yml"),
-        r#"
-      name: separate
-      filter_title: "Google"
-
-      matches:
-        - trigger: "eren"
-          replace: "mikasa"
-      "#,
-      )
-      .unwrap();
-
-      let (config_store, match_store) = load(base, packages).unwrap();
-
-      let default_config = config_store.default();
-      assert_eq!(default_config.match_paths().len(), 1);
-
-      let active_config = config_store.active(&AppProperties {
-        title: Some("Google"),
-        class: None,
-        exec: None,
-      });
-      assert_eq!(active_config.match_paths().len(), 1);
-
-      let default_fallback = config_store.active(&AppProperties {
-        title: Some("Yahoo"),
-        class: None,
-        exec: None,
-      });
-      assert_eq!(default_fallback.match_paths().len(), 1);
-
-      assert_eq!(
-        match_store
-          .query(default_config.match_paths())
-          .matches
-          .len(),
-        2
-      );
-      assert_eq!(
-        match_store
-          .query(default_config.match_paths())
-          .global_vars
-          .len(),
-        1
-      );
-
-      assert_eq!(
-        match_store.query(active_config.match_paths()).matches.len(),
-        3
-      );
-      assert_eq!(
-        match_store
-          .query(active_config.match_paths())
-          .global_vars
-          .len(),
-        1
-      );
-
-      assert_eq!(
-        match_store
-          .query(default_fallback.match_paths())
-          .matches
-          .len(),
-        2
-      );
-      assert_eq!(
-        match_store
-          .query(default_fallback.match_paths())
-          .global_vars
-          .len(),
-        1
-      );
-    });
-  }
-
-  #[test]
-  fn load_legacy_deduplicates_ids_correctly() {
-    use_test_directory(|base, user, packages| {
-      std::fs::write(
-        base.join("default.yml"),
-        r#"
-      backend: Clipboard
-
-      global_vars:
-        - name: var1
-          type: test
-
-      matches:
-        - trigger: "hello"
-          replace: "world"
-
-        - trigger: "withvars"
-          replace: "{{output}}"
-          vars:
-            - name: "output"
-              type: "echo"
-              params:
-                echo: "test"
-      "#,
-      )
-      .unwrap();
-
-      std::fs::write(
-        user.join("specific.yml"),
-        r#"
-      name: specific
-      filter_title: "Google"
-      "#,
-      )
-      .unwrap();
-
-      let (config_store, match_store) = load(base, packages).unwrap();
-
-      let default_config = config_store.default();
-      let active_config = config_store.active(&AppProperties {
-        title: Some("Google"),
-        class: None,
-        exec: None,
-      });
-
-      for (i, m) in match_store
-        .query(default_config.match_paths())
-        .matches
-        .into_iter()
-        .enumerate()
-      {
-        assert_eq!(
-          m.id,
-          match_store
-            .query(active_config.match_paths())
-            .matches
-            .get(i)
-            .unwrap()
-            .id
-        );
-      }
-
-      assert_eq!(
-        match_store
-          .query(default_config.match_paths())
-          .global_vars
-          .first()
-          .unwrap()
-          .id,
-        match_store
-          .query(active_config.match_paths())
-          .global_vars
-          .first()
-          .unwrap()
-          .id,
-      );
-    });
-  }
-
-  #[test]
-  fn load_legacy_with_packages() {
-    use_test_directory(|base, _, packages| {
-      std::fs::write(
-        base.join("default.yml"),
-        r#"
-      backend: Clipboard
-
-      matches:
-        - trigger: "hello"
-          replace: "world"
-      "#,
-      )
-      .unwrap();
-
-      create_dir_all(packages.join("test-package")).unwrap();
-      std::fs::write(
-        packages.join("test-package").join("package.yml"),
-        r#"
-      name: test-package
-      parent: default
-
-      matches:
-        - trigger: "foo"
-          replace: "bar"
-      "#,
-      )
-      .unwrap();
-
-      let (config_store, match_store) = load(base, packages).unwrap();
-
-      let default_config = config_store.default();
-      assert_eq!(default_config.match_paths().len(), 1);
-
-      assert_eq!(
-        match_store
-          .query(default_config.match_paths())
-          .matches
-          .len(),
-        2
-      );
-    });
-  }
-}
diff --git a/espanso-config/src/legacy/model.rs b/espanso-config/src/legacy/model.rs
deleted file mode 100644
index 02c0ff418..000000000
--- a/espanso-config/src/legacy/model.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * This file is part of espanso.
- *
- * Copyright (C) 2019-2021 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use serde::{Deserialize, Serialize};
-
-#[allow(non_camel_case_types)]
-#[allow(clippy::upper_case_acronyms)]
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq)]
-pub enum KeyModifier {
-  CTRL,
-  SHIFT,
-  ALT,
-  META,
-  BACKSPACE,
-  OFF,
-
-  // These are specific variants of the ones above. See issue: #117
-  // https://github.com/espanso/espanso/issues/117
-  LEFT_CTRL,
-  RIGHT_CTRL,
-  LEFT_ALT,
-  RIGHT_ALT,
-  LEFT_META,
-  RIGHT_META,
-  LEFT_SHIFT,
-  RIGHT_SHIFT,
-
-  // Special cases, should not be used in config
-  CAPS_LOCK,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone, Default)]
-pub enum PasteShortcut {
-  #[default]
-  Default, // Default one for the current system
-  CtrlV,       // Classic Ctrl+V shortcut
-  CtrlShiftV,  // Could be used to paste without formatting in many applications
-  ShiftInsert, // Often used in Linux systems
-  CtrlAltV,    // Used in some Linux terminals (urxvt)
-  MetaV,       // Corresponding to Win+V on Windows and Linux, CMD+V on macOS
-}
diff --git a/espanso-config/src/lib.rs b/espanso-config/src/lib.rs
index e2d17fe03..09a620400 100644
--- a/espanso-config/src/lib.rs
+++ b/espanso-config/src/lib.rs
@@ -26,7 +26,6 @@ use thiserror::Error;
 pub mod config;
 mod counter;
 pub mod error;
-mod legacy;
 pub mod matches;
 mod util;
 
@@ -59,17 +58,6 @@ pub fn load(base_path: &Path) -> Result<LoadableConfig> {
   ))
 }
 
-pub fn load_legacy(
-  config_dir: &Path,
-  package_dir: &Path,
-) -> Result<(Box<dyn ConfigStore>, Box<dyn MatchStore>)> {
-  legacy::load(config_dir, package_dir)
-}
-
-pub fn is_legacy_config(base_dir: &Path) -> bool {
-  base_dir.join("user").is_dir() && base_dir.join("default.yml").is_file()
-}
-
 #[derive(Error, Debug)]
 pub enum ConfigError {
   #[error("missing config directory")]
diff --git a/espanso-migrate/Cargo.toml b/espanso-migrate/Cargo.toml
deleted file mode 100644
index 6ecbbee11..000000000
--- a/espanso-migrate/Cargo.toml
+++ /dev/null
@@ -1,25 +0,0 @@
-[package]
-name = "espanso-migrate"
-version = "0.1.0"
-authors = ["Federico Terzi <federico-terzi@users.noreply.github.com>"]
-edition = "2021"
-
-[dependencies]
-anyhow.workspace = true
-thiserror.workspace = true
-glob.workspace = true
-regex.workspace = true
-lazy_static.workspace = true
-walkdir.workspace = true
-yaml-rust = { version = "0.4.6", git = "https://github.com/federico-terzi/yaml-rust" }
-path-slash = "0.1.4"
-tempdir.workspace = true
-fs_extra.workspace = true
-
-[dev-dependencies]
-include_dir = { version = "0.6.0", features = ["search"] }
-test-case = "1.1.0"
-pretty_assertions = "0.7.2"
-
-[lints]
-workspace = true
diff --git a/espanso-migrate/src/convert.rs b/espanso-migrate/src/convert.rs
deleted file mode 100644
index f36862838..000000000
--- a/espanso-migrate/src/convert.rs
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * This file is part of espanso.
- *
- * Copyright (C) 2019-2021 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use lazy_static::lazy_static;
-use regex::{Captures, Regex};
-use std::{cmp::Ordering, collections::HashMap, path::PathBuf};
-use yaml_rust::{yaml::Hash, Yaml};
-
-pub struct ConvertedFile {
-  pub origin: String,
-  pub content: Hash,
-}
-
-pub fn convert(input_files: HashMap<String, Hash>) -> HashMap<String, ConvertedFile> {
-  let mut output_files = HashMap::new();
-
-  let sorted_input_files = sort_input_files(&input_files);
-
-  for input_path in sorted_input_files {
-    let yaml = input_files
-      .get(&input_path)
-      .expect("received unexpected file in input function");
-
-    let yaml_matches = yaml_get_vec(yaml, "matches");
-    let yaml_global_vars = yaml_get_vec(yaml, "global_vars");
-
-    let yaml_parent = yaml_get_string(yaml, "parent");
-
-    if let Some(parent) = yaml_parent {
-      if parent != "default" {
-        eprintln!(
-          "WARNING: nested 'parent' instructions are not currently supported by the migration tool"
-        );
-      }
-    }
-
-    let should_generate_match = yaml_matches.is_some() || yaml_global_vars.is_some();
-    let match_file_path_if_unlisted = if should_generate_match {
-      let should_underscore = !input_path.starts_with("default") && yaml_parent != Some("default");
-      let match_output_path = calculate_output_match_path(&input_path, should_underscore);
-      if match_output_path.is_none() {
-        eprintln!("unable to determine output path for {input_path}, skipping...");
-        continue;
-      }
-      let match_output_path = match_output_path.unwrap();
-
-      let output_yaml = output_files
-        .entry(match_output_path.clone())
-        .or_insert(ConvertedFile {
-          origin: input_path.to_string(),
-          content: Hash::new(),
-        });
-
-      if let Some(global_vars) = yaml_global_vars {
-        let mut patched_global_vars: Vec<Yaml> = global_vars.clone();
-        patched_global_vars
-          .iter_mut()
-          .for_each(apply_form_syntax_patch_to_variable);
-
-        let output_global_vars = output_yaml
-          .content
-          .entry(Yaml::String("global_vars".to_string()))
-          .or_insert(Yaml::Array(Vec::new()));
-        if let Yaml::Array(out_global_vars) = output_global_vars {
-          out_global_vars.extend(patched_global_vars);
-        } else {
-          eprintln!("unable to transform global_vars for file: {input_path}");
-        }
-      }
-
-      if let Some(matches) = yaml_matches {
-        let mut patched_matches = matches.clone();
-        apply_form_syntax_patch(&mut patched_matches);
-
-        let output_matches = output_yaml
-          .content
-          .entry(Yaml::String("matches".to_string()))
-          .or_insert(Yaml::Array(Vec::new()));
-        if let Yaml::Array(out_matches) = output_matches {
-          out_matches.extend(patched_matches);
-        } else {
-          eprintln!("unable to transform matches for file: {input_path}");
-        }
-      }
-
-      if should_underscore {
-        Some(match_output_path)
-      } else {
-        None
-      }
-    } else {
-      None
-    };
-
-    let yaml_filter_class = yaml_get_string(yaml, "filter_class");
-    let yaml_filter_title = yaml_get_string(yaml, "filter_title");
-    let yaml_filter_exec = yaml_get_string(yaml, "filter_exec");
-
-    let should_generate_config = input_path.starts_with("default")
-      || yaml_filter_class.is_some()
-      || yaml_filter_exec.is_some()
-      || yaml_filter_title.is_some();
-
-    if should_generate_config {
-      let config_output_path = calculate_output_config_path(&input_path);
-
-      let mut output_yaml = Hash::new();
-
-      copy_field_if_present(yaml, "filter_title", &mut output_yaml, "filter_title");
-      copy_field_if_present(yaml, "filter_class", &mut output_yaml, "filter_class");
-      copy_field_if_present(yaml, "filter_exec", &mut output_yaml, "filter_exec");
-      copy_field_if_present(yaml, "enable_active", &mut output_yaml, "enable");
-      copy_field_if_present(yaml, "backend", &mut output_yaml, "backend");
-      map_field_if_present(
-        yaml,
-        "paste_shortcut",
-        &mut output_yaml,
-        "paste_shortcut",
-        |val| match val {
-          Yaml::String(shortcut) if shortcut == "CtrlV" => Some(Yaml::String("CTRL+V".to_string())),
-          Yaml::String(shortcut) if shortcut == "CtrlShiftV" => {
-            Some(Yaml::String("CTRL+SHIFT+V".to_string()))
-          }
-          Yaml::String(shortcut) if shortcut == "ShiftInsert" => {
-            Some(Yaml::String("SHIFT+INSERT".to_string()))
-          }
-          Yaml::String(shortcut) if shortcut == "CtrlAltV" => {
-            Some(Yaml::String("CTRL+ALT+V".to_string()))
-          }
-          Yaml::String(shortcut) if shortcut == "MetaV" => Some(Yaml::String("META+V".to_string())),
-          Yaml::String(_) => None,
-          _ => None,
-        },
-      );
-      //copy_field_if_present(yaml, "secure_input_watcher_enabled", &mut output_yaml, "secure_input_watcher_enabled");
-      //copy_field_if_present(yaml, "secure_input_watcher_interval", &mut output_yaml, "secure_input_watcher_interval");
-      //copy_field_if_present(yaml, "config_caching_interval", &mut output_yaml, "config_caching_interval");
-      //copy_field_if_present(yaml, "use_system_agent", &mut output_yaml, "use_system_agent");
-
-      copy_field_if_present(
-        yaml,
-        "secure_input_notification",
-        &mut output_yaml,
-        "secure_input_notification",
-      );
-      copy_field_if_present(yaml, "toggle_interval", &mut output_yaml, "toggle_interval");
-      copy_field_if_present(yaml, "toggle_key", &mut output_yaml, "toggle_key");
-      copy_field_if_present(
-        yaml,
-        "preserve_clipboard",
-        &mut output_yaml,
-        "preserve_clipboard",
-      );
-      copy_field_if_present(yaml, "backspace_limit", &mut output_yaml, "backspace_limit");
-      map_field_if_present(
-        yaml,
-        "fast_inject",
-        &mut output_yaml,
-        "disable_x11_fast_inject",
-        |val| match val {
-          Yaml::Boolean(false) => Some(Yaml::Boolean(true)),
-          Yaml::Boolean(true) => Some(Yaml::Boolean(false)),
-          _ => None,
-        },
-      );
-
-      copy_field_if_present(yaml, "auto_restart", &mut output_yaml, "auto_restart");
-      copy_field_if_present(yaml, "undo_backspace", &mut output_yaml, "undo_backspace");
-      copy_field_if_present(yaml, "show_icon", &mut output_yaml, "show_icon");
-      copy_field_if_present(
-        yaml,
-        "show_notifications",
-        &mut output_yaml,
-        "show_notifications",
-      );
-      copy_field_if_present(yaml, "inject_delay", &mut output_yaml, "inject_delay");
-      copy_field_if_present(
-        yaml,
-        "restore_clipboard_delay",
-        &mut output_yaml,
-        "restore_clipboard_delay",
-      );
-      copy_field_if_present(yaml, "backspace_delay", &mut output_yaml, "key_delay");
-      copy_field_if_present(yaml, "word_separators", &mut output_yaml, "word_separators");
-
-      if yaml
-        .get(&Yaml::String("enable_passive".to_string()))
-        .is_some()
-      {
-        eprintln!("WARNING: passive-mode directives were detected, but passive-mode is not supported anymore.");
-        eprintln!("Please follow this issue to discover the alternatives: https://github.com/espanso/espanso/issues/540");
-      }
-
-      // Link any unlisted match file (the ones starting with the _ underscore, which are excluded by the
-      // default.yml config) explicitly, if present.
-      if let Some(match_file_path) = match_file_path_if_unlisted {
-        let yaml_exclude_default_entries =
-          yaml_get_bool(yaml, "exclude_default_entries").unwrap_or(false);
-        let key_name = if yaml_exclude_default_entries {
-          "includes"
-        } else {
-          "extra_includes"
-        };
-
-        let includes = vec![Yaml::String(format!("../{match_file_path}"))];
-
-        output_yaml.insert(Yaml::String(key_name.to_string()), Yaml::Array(includes));
-      }
-
-      output_files.insert(
-        config_output_path,
-        ConvertedFile {
-          origin: input_path,
-          content: output_yaml,
-        },
-      );
-    }
-  }
-
-  output_files
-}
-
-fn sort_input_files(input_files: &HashMap<String, Hash>) -> Vec<String> {
-  let mut files: Vec<String> = input_files.iter().map(|(key, _)| key.clone()).collect();
-  files.sort_by(|f1, f2| {
-    // count how many `/` are there in f1 and compare it with f2
-    match f1.matches('/').count().cmp(&f2.matches('/').count()) {
-      Ordering::Greater => Ordering::Greater,
-      Ordering::Less => Ordering::Less,
-      Ordering::Equal => f1.cmp(f2),
-    }
-  });
-  files
-}
-
-// TODO: test
-fn calculate_output_match_path(path: &str, is_underscored: bool) -> Option<String> {
-  let path_buf = PathBuf::from(path);
-  let file_name = path_buf.file_name()?.to_string_lossy().to_string();
-
-  let path = if is_underscored {
-    path.replace(&file_name, &format!("_{file_name}"))
-  } else {
-    path.to_string()
-  };
-
-  Some(if path.starts_with("user/") {
-    format!("match/{}", path.trim_start_matches("user/"))
-  } else if path.starts_with("packages/") {
-    format!("match/packages/{}", path.trim_start_matches("packages/"))
-  } else if path == "default.yml" {
-    "match/base.yml".to_string()
-  } else {
-    format!("match/{path}")
-  })
-}
-
-// TODO: test
-fn calculate_output_config_path(path: &str) -> String {
-  if path.starts_with("user/") {
-    format!("config/{}", path.trim_start_matches("user/"))
-  } else if path.starts_with("packages/") {
-    format!("config/packages/{}", path.trim_start_matches("packages/"))
-  } else {
-    format!("config/{path}")
-  }
-}
-
-fn yaml_get_vec<'a>(yaml: &'a Hash, name: &str) -> Option<&'a Vec<Yaml>> {
-  yaml
-    .get(&Yaml::String(name.to_string()))
-    .and_then(|v| v.as_vec())
-}
-
-fn yaml_get_string<'a>(yaml: &'a Hash, name: &str) -> Option<&'a str> {
-  yaml
-    .get(&Yaml::String(name.to_string()))
-    .and_then(|v| v.as_str())
-}
-
-fn yaml_get_bool(yaml: &Hash, name: &str) -> Option<bool> {
-  yaml
-    .get(&Yaml::String(name.to_string()))
-    .and_then(Yaml::as_bool)
-}
-
-fn copy_field_if_present(
-  input_yaml: &Hash,
-  input_field_name: &str,
-  output_yaml: &mut Hash,
-  output_field_name: &str,
-) {
-  if let Some(value) = input_yaml.get(&Yaml::String(input_field_name.to_string())) {
-    output_yaml.insert(Yaml::String(output_field_name.to_string()), value.clone());
-  }
-}
-
-fn map_field_if_present(
-  input_yaml: &Hash,
-  input_field_name: &str,
-  output_yaml: &mut Hash,
-  output_field_name: &str,
-  transform: impl FnOnce(&Yaml) -> Option<Yaml>,
-) {
-  if let Some(value) = input_yaml.get(&Yaml::String(input_field_name.to_string())) {
-    let transformed = transform(value);
-    if let Some(transformed) = transformed {
-      output_yaml.insert(Yaml::String(output_field_name.to_string()), transformed);
-    } else {
-      eprintln!("could not convert value for field: {input_field_name}");
-    }
-  }
-}
-
-// This is needed to convert the old form's {{control}} syntax to the new [[control]] one.
-fn apply_form_syntax_patch(matches: &mut [Yaml]) {
-  for m in matches.iter_mut() {
-    if let Yaml::Hash(fields) = m {
-      if let Some(Yaml::String(form_option)) = fields.get_mut(&Yaml::String("form".to_string())) {
-        let converted = replace_legacy_form_syntax_with_new_one(form_option);
-        if &converted != form_option {
-          form_option.clear();
-          form_option.push_str(&converted);
-        }
-      }
-
-      if let Some(Yaml::Array(vars)) = fields.get_mut(&Yaml::String("vars".to_string())) {
-        vars
-          .iter_mut()
-          .for_each(apply_form_syntax_patch_to_variable);
-      }
-    }
-  }
-}
-
-fn apply_form_syntax_patch_to_variable(variable: &mut Yaml) {
-  if let Yaml::Hash(fields) = variable {
-    if let Some(Yaml::String(var_type)) = fields.get(&Yaml::String("type".to_string())) {
-      if var_type != "form" {
-        return;
-      }
-    }
-
-    if let Some(Yaml::Hash(params)) = fields.get_mut(&Yaml::String("params".to_string())) {
-      if let Some(Yaml::String(layout)) = params.get_mut(&Yaml::String("layout".to_string())) {
-        let converted = replace_legacy_form_syntax_with_new_one(layout);
-        if &converted != layout {
-          layout.clear();
-          layout.push_str(&converted);
-        }
-      }
-    }
-  }
-}
-
-lazy_static! {
-  static ref LEGACY_FIELD_REGEX: Regex = Regex::new(r"\{\{(?P<name>.*?)\}\}").unwrap();
-}
-
-fn replace_legacy_form_syntax_with_new_one(layout: &str) -> String {
-  LEGACY_FIELD_REGEX
-    .replace_all(layout, |caps: &Captures| {
-      let field_name = caps.name("name").unwrap().as_str();
-      format!("[[{field_name}]]")
-    })
-    .to_string()
-}
diff --git a/espanso-migrate/src/lib.rs b/espanso-migrate/src/lib.rs
deleted file mode 100644
index 6a83d63a3..000000000
--- a/espanso-migrate/src/lib.rs
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * This file is part of espanso.
- *
- * Copyright (C) 2019-2021 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::path::Path;
-
-use anyhow::Result;
-use fs_extra::dir::CopyOptions;
-use tempdir::TempDir;
-use thiserror::Error;
-
-mod convert;
-mod load;
-mod render;
-
-// TODO: implement
-// Use yaml-rust with "preserve-order" = true
-// Strategy:
-// 1. Backup the current config directory in a zip archive (also with the packages)
-// 2. Create a temporary directory alonside the legacy one called "espanso-new"
-// 3. Convert all the files and write the output into "espanso-new"
-// 4. Rename the legacy dir to "espanso-old"
-// 5. Rename new dir to "espanso"
-// 6. If the legacy directory was a symlink, try to recreate it (ask the user first)
-
-// TODO: before attempting the migration strategy, check if the current
-// espanso config directory is a symlink and, if so, attempt to remap
-// the symlink with the new dir (after asking the user)
-// This is necessary because in order to be safe, the migration strategy
-// creates the new config on a new temporary directory and then "swaps"
-// the old with the new one
-
-// TODO: test also with non-lowercase file names
-
-pub fn migrate(config_dir: &Path, packages_dir: &Path, output_dir: &Path) -> Result<()> {
-  if !config_dir.is_dir() {
-    return Err(MigrationError::InvalidConfigDir.into());
-  }
-
-  let working_dir = TempDir::new("espanso-migration")?;
-
-  fs_extra::dir::copy(
-    config_dir,
-    working_dir.path(),
-    &CopyOptions {
-      content_only: true,
-      ..Default::default()
-    },
-  )?;
-
-  // If packages are located within the config_dir, no need to copy them in
-  // the working directory
-  if packages_dir.parent() != Some(config_dir) {
-    fs_extra::dir::copy(packages_dir, working_dir.path(), &CopyOptions::new())?;
-  }
-
-  // Create the output directory
-  if output_dir.exists() {
-    return Err(MigrationError::OutputDirAlreadyPresent.into());
-  }
-
-  std::fs::create_dir_all(output_dir)?;
-
-  // Convert the configurations
-  let legacy_files = load::load(working_dir.path())?;
-  let converted_files = convert::convert(legacy_files);
-  let rendered_files = render::render(converted_files)?;
-
-  for (file, content) in rendered_files {
-    let target = output_dir.join(file);
-
-    if let Some(parent) = target.parent() {
-      if !parent.is_dir() {
-        std::fs::create_dir_all(parent)?;
-      }
-    }
-
-    std::fs::write(target, content)?;
-  }
-
-  // Copy all non-YAML directories
-  for entry in std::fs::read_dir(working_dir.path())? {
-    let entry = entry?;
-    let path = entry.path();
-    if path.is_dir() {
-      let dir_name = path.file_name();
-      if let Some(name) = dir_name.map(|s| s.to_string_lossy().to_string().to_lowercase()) {
-        if name != "user" && name != "packages" {
-          fs_extra::dir::copy(path, output_dir, &CopyOptions::new())?;
-        }
-      }
-    }
-  }
-
-  Ok(())
-}
-
-#[derive(Error, Debug)]
-pub enum MigrationError {
-  #[error("invalid config directory")]
-  InvalidConfigDir,
-
-  #[error("output directory already present")]
-  OutputDirAlreadyPresent,
-}
-
-#[cfg(test)]
-mod tests {
-  use std::{collections::HashMap, fs::create_dir_all, path::Path};
-
-  use super::*;
-  use include_dir::{include_dir, Dir};
-  use tempdir::TempDir;
-  use test_case::test_case;
-
-  use pretty_assertions::assert_eq as assert_peq;
-  use yaml_rust::{yaml::Hash, Yaml};
-
-  fn run_with_temp_dir(test_data: &Dir, action: impl FnOnce(&Path, &Path)) {
-    let tmp_dir = TempDir::new("espanso-migrate").unwrap();
-    let tmp_path = tmp_dir.path();
-    let legacy_path = tmp_dir.path().join("legacy");
-    let expected_path = tmp_dir.path().join("expected");
-
-    for entry in test_data.find("**/*").unwrap() {
-      let entry_path = entry.path();
-
-      let entry_path_str = entry_path.to_string_lossy().to_string();
-      if entry_path_str.is_empty() {
-        continue;
-      }
-
-      let target = tmp_path.join(entry_path);
-
-      if entry_path.extension().is_none() {
-        create_dir_all(target).unwrap();
-      } else {
-        std::fs::write(target, test_data.get_file(entry_path).unwrap().contents()).unwrap();
-      }
-    }
-
-    action(&legacy_path, &expected_path);
-  }
-
-  fn to_sorted_list<T>(hash: HashMap<String, T>) -> Vec<(String, T)> {
-    let mut tuples: Vec<(String, T)> = hash.into_iter().collect();
-    tuples.sort_by_key(|(k, _)| k.clone());
-    tuples
-  }
-
-  fn to_sorted_hash(hash: &Hash) -> Vec<(String, &Yaml)> {
-    let mut tuples: Vec<(String, &Yaml)> = hash
-      .into_iter()
-      .map(|(k, v)| (k.as_str().unwrap().to_string(), v))
-      .collect();
-    tuples.sort_by_key(|(k, _)| k.clone());
-    tuples
-  }
-
-  fn list_files_in_dir(dir: &Path) -> Vec<String> {
-    let prefix = dir.to_string_lossy().to_string();
-    fs_extra::dir::get_dir_content(dir)
-      .unwrap()
-      .files
-      .into_iter()
-      .map(|file| file.trim_start_matches(&prefix).to_string())
-      .collect()
-  }
-
-  static SIMPLE_CASE: Dir = include_dir!("test/simple");
-  static BASE_CASE: Dir = include_dir!("test/base");
-  static ALL_PARAMS_CASE: Dir = include_dir!("test/all_params");
-  static OTHER_DIRS_CASE: Dir = include_dir!("test/other_dirs");
-  static FORM_SYNTAX: Dir = include_dir!("test/form_syntax");
-
-  #[allow(clippy::unused_unit)]
-  #[test_case(&SIMPLE_CASE; "simple case")]
-  #[test_case(&BASE_CASE; "base case")]
-  #[test_case(&ALL_PARAMS_CASE; "all config parameters case")]
-  #[test_case(&OTHER_DIRS_CASE; "other directories case")]
-  #[test_case(&FORM_SYNTAX; "form syntax")]
-  fn test_migration(test_data: &Dir) {
-    run_with_temp_dir(test_data, |legacy, expected| {
-      let tmp_out_dir = TempDir::new("espanso-migrate-out").unwrap();
-      let output_dir = tmp_out_dir.path().join("out");
-
-      migrate(legacy, &legacy.join("packages"), &output_dir).unwrap();
-
-      let converted_files = load::load(&output_dir).unwrap();
-
-      // Verify configuration content
-      let expected_files = load::load(expected).unwrap();
-      assert_eq!(converted_files.len(), expected_files.len());
-      for (file, converted) in to_sorted_list(converted_files) {
-        assert_peq!(
-          to_sorted_hash(&converted),
-          to_sorted_hash(expected_files.get(&file).unwrap())
-        );
-      }
-
-      // Verify file structure
-      assert_peq!(list_files_in_dir(expected), list_files_in_dir(&output_dir));
-    });
-  }
-}
diff --git a/espanso-migrate/src/load.rs b/espanso-migrate/src/load.rs
deleted file mode 100644
index d95706ac9..000000000
--- a/espanso-migrate/src/load.rs
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * This file is part of espanso.
- *
- * Copyright (C) 2019-2021 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use anyhow::Result;
-use path_slash::PathExt;
-use std::{collections::HashMap, path::Path};
-use thiserror::Error;
-use walkdir::WalkDir;
-use yaml_rust::{yaml::Hash, Yaml, YamlLoader};
-
-pub fn load(config_dir: &Path) -> Result<HashMap<String, Hash>> {
-  if !config_dir.is_dir() {
-    return Err(LoadError::NotDirectory.into());
-  }
-
-  let mut input_files = HashMap::new();
-
-  for entry in WalkDir::new(config_dir) {
-    match entry {
-      Ok(entry) => {
-        // Skip directories
-        if entry.path().is_dir() {
-          continue;
-        }
-
-        // Skip non-yaml files
-        let extension = entry
-          .path()
-          .extension()
-          .map(|s| s.to_string_lossy().to_ascii_lowercase())
-          .unwrap_or_default();
-
-        if extension != "yaml" && extension != "yml" {
-          continue;
-        }
-
-        match entry.path().strip_prefix(config_dir) {
-          Ok(relative_path) => {
-            let corrected_path = relative_path.to_slash_lossy();
-
-            if corrected_path.is_empty() {
-              continue;
-            }
-
-            match std::fs::read_to_string(entry.path()) {
-              Ok(content) => {
-                // Empty files are not valid YAML, but we want to handle them anyway
-                if content.trim().is_empty() {
-                  input_files.insert(corrected_path, Hash::new());
-                } else {
-                  match YamlLoader::load_from_str(&content) {
-                    Ok(mut yaml) => {
-                      if yaml.is_empty() {
-                        eprintln!(
-                          "error, found empty document while reading entry: {}",
-                          entry.path().display()
-                        );
-                      } else {
-                        let yaml = yaml.remove(0);
-                        if let Yaml::Hash(hash) = yaml {
-                          input_files.insert(corrected_path, hash);
-                        } else {
-                          eprintln!(
-                            "yaml file does not have a valid format: {}",
-                            entry.path().display()
-                          );
-                        }
-                      }
-                    }
-                    Err(err) => {
-                      eprintln!(
-                        "experienced error while parsing file: {}, error: {}",
-                        entry.path().display(),
-                        err
-                      );
-                    }
-                  }
-                }
-              }
-              Err(err) => {
-                eprintln!(
-                  "error while reading entry: {}, error: {}",
-                  entry.path().display(),
-                  err
-                );
-              }
-            }
-          }
-          Err(err) => {
-            eprintln!(
-              "error while analyzing entry: {}, error: {}",
-              entry.path().display(),
-              err
-            );
-          }
-        }
-      }
-      Err(err) => {
-        eprintln!("experienced error while reading entry: {err}");
-      }
-    }
-  }
-
-  Ok(input_files)
-}
-
-#[derive(Error, Debug)]
-pub enum LoadError {
-  #[error("the provided legacy_config_dir is not a directory")]
-  NotDirectory,
-}
diff --git a/espanso-migrate/src/render.rs b/espanso-migrate/src/render.rs
deleted file mode 100644
index 8d161fefa..000000000
--- a/espanso-migrate/src/render.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * This file is part of espanso.
- *
- * Copyright (C) 2019-2021 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use anyhow::Result;
-use std::collections::HashMap;
-use yaml_rust::{Yaml, YamlEmitter};
-
-use crate::convert::ConvertedFile;
-
-pub fn render(converted_files: HashMap<String, ConvertedFile>) -> Result<Vec<(String, String)>> {
-  let mut output = Vec::new();
-  for (file, content) in converted_files {
-    let rendered = render_file(content)?;
-    output.push((file, rendered));
-  }
-  Ok(output)
-}
-
-fn render_file(file: ConvertedFile) -> Result<String> {
-  let mut dump_str = String::new();
-  let mut emitter = YamlEmitter::new(&mut dump_str);
-  emitter.multiline_strings(true);
-  emitter.dump(&Yaml::Hash(file.content))?;
-
-  let lines: Vec<&str> = dump_str.lines().collect();
-  let header = format!("# Original file: {}", file.origin);
-  let mut output_lines: Vec<&str> = vec![
-    "# Automatically generated by espanso migration tool",
-    &header,
-    "",
-  ];
-  if !lines.is_empty() {
-    output_lines.extend(&lines[1..]);
-  }
-
-  let output = output_lines.join("\n");
-  Ok(output)
-}
diff --git a/espanso-modulo/build.rs b/espanso-modulo/build.rs
index 0a0900417..dca172076 100644
--- a/espanso-modulo/build.rs
+++ b/espanso-modulo/build.rs
@@ -432,11 +432,6 @@ fn macos_link_search_path() -> Option<String> {
   None
 }
 
-// TODO: add documentation for linux
-// Install wxWidgets:
-// sudo apt install libwxgtk3.0-0v5 libwxgtk3.0-dev
-//
-// cargo run
 #[cfg(target_os = "linux")]
 fn build_native() {
   // Make sure wxWidgets is installed
diff --git a/espanso-modulo/src/sys/interop/mod.rs b/espanso-modulo/src/sys/interop/mod.rs
index c6a61cdb3..821cb8000 100644
--- a/espanso-modulo/src/sys/interop/mod.rs
+++ b/espanso-modulo/src/sys/interop/mod.rs
@@ -110,11 +110,6 @@ pub struct SearchMetadata {
   pub hintText: *const ::std::os::raw::c_char,
 }
 
-pub const WIZARD_MIGRATE_RESULT_SUCCESS: i32 = 0;
-pub const WIZARD_MIGRATE_RESULT_CLEAN_FAILURE: i32 = 1;
-pub const WIZARD_MIGRATE_RESULT_DIRTY_FAILURE: i32 = 2;
-pub const WIZARD_MIGRATE_RESULT_UNKNOWN_FAILURE: i32 = 3;
-
 pub const WIZARD_DETECTED_OS_UNKNOWN: i32 = 0;
 pub const WIZARD_DETECTED_OS_X11: i32 = 1;
 pub const WIZARD_DETECTED_OS_WAYLAND: i32 = 2;
@@ -126,9 +121,7 @@ pub struct WizardMetadata {
 
   pub is_welcome_page_enabled: c_int,
   pub is_move_bundle_page_enabled: c_int,
-  pub is_legacy_version_page_enabled: c_int,
   pub is_wrong_edition_page_enabled: c_int,
-  pub is_migrate_page_enabled: c_int,
   pub is_auto_start_page_enabled: c_int,
   pub is_add_path_page_enabled: c_int,
   pub is_accessibility_page_enabled: c_int,
diff --git a/espanso-modulo/src/sys/wizard/mod.rs b/espanso-modulo/src/sys/wizard/mod.rs
index 3c0921966..23e54d118 100644
--- a/espanso-modulo/src/sys/wizard/mod.rs
+++ b/espanso-modulo/src/sys/wizard/mod.rs
@@ -25,10 +25,7 @@ use crate::sys::interop::{
 };
 use crate::sys::util::convert_to_cstring_or_null;
 use crate::{
-  sys::interop::{
-    WizardMetadata, WIZARD_MIGRATE_RESULT_CLEAN_FAILURE, WIZARD_MIGRATE_RESULT_DIRTY_FAILURE,
-    WIZARD_MIGRATE_RESULT_SUCCESS, WIZARD_MIGRATE_RESULT_UNKNOWN_FAILURE,
-  },
+  sys::interop::WizardMetadata,
   wizard::{WizardHandlers, WizardOptions},
 };
 use lazy_static::lazy_static;
@@ -50,32 +47,11 @@ pub fn show(options: WizardOptions) -> bool {
     convert_to_cstring_or_null(options.accessibility_image_2_path);
 
   extern "C" fn is_legacy_version_running() -> c_int {
-    let lock = HANDLERS
-      .lock()
-      .expect("unable to acquire lock in is_legacy_version_running method");
-    let handlers_ref = (*lock).as_ref().expect("unable to unwrap handlers");
-    if let Some(handler_ref) = handlers_ref.is_legacy_version_running.as_ref() {
-      i32::from((*handler_ref)())
-    } else {
-      -1
-    }
+    -1
   }
 
   extern "C" fn backup_and_migrate() -> c_int {
-    let lock = HANDLERS
-      .lock()
-      .expect("unable to acquire lock in backup_and_migrate method");
-    let handlers_ref = (*lock).as_ref().expect("unable to unwrap handlers");
-    if let Some(handler_ref) = handlers_ref.backup_and_migrate.as_ref() {
-      match (*handler_ref)() {
-        crate::wizard::MigrationResult::Success => WIZARD_MIGRATE_RESULT_SUCCESS,
-        crate::wizard::MigrationResult::CleanFailure => WIZARD_MIGRATE_RESULT_CLEAN_FAILURE,
-        crate::wizard::MigrationResult::DirtyFailure => WIZARD_MIGRATE_RESULT_DIRTY_FAILURE,
-        crate::wizard::MigrationResult::UnknownFailure => WIZARD_MIGRATE_RESULT_UNKNOWN_FAILURE,
-      }
-    } else {
-      WIZARD_MIGRATE_RESULT_UNKNOWN_FAILURE
-    }
+    3 // WIZARD_MIGRATE_RESULT_UNKNOWN_FAILURE
   }
 
   extern "C" fn auto_start(auto_start: c_int) -> c_int {
@@ -147,9 +123,7 @@ pub fn show(options: WizardOptions) -> bool {
 
     is_welcome_page_enabled: i32::from(options.is_welcome_page_enabled),
     is_move_bundle_page_enabled: i32::from(options.is_move_bundle_page_enabled),
-    is_legacy_version_page_enabled: i32::from(options.is_legacy_version_page_enabled),
     is_wrong_edition_page_enabled: i32::from(options.is_wrong_edition_page_enabled),
-    is_migrate_page_enabled: i32::from(options.is_migrate_page_enabled),
     is_auto_start_page_enabled: i32::from(options.is_auto_start_page_enabled),
     is_add_path_page_enabled: i32::from(options.is_add_path_page_enabled),
     is_accessibility_page_enabled: i32::from(options.is_accessibility_page_enabled),
diff --git a/espanso-modulo/src/wizard/mod.rs b/espanso-modulo/src/wizard/mod.rs
index afe90db92..a8cd7320f 100644
--- a/espanso-modulo/src/wizard/mod.rs
+++ b/espanso-modulo/src/wizard/mod.rs
@@ -24,9 +24,7 @@ pub struct WizardOptions {
 
   pub is_welcome_page_enabled: bool,
   pub is_move_bundle_page_enabled: bool,
-  pub is_legacy_version_page_enabled: bool,
   pub is_wrong_edition_page_enabled: bool,
-  pub is_migrate_page_enabled: bool,
   pub is_auto_start_page_enabled: bool,
   pub is_add_path_page_enabled: bool,
   pub is_accessibility_page_enabled: bool,
@@ -41,8 +39,6 @@ pub struct WizardOptions {
 }
 
 pub struct WizardHandlers {
-  pub is_legacy_version_running: Option<Box<dyn Fn() -> bool + Send>>,
-  pub backup_and_migrate: Option<Box<dyn Fn() -> MigrationResult + Send>>,
   pub auto_start: Option<Box<dyn Fn(bool) -> bool + Send>>,
   pub add_to_path: Option<Box<dyn Fn() -> bool + Send>>,
   pub enable_accessibility: Option<Box<dyn Fn() + Send>>,
@@ -50,14 +46,6 @@ pub struct WizardHandlers {
   pub on_completed: Option<Box<dyn Fn() + Send>>,
 }
 
-#[derive(Debug)]
-pub enum MigrationResult {
-  Success,
-  CleanFailure,
-  DirtyFailure,
-  UnknownFailure,
-}
-
 #[derive(Debug)]
 pub enum DetectedOS {
   Unknown,
diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml
index f77885f5d..f692dd75c 100644
--- a/espanso/Cargo.toml
+++ b/espanso/Cargo.toml
@@ -47,7 +47,6 @@ espanso-render = { path = "../espanso-render" }
 espanso-path = { path = "../espanso-path" }
 espanso-ipc = { path = "../espanso-ipc" }
 espanso-modulo = { path = "../espanso-modulo", optional = true }
-espanso-migrate = { path = "../espanso-migrate" }
 espanso-kvs = { path = "../espanso-kvs" }
 espanso-engine = { path = "../espanso-engine" }
 espanso-package = { path = "../espanso-package" }
diff --git a/espanso/src/cli/daemon/mod.rs b/espanso/src/cli/daemon/mod.rs
index 9a24a285b..148b4a505 100644
--- a/espanso/src/cli/daemon/mod.rs
+++ b/espanso/src/cli/daemon/mod.rs
@@ -31,12 +31,11 @@ use crate::{
   cli::util::{prevent_running_as_root_on_macos, CommandExt},
   common_flags::*,
   exit_code::{
-    DAEMON_ALREADY_RUNNING, DAEMON_FATAL_CONFIG_ERROR, DAEMON_GENERAL_ERROR,
-    DAEMON_LEGACY_ALREADY_RUNNING, DAEMON_SUCCESS, WORKER_ERROR_EXIT_NO_CODE,
-    WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART, WORKER_SUCCESS,
+    DAEMON_ALREADY_RUNNING, DAEMON_FATAL_CONFIG_ERROR, DAEMON_GENERAL_ERROR, DAEMON_SUCCESS,
+    WORKER_ERROR_EXIT_NO_CODE, WORKER_EXIT_ALL_PROCESSES, WORKER_RESTART, WORKER_SUCCESS,
   },
   ipc::{create_ipc_client_to_worker, IPCEvent},
-  lock::{acquire_daemon_lock, acquire_legacy_lock, acquire_worker_lock},
+  lock::{acquire_daemon_lock, acquire_worker_lock},
   VERSION,
 };
 
@@ -74,15 +73,6 @@ fn daemon_main(args: CliModuleArgs) -> i32 {
     return DAEMON_ALREADY_RUNNING;
   }
 
-  let legacy_lock_file = acquire_legacy_lock(&paths.runtime);
-  if legacy_lock_file.is_none() {
-    // TODO: show a (blocking) alert message using modulo
-
-    error!("an instance of legacy espanso is running, please terminate it, otherwise the new version cannot start");
-    return DAEMON_LEGACY_ALREADY_RUNNING;
-  }
-  drop(legacy_lock_file);
-
   // TODO: we might need to check preconditions: accessibility on macOS, presence of binaries on Linux, etc
 
   // This variable holds the current troubleshooter guard.
diff --git a/espanso/src/cli/daemon/troubleshoot.rs b/espanso/src/cli/daemon/troubleshoot.rs
index e103cc527..028c1058c 100644
--- a/espanso/src/cli/daemon/troubleshoot.rs
+++ b/espanso/src/cli/daemon/troubleshoot.rs
@@ -75,7 +75,7 @@ pub enum LoadResult {
 }
 
 pub fn load_config_or_troubleshoot(paths: &Paths, paths_overrides: &PathsOverrides) -> LoadResult {
-  match crate::load_config(&paths.config, &paths.packages) {
+  match crate::load_config(&paths.config) {
     Ok(load_result) => {
       if load_result.non_fatal_errors.is_empty() {
         LoadResult::Correct(load_result)
diff --git a/espanso/src/cli/edit.rs b/espanso/src/cli/edit.rs
index f1ec014f1..33675b841 100644
--- a/espanso/src/cli/edit.rs
+++ b/espanso/src/cli/edit.rs
@@ -138,20 +138,8 @@ fn edit_main(args: CliModuleArgs) -> i32 {
 fn determine_target_path(config_path: &Path, target_file: Option<&str>) -> PathBuf {
   if let Some(target_file) = target_file {
     match target_file {
-      "default" => {
-        if espanso_config::is_legacy_config(config_path) {
-          config_path.join("default.yml")
-        } else {
-          config_path.join("config").join("default.yml")
-        }
-      }
-      "base" => {
-        if espanso_config::is_legacy_config(config_path) {
-          panic!("'base' alias cannot be used in compatibility mode, please migrate your configuration by running 'espanso migrate'")
-        } else {
-          config_path.join("match").join("base.yml")
-        }
-      }
+      "default" => config_path.join("config").join("default.yml"),
+      "base" => config_path.join("match").join("base.yml"),
       custom => {
         if !std::path::Path::new(custom)
           .extension()
@@ -160,18 +148,12 @@ fn determine_target_path(config_path: &Path, target_file: Option<&str>) -> PathB
             .extension()
             .map_or(false, |ext| ext.eq_ignore_ascii_case("yaml"))
         {
-          if espanso_config::is_legacy_config(config_path) {
-            config_path.join("user").join(format!("{custom}.yml"))
-          } else {
-            config_path.join("match").join(format!("{custom}.yml"))
-          }
+          config_path.join("match").join(format!("{custom}.yml"))
         } else {
           config_path.join(custom)
         }
       }
     }
-  } else if espanso_config::is_legacy_config(config_path) {
-    config_path.join("default.yml")
   } else {
     config_path.join("match").join("base.yml")
   }
diff --git a/espanso/src/cli/launcher/mod.rs b/espanso/src/cli/launcher/mod.rs
index f6da79445..a852f9481 100644
--- a/espanso/src/cli/launcher/mod.rs
+++ b/espanso/src/cli/launcher/mod.rs
@@ -17,7 +17,6 @@
  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use self::util::MigrationError;
 use crate::preferences::Preferences;
 use crate::{
   exit_code::{LAUNCHER_ALREADY_RUNNING, LAUNCHER_CONFIG_DIR_POPULATION_FAILURE, LAUNCHER_SUCCESS},
@@ -49,7 +48,7 @@ pub fn new() -> CliModule {
 
 #[cfg(feature = "modulo")]
 fn launcher_main(args: CliModuleArgs) -> i32 {
-  use espanso_modulo::wizard::{MigrationResult, WizardHandlers, WizardOptions};
+  use espanso_modulo::wizard::{WizardHandlers, WizardOptions};
   let paths = args.paths.expect("missing paths in launcher main");
 
   // TODO: should we create a non-gui wizard? We can also use it for the non-modulo versions of espanso
@@ -74,26 +73,9 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
 
   let is_move_bundle_page_enabled = crate::cli::util::is_subject_to_app_translocation_on_macos();
 
-  let is_legacy_version_page_enabled = util::is_legacy_version_running(&paths.runtime);
-  let runtime_dir_clone = paths.runtime.clone();
-  let is_legacy_version_running_handler =
-    Box::new(move || util::is_legacy_version_running(&runtime_dir_clone));
-
   let (is_wrong_edition_page_enabled, wrong_edition_detected_os) =
     edition_check::is_wrong_edition();
 
-  let is_migrate_page_enabled = espanso_config::is_legacy_config(&paths.config);
-  let paths_clone = paths.clone();
-  let backup_and_migrate_handler =
-    Box::new(move || match util::migrate_configuration(&paths_clone) {
-      Ok(()) => MigrationResult::Success,
-      Err(error) => match error.downcast_ref::<MigrationError>() {
-        Some(MigrationError::Dirty) => MigrationResult::DirtyFailure,
-        Some(MigrationError::Clean) => MigrationResult::CleanFailure,
-        _ => MigrationResult::UnknownFailure,
-      },
-    });
-
   let is_auto_start_page_enabled =
     !preferences.has_selected_auto_start_option() && !cfg!(target_os = "linux");
   let preferences_clone = preferences.clone();
@@ -150,8 +132,6 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
   // Only show the wizard if a panel should be displayed
   let should_launch_daemon = if is_welcome_page_enabled
     || is_move_bundle_page_enabled
-    || is_legacy_version_page_enabled
-    || is_migrate_page_enabled
     || is_auto_start_page_enabled
     || is_add_path_page_enabled
     || is_accessibility_page_enabled
@@ -161,9 +141,7 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
       version: crate::VERSION.to_string(),
       is_welcome_page_enabled,
       is_move_bundle_page_enabled,
-      is_legacy_version_page_enabled,
       is_wrong_edition_page_enabled,
-      is_migrate_page_enabled,
       is_auto_start_page_enabled,
       is_add_path_page_enabled,
       is_accessibility_page_enabled,
@@ -181,8 +159,6 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
         .map(|path| path.to_string_lossy().to_string()),
       detected_os: wrong_edition_detected_os,
       handlers: WizardHandlers {
-        is_legacy_version_running: Some(is_legacy_version_running_handler),
-        backup_and_migrate: Some(backup_and_migrate_handler),
         auto_start: Some(auto_start_handler),
         add_to_path: Some(add_to_path_handler),
         enable_accessibility: Some(enable_accessibility_handler),
@@ -194,13 +170,11 @@ fn launcher_main(args: CliModuleArgs) -> i32 {
     true
   };
 
-  if !espanso_config::is_legacy_config(&paths.config) {
-    if let Err(err) = crate::config::populate_default_config(&paths.config) {
-      error!("Error populating the config directory: {:?}", err);
+  if let Err(err) = crate::config::populate_default_config(&paths.config) {
+    error!("Error populating the config directory: {:?}", err);
 
-      // TODO: show an error message with GUI
-      return LAUNCHER_CONFIG_DIR_POPULATION_FAILURE;
-    }
+    // TODO: show an error message with GUI
+    return LAUNCHER_CONFIG_DIR_POPULATION_FAILURE;
   }
 
   if should_launch_daemon {
diff --git a/espanso/src/cli/launcher/util.rs b/espanso/src/cli/launcher/util.rs
index 204b7b9e3..ac4514032 100644
--- a/espanso/src/cli/launcher/util.rs
+++ b/espanso/src/cli/launcher/util.rs
@@ -17,65 +17,12 @@
  * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{path::Path, process::Command};
+use std::process::Command;
 
 use anyhow::{bail, Result};
-use espanso_path::Paths;
 use thiserror::Error;
 
-use crate::{
-  exit_code::{MIGRATE_CLEAN_FAILURE, MIGRATE_DIRTY_FAILURE},
-  lock::acquire_legacy_lock,
-  util::set_command_flags,
-};
-
-pub fn is_legacy_version_running(runtime_path: &Path) -> bool {
-  let legacy_lock_file = acquire_legacy_lock(runtime_path);
-  legacy_lock_file.is_none()
-}
-
-pub fn migrate_configuration(paths: &Paths) -> Result<()> {
-  let espanso_exe_path = std::env::current_exe()?;
-  let mut command = Command::new(espanso_exe_path.to_string_lossy().to_string());
-  command.args(["migrate", "--noconfirm"]);
-  command.env(
-    "ESPANSO_CONFIG_DIR",
-    paths.config.to_string_lossy().to_string(),
-  );
-  command.env(
-    "ESPANSO_PACKAGE_DIR",
-    paths.packages.to_string_lossy().to_string(),
-  );
-  command.env(
-    "ESPANSO_RUNTIME_DIR",
-    paths.runtime.to_string_lossy().to_string(),
-  );
-
-  let mut child = command.spawn()?;
-  let result = child.wait()?;
-
-  if result.success() {
-    Ok(())
-  } else {
-    match result.code() {
-      Some(code) if code == MIGRATE_CLEAN_FAILURE => Err(MigrationError::Clean.into()),
-      Some(code) if code == MIGRATE_DIRTY_FAILURE => Err(MigrationError::Dirty.into()),
-      _ => Err(MigrationError::Unexpected.into()),
-    }
-  }
-}
-
-#[derive(Error, Debug)]
-pub enum MigrationError {
-  #[error("clean error")]
-  Clean,
-
-  #[error("dirty error")]
-  Dirty,
-
-  #[error("unexpected error")]
-  Unexpected,
-}
+use crate::util::set_command_flags;
 
 pub fn add_espanso_to_path() -> Result<()> {
   let espanso_exe_path = std::env::current_exe()?;
diff --git a/espanso/src/cli/migrate.rs b/espanso/src/cli/migrate.rs
deleted file mode 100644
index e109c386c..000000000
--- a/espanso/src/cli/migrate.rs
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * This file is part of espanso.
- *
- * Copyright (C) 2019-2021 Federico Terzi
- *
- * espanso is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * espanso is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::path::PathBuf;
-
-use crate::{
-  exit_code::{
-    configure_custom_panic_hook, update_panic_exit_code, MIGRATE_ALREADY_NEW_FORMAT,
-    MIGRATE_CLEAN_FAILURE, MIGRATE_DIRTY_FAILURE, MIGRATE_LEGACY_INSTANCE_RUNNING, MIGRATE_SUCCESS,
-    MIGRATE_USER_ABORTED,
-  },
-  lock::acquire_legacy_lock,
-};
-
-use super::{CliModule, CliModuleArgs};
-use colored::*;
-use dialoguer::Confirm;
-use fs_extra::dir::CopyOptions;
-use log::{error, info};
-use tempdir::TempDir;
-
-pub fn new() -> CliModule {
-  CliModule {
-    enable_logs: true,
-    disable_logs_terminal_output: true,
-    requires_paths: true,
-    requires_config: true,
-    log_mode: super::LogMode::AppendOnly,
-    subcommand: "migrate".to_string(),
-    entry: migrate_main,
-    ..Default::default()
-  }
-}
-
-fn migrate_main(args: CliModuleArgs) -> i32 {
-  let paths = args.paths.expect("missing paths argument");
-  let cli_args = args.cli_args.expect("missing cli_args");
-
-  info!("--- MIGRATION STARTED ---");
-
-  configure_custom_panic_hook();
-
-  if !args.is_legacy_config {
-    error_print_and_log("Can't migrate configurations, as the default directory [1] is already encoded with the new format");
-    error_print_and_log(&format!("[1]: {:?}", paths.config));
-    error_print_and_log("The migration tool is only meant to convert the espanso's legacy configuration format (prior to");
-    error_print_and_log("version 0.7.3) to the new one (since version 2.0)");
-    return MIGRATE_ALREADY_NEW_FORMAT;
-  }
-
-  let legacy_lock_file = acquire_legacy_lock(&paths.runtime);
-  if legacy_lock_file.is_none() {
-    error_print_and_log("An instance of legacy espanso is running, please terminate it, otherwise the migration can't be performed");
-    return MIGRATE_LEGACY_INSTANCE_RUNNING;
-  }
-
-  let target_backup_dir = find_available_backup_dir();
-
-  println!("\n{}\n", "Welcome to espanso v2!".bold());
-  println!("This migration tool will help you to smoothly transition to the new espanso v2 configuration format.");
-  println!();
-  println!(
-    "1. Firstly, espanso will {} your current configuration, located in:\n",
-    "backup".green().bold()
-  );
-  println!("   {}\n", paths.config.to_string_lossy().italic());
-  println!("   into this folder:\n");
-  println!("   {}\n", target_backup_dir.to_string_lossy().italic());
-  println!(
-    "2. Then, it will {} your configuration to the new format, replacing",
-    "convert".bold().green()
-  );
-  println!("   the current content of the config directory.");
-  println!();
-
-  info!(
-    "backing up the configuration directory: '{}'",
-    paths.config.to_string_lossy()
-  );
-  info!(
-    " -> into this folder: '{}'",
-    target_backup_dir.to_string_lossy()
-  );
-
-  if !cli_args.is_present("noconfirm")
-    && !Confirm::new()
-      .with_prompt("Do you want to proceed?")
-      .default(true)
-      .interact()
-      .expect("unable to read choice")
-  {
-    return MIGRATE_USER_ABORTED;
-  }
-
-  println!("Backing up your configuration...");
-  update_panic_exit_code(MIGRATE_CLEAN_FAILURE);
-
-  fs_extra::dir::copy(
-    &paths.config,
-    &target_backup_dir,
-    &CopyOptions {
-      copy_inside: true,
-      ..Default::default()
-    },
-  )
-  .expect("unable to backup the current config");
-  println!("{}", "Backup completed!".green());
-  info!("backup completed!");
-
-  println!("Converting the configuration...");
-  info!("converting the configuration...");
-  let temp_dir = TempDir::new("espanso-migrate-out").expect("unable to create temporary directory");
-  let temp_out_dir = temp_dir.path().join("out");
-  espanso_migrate::migrate(&paths.config, &paths.packages, &temp_out_dir)
-    .expect("an error occurred while converting the configuration");
-  println!("{}", "Conversion completed!".green());
-  info!("conversion completed!");
-
-  println!("Replacing old configuration with the new one...");
-  info!("replacing old configuration with the new one...");
-  update_panic_exit_code(MIGRATE_DIRTY_FAILURE);
-
-  let mut to_be_removed = Vec::new();
-  let legacy_dir_content =
-    fs_extra::dir::get_dir_content(&paths.config).expect("unable to list legacy dir files");
-  to_be_removed.extend(legacy_dir_content.files);
-  to_be_removed.extend(legacy_dir_content.directories);
-
-  // Skip the config directory itself to preserve the symbolic link (if present)
-  let config_dir_as_str = paths.config.to_string_lossy().to_string();
-  to_be_removed.retain(|path| path != &config_dir_as_str);
-
-  fs_extra::remove_items(&to_be_removed).expect("unable to remove previous configuration");
-  fs_extra::dir::copy(
-    &temp_out_dir,
-    &paths.config,
-    &CopyOptions {
-      copy_inside: true,
-      content_only: true,
-      ..Default::default()
-    },
-  )
-  .expect("unable to copy new configuration into target location");
-
-  let target_packages_dir = &paths.config.join("match").join("packages");
-  if !target_packages_dir.is_dir() {
-    std::fs::create_dir_all(target_packages_dir).expect("unable to create new packages directory");
-  }
-
-  println!("{}", "Configuration successfully migrated!".green());
-  info!("configuration migrated!");
-
-  MIGRATE_SUCCESS
-}
-
-fn find_available_backup_dir() -> PathBuf {
-  for i in 1..20 {
-    let num = if i > 1 {
-      format!("-{i}")
-    } else {
-      String::new()
-    };
-
-    let target_backup_dir = find_backup_dir_location().join(format!("espanso-migrate-backup{num}"));
-
-    if !target_backup_dir.is_dir() {
-      return target_backup_dir;
-    }
-  }
-
-  panic!("could not generate valid backup directory");
-}
-
-fn find_backup_dir_location() -> PathBuf {
-  if let Some(documents_dir) = dirs::document_dir() {
-    return documents_dir;
-  }
-
-  if let Some(home_dir) = dirs::home_dir() {
-    return home_dir;
-  }
-
-  if let Ok(current_dir) = std::env::current_dir() {
-    return current_dir;
-  }
-
-  panic!("unable to generate suitable backup location directory");
-}
-
-fn error_print_and_log(msg: &str) {
-  error!("{msg}");
-  eprintln!("{msg}");
-}
diff --git a/espanso/src/cli/mod.rs b/espanso/src/cli/mod.rs
index 4103c4441..ec850c2cd 100644
--- a/espanso/src/cli/mod.rs
+++ b/espanso/src/cli/mod.rs
@@ -30,7 +30,6 @@ pub mod env_path;
 pub mod launcher;
 pub mod log;
 pub mod match_cli;
-pub mod migrate;
 pub mod modulo;
 pub mod package;
 pub mod path;
diff --git a/espanso/src/cli/modulo/troubleshoot.rs b/espanso/src/cli/modulo/troubleshoot.rs
index 570450a8f..c86162fdc 100644
--- a/espanso/src/cli/modulo/troubleshoot.rs
+++ b/espanso/src/cli/modulo/troubleshoot.rs
@@ -38,55 +38,54 @@ pub fn troubleshoot_main(paths: &Paths, icon_paths: &IconPaths) -> i32 {
     }
   });
 
-  let (is_fatal_error, error_sets) =
-    match crate::config::load_config(&paths.config, &paths.packages) {
-      Ok(config_result) => {
-        let error_sets = config_result
-          .non_fatal_errors
-          .into_iter()
-          .map(|error_set| espanso_modulo::troubleshooting::ErrorSet {
-            file: Some(error_set.file),
-            errors: error_set
-              .errors
-              .into_iter()
-              .map(|error| espanso_modulo::troubleshooting::ErrorRecord {
-                level: match error.level {
-                  espanso_config::error::ErrorLevel::Error => {
-                    espanso_modulo::troubleshooting::ErrorLevel::Error
-                  }
-                  espanso_config::error::ErrorLevel::Warning => {
-                    espanso_modulo::troubleshooting::ErrorLevel::Warning
-                  }
-                },
-                message: format!("{:?}", error.error),
-              })
-              .collect(),
-          })
-          .collect();
+  let (is_fatal_error, error_sets) = match crate::config::load_config(&paths.config) {
+    Ok(config_result) => {
+      let error_sets = config_result
+        .non_fatal_errors
+        .into_iter()
+        .map(|error_set| espanso_modulo::troubleshooting::ErrorSet {
+          file: Some(error_set.file),
+          errors: error_set
+            .errors
+            .into_iter()
+            .map(|error| espanso_modulo::troubleshooting::ErrorRecord {
+              level: match error.level {
+                espanso_config::error::ErrorLevel::Error => {
+                  espanso_modulo::troubleshooting::ErrorLevel::Error
+                }
+                espanso_config::error::ErrorLevel::Warning => {
+                  espanso_modulo::troubleshooting::ErrorLevel::Warning
+                }
+              },
+              message: format!("{:?}", error.error),
+            })
+            .collect(),
+        })
+        .collect();
 
-        (false, error_sets)
-      }
-      Err(err) => {
-        let message = format!("{err:?}");
-        let file_path = if message.contains("default.yml") {
-          let default_file_path = paths.config.join("config").join("default.yml");
-          Some(default_file_path)
-        } else {
-          None
-        };
+      (false, error_sets)
+    }
+    Err(err) => {
+      let message = format!("{err:?}");
+      let file_path = if message.contains("default.yml") {
+        let default_file_path = paths.config.join("config").join("default.yml");
+        Some(default_file_path)
+      } else {
+        None
+      };
 
-        (
-          true,
-          vec![espanso_modulo::troubleshooting::ErrorSet {
-            file: file_path,
-            errors: vec![espanso_modulo::troubleshooting::ErrorRecord {
-              level: espanso_modulo::troubleshooting::ErrorLevel::Error,
-              message: format!("{err:?}"),
-            }],
+      (
+        true,
+        vec![espanso_modulo::troubleshooting::ErrorSet {
+          file: file_path,
+          errors: vec![espanso_modulo::troubleshooting::ErrorRecord {
+            level: espanso_modulo::troubleshooting::ErrorLevel::Error,
+            message: format!("{err:?}"),
           }],
-        )
-      }
-    };
+        }],
+      )
+    }
+  };
 
   espanso_modulo::troubleshooting::show(TroubleshootingOptions {
     window_icon_path: icon_paths
diff --git a/espanso/src/cli/worker/mod.rs b/espanso/src/cli/worker/mod.rs
index b2cf019c5..83fcfb6ba 100644
--- a/espanso/src/cli/worker/mod.rs
+++ b/espanso/src/cli/worker/mod.rs
@@ -24,10 +24,10 @@ use log::{debug, error, info};
 use crate::{
   cli::util::prevent_running_as_root_on_macos,
   exit_code::{
-    WORKER_ALREADY_RUNNING, WORKER_EXIT_ALL_PROCESSES, WORKER_GENERAL_ERROR,
-    WORKER_LEGACY_ALREADY_RUNNING, WORKER_RESTART, WORKER_SUCCESS,
+    WORKER_ALREADY_RUNNING, WORKER_EXIT_ALL_PROCESSES, WORKER_GENERAL_ERROR, WORKER_RESTART,
+    WORKER_SUCCESS,
   },
-  lock::{acquire_legacy_lock, acquire_worker_lock},
+  lock::acquire_worker_lock,
 };
 
 use self::ui::util::convert_icon_paths_to_tray_vec;
@@ -75,13 +75,6 @@ fn worker_main(args: CliModuleArgs) -> i32 {
     return WORKER_ALREADY_RUNNING;
   }
 
-  let legacy_lock_file = acquire_legacy_lock(&paths.runtime);
-  if legacy_lock_file.is_none() {
-    error!("an instance of legacy espanso is running, please terminate it, otherwise the new version cannot start");
-    return WORKER_LEGACY_ALREADY_RUNNING;
-  }
-  drop(legacy_lock_file);
-
   let config_store = args
     .config_store
     .expect("missing config store in worker main");
diff --git a/espanso/src/config.rs b/espanso/src/config.rs
index d091b424a..0cb1d691d 100644
--- a/espanso/src/config.rs
+++ b/espanso/src/config.rs
@@ -74,51 +74,36 @@ pub fn populate_default_config(config_dir: &Path) -> Result<()> {
 pub struct ConfigLoadResult {
   pub config_store: Box<dyn ConfigStore>,
   pub match_store: Box<dyn MatchStore>,
-  pub is_legacy_config: bool,
   pub non_fatal_errors: Vec<NonFatalErrorSet>,
 }
 
-pub fn load_config(config_path: &Path, packages_path: &Path) -> Result<ConfigLoadResult> {
-  if espanso_config::is_legacy_config(config_path) {
-    let (config_store, match_store) = espanso_config::load_legacy(config_path, packages_path)
-      .context("unable to load legacy config")?;
+pub fn load_config(config_path: &Path) -> Result<ConfigLoadResult> {
+  let (config_store, match_store, non_fatal_errors) =
+    espanso_config::load(config_path).context("unable to load config")?;
 
-    Ok(ConfigLoadResult {
-      // Apply the built-in patches
-      config_store: crate::patch::patch_store(config_store),
-      match_store,
-      is_legacy_config: true,
-      non_fatal_errors: Vec::new(),
-    })
-  } else {
-    let (config_store, match_store, non_fatal_errors) =
-      espanso_config::load(config_path).context("unable to load config")?;
-
-    // TODO: add an option to avoid dumping the errors in the logs
-    if !non_fatal_errors.is_empty() {
-      warn!("------- detected some errors in the configuration: -------");
-      for non_fatal_error_set in &non_fatal_errors {
-        warn!(
-          ">>> {}",
-          non_fatal_error_set.file.to_string_lossy().to_string()
-        );
-        for record in &non_fatal_error_set.errors {
-          if record.level == ErrorLevel::Error {
-            error!("{:?}", record.error);
-          } else {
-            warn!("{:?}", record.error);
-          }
+  // TODO: add an option to avoid dumping the errors in the logs
+  if !non_fatal_errors.is_empty() {
+    warn!("------- detected some errors in the configuration: -------");
+    for non_fatal_error_set in &non_fatal_errors {
+      warn!(
+        ">>> {}",
+        non_fatal_error_set.file.to_string_lossy().to_string()
+      );
+      for record in &non_fatal_error_set.errors {
+        if record.level == ErrorLevel::Error {
+          error!("{:?}", record.error);
+        } else {
+          warn!("{:?}", record.error);
         }
       }
-      warn!("-----------------------------------------------------------");
     }
-
-    Ok(ConfigLoadResult {
-      // Apply the built-in patches
-      config_store: crate::patch::patch_store(config_store),
-      match_store,
-      is_legacy_config: false,
-      non_fatal_errors,
-    })
+    warn!("-----------------------------------------------------------");
   }
+
+  Ok(ConfigLoadResult {
+    // Apply the built-in patches
+    config_store: crate::patch::patch_store(config_store),
+    match_store,
+    non_fatal_errors,
+  })
 }
diff --git a/espanso/src/exit_code.rs b/espanso/src/exit_code.rs
index 6500c539d..b1bdf39ad 100644
--- a/espanso/src/exit_code.rs
+++ b/espanso/src/exit_code.rs
@@ -20,7 +20,6 @@
 pub const WORKER_SUCCESS: i32 = 0;
 pub const WORKER_ALREADY_RUNNING: i32 = 1;
 pub const WORKER_GENERAL_ERROR: i32 = 2;
-pub const WORKER_LEGACY_ALREADY_RUNNING: i32 = 3;
 pub const WORKER_EXIT_ALL_PROCESSES: i32 = 50;
 pub const WORKER_RESTART: i32 = 51;
 pub const WORKER_ERROR_EXIT_NO_CODE: i32 = 90;
@@ -28,17 +27,8 @@ pub const WORKER_ERROR_EXIT_NO_CODE: i32 = 90;
 pub const DAEMON_SUCCESS: i32 = 0;
 pub const DAEMON_ALREADY_RUNNING: i32 = 1;
 pub const DAEMON_GENERAL_ERROR: i32 = 2;
-pub const DAEMON_LEGACY_ALREADY_RUNNING: i32 = 3;
 pub const DAEMON_FATAL_CONFIG_ERROR: i32 = 4;
 
-pub const MIGRATE_SUCCESS: i32 = 0;
-pub const MIGRATE_ALREADY_NEW_FORMAT: i32 = 1;
-pub const MIGRATE_LEGACY_INSTANCE_RUNNING: i32 = 2;
-pub const MIGRATE_USER_ABORTED: i32 = 3;
-pub const MIGRATE_CLEAN_FAILURE: i32 = 50;
-pub const MIGRATE_DIRTY_FAILURE: i32 = 51;
-pub const MIGRATE_UNEXPECTED_FAILURE: i32 = 52;
-
 pub const ADD_TO_PATH_SUCCESS: i32 = 0;
 pub const ADD_TO_PATH_FAILURE: i32 = 1;
 
@@ -72,15 +62,8 @@ pub const PACKAGE_UPDATE_PARTIAL_FAILURE: i32 = 6;
 #[allow(dead_code)]
 pub const UNEXPECTED_RUN_AS_ROOT: i32 = 42;
 
-use lazy_static::lazy_static;
-use std::sync::Mutex;
-
 use crate::error_eprintln;
 
-lazy_static! {
-  static ref CURRENT_PANIC_EXIT_CODE: Mutex<i32> = Mutex::new(MIGRATE_UNEXPECTED_FAILURE);
-}
-
 pub fn configure_custom_panic_hook() {
   let previous_hook = std::panic::take_hook();
   std::panic::set_hook(Box::new(move |info| {
@@ -110,14 +93,6 @@ pub fn configure_custom_panic_hook() {
       error_eprintln!("ERROR: '{}' panicked at '{}'", thread, msg);
     }
 
-    let exit_code = CURRENT_PANIC_EXIT_CODE.lock().unwrap();
-    std::process::exit(*exit_code);
+    std::process::exit(52); // MIGRATE_UNEXPECTED_FAILURE
   }));
 }
-
-pub fn update_panic_exit_code(exit_code: i32) {
-  let mut lock = CURRENT_PANIC_EXIT_CODE
-    .lock()
-    .expect("unable to update panic exit code");
-  *lock = exit_code;
-}
diff --git a/espanso/src/lock.rs b/espanso/src/lock.rs
index 1f58d2aa0..85b03d3e5 100644
--- a/espanso/src/lock.rs
+++ b/espanso/src/lock.rs
@@ -68,7 +68,3 @@ pub fn acquire_daemon_lock(runtime_dir: &Path) -> Option<Lock> {
 pub fn acquire_worker_lock(runtime_dir: &Path) -> Option<Lock> {
   Lock::acquire(runtime_dir, "espanso-worker")
 }
-
-pub fn acquire_legacy_lock(runtime_dir: &Path) -> Option<Lock> {
-  Lock::acquire(runtime_dir, "espanso")
-}
diff --git a/espanso/src/main.rs b/espanso/src/main.rs
index 0e6fc644d..3a05f215a 100644
--- a/espanso/src/main.rs
+++ b/espanso/src/main.rs
@@ -24,7 +24,7 @@ use std::path::PathBuf;
 
 use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
 use cli::{CliAlias, CliModule, CliModuleArgs};
-use log::{error, info, warn};
+use log::{error, info};
 use logging::FileProxy;
 use simplelog::{
   CombinedLogger, ConfigBuilder, LevelFilter, SharedLogger, TermLogger, TerminalMode, WriteLogger,
@@ -66,7 +66,6 @@ lazy_static! {
     cli::worker::new(),
     cli::daemon::new(),
     cli::modulo::new(),
-    cli::migrate::new(),
     cli::env_path::new(),
     cli::service::new(),
     cli::workaround::new(),
@@ -343,12 +342,6 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#))
         )
         .subcommand(SubCommand::with_name("base").about("Print the default match file path.")),
     )
-    .subcommand(
-      SubCommand::with_name("migrate")
-        .about("Automatically migrate legacy config files to the new v2 format.")
-        .arg(Arg::with_name("noconfirm").long("noconfirm"))
-        .help("Migrate the configuration without asking for confirmation"),
-    )
     .subcommand(
       SubCommand::with_name("service")
         .subcommand(SubCommand::with_name("register").about("Register espanso as a system service"))
@@ -589,18 +582,11 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#))
       log_system_info();
 
       if handler.requires_config {
-        let config_result =
-          load_config(&paths.config, &paths.packages).expect("unable to load config");
+        let config_result = load_config(&paths.config).expect("unable to load config");
 
-        cli_args.is_legacy_config = config_result.is_legacy_config;
         cli_args.config_store = Some(config_result.config_store);
         cli_args.match_store = Some(config_result.match_store);
         cli_args.non_fatal_errors = config_result.non_fatal_errors;
-
-        if config_result.is_legacy_config {
-          warn!("espanso is reading the configuration using compatibility mode, thus some features might not be available");
-          warn!("you can migrate to the new configuration format by running 'espanso migrate' in a terminal");
-        }
       }
 
       if handler.enable_logs {


Test patch:
diff --git a/espanso-config/src/legacy/res/test/config_with_bad_yaml.yml b/espanso-config/src/legacy/res/test/config_with_bad_yaml.yml
deleted file mode 100644
index b5d68227f..000000000
--- a/espanso-config/src/legacy/res/test/config_with_bad_yaml.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-backend: Clipboard
-
-definitely a bad yaml
-
-matches:
-  # Default
-  - trigger: ":espanso"
-    replace: "Hi there!"
-
-  # Emojis
-  - trigger: ":lol"
-    replace: "😂"
\ No newline at end of file
diff --git a/espanso-config/src/legacy/res/test/default.yml b/espanso-config/src/legacy/res/test/default.yml
deleted file mode 100644
index 7ad3e30b7..000000000
--- a/espanso-config/src/legacy/res/test/default.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-# espanso configuration file
-
-# This is the default configuration file, change it as you like it
-# You can refer to the official documentation:
-# https://espanso.org/docs/
-
-# Matches are the substitution rules, when you type the "trigger" string
-# it gets replaced by the "replace" string.
-matches:
-  # Simple text replacement
-  - trigger: ":espanso"
-    replace: "Hi there!"
-
-  # Dates
-  - trigger: ":date"
-    replace: "{{mydate}}"
-    vars:
-      - name: mydate
-        type: date
-        params:
-          format: "%m/%d/%Y"
-
-  # Shell commands
-  - trigger: ":shell"
-    replace: "{{output}}"
-    vars:
-      - name: output
-        type: shell
-        params:
-          cmd: "echo Hello from your shell"
\ No newline at end of file
diff --git a/espanso-config/src/legacy/res/test/working_config.yml b/espanso-config/src/legacy/res/test/working_config.yml
deleted file mode 100644
index bcf8bf615..000000000
--- a/espanso-config/src/legacy/res/test/working_config.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-backend: Clipboard
-
-matches:
-  # Default
-  - trigger: ":espanso"
-    replace: "Hi there!"
-
-  # Emojis
-  - trigger: ":lol"
-    replace: "😂"
\ No newline at end of file
diff --git a/espanso-migrate/test/README.md b/espanso-migrate/test/README.md
deleted file mode 100644
index 5ccf659fe..000000000
--- a/espanso-migrate/test/README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-This directory contains the files used to test the migration strategy.
\ No newline at end of file
diff --git a/espanso-migrate/test/all_params/expected/config/default.yml b/espanso-migrate/test/all_params/expected/config/default.yml
deleted file mode 100644
index 110e976e5..000000000
--- a/espanso-migrate/test/all_params/expected/config/default.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-toggle_interval: 500
-toggle_key: "RIGHT_CTRL"
-
-preserve_clipboard: false
-
-backspace_limit: 5
-
-inject_delay: 10
-restore_clipboard_delay: 400
-key_delay: 300
-
-secure_input_notification: false
-
-show_notifications: false
-show_icon: false
-
-auto_restart: false
-undo_backspace: false
-
-disable_x11_fast_inject: true
-
-paste_shortcut: "CTRL+ALT+V"
-
-backend: Clipboard
-
-word_separators: ['.']
\ No newline at end of file
diff --git a/espanso-migrate/test/all_params/expected/match/base.yml b/espanso-migrate/test/all_params/expected/match/base.yml
deleted file mode 100644
index f89c90c2d..000000000
--- a/espanso-migrate/test/all_params/expected/match/base.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-global_vars:
-  - name: "name"
-    type: "dummy"
-    params:
-      echo: "John"
-
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file
diff --git a/espanso-migrate/test/all_params/legacy/default.yml b/espanso-migrate/test/all_params/legacy/default.yml
deleted file mode 100644
index a788446be..000000000
--- a/espanso-migrate/test/all_params/legacy/default.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-config_caching_interval: 1200
-
-toggle_interval: 500
-toggle_key: "RIGHT_CTRL"
-
-preserve_clipboard: false
-
-backspace_limit: 5
-
-inject_delay: 10
-restore_clipboard_delay: 400
-backspace_delay: 300
-
-secure_input_watcher_enabled: false
-secure_input_notification: false
-secure_input_watcher_interval: 3000
-
-show_notifications: false
-show_icon: false
-
-auto_restart: false
-undo_backspace: false
-
-fast_inject: false
-
-post_inject_delay: 200
-
-paste_shortcut: CtrlAltV
-
-backend: Clipboard
-
-use_system_agent: false
-word_separators: ['.']
-
-global_vars:
-  - name: "name"
-    type: "dummy"
-    params:
-      echo: "John"
-
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/config/default.yml b/espanso-migrate/test/base/expected/config/default.yml
deleted file mode 100644
index 3b6eb1060..000000000
--- a/espanso-migrate/test/base/expected/config/default.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-backend: Clipboard
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/config/disabled.yml b/espanso-migrate/test/base/expected/config/disabled.yml
deleted file mode 100644
index e6d056919..000000000
--- a/espanso-migrate/test/base/expected/config/disabled.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-filter_title: "Disabled program"
-
-enable: false
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/config/extended.yml b/espanso-migrate/test/base/expected/config/extended.yml
deleted file mode 100644
index e1a3e61c6..000000000
--- a/espanso-migrate/test/base/expected/config/extended.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-filter_exec: "Executable"
-
-extra_includes:
-  - "../match/_extended.yml"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/config/standalone.yml b/espanso-migrate/test/base/expected/config/standalone.yml
deleted file mode 100644
index 6697559b2..000000000
--- a/espanso-migrate/test/base/expected/config/standalone.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-filter_class: "Standalone"
-
-includes:
-  - "../match/_standalone.yml"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/match/_extended.yml b/espanso-migrate/test/base/expected/match/_extended.yml
deleted file mode 100644
index 5e95626f4..000000000
--- a/espanso-migrate/test/base/expected/match/_extended.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-global_vars:
-  - name: "test"
-    type: "date"
-    params:
-      format: "%H"
-
-matches:
-  - trigger: "extend"
-    replace: "match"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/match/_standalone.yml b/espanso-migrate/test/base/expected/match/_standalone.yml
deleted file mode 100644
index c20493055..000000000
--- a/espanso-migrate/test/base/expected/match/_standalone.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-matches:
-  - trigger: "hello"
-    replace: "world"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/match/base.yml b/espanso-migrate/test/base/expected/match/base.yml
deleted file mode 100644
index f89c90c2d..000000000
--- a/espanso-migrate/test/base/expected/match/base.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-global_vars:
-  - name: "name"
-    type: "dummy"
-    params:
-      echo: "John"
-
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/match/packages/foo/custom.yml b/espanso-migrate/test/base/expected/match/packages/foo/custom.yml
deleted file mode 100644
index 1f2697ae9..000000000
--- a/espanso-migrate/test/base/expected/match/packages/foo/custom.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-matches:
-  - trigger: "custom"
-    replace: "match"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/match/packages/foo/package.yml b/espanso-migrate/test/base/expected/match/packages/foo/package.yml
deleted file mode 100644
index dc0b3c520..000000000
--- a/espanso-migrate/test/base/expected/match/packages/foo/package.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-matches:
-  - trigger: "package_foo"
-    replace: "package_bar"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/expected/match/split.yml b/espanso-migrate/test/base/expected/match/split.yml
deleted file mode 100644
index c5d3bd307..000000000
--- a/espanso-migrate/test/base/expected/match/split.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-matches:
-  - trigger: "jon"
-    replace: "snow"
-
diff --git a/espanso-migrate/test/base/legacy/default.yml b/espanso-migrate/test/base/legacy/default.yml
deleted file mode 100644
index 6afad09b8..000000000
--- a/espanso-migrate/test/base/legacy/default.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-backend: Clipboard
-
-global_vars:
-  - name: "name"
-    type: "dummy"
-    params:
-      echo: "John"
-
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/legacy/packages/foo/README.md b/espanso-migrate/test/base/legacy/packages/foo/README.md
deleted file mode 100644
index a5f9d5db9..000000000
--- a/espanso-migrate/test/base/legacy/packages/foo/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-package_name: "foo"
-package_title: "Foo"
-package_desc: "Foo package"
-package_version: "0.1.0"
-package_author: "Federico Terzi"
-package_repo: "https://github.com/espanso/espanso-hub-core"
----
-Test package
\ No newline at end of file
diff --git a/espanso-migrate/test/base/legacy/packages/foo/custom.yml b/espanso-migrate/test/base/legacy/packages/foo/custom.yml
deleted file mode 100644
index 13c3b1da5..000000000
--- a/espanso-migrate/test/base/legacy/packages/foo/custom.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: foo
-parent: default
-
-matches:
-  - trigger: "custom"
-    replace: "match"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/legacy/packages/foo/package.yml b/espanso-migrate/test/base/legacy/packages/foo/package.yml
deleted file mode 100644
index d2161350a..000000000
--- a/espanso-migrate/test/base/legacy/packages/foo/package.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: foo
-parent: default
-
-matches:
-  - trigger: "package_foo"
-    replace: "package_bar"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/legacy/user/disabled.yml b/espanso-migrate/test/base/legacy/user/disabled.yml
deleted file mode 100644
index 030e78344..000000000
--- a/espanso-migrate/test/base/legacy/user/disabled.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-filter_title: "Disabled program"
-
-enable_active: false
\ No newline at end of file
diff --git a/espanso-migrate/test/base/legacy/user/extended.yml b/espanso-migrate/test/base/legacy/user/extended.yml
deleted file mode 100644
index 08e2fd90a..000000000
--- a/espanso-migrate/test/base/legacy/user/extended.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-filter_exec: "Executable"
-
-global_vars:
-  - name: "test"
-    type: "date"
-    params:
-      format: "%H"
-
-matches:
-  - trigger: "extend"
-    replace: "match"
\ No newline at end of file
diff --git a/espanso-migrate/test/base/legacy/user/split.yml b/espanso-migrate/test/base/legacy/user/split.yml
deleted file mode 100644
index 5e1eab466..000000000
--- a/espanso-migrate/test/base/legacy/user/split.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-parent: default
-
-matches:
-  - trigger: "jon"
-    replace: "snow"
diff --git a/espanso-migrate/test/base/legacy/user/standalone.yml b/espanso-migrate/test/base/legacy/user/standalone.yml
deleted file mode 100644
index bb41d9865..000000000
--- a/espanso-migrate/test/base/legacy/user/standalone.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-filter_class: "Standalone"
-
-exclude_default_entries: true
-
-matches:
-  - trigger: "hello"
-    replace: "world"
\ No newline at end of file
diff --git a/espanso-migrate/test/form_syntax/expected/config/default.yml b/espanso-migrate/test/form_syntax/expected/config/default.yml
deleted file mode 100644
index e69de29bb..000000000
diff --git a/espanso-migrate/test/form_syntax/expected/match/base.yml b/espanso-migrate/test/form_syntax/expected/match/base.yml
deleted file mode 100644
index 474aecce7..000000000
--- a/espanso-migrate/test/form_syntax/expected/match/base.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-global_vars:
-  - name: global_form
-    type: form
-    params:
-      layout: |
-        Reverse [[name]]
-
-matches:
-  - trigger: ":greet"
-    form: |
-      Hey [[name]],
-      Happy Birthday!
-  
-  - trigger: ":rev"
-    replace: "{{reversed}}"
-    vars:
-    - name: form1
-      type: form
-      params:
-        layout: |
-          Reverse [[name]]
-    - name: reversed
-      type: shell
-      params:
-       cmd: "echo $ESPANSO_FORM1_NAME | rev"
\ No newline at end of file
diff --git a/espanso-migrate/test/form_syntax/legacy/default.yml b/espanso-migrate/test/form_syntax/legacy/default.yml
deleted file mode 100644
index 22324a1e7..000000000
--- a/espanso-migrate/test/form_syntax/legacy/default.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-global_vars:
-  - name: global_form
-    type: form
-    params:
-      layout: |
-        Reverse {{name}}
-
-matches:
-  - trigger: ":greet"
-    form: |
-      Hey {{name}},
-      Happy Birthday!
-  
-  - trigger: ":rev"
-    replace: "{{reversed}}"
-    vars:
-    - name: form1
-      type: form
-      params:
-        layout: |
-          Reverse {{name}}
-    - name: reversed
-      type: shell
-      params:
-       cmd: "echo $ESPANSO_FORM1_NAME | rev"
\ No newline at end of file
diff --git a/espanso-migrate/test/other_dirs/expected/config/default.yml b/espanso-migrate/test/other_dirs/expected/config/default.yml
deleted file mode 100644
index 3b6eb1060..000000000
--- a/espanso-migrate/test/other_dirs/expected/config/default.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-backend: Clipboard
\ No newline at end of file
diff --git a/espanso-migrate/test/other_dirs/expected/config/disabled.yml b/espanso-migrate/test/other_dirs/expected/config/disabled.yml
deleted file mode 100644
index e6d056919..000000000
--- a/espanso-migrate/test/other_dirs/expected/config/disabled.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-filter_title: "Disabled program"
-
-enable: false
\ No newline at end of file
diff --git a/espanso-migrate/test/other_dirs/expected/match/base.yml b/espanso-migrate/test/other_dirs/expected/match/base.yml
deleted file mode 100644
index f89c90c2d..000000000
--- a/espanso-migrate/test/other_dirs/expected/match/base.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-global_vars:
-  - name: "name"
-    type: "dummy"
-    params:
-      echo: "John"
-
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file
diff --git a/espanso-migrate/test/other_dirs/expected/scripts/test.py b/espanso-migrate/test/other_dirs/expected/scripts/test.py
deleted file mode 100644
index 0832ecaeb..000000000
--- a/espanso-migrate/test/other_dirs/expected/scripts/test.py
+++ /dev/null
@@ -1,1 +0,0 @@
-print("this is a test")
\ No newline at end of file
diff --git a/espanso-migrate/test/other_dirs/legacy/default.yml b/espanso-migrate/test/other_dirs/legacy/default.yml
deleted file mode 100644
index 6afad09b8..000000000
--- a/espanso-migrate/test/other_dirs/legacy/default.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-backend: Clipboard
-
-global_vars:
-  - name: "name"
-    type: "dummy"
-    params:
-      echo: "John"
-
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file
diff --git a/espanso-migrate/test/other_dirs/legacy/scripts/test.py b/espanso-migrate/test/other_dirs/legacy/scripts/test.py
deleted file mode 100644
index 0832ecaeb..000000000
--- a/espanso-migrate/test/other_dirs/legacy/scripts/test.py
+++ /dev/null
@@ -1,1 +0,0 @@
-print("this is a test")
\ No newline at end of file
diff --git a/espanso-migrate/test/other_dirs/legacy/user/disabled.yml b/espanso-migrate/test/other_dirs/legacy/user/disabled.yml
deleted file mode 100644
index 030e78344..000000000
--- a/espanso-migrate/test/other_dirs/legacy/user/disabled.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-filter_title: "Disabled program"
-
-enable_active: false
\ No newline at end of file
diff --git a/espanso-migrate/test/simple/expected/config/default.yml b/espanso-migrate/test/simple/expected/config/default.yml
deleted file mode 100644
index e69de29bb..000000000
diff --git a/espanso-migrate/test/simple/expected/match/base.yml b/espanso-migrate/test/simple/expected/match/base.yml
deleted file mode 100644
index b5fb8291c..000000000
--- a/espanso-migrate/test/simple/expected/match/base.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file
diff --git a/espanso-migrate/test/simple/legacy/default.yml b/espanso-migrate/test/simple/legacy/default.yml
deleted file mode 100644
index b5fb8291c..000000000
--- a/espanso-migrate/test/simple/legacy/default.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-matches:
-  - name: ":hi"
-    trigger: "Hello"
-  
-  - name: ":greet"
-    trigger: "Hi {{name}}"
\ No newline at end of file