diff --git a/Cargo.lock b/Cargo.lock
index 998566d4c9b64e51611c56ebc10c2a870a86858c..2055200c09c0012f751add3eab1779a7faf06258 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
 
 [[package]]
 name = "ab_glyph"
-version = "0.2.28"
+version = "0.2.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb"
+checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0"
 dependencies = [
  "ab_glyph_rasterizer",
  "owned_ttf_parser",
@@ -89,9 +89,9 @@ dependencies = [
 
 [[package]]
 name = "addr2line"
-version = "0.24.1"
+version = "0.24.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
 dependencies = [
  "gimli",
 ]
@@ -150,11 +150,10 @@ checksum = "3aa2999eb46af81abb65c2d30d446778d7e613b60bbf4e174a027e80f90a3c14"
 
 [[package]]
 name = "amadeus"
-version = "0.0.1"
+version = "3.0.1"
 dependencies = [
  "anyhow",
  "async-channel",
- "async-trait",
  "chrono",
  "derive_more",
  "futures",
@@ -299,9 +298,9 @@ dependencies = [
 
 [[package]]
 name = "ashpd"
-version = "0.9.1"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a"
+checksum = "4d43c03d9e36dd40cab48435be0b09646da362c278223ca535493877b2c1dee9"
 dependencies = [
  "enumflags2",
  "futures-channel",
@@ -822,9 +821,9 @@ dependencies = [
 
 [[package]]
 name = "bytemuck_derive"
-version = "1.7.1"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26"
+checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -897,9 +896,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.1.24"
+version = "1.1.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938"
+checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
 dependencies = [
  "jobserver",
  "libc",
@@ -946,9 +945,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.18"
+version = "4.5.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
+checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -956,9 +955,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.18"
+version = "4.5.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
+checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
 dependencies = [
  "anstyle",
  "clap_lex",
@@ -967,9 +966,9 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.5.29"
+version = "4.5.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8937760c3f4c60871870b8c3ee5f9b30771f792a7045c48bcbba999d7d6b3b8e"
+checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb"
 dependencies = [
  "clap",
 ]
@@ -1198,7 +1197,7 @@ dependencies = [
 [[package]]
 name = "cosmic-config"
 version = "0.1.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "atomicwrites",
  "cosmic-config-derive",
@@ -1220,7 +1219,7 @@ dependencies = [
 [[package]]
 name = "cosmic-config-derive"
 version = "0.1.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "quote",
  "syn 1.0.109",
@@ -1229,7 +1228,7 @@ dependencies = [
 [[package]]
 name = "cosmic-settings-daemon"
 version = "0.1.0"
-source = "git+https://github.com/pop-os/dbus-settings-bindings#01ee80cd975ad3f41a47738ed21d778a7cd07552"
+source = "git+https://github.com/pop-os/dbus-settings-bindings#931f5db558bf3fcb572ff4e18f7f1618a7430046"
 dependencies = [
  "zbus 4.4.0",
 ]
@@ -1260,7 +1259,7 @@ dependencies = [
 [[package]]
 name = "cosmic-theme"
 version = "0.1.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "almost",
  "cosmic-config",
@@ -1865,15 +1864,15 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
 [[package]]
 name = "foldhash"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33d5ac82bdbbd872ce0dfea4e848a678cfcfdc0d210a5b20549b8c659fec0392"
+checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
 
 [[package]]
 name = "font-types"
-version = "0.6.0"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60"
+checksum = "dda6e36206148f69fc6ecb1bb6c0dedd7ee469f3db1d0dc2045beea28430ca43"
 dependencies = [
  "bytemuck",
 ]
@@ -1971,9 +1970,9 @@ dependencies = [
 
 [[package]]
 name = "futures"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1986,9 +1985,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -1996,15 +1995,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -2014,9 +2013,9 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
 
 [[package]]
 name = "futures-lite"
@@ -2048,9 +2047,9 @@ dependencies = [
 
 [[package]]
 name = "futures-macro"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2059,21 +2058,21 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
 
 [[package]]
 name = "futures-task"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
 
 [[package]]
 name = "futures-util"
-version = "0.3.30"
+version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -2140,9 +2139,9 @@ dependencies = [
 
 [[package]]
 name = "gimli"
-version = "0.31.0"
+version = "0.31.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
 
 [[package]]
 name = "gl_generator"
@@ -2563,7 +2562,7 @@ dependencies = [
 [[package]]
 name = "iced"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "dnd",
  "iced_accessibility",
@@ -2581,7 +2580,7 @@ dependencies = [
 [[package]]
 name = "iced_accessibility"
 version = "0.1.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "accesskit",
  "accesskit_winit",
@@ -2590,7 +2589,7 @@ dependencies = [
 [[package]]
 name = "iced_core"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "bitflags 2.6.0",
  "dnd",
@@ -2610,7 +2609,7 @@ dependencies = [
 [[package]]
 name = "iced_futures"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "futures",
  "iced_core",
@@ -2623,7 +2622,7 @@ dependencies = [
 [[package]]
 name = "iced_graphics"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "bitflags 2.6.0",
  "bytemuck",
@@ -2647,7 +2646,7 @@ dependencies = [
 [[package]]
 name = "iced_renderer"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "iced_graphics",
  "iced_tiny_skia",
@@ -2659,7 +2658,7 @@ dependencies = [
 [[package]]
 name = "iced_runtime"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "dnd",
  "iced_core",
@@ -2671,7 +2670,7 @@ dependencies = [
 [[package]]
 name = "iced_style"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "iced_core",
  "once_cell",
@@ -2681,7 +2680,7 @@ dependencies = [
 [[package]]
 name = "iced_tiny_skia"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "bytemuck",
  "cosmic-text",
@@ -2698,7 +2697,7 @@ dependencies = [
 [[package]]
 name = "iced_wgpu"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "as-raw-xcb-connection",
  "bitflags 2.6.0",
@@ -2727,7 +2726,7 @@ dependencies = [
 [[package]]
 name = "iced_widget"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "dnd",
  "iced_renderer",
@@ -2743,7 +2742,7 @@ dependencies = [
 [[package]]
 name = "iced_winit"
 version = "0.12.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "dnd",
  "iced_graphics",
@@ -2821,12 +2820,12 @@ checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
 
 [[package]]
 name = "indexmap"
-version = "2.5.0"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
+checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
 dependencies = [
  "equivalent",
- "hashbrown 0.14.5",
+ "hashbrown 0.15.0",
 ]
 
 [[package]]
@@ -2890,9 +2889,9 @@ dependencies = [
 
 [[package]]
 name = "ipnet"
-version = "2.10.0"
+version = "2.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
+checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
 
 [[package]]
 name = "itoa"
@@ -2942,9 +2941,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.70"
+version = "0.3.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -3017,8 +3016,10 @@ dependencies = [
 name = "kurisu_api"
 version = "8.0.1"
 dependencies = [
+ "derive_more",
  "hashbrown 0.15.0",
  "lektor_utils",
+ "log",
  "serde",
  "serde_json",
  "sha256",
@@ -3041,7 +3042,6 @@ name = "lektor_lib"
 version = "8.0.1"
 dependencies = [
  "anyhow",
- "async-trait",
  "futures",
  "lektor_payloads",
  "lektor_utils",
@@ -3071,8 +3071,8 @@ name = "lektor_nkdb"
 version = "8.0.1"
 dependencies = [
  "anyhow",
- "async-trait",
  "chrono",
+ "derive_more",
  "futures",
  "hashbrown 0.15.0",
  "kurisu_api",
@@ -3098,6 +3098,7 @@ dependencies = [
  "axum",
  "futures",
  "lektor_nkdb",
+ "lektor_procmacros",
  "lektor_utils",
  "serde",
  "serde_json",
@@ -3151,7 +3152,6 @@ name = "lektord"
 version = "8.0.1"
 dependencies = [
  "anyhow",
- "async-trait",
  "axum",
  "clap",
  "futures",
@@ -3181,10 +3181,10 @@ checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
 [[package]]
 name = "libcosmic"
 version = "0.1.0"
-source = "git+https://github.com/pop-os/libcosmic.git#228eb4d70d581be88bacb1e261106a58603d847b"
+source = "git+https://github.com/pop-os/libcosmic.git#8da25f94e9c363c8c6b93284adf4cf6e07bc3a45"
 dependencies = [
  "apply",
- "ashpd 0.9.1",
+ "ashpd 0.9.2",
  "chrono",
  "cosmic-config",
  "cosmic-settings-daemon",
@@ -3215,6 +3215,7 @@ dependencies = [
  "tracing",
  "unicode-segmentation",
  "url",
+ "ustr",
  "zbus 4.4.0",
 ]
 
@@ -3289,7 +3290,6 @@ name = "lkt"
 version = "8.0.1"
 dependencies = [
  "anyhow",
- "async-trait",
  "chrono",
  "clap",
  "clap_complete",
@@ -3336,11 +3336,11 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
 
 [[package]]
 name = "lru"
-version = "0.12.4"
+version = "0.12.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
+checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
 dependencies = [
- "hashbrown 0.14.5",
+ "hashbrown 0.15.0",
 ]
 
 [[package]]
@@ -3808,21 +3808,18 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.36.4"
+version = "0.36.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
+checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.20.1"
+version = "1.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
-dependencies = [
- "portable-atomic",
-]
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
 
 [[package]]
 name = "option-ext"
@@ -3885,11 +3882,11 @@ dependencies = [
 
 [[package]]
 name = "owned_ttf_parser"
-version = "0.24.0"
+version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90"
+checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
 dependencies = [
- "ttf-parser 0.24.1",
+ "ttf-parser 0.25.0",
 ]
 
 [[package]]
@@ -4110,12 +4107,6 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
 
-[[package]]
-name = "portable-atomic"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
-
 [[package]]
 name = "ppv-lite86"
 version = "0.2.20"
@@ -4176,9 +4167,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.86"
+version = "1.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
 dependencies = [
  "unicode-ident",
 ]
@@ -4340,9 +4331,9 @@ checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f"
 
 [[package]]
 name = "read-fonts"
-version = "0.20.0"
+version = "0.22.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd"
+checksum = "fb94d9ac780fdcf9b6b252253f7d8f221379b84bd3573131139b383df69f85e1"
 dependencies = [
  "bytemuck",
  "font-types",
@@ -4657,9 +4648,9 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.23.13"
+version = "0.23.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
+checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8"
 dependencies = [
  "once_cell",
  "ring",
@@ -4937,9 +4928,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
 
 [[package]]
 name = "skrifa"
-version = "0.20.0"
+version = "0.22.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23"
+checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe"
 dependencies = [
  "bytemuck",
  "read-fonts",
@@ -5155,9 +5146,9 @@ dependencies = [
 
 [[package]]
 name = "swash"
-version = "0.1.18"
+version = "0.1.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac"
+checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2"
 dependencies = [
  "skrifa",
  "yazi",
@@ -5245,12 +5236,12 @@ dependencies = [
 
 [[package]]
 name = "terminal_size"
-version = "0.3.0"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
+checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
 dependencies = [
  "rustix 0.38.37",
- "windows-sys 0.48.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -5366,7 +5357,6 @@ dependencies = [
  "bytes",
  "libc",
  "mio 1.0.2",
- "parking_lot 0.12.3",
  "pin-project-lite",
  "signal-hook-registry",
  "socket2 0.5.7",
@@ -5553,9 +5543,9 @@ checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
 
 [[package]]
 name = "ttf-parser"
-version = "0.24.1"
+version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a"
+checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e"
 
 [[package]]
 name = "type-map"
@@ -5604,9 +5594,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-bidi"
-version = "0.3.15"
+version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
 
 [[package]]
 name = "unicode-bidi-mirroring"
@@ -5713,6 +5703,19 @@ version = "2.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
 
+[[package]]
+name = "ustr"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e904a2279a4a36d2356425bb20be271029cc650c335bc82af8bfae30085a3d0"
+dependencies = [
+ "ahash",
+ "byteorder",
+ "lazy_static",
+ "parking_lot 0.12.3",
+ "serde",
+]
+
 [[package]]
 name = "usvg"
 version = "0.37.0"
@@ -5813,9 +5816,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -5824,9 +5827,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
 dependencies = [
  "bumpalo",
  "log",
@@ -5839,9 +5842,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.43"
+version = "0.4.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
+checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -5851,9 +5854,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -5861,9 +5864,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5874,9 +5877,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
 
 [[package]]
 name = "wasm-timer"
@@ -6029,9 +6032,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.70"
+version = "0.3.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
diff --git a/Cargo.toml b/Cargo.toml
index 693ef9df0dd66186ae4b1bcd4487ed1dd9bd1b9c..638ca35acb8a20e935462f3f507fbbfa105fb55c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@ members = [
 [workspace.package]
 edition = "2021"
 authors = [
-    "Maël MARTIN <mael.martin@protonmail.com>",
+    "Maëlle MARTIN <maelle.martin@proton.me>",
     "Louis GOYARD <elliu@hashi.re>",
     "Loïc ALLEGRE <lallegre26@gmail.com>",
     "Kevin COCCHI <salixor@pm.me>",
@@ -44,7 +44,7 @@ zbus   = { version = "*", default-features = false, features = ["tokio"] }
 chrono = { version = "*", default-features = false, features = ["clock"] }
 sha256 = { version = "*", default-features = false, features = ["async"] }
 anyhow = { version = "*", default-features = false, features = ["std"] }
-regex  = { version = "*", default-features = false, features = ["std", "perf"] }
+regex  = { version = "*", default-features = false, features = ["std"] }
 log    = "*"
 rand   = "*"
 base64 = "*"
@@ -63,6 +63,7 @@ lektor_utils      = { path = "lektor_utils" }
 lektor_mpris      = { path = "lektor_mpris" }
 lektor_payloads   = { path = "lektor_payloads" }
 lektor_procmacros = { path = "lektor_procmacros" }
+lektor_nkdb       = { path = "lektor_nkdb" }
 
 # Data Structures
 hashbrown     = { version = "*", features = ["serde"] }
@@ -71,24 +72,20 @@ async-channel = { version = "*", default-features = false }
 # Serialization & Deserialization
 toml = "*"
 serde_json = { version = "*", default-features = false, features = [
-    "std",
-    "preserve_order",
+    "std", "preserve_order"
 ] }
 serde = { version = "*", default-features = false, features = [
-    "rc",
-    "std",
-    "derive",
+    "rc", "std", "derive"
 ] }
 
 # Async stuff
 async-trait  = "*"
 futures-util = "*"
-futures = { version = "*", default-features = false, features = [
-    "std",
-    "async-await",
+futures      = { version = "*", default-features = false, features = ["std", "async-await"] }
+tokio-stream = { version = "*", default-features = false, features = ["net"]}
+tokio        = { version = "*", default-features = false, features = [
+    "rt-multi-thread", "net", "time", "sync", "fs", "signal"
 ] }
-tokio        = { version = "*", features = [ "full" ] }
-tokio-stream = { version = "*", features = [ "net" ], default-features = false }
 
 # Web stuff
 reqwest = { version = "*", default-features = false, features = [
@@ -101,22 +98,15 @@ axum = { version = "*", default-features = false, features = [
     "macros",
     "tokio",
 ] }
-tower = { version = "*", features = ["util"] }
-hyper-util = { version = "*", features = ["tokio", "server-auto", "http1"] }
-hyper = { version = "*", default-features = false, features = [
-    "http1",
-    "server",
-] }
+tower      = { version = "*", default-features = false, features = ["util"] }
+hyper-util = { version = "*", default-features = false, features = ["http1", "tokio", "server-auto"] }
+hyper      = { version = "*", default-features = false, features = ["http1", "server"] }
 
 # Arguments
-roff = "*"
+roff          = "*"
 clap_complete = { version = "*", default-features = false }
-clap = { version = "*", default-features = false, features = [
-    "usage",
-    "help",
-    "std",
-    "wrap_help",
-    "derive",
+clap          = { version = "*", default-features = false, features = [
+    "usage", "help", "std", "wrap_help", "derive"
 ] }
 
 # Proc macro things
diff --git a/README.md b/README.md
index d702083403795916ef97238c9d159bc28ea54c2a..f8ce1125caec5c544c09e32424a93269f2afcc16 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 [![matrix chat](https://img.shields.io/badge/matrix-%23baka--dev--lektor%3Aiiens.net-brightgreen)](https://matrix.to/#/#baka-dev-lektor:iiens.net)
 [![platform support](https://img.shields.io/badge/platform-Linux|FreeBSD|Windows-lightgrey)](https://git.iiens.net/martin2018/lektor/-/commits/master)
 [![lektord version](https://img.shields.io/badge/lektord-v8.0.1-blueviolet)](https://git.iiens.net/martin2018/lektor/-/tags)
-[![amadeus version](https://img.shields.io/badge/amadeus-v0.0.1-blueviolet)](https://git.iiens.net/martin2018/lektor/-/tags)
+[![amadeus version](https://img.shields.io/badge/amadeus-v3.0.1-blueviolet)](https://git.iiens.net/martin2018/lektor/-/tags)
 <a href="https://github.com/iced-rs/iced">
   <img src="https://gist.githubusercontent.com/hecrj/ad7ecd38f6e47ff3688a38c79fd108f0/raw/74384875ecbad02ae2a926425e9bcafd0695bade/color.svg" width="100px">
 </a>
diff --git a/amadeus/Cargo.toml b/amadeus/Cargo.toml
index dc72b513fb815cfb372cb57b0d9cfb20cce45086..df2c16122137037391c5ee3fff1ab47480b034d8 100644
--- a/amadeus/Cargo.toml
+++ b/amadeus/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name        = "amadeus"
 description = "Amadeus-RS, graphical interface for lektord"
-version     = "0.0.1"
+version     = "3.0.1"
 
 rust-version.workspace = true
 edition.workspace      = true
@@ -31,7 +31,6 @@ futures-util.workspace = true
 tokio.workspace        = true
 reqwest.workspace      = true
 futures.workspace      = true
-async-trait.workspace  = true
 
 i18n-embed-fl.workspace = true
 rust-embed.workspace    = true
diff --git a/amadeus/i18n/en/amadeus.ftl b/amadeus/i18n/en/amadeus.ftl
index 570f09ac5211e49fb71d7438a6470addb8fa588e..64b16d88b8421abf0c646495c6296febd4ae88cb 100644
--- a/amadeus/i18n/en/amadeus.ftl
+++ b/amadeus/i18n/en/amadeus.ftl
@@ -12,6 +12,10 @@ settings  = Settings
 playback  = Playback
 page-id   = Page { $num }
 
+empty-queue     = Empty Queue
+empty-history   = Empty History
+empty-playlists = Empty playlists
+
 next-kara        = Next kara
 prev-kara        = Previous kara
 toggle-playback  = Play/Pause
@@ -27,7 +31,7 @@ home      = Home
 queue     = Queue
 search    = Search
 playlists = Playlists
-playlist  = Playlist { $name }
+playlist  = Playlist “{ $name }”
 
 log-level        = Log level
 icon-theme       = Icon theme
@@ -47,3 +51,7 @@ epoch            = { $what } epoch
 user             = User
 config           = { $what } configuration
 url-of           = { $what } URL
+
+kara        = Kara
+click-to-dl = Click to download
+
diff --git a/amadeus/i18n/es-ES/amadeus.ftl b/amadeus/i18n/es-ES/amadeus.ftl
index 4f30527ff4d0ec1934d86299c3c5ff18b4ed46f4..53619d631fdcbb094d895307384c106a2c06acbb 100644
--- a/amadeus/i18n/es-ES/amadeus.ftl
+++ b/amadeus/i18n/es-ES/amadeus.ftl
@@ -12,6 +12,10 @@ settings  = Ajustes
 playback  = Reproducción
 page-id   = Pagina { $num }
 
+empty-queue     = No hay nada en la cola de reproducción
+empty-history   = No has escuchado nada recientemente
+empty-playlists = No tienes listas de reproducción
+
 next-kara        = Póxima kara
 prev-kara        = Previo kara
 toggle-playback  = Alternar reprod.
@@ -27,7 +31,7 @@ home      = Inicio
 queue     = Cola de reproducción
 search    = Buscar
 playlists = Listas de reproducción
-playlist  = Lista { $name }
+playlist  = Lista “{ $name }”
 
 log-level        = Nivel de registro
 icon-theme       = Tema de icono
@@ -47,3 +51,7 @@ epoch            = Época de { $what }
 user             = Usuario·a
 config           = Ajustes de { $what }
 url-of           = Dirección de { $what }
+
+kara        = Kara
+click-to-dl = Haga clic para descargar
+
diff --git a/amadeus/i18n/fr-FR/amadeus.ftl b/amadeus/i18n/fr-FR/amadeus.ftl
new file mode 100644
index 0000000000000000000000000000000000000000..8d581be677a351980f6b1e08f27deeaa588012a5
--- /dev/null
+++ b/amadeus/i18n/fr-FR/amadeus.ftl
@@ -0,0 +1,57 @@
+app-title = Amadeus
+
+unimplemented = Il faut implémenter { $what }
+error-found   = Une erreur est survenue
+
+about     = À propos de
+edit      = Éditer
+view      = Vue
+history   = Historique
+welcome   = Bonjour!
+settings  = Paramettres
+playback  = Letcure
+page-id   = Page { $num }
+
+empty-queue     = La queue est vide
+empty-history   = L'historique est vide
+empty-playlists = Il n'y a pas de listes de lecture de disponibles
+
+next-kara        = Kara suivant
+prev-kara        = Kara précédent
+toggle-playback  = Play/Pause
+play-playback    = Play
+pause-playback   = Pause
+stop-playback    = Stop
+playback-shuffle = Mélanger
+playback-crop    = Recadrer
+playback-clear   = Effacer
+menu-queue       = Queue
+
+home      = Accueil
+queue     = Queue
+search    = Chercher
+playlists = Listes de lecture
+playlist  = Liste « { $name } »
+
+log-level        = Log level
+icon-theme       = Theme d'icones
+dark-theme       = Theme sombre
+address-ip       = Address
+scheme           = Scheme
+kurisu-token     = Token pour Kurisu
+open-url         = Ouvrir l'URL { $url }
+token            = Token
+port             = Port
+status           = Status
+connected        = Connecté
+disconnected     = Déconnecté
+version          = Version
+info-about       = Information sur { $what }
+epoch            = Époch { $what }
+user             = Utilisateur
+config           = Configuration de { $what }
+url-of           = URL pour { $what }
+
+kara        = Kara
+click-to-dl = Clicker pour télécharger
+
diff --git a/amadeus/rsc/icons/fontawesome/crop.svg b/amadeus/rsc/icons/fontawesome/crop.svg
new file mode 100644
index 0000000000000000000000000000000000000000..eae7672457ab832a79f159b1a2c682b720133a8c
--- /dev/null
+++ b/amadeus/rsc/icons/fontawesome/crop.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M128 32c0-17.7-14.3-32-32-32S64 14.3 64 32l0 32L32 64C14.3 64 0 78.3 0 96s14.3 32 32 32l32 0 0 256c0 35.3 28.7 64 64 64l224 0 0-64-224 0 0-352zM384 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-32 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-32 0 0-256c0-35.3-28.7-64-64-64L160 64l0 64 224 0 0 352z"/></svg>
diff --git a/amadeus/rsc/icons/fontawesome/retry.svg b/amadeus/rsc/icons/fontawesome/retry.svg
new file mode 100644
index 0000000000000000000000000000000000000000..50c27ae92b2b335e61d62ce4e24a8ddcb6c30341
--- /dev/null
+++ b/amadeus/rsc/icons/fontawesome/retry.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>
diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs
index df1846dc8dd431a00b90592da15814a989476bfc..359180a2b8a2291676ab79c0b750a1026a22a2b0 100644
--- a/amadeus/src/app.rs
+++ b/amadeus/src/app.rs
@@ -1,9 +1,12 @@
+//! Implements the whole application model.
+
 mod bottom_bar;
 mod context_pages;
 mod kard;
 mod menu;
 mod pages;
 mod progress_bar;
+mod subscriptions;
 
 use crate::{
     app::{
@@ -14,23 +17,23 @@ use crate::{
     config::{Config, LogLevel},
     fl,
     store::Store,
-    subscriptions,
 };
 use cosmic::{
     app::{Command, Core},
-    iced::{
-        alignment::{Horizontal, Vertical},
-        Length, Subscription,
-    },
+    iced::{Length, Subscription},
+    prelude::*,
+    style, theme, widget, Application,
+};
+use futures::{
     prelude::*,
-    theme, widget, Application,
+    stream::{self, FuturesUnordered},
 };
-use lektor_lib::ConnectConfig;
+use lektor_lib::{requests, ConnectConfig};
 use lektor_payloads::{
-    KId, PlayStateWithCurrent, Playlist, PlaylistName, Priority, PRIORITY_LENGTH, PRIORITY_VALUES,
+    KId, Kara, PlayStateWithCurrent, Priority, SearchFrom, PRIORITY_LENGTH, PRIORITY_VALUES,
 };
 use lektor_utils::{config::SocketScheme, open};
-use pages::search::{FilterAtom, FilterAtomId};
+use pages::search::FilterAtomId;
 use std::{
     borrow::Cow,
     collections::HashMap,
@@ -87,13 +90,22 @@ pub struct AppModel {
     tmp_remote_token: String,
 }
 
+/// The state of lektord that we queried.
 #[derive(Debug, Clone, Default)]
 enum LektordState {
+    /// Lektord is disconnected.
     #[default]
     Disconnected,
+
+    /// Lektord is connected.
     Connected {
+        /// A version string, which is the build string from git.
         version: String,
+
+        /// The DB epoch.
         last_epoch: Option<u64>,
+
+        /// The state of the playback.
         state: Option<lektor_payloads::PlayStateWithCurrent>,
     },
 }
@@ -126,8 +138,9 @@ impl LektordState {
 }
 
 /// A command to send to the lektord instance.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone)]
 pub enum LektordCommand {
+    // Playback stuff
     PlaybackToggle,
     PlaybackPlay,
     PlaybackPause,
@@ -135,9 +148,33 @@ pub enum LektordCommand {
     PlaybackNext,
     PlaybackPrevious,
 
+    // Queue stuff
     QueueShuffle,
     QueueClear,
     QueueCrop,
+    QueueGet,
+
+    // QueueLevel stuff
+    QueueLevelShuffle(Priority),
+    QueueLevelClear(Priority),
+    QueueLevelGet(Priority),
+
+    // History stuff
+    HistoryClear,
+    HistoryGet,
+
+    // Playlists stuff
+    PlaylistsGet,
+
+    // Playlist stuff
+    PlaylistDelete(KId),
+    PlaylistRemoveKara(KId, KId),
+    PlaylistGetContent(KId),
+    PlaylistShuffleContent(KId),
+
+    // Misc stuff
+    DownloadKaraInfo(KId),
+    DownloadKarasInfo(Vec<KId>),
 }
 
 /// Something changed with the config.
@@ -162,33 +199,40 @@ pub enum LektordMessage {
     Connected(lektor_payloads::Infos),
     PlaybackUpdate(lektor_payloads::PlayStateWithCurrent),
 
-    DownloadKaraInfo(KId),
-    DownloadKarasInfo(Vec<KId>),
+    DownloadedKaraInfo(Kara),
+    DownloadedKarasInfo(Vec<Kara>),
 
     ChangedQueue([Vec<KId>; PRIORITY_LENGTH]),
     ChangedQueueLevel(Priority, Vec<KId>),
     ChangedHistory(Vec<KId>),
-    ChangedAvailablePlaylists(Vec<PlaylistName>),
-    ChangedPlaylistContent(PlaylistName, Playlist),
+    ChangedAvailablePlaylists(Vec<KId>),
+    ChangedPlaylistContent(KId, Vec<KId>),
+    ChangedPlaylistsContent(Vec<(KId, Vec<KId>)>),
 }
 
 /// Messages emitted by the application and its widgets.
 #[derive(Debug, Clone)]
 pub enum Message {
+    // Misc UI things
     OpenUrl(&'static str),
     OpenKaraInfo(KId),
     ToggleContextPage(ContextPage),
 
+    // Update the configuration
     UpdateConfig(ConfigMessage),
 
+    // Lektord stuff
     SendCommand(LektordCommand),
-    Lektord(LektordMessage),
+    LektordUpdate(LektordMessage),
 
+    // Search stuff
     ChangeFilterStageBuffer(String),
     AddFilterAtomFromStageBuffer,
     RemoveFilterAtom(FilterAtomId),
+    QueryWithFiltersResults(Vec<KId>),
     ClearFilters,
     QueryWithFilters,
+    SearchOnlyAuthor(String),
 }
 
 /// Create a COSMIC application from the app model
@@ -255,15 +299,18 @@ impl Application for AppModel {
     fn header_center(&self) -> Vec<Element<Self::Message>> {
         let is_connected = !matches!(self.lektord_state, LektordState::Disconnected);
         macro_rules! icon {
-            ($icon:ident => $action:ident) => {{
+            ($icon:ident => $action:ident $(| $true:ident)?) => {{
                 let button = widget::icon::from_svg_bytes(crate::icons::$icon)
                     .symbolic(true)
                     .apply(widget::button::icon);
                 Element::from(match is_connected {
-                    true => button.on_press(Message::SendCommand(LektordCommand::$action)),
-                    false => button,
+                    true  => icon!(@ $($true)?, button).on_press(Message::SendCommand(LektordCommand::$action)),
+                    false => icon!(@ $($true)?, button),
                 })
             }};
+
+            (@ destructive, $expr:expr) => { $expr.style(style::Button::AppletIcon) };
+            (@            , $expr:expr) => { $expr };
         }
 
         let pre = [
@@ -274,7 +321,8 @@ impl Application for AppModel {
         let post = [
             icon!(NEXT    => PlaybackNext),
             icon!(SHUFFLE => QueueShuffle),
-            icon!(METEOR  => QueueClear),
+            icon!(CROP    => QueueCrop  | destructive),
+            icon!(METEOR  => QueueClear | destructive),
             Element::from(widget::horizontal_space(Length::Fill)),
         ];
 
@@ -322,25 +370,21 @@ impl Application for AppModel {
         (self.core.window.show_context).then(|| match self.context_page {
             ContextPage::About => context_pages::about::view(self),
             ContextPage::Settings => context_pages::config::view(self),
+            ContextPage::KaraInfo(kid) => context_pages::kara_info::view(self.store.get(kid)),
         })
     }
 
     /// Describes the interface based on the current state of the application model.
     fn view(&self) -> Element<Self::Message> {
-        let page = match self.nav.active_data::<Page>() {
-            Some(Page::Home) => pages::home::view(),
-            Some(Page::Queue) => pages::queue::view(&self.store),
-            Some(Page::History) => pages::history::view(&self.store),
-            Some(Page::Search) => pages::search::view(self),
-            Some(Page::Playlists) => pages::playlists::view(&self.store),
-            Some(Page::Playlist(name)) => pages::playlist::view(&self.store, name),
-            page => pages::not_found(page),
-        }
-        .apply(widget::container)
-        .width(Length::Fill)
-        .height(Length::Fill)
-        .align_x(Horizontal::Center)
-        .align_y(Vertical::Top);
+        let page = match self.nav.active_data::<Page>().copied() {
+            Some(Page::Home) => pages::home::view().into(),
+            Some(Page::Queue) => pages::queue::view(&self.store).into(),
+            Some(Page::History) => pages::history::view(&self.store).into(),
+            Some(Page::Search) => pages::search::view(self).into(),
+            Some(Page::Playlists) => pages::playlists::view(&self.store).into(),
+            Some(Page::Playlist(id)) => pages::playlist::view(&self.store, id).into(),
+            page => pages::not_found(page).into(),
+        };
 
         widget::column()
             .push(page)
@@ -372,7 +416,7 @@ impl Application for AppModel {
         match message {
             Message::UpdateConfig(message) => self.update_config(message),
 
-            Message::Lektord(message) => self.handle_lektord_message(message),
+            Message::LektordUpdate(message) => self.handle_lektord_message(message),
             Message::OpenUrl(url) => self.open_url(url),
             Message::ToggleContextPage(context_page) => self.toggle_context_page(context_page),
 
@@ -396,13 +440,32 @@ impl Application for AppModel {
             }
             Message::ClearFilters => {
                 self.search_filter.clear();
-                self.search_results.clear();;
+                self.search_results.clear();
                 Command::none()
             }
-            Message::QueryWithFilters => {
-                log::error!("query with filters");
+            Message::QueryWithFiltersResults(results) => {
+                self.search_results = results;
+                Command::none()
+            }
+            Message::SearchOnlyAuthor(author) => {
+                log::error!("implement search of only author {author}, need to clear everything and do the query");
                 Command::none()
             }
+            Message::QueryWithFilters => {
+                let config = self.connect_config.clone();
+                let filters: Vec<_> = self.search_filter.iter_cloned().collect();
+                cosmic::command::future(async move {
+                    requests::search_karas(&*config.read().await, SearchFrom::Database, filters)
+                        .await
+                        .map(|matches| {
+                            cosmic::app::message::app(Message::QueryWithFiltersResults(matches))
+                        })
+                        .unwrap_or_else(|err| {
+                            log::error!("failed to query with filters: {err}");
+                            cosmic::app::message::none()
+                        })
+                })
+            }
         }
     }
 
@@ -429,7 +492,7 @@ impl AppModel {
     fn update_connect_config(&self) -> Command<Message> {
         let config = self.config.get_connect_config();
         let connect_config = self.connect_config.clone();
-        cosmic::command::future(async {
+        cosmic::command::future(async move {
             *connect_config.write_owned().await = config;
             cosmic::app::message::none()
         })
@@ -443,7 +506,7 @@ impl AppModel {
             self.context_page = context_page;
             self.core.window.show_context = true;
         }
-        self.set_context_title(context_page.title());
+        self.set_context_title(self.context_page.title());
         Command::none()
     }
 
@@ -542,9 +605,9 @@ impl AppModel {
     fn handle_lektord_message(&mut self, message: LektordMessage) -> Command<Message> {
         use LektordMessage::*;
         match message {
-            // Asked to download metadata informations.
-            DownloadKaraInfo(kid) => log::error!("download kara info {kid}"),
-            DownloadKarasInfo(kids) => log::error!("download kara info {kids:?}"),
+            // Downloaded metadata informations.
+            DownloadedKaraInfo(kara) => self.store.set(kara),
+            DownloadedKarasInfo(karas) => karas.into_iter().for_each(|kara| self.store.set(kara)),
 
             // Disconnected, if any query failed we set the disconnected status
             Disconnected => {
@@ -576,14 +639,38 @@ impl AppModel {
             }
 
             // Down here, got updates from lektord.
-            ChangedAvailablePlaylists(names) => self.store.keep_playlists(names),
-            ChangedPlaylistContent(name, plt) => self.store.set_playlist(name, plt),
+            ChangedAvailablePlaylists(names) => {
+                let config = self.connect_config.clone();
+                let playlists = self.store.keep_playlists(names);
+                return cosmic::command::future(async move {
+                    let updated_playlists = stream::iter(playlists)
+                        .zip(stream::repeat_with(move || config.clone()))
+                        .then(|(id, config)| async move {
+                            let config = config.read().await;
+                            requests::get_playlist_content(config.as_ref(), id)
+                                .await
+                                .map(|content| (id, content))
+                        })
+                        .collect::<FuturesUnordered<_>>()
+                        .await;
+                    Message::LektordUpdate(ChangedPlaylistsContent(
+                        updated_playlists.into_iter().flatten().collect::<Vec<_>>(),
+                    ))
+                });
+            }
+
             ChangedHistory(kids) => self.store.set_history(kids),
+
             ChangedQueueLevel(lvl, kids) => self.store.set_queue_level(lvl, kids),
             ChangedQueue(mut queue) => PRIORITY_VALUES.iter().for_each(|&level| {
                 let kids = mem::take(&mut queue[level.index()]);
                 self.store.set_queue_level(level, kids);
             }),
+
+            ChangedPlaylistContent(name, plt) => self.store.set_playlist_content(name, plt),
+            ChangedPlaylistsContent(changes) => changes
+                .into_iter()
+                .for_each(|(name, plt)| self.store.set_playlist_content(name, plt)),
         }
         Command::none()
     }
@@ -592,11 +679,16 @@ impl AppModel {
     fn send_command(&self, cmd: LektordCommand) -> Command<Message> {
         let config = self.connect_config.clone();
         use lektor_payloads::*;
+        macro_rules! msg {
+            ($msg:ident $(($($args:expr),+))?) => {
+                cosmic::app::message::app(Message::LektordUpdate(LektordMessage::$msg $(($($args),+))?))
+            };
+        }
         macro_rules! cmd {
             ($req:ident ($($arg:expr),*) $(, $res:pat => $handle:expr)?) => {
                 cosmic::command::future(async move {
                     cmd!(@handle $req:
-                        lektor_lib::requests::$req(config.read().await.as_ref() $(, $arg)*).await,
+                        requests::$req(config.read().await.as_ref() $(, $arg)*).await,
                         $($res => $handle)?
                     )
                 })
@@ -620,12 +712,40 @@ impl AppModel {
             LektordCommand::PlaybackNext => cmd!(play_next()),
             LektordCommand::PlaybackPrevious => cmd!(play_previous()),
 
-            LektordCommand::QueueShuffle => cmd!(shuffle_queue()),
+            LektordCommand::QueueShuffle => cmd!(shuffle_queue_range(..)),
+            LektordCommand::QueueClear => cmd!(remove_range_from_queue(..)),
+            LektordCommand::QueueCrop => cmd!(remove_range_from_queue(1..)),
+            LektordCommand::QueueGet => cmd!(get_queue_range(..), queue => {
+                msg!(ChangedQueue(queue.into_iter().fold(<[Vec<KId>; PRIORITY_LENGTH]>::default(), |mut ret, (level, kid)| {
+                    ret[level.index()].push(kid);
+                    ret
+                })))
+            }),
+            LektordCommand::QueueLevelShuffle(lvl) => cmd!(shuffle_level_queue(lvl)),
+            LektordCommand::QueueLevelClear(lvl) => cmd!(remove_level_from_queue(lvl)),
+            LektordCommand::QueueLevelGet(lvl) => cmd!(get_queue_level(lvl), queue => {
+                msg!(ChangedQueueLevel(lvl, queue))
+            }),
 
-            cmd => {
-                log::error!("need to implement {cmd:?}");
-                Command::none()
-            }
+            LektordCommand::DownloadKaraInfo(kid) => cmd!(get_kara_by_kid(kid), kara => {
+                msg!(DownloadedKaraInfo(kara))
+            }),
+            LektordCommand::DownloadKarasInfo(kids) => cmd!(get_karas_by_kid(kids), karas => {
+                msg!(DownloadedKarasInfo(karas))
+            }),
+
+            LektordCommand::HistoryClear => cmd!(remove_range_from_history(..)),
+            LektordCommand::HistoryGet => cmd!(get_history_range(..), history => {
+                msg!(ChangedHistory(history))
+            }),
+
+            LektordCommand::PlaylistGetContent(id) => cmd!(get_playlist_content(id), content => {
+                msg!(ChangedPlaylistContent(id, content))
+            }),
+            LektordCommand::PlaylistsGet => todo!(),
+            LektordCommand::PlaylistShuffleContent(_) => todo!(),
+            LektordCommand::PlaylistDelete(_) => todo!(),
+            LektordCommand::PlaylistRemoveKara(..) => todo!(),
         }
     }
 }
diff --git a/amadeus/src/app/bottom_bar.rs b/amadeus/src/app/bottom_bar.rs
index e9f5528894f1e98d8a120e81ce6449a5c0e5fba7..f4d15607a64e1822ce2417b9754662937de59d32 100644
--- a/amadeus/src/app/bottom_bar.rs
+++ b/amadeus/src/app/bottom_bar.rs
@@ -1,5 +1,6 @@
 use crate::{
-    app::{AppModel, LektordMessage, Message},
+    app::{AppModel, LektordCommand, Message},
+    fl,
     store::KaraOrId,
 };
 use cosmic::{
@@ -10,7 +11,9 @@ use cosmic::{
         Alignment, Length,
     },
     iced_core::text::Wrap,
-    style, theme, widget, Apply, Element,
+    prelude::*,
+    style, theme,
+    widget::{self, tooltip::Position},
 };
 use lektor_payloads::{KId, Kara};
 
@@ -79,7 +82,7 @@ fn view_left_part<'a>(kara: &Kara) -> Element<'a, Message> {
         .style(theme::Text::Color(
             theme::active().cosmic().accent_text_color().into(),
         ))
-        .font(font::FONT_LIGHT)
+        .font(font::light())
         .wrap(Wrap::None);
 
     widget::column()
@@ -87,7 +90,7 @@ fn view_left_part<'a>(kara: &Kara) -> Element<'a, Message> {
         .push(source)
         .apply(widget::button::custom)
         .style(style::Button::Transparent)
-        .on_press(Message::OpenKaraInfo(kara.id.clone()))
+        .on_press(Message::OpenKaraInfo(kara.id))
         .apply(widget::container)
         .align_x(Horizontal::Left)
         .align_y(Vertical::Center)
@@ -104,7 +107,8 @@ fn view_kara_id<'a>(kid: KId) -> Element<'a, Message> {
         .wrap(Wrap::None)
         .apply(widget::button::custom)
         .style(style::Button::Transparent)
-        .on_press(Message::Lektord(LektordMessage::DownloadKaraInfo(kid)))
+        .on_press(Message::SendCommand(LektordCommand::DownloadKaraInfo(kid)))
+        .apply(|btn| widget::tooltip(btn, fl!("click-to-dl"), Position::Top))
         .apply(widget::container)
         .align_x(Horizontal::Left)
         .align_y(Vertical::Center)
@@ -118,7 +122,7 @@ fn view_kara_id<'a>(kid: KId) -> Element<'a, Message> {
 pub fn view<'a>(app: &AppModel) -> Element<'a, Message> {
     match app.lektord_state.current_kid() {
         None => return widget::row().into(),
-        Some(kid) => match app.store.get(kid) {
+        Some(&kid) => match app.store.get(kid) {
             KaraOrId::Kara(kara) => vec![view_left_part(kara), view_right_part(kara)],
             KaraOrId::Id(kid) => vec![view_kara_id(kid)],
         }
diff --git a/amadeus/src/app/context_pages.rs b/amadeus/src/app/context_pages.rs
index e22a40fa2d1567f42546ca56f9f9e9b6c2d704b7..f74507bc738cbb230c2f8cf5ad0f56d135436cbd 100644
--- a/amadeus/src/app/context_pages.rs
+++ b/amadeus/src/app/context_pages.rs
@@ -1,23 +1,33 @@
 use crate::fl;
+use derive_more::Display;
+use lektor_payloads::KId;
 
 pub mod about;
 pub mod config;
+pub mod kara_info;
 
 /// The context page to display in the context drawer. This is the pane on the right that can be
 /// hidden or shown.
-#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Display)]
 pub enum ContextPage {
+    /// Show the about page, with informations on Amadeus and the Connected lektord instance.
     #[default]
+    #[display("{}", fl!("about"))]
     About,
 
+    /// Show the settings page for Amadeus.
+    #[display("{}", fl!("settings"))]
     Settings,
+
+    /// Show more informations about a kara than what is shown in tables (more tags, time, size,
+    /// etc...)
+    #[display("{}", fl!("kara"))]
+    KaraInfo(KId),
 }
 
 impl ContextPage {
+    /// The title of the context page.
     pub fn title(&self) -> String {
-        match self {
-            Self::About => fl!("about"),
-            Self::Settings => fl!("settings"),
-        }
+        self.to_string()
     }
 }
diff --git a/amadeus/src/app/context_pages/about.rs b/amadeus/src/app/context_pages/about.rs
index 7b8e61534eeb1255efd9fcc3687b7a832bfd02dc..da39f35309a66ffb6e91d38bcea7d5dfce023f3d 100644
--- a/amadeus/src/app/context_pages/about.rs
+++ b/amadeus/src/app/context_pages/about.rs
@@ -7,6 +7,7 @@ use cosmic::{
     theme, widget, Apply as _, Element,
 };
 
+/// Show more informations about Amadeus and the connected Lektord instance (if connected.)
 pub fn view(app: &AppModel) -> Element<Message> {
     macro_rules! url {
         ($icon:ident => $url:literal) => {{
diff --git a/amadeus/src/app/context_pages/config.rs b/amadeus/src/app/context_pages/config.rs
index 6452dbe40423c2422cae9bbec08ad160f13514db..d6ba36249807421de9768d66c207fa774d70f3f2 100644
--- a/amadeus/src/app/context_pages/config.rs
+++ b/amadeus/src/app/context_pages/config.rs
@@ -7,6 +7,7 @@ use cosmic::{iced::Length, theme, widget, Element};
 use lektor_utils::config::{SocketScheme, UserConfig};
 use std::sync::LazyLock;
 
+/// View and edit the settings for Amadeus
 pub fn view(app: &AppModel) -> Element<Message> {
     use {std::borrow::Cow::*, ConfigMessage::*, Message::*};
 
diff --git a/amadeus/src/app/context_pages/kara_info.rs b/amadeus/src/app/context_pages/kara_info.rs
new file mode 100644
index 0000000000000000000000000000000000000000..39096063c9cf4fb1d004bd58c9172e0f4541b57b
--- /dev/null
+++ b/amadeus/src/app/context_pages/kara_info.rs
@@ -0,0 +1,24 @@
+use crate::{
+    app::{LektordCommand, Message},
+    fl,
+    store::KaraOrId,
+};
+use cosmic::{
+    prelude::*,
+    style,
+    widget::{self, tooltip::Position},
+};
+
+/// View more details about a single kara.
+pub fn view<'a>(kara_or_kid: KaraOrId<'_>) -> Element<'a, Message> {
+    match kara_or_kid {
+        KaraOrId::Id(kid) => widget::text::monotext(kid.to_string())
+            .apply(widget::button::custom)
+            .style(style::Button::Transparent)
+            .on_press(Message::SendCommand(LektordCommand::DownloadKaraInfo(kid)))
+            .apply(|btn| widget::tooltip(btn, fl!("click-to-dl"), Position::Left))
+            .apply(Element::from),
+
+        KaraOrId::Kara(kara) => widget::text::monotext(format!("{kara:#?}")).into(),
+    }
+}
diff --git a/amadeus/src/app/kard.rs b/amadeus/src/app/kard.rs
index a2d3e307b4485d322d290d2c9aca1e340b111a27..3bdb3c05acc74fb9508c9394882c51e876e75a52 100644
--- a/amadeus/src/app/kard.rs
+++ b/amadeus/src/app/kard.rs
@@ -1,12 +1,17 @@
+//! Contains everything to display kara in a card (here more precisely a row.)
+
 use crate::{
-    app::{LektordMessage, Message},
+    app::{LektordCommand, Message},
+    fl,
     store::KaraOrId,
 };
 use cosmic::{
     cosmic_theme::Spacing,
     font,
     iced::{Alignment, Length},
-    style, theme, widget, Apply, Element,
+    prelude::*,
+    style, theme,
+    widget::{self, tooltip::Position},
 };
 use lektor_payloads::Kara;
 
@@ -19,12 +24,12 @@ fn kara_title<'a>(kara: &Kara) -> Element<'a, Message> {
             .map(|num| format!("{}{num} - {}", kara.song_type, kara.song_source))
             .unwrap_or_else(|| format!("{} - {}", kara.song_type, kara.song_source))
             .apply(widget::text::text)
-            .font(font::FONT_LIGHT)
+            .font(font::light())
             .into(),
     ])
     .apply(widget::button::custom)
     .style(style::Button::Transparent)
-    .on_press(Message::OpenKaraInfo(kara.id.clone()))
+    .on_press(Message::OpenKaraInfo(kara.id))
     .apply(Element::from)
 }
 
@@ -78,15 +83,15 @@ pub fn view<'a>(kara_or_id: KaraOrId) -> Element<'a, Message> {
             .style(style::Text::Color(
                 theme::active().cosmic().destructive_text_color().into(),
             ))
+            .font(font::mono())
             .apply(widget::button::custom)
             .style(style::Button::Transparent)
-            .on_press(Message::Lektord(LektordMessage::DownloadKaraInfo(
-                kid.clone(),
-            )))
-            .apply(Element::from)],
+            .on_press(Message::SendCommand(LektordCommand::DownloadKaraInfo(kid)))
+            .apply(|btn| widget::tooltip(btn, fl!("click-to-dl"), Position::Top))
+            .into()],
         KaraOrId::Kara(kara) => vec![
             kara_title(kara),
-            widget::horizontal_space(Length::Fill).apply(Element::from),
+            widget::horizontal_space(Length::Fill).into(),
             kara_tags(kara),
             // TODO: Add the controls here...
         ],
diff --git a/amadeus/src/app/menu.rs b/amadeus/src/app/menu.rs
index 4e78b6845db56bb46b1ac57838de9a96a1a1f5d4..50d8f142b57ca1c34c3c7cd13f3e1d8283504d1c 100644
--- a/amadeus/src/app/menu.rs
+++ b/amadeus/src/app/menu.rs
@@ -1,3 +1,5 @@
+//! Contains everything about the application menu, from the top-left-hand corner
+
 use crate::{
     app::{context_pages::ContextPage, LektordCommand, Message},
     fl,
diff --git a/amadeus/src/app/pages.rs b/amadeus/src/app/pages.rs
index 0e1e03f7bb641e5e19deec211f29b44a898d76fc..0d2fc73a314eefbfcdeee0967d7274ef1f430dec 100644
--- a/amadeus/src/app/pages.rs
+++ b/amadeus/src/app/pages.rs
@@ -1,17 +1,19 @@
+//! Contains everything to help display the main pages of the application.
+
 use crate::{app::Message, fl};
 use cosmic::{
-    font,
+    cosmic_theme::Spacing,
     iced::{
         alignment::{Horizontal, Vertical},
         Alignment, Length,
     },
-    prelude::CollectionWidget as _,
+    prelude::*,
     style, theme,
     widget::{self, icon, nav_bar, Icon},
-    Apply as _, Element,
 };
 use derive_more::Display;
-use std::sync::Arc;
+use lektor_payloads::KId;
+use std::marker;
 
 pub mod history;
 pub mod home;
@@ -21,7 +23,7 @@ pub mod queue;
 pub mod search;
 
 /// The page to display in the application.
-#[derive(Default, Debug, Eq, PartialEq, Clone, Display)]
+#[derive(Default, Debug, Eq, PartialEq, Clone, Copy, Display)]
 #[non_exhaustive]
 pub enum Page {
     #[default]
@@ -40,11 +42,12 @@ pub enum Page {
     #[display("{}", fl!("playlists"))]
     Playlists,
 
-    #[display("{}", fl!("playlist", name = _0.as_ref()))]
-    Playlist(Arc<str>),
+    #[display("{}", fl!("playlist"))]
+    Playlist(KId),
 }
 
 impl Page {
+    /// Get the icon associated with the page, to be displayed along with the [nav_bar::Model].
     pub fn icon(&self) -> Icon {
         let icon = match self {
             Page::Home => crate::icons::USER,
@@ -57,6 +60,7 @@ impl Page {
     }
 }
 
+/// Get the navigation bar for the pages.
 pub fn nav_bar_model() -> nav_bar::Model {
     macro_rules! insert {
         ($b:expr, $page:ident) => {
@@ -74,7 +78,8 @@ pub fn nav_bar_model() -> nav_bar::Model {
         .build()
 }
 
-pub fn not_found(page: Option<&Page>) -> Element<Message> {
+/// We got a page that was not implemented, or no page at all.
+pub fn not_found<'a>(page: Option<Page>) -> impl Into<Element<'a, Message>> {
     widget::column()
         .push(widget::text::title1(fl!("error-found")))
         .push_maybe(
@@ -86,35 +91,245 @@ pub fn not_found(page: Option<&Page>) -> Element<Message> {
         .height(Length::Fill)
         .align_x(Horizontal::Center)
         .align_y(Vertical::Center)
-        .into()
 }
 
-pub fn empty_page_label<'a>(title: impl AsRef<str>) -> widget::Row<'a, Message> {
-    vec![
-        (title.as_ref().to_string())
-            .apply(widget::text::title2)
-            .style(style::Text::Color(
-                theme::active().cosmic().warning_text_color().into(),
-            ))
-            .font(font::FONT_LIGHT)
-            .into(),
-        widget::horizontal_space(Length::Fill).into(),
-    ]
-    .apply(widget::row::with_children)
-    .width(Length::Fill)
-    .padding(theme::active().cosmic().space_m())
+/// Helper for the icons at the right of the title in pages.
+#[must_use]
+struct PageViewControl<'a> {
+    icon: &'static [u8],
+    message: Message,
+    on_non_empty_content: bool,
+    is_destructive_icon: bool,
+    marker: marker::PhantomData<Element<'a, Message>>,
+}
+
+impl<'a> PageViewControl<'a> {
+    /// Create a new control with an icon and an associated message.
+    pub fn new(icon: &'static [u8], message: Message) -> Self {
+        Self {
+            icon,
+            message,
+            on_non_empty_content: false,
+            is_destructive_icon: false,
+            marker: marker::PhantomData,
+        }
+    }
+
+    /// Make the button a destructive one.
+    pub fn destructive(self) -> Self {
+        Self {
+            is_destructive_icon: true,
+            ..self
+        }
+    }
+
+    /// Make the button active only when there is some content in the page.
+    pub fn need_content(self) -> Self {
+        Self {
+            on_non_empty_content: true,
+            ..self
+        }
+    }
+
+    /// Turn the helper into a proper [Element]. Needs to pass a flag to know if the page is empty
+    /// or not.
+    fn into_maybe_element(self, page_is_empty: bool) -> Element<'a, Message> {
+        let Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
+        let Self {
+            icon,
+            message,
+            on_non_empty_content,
+            is_destructive_icon,
+            marker: _,
+        } = self;
+
+        let button = widget::icon::from_svg_bytes(icon)
+            .symbolic(true)
+            .apply(widget::button::icon)
+            .on_press_maybe((!page_is_empty || !on_non_empty_content).then_some(message))
+            .padding(space_xxs)
+            .width(32)
+            .height(32);
+
+        match is_destructive_icon {
+            true => button.style(style::Button::Destructive).into(),
+            false => button.into(),
+        }
+    }
+}
+
+/// Helper to render main pages of the application.
+#[derive(Default)]
+#[must_use]
+struct PageView<'a> {
+    /// The title of the page. If none then we have an error page.
+    title: Option<Element<'a, Message>>,
+
+    /// Tells wether the title is a custom one. If the title is custom, then it's up to the caller
+    /// to pad the controls correctly, we won't insert the space in the [Self::view] function.
+    title_is_custom: bool,
+
+    /// If the page is empty (no content), we can display an alterntive title.
+    empty_title: Option<Element<'a, Message>>,
+
+    /// If we force the fact that there is content on the page (because the state of an ignore
+    /// element changed… like with the search thing…).
+    trust_me_i_have_content: bool,
+
+    /// The controls, at the right of the title or alternative title, from left icon to the right
+    /// one. The first element of the tuple is the data for the SVG icon, the second is wether the
+    /// button is enabled only when there is content in the page, the last one is the message to
+    /// send when the button is pressed.
+    controls: Vec<PageViewControl<'a>>,
+
+    /// The content of the page. From the top one to the bottom one.
+    content: Vec<Element<'a, Message>>,
+
+    /// The number of elements in [Self::content] that are ignore to decide if the page is empty.
+    ignore_content_count: usize,
+}
+
+impl<'a> PageView<'a> {
+    /// Set the title of the page. If the title is not set, a default page that shows that an error
+    /// was encountred will be shown.
+    pub fn title(self, title: impl ToString) -> Self {
+        Self {
+            title: Some(widget::text::title2(title.to_string()).into()),
+            ..self
+        }
+    }
+
+    /// Like [Self::title], but can use any [cosmic::Element].
+    pub fn custom_title(self, title: impl Into<Element<'a, Message>>) -> Self {
+        Self {
+            title: Some(title.into()),
+            title_is_custom: true,
+            ..self
+        }
+    }
+
+    /// If set, when the page is empty, this title will be shown instead of the normal one.
+    pub fn empty_title(self, title: impl ToString) -> Self {
+        Self {
+            empty_title: Some(widget::text::title2(title.to_string()).into()),
+            ..self
+        }
+    }
+
+    /// Sets the title like [Self::title] with the first passed string and the empty title like
+    /// [Self::empty_title] with the second passed string.
+    pub fn titles(self, title: impl ToString, empty: impl ToString) -> Self {
+        self.title(title).empty_title(empty)
+    }
+
+    /// Adds an element to the page. Elements are pushed at the bottom of the page.
+    pub fn push(mut self, element: impl Into<Element<'a, Message>>) -> Self {
+        self.content.push(element.into());
+        self
+    }
+
+    /// Adds an element to the page. Elements are pushed at the bottom of the page. The pushed
+    /// element will be ignore to decide if the page is empty or not. This is usefull for when we
+    /// push more controls or informations, but are not relevent about if we have data in the page.
+    pub fn push_and_ignore_for_empty(mut self, element: impl Into<Element<'a, Message>>) -> Self {
+        self.content.push(element.into());
+        self.ignore_content_count += 1;
+        self
+    }
+
+    /// Adds an element to the page. Elements are pushed at the bottom of the page. If the push
+    /// flag is false, the element is not pushed into the content list.
+    pub fn push_when<E>(self, push: bool, cb: impl FnOnce() -> E) -> Self
+    where
+        E: Into<Element<'a, Message>>,
+    {
+        match push {
+            true => self.push(cb()),
+            false => self,
+        }
+    }
+
+    /// Push elements from a thing that can be turned into an iterator.
+    pub fn extend(self, iter: impl IntoIterator<Item = impl Into<Element<'a, Message>>>) -> Self {
+        iter.into_iter().fold(self, |this, e| this.push(e))
+    }
+
+    /// Push a button in the control part of the page's title bar. Buttons are pushed at the right
+    /// of the control pane.
+    pub fn controls(self, controls: impl IntoIterator<Item = PageViewControl<'a>>) -> Self {
+        controls.into_iter().fold(self, |mut this, control| {
+            this.controls.push(control);
+            this
+        })
+    }
+
+    /// Set the flag about having content to true, this forces the check to see if the page is
+    /// empty or not to false, thus activating some buttons if needed.
+    pub fn has_content(self, flag: bool) -> Self {
+        Self {
+            trust_me_i_have_content: flag,
+            ..self
+        }
+    }
+
+    /// Consumte the setting struct and returns the page.
+    pub fn view(self) -> impl Into<Element<'a, Message>> {
+        let Some(title) = self.title else {
+            // Not found, got an error…
+            return widget::column()
+                .push(
+                    widget::text::title1(fl!("error-found")).style(style::Text::Color(
+                        theme::active().cosmic().warning_text_color().into(),
+                    )),
+                )
+                .align_items(Alignment::Center)
+                .apply(widget::container)
+                .width(Length::Fill)
+                .height(Length::Fill)
+                .align_x(Horizontal::Center)
+                .align_y(Vertical::Center);
+        };
+
+        // Handle the page.
+        let is_empty =
+            (self.content.len() <= self.ignore_content_count) && !self.trust_me_i_have_content;
+        let title = match is_empty {
+            true => self.empty_title.unwrap_or(title),
+            false => title,
+        };
+        let Spacing { space_m, .. } = theme::active().cosmic().spacing;
+
+        let title_row = page_title(title, self.title_is_custom, self.controls, is_empty);
+        widget::column::with_capacity(1 + self.content.len())
+            .push(title_row)
+            .extend(self.content.into_iter())
+            .spacing(space_m)
+            .apply(widget::container)
+            .padding(theme::active().cosmic().space_m())
+            .width(Length::Fill)
+            .height(Length::Fill)
+            .align_x(Horizontal::Center)
+            .align_y(Vertical::Top)
+    }
 }
 
-pub fn page_label<'a>(title: impl AsRef<str>) -> widget::Row<'a, Message> {
-    vec![
-        (title.as_ref().to_string())
-            .apply(widget::text::title2)
-            .style(style::Text::Default)
-            // .font(font::FONT_LIGHT)
-            .into(),
-        widget::horizontal_space(Length::Fill).into(),
-    ]
-    .apply(widget::row::with_children)
-    .width(Length::Fill)
-    .padding(theme::active().cosmic().space_m())
+fn page_title<'a>(
+    title: impl Into<Element<'a, Message>>,
+    title_is_custom: bool,
+    controls: Vec<PageViewControl<'a>>,
+    page_is_empty: bool,
+) -> impl Into<Element<'a, Message>> {
+    let Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
+    widget::row::with_capacity(2 + controls.len())
+        .push(title)
+        .push_maybe((!title_is_custom).then(|| widget::horizontal_space(Length::Fill)))
+        .extend(
+            controls
+                .into_iter()
+                .map(|control| control.into_maybe_element(page_is_empty)),
+        )
+        .width(Length::Fill)
+        .height(48)
+        .align_items(Alignment::Center)
+        .spacing(space_xxs)
 }
diff --git a/amadeus/src/app/pages/history.rs b/amadeus/src/app/pages/history.rs
index 459628a7f9985d5cf7db6b8d350d0256caf61caf..7f768e0503eb6f527cf9733eadae1e509423390f 100644
--- a/amadeus/src/app/pages/history.rs
+++ b/amadeus/src/app/pages/history.rs
@@ -1,16 +1,34 @@
 use crate::{
-    app::{kard, pages, Message},
+    app::{
+        kard,
+        pages::{PageView, PageViewControl},
+        LektordCommand, Message,
+    },
+    fl,
     store::Store,
 };
-use cosmic::{widget, Apply as _, Element};
+use cosmic::{prelude::*, widget};
 
-pub fn view(store: &Store) -> Element<Message> {
-    match store.iter_history().count() == 0 {
-        true => pages::empty_page_label("The history is empty").apply(Element::<Message>::from),
-        false => (store.iter_history())
-            .fold(widget::list_column(), |list, kid| {
+/// Display the history page.
+pub fn view(store: &Store) -> impl Into<Element<Message>> {
+    PageView::default()
+        .titles(fl!("history"), fl!("empty-history"))
+        .controls([
+            PageViewControl::new(
+                crate::icons::RETRY,
+                Message::SendCommand(LektordCommand::HistoryGet),
+            ),
+            PageViewControl::new(
+                crate::icons::METEOR,
+                Message::SendCommand(LektordCommand::HistoryClear),
+            )
+            .destructive()
+            .need_content(),
+        ])
+        .push_when(!store.iter_history().is_empty(), || {
+            (store.iter_history()).fold(widget::list_column(), |list, kid| {
                 list.add(kard::view(store.get(kid)))
             })
-            .apply(Element::<Message>::from),
-    }
+        })
+        .view()
 }
diff --git a/amadeus/src/app/pages/home.rs b/amadeus/src/app/pages/home.rs
index 9bf742d780d174570c2d9d53f4c237a7f18a2d3d..71d31c083db8fe707988f7542e4ba0046d07496e 100644
--- a/amadeus/src/app/pages/home.rs
+++ b/amadeus/src/app/pages/home.rs
@@ -1,9 +1,10 @@
 use crate::{
-    app::{pages, Message},
+    app::{pages::PageView, Message},
     fl,
 };
-use cosmic::{Apply as _, Element};
+use cosmic::prelude::*;
 
-pub fn view<'a>() -> Element<'a, Message> {
-    pages::page_label(fl!("home")).apply(Element::<Message>::from)
+/// Display the home page.
+pub fn view<'a>() -> impl Into<Element<'a, Message>> {
+    PageView::default().title(fl!("home")).view()
 }
diff --git a/amadeus/src/app/pages/playlist.rs b/amadeus/src/app/pages/playlist.rs
index 4151fff2fe7b80e9e067e337c7654b30ac1afc86..a12026625e141e30ed1dabcf4519a17a9efe481f 100644
--- a/amadeus/src/app/pages/playlist.rs
+++ b/amadeus/src/app/pages/playlist.rs
@@ -1,23 +1,84 @@
 use crate::{
-    app::{kard, pages, Message},
+    app::{
+        kard,
+        pages::{PageView, PageViewControl},
+        LektordCommand, Message,
+    },
     fl,
     store::Store,
 };
-use cosmic::{widget, Apply as _, Element};
+use cosmic::{prelude::*, style, widget};
+use lektor_payloads::{KId, PlaylistInfo};
 
-pub fn view<'a>(store: &'a Store, playlist: &str) -> Element<'a, Message> {
-    match store.iter_playlist_content(playlist).count() == 0 {
-        true => pages::empty_page_label(format!("The playlist {playlist} is empty"))
-            .apply(Element::<Message>::from),
-        false => vec![
-            pages::page_label(fl!("playlist", name = playlist)).apply(Element::<Message>::from),
-            (store.iter_playlist_content(playlist))
-                .fold(widget::list_column(), |list, kid| {
-                    list.add(kard::view(store.get(kid)))
-                })
-                .apply(Element::<Message>::from),
-        ]
-        .apply(widget::row::with_children)
-        .apply(Element::<Message>::from),
-    }
+/// Display the page about a specific playlist.
+pub fn view(store: &Store, id: KId) -> impl Into<Element<Message>> {
+    let Some(infos) = store.playlist(id) else {
+        todo!()
+    };
+
+    let name = infos
+        .name()
+        .map(|name| fl!("playlist", name = name))
+        .unwrap_or_else(|| "untitled".to_string());
+
+    let owners = infos
+        .infos()
+        .into_iter()
+        .flat_map(PlaylistInfo::owners)
+        .map(|owner| -> Element<'_, Message> {
+            widget::text::body(owner)
+                .apply(widget::button::custom)
+                .style(style::Button::Transparent)
+                .into()
+        });
+
+    let (created_at, updated_at) = infos
+        .infos()
+        .map(|infos| (infos.created_at(), infos.updated_at()))
+        .unwrap_or_default();
+
+    let infos = widget::settings::section()
+        .add(widget::settings::item(
+            "owners",
+            widget::row::with_children(owners.collect()),
+        ))
+        .add(widget::settings::item(
+            "created at",
+            widget::text::body(created_at.format("%Y-%m-%d %H:%M:%S").to_string()),
+        ))
+        .add(widget::settings::item(
+            "last modified at",
+            widget::text::body(updated_at.format("%Y-%m-%d %H:%M:%S").to_string()),
+        ));
+
+    let display_playlist_content = || {
+        (store.iter_playlist_content(id)).fold(widget::list_column(), |list, kid| {
+            list.add(kard::view(store.get(kid)))
+        })
+    };
+
+    PageView::default()
+        .titles(&name, format!("The playlist {name} is empty"))
+        .controls([
+            PageViewControl::new(
+                crate::icons::SHUFFLE,
+                Message::SendCommand(LektordCommand::PlaylistShuffleContent(id)),
+            )
+            .need_content(),
+            PageViewControl::new(
+                crate::icons::RETRY,
+                Message::SendCommand(LektordCommand::PlaylistGetContent(id)),
+            ),
+            PageViewControl::new(
+                crate::icons::METEOR,
+                Message::SendCommand(LektordCommand::PlaylistDelete(id)),
+            )
+            .destructive(),
+        ])
+        .push_and_ignore_for_empty(infos)
+        .push_when(
+            !store.iter_playlist_content(id).is_empty(),
+            display_playlist_content,
+        )
+        .view()
 }
diff --git a/amadeus/src/app/pages/playlists.rs b/amadeus/src/app/pages/playlists.rs
index 839f0db919038fada07f07536079b7c314c3b9ed..51745b73d0e136c4cd084e24921b39ae7dafbf70 100644
--- a/amadeus/src/app/pages/playlists.rs
+++ b/amadeus/src/app/pages/playlists.rs
@@ -1,10 +1,33 @@
 use crate::{
-    app::{pages, Message},
+    app::{
+        pages::{PageView, PageViewControl},
+        LektordCommand, Message,
+    },
     fl,
+    playlist::Playlist,
     store::Store,
 };
-use cosmic::{Apply as _, Element};
+use cosmic::{prelude::*, widget};
 
-pub fn view(_store: &Store) -> Element<Message> {
-    pages::page_label(fl!("playlists")).apply(Element::<Message>::from)
+fn view_playlist_card(playlist: &Playlist) -> impl Into<Element<Message>> {
+    widget::text(playlist.name().unwrap_or("untitled"))
+}
+
+/// Display all playlists in a page.
+pub fn view(store: &Store) -> impl Into<Element<Message>> {
+    PageView::default()
+        .titles(fl!("playlists"), fl!("empty-playlists"))
+        .controls([PageViewControl::new(
+            crate::icons::RETRY,
+            Message::SendCommand(LektordCommand::PlaylistsGet),
+        )])
+        .push_when(store.iter_playlists().count() != 0, || {
+            store
+                .iter_playlists()
+                .map(view_playlist_card)
+                .map(Into::into)
+                .collect::<Vec<_>>()
+                .apply(widget::flex_row)
+        })
+        .view()
 }
diff --git a/amadeus/src/app/pages/queue.rs b/amadeus/src/app/pages/queue.rs
index 86f851c6b8429c2f8581bad58b7399557ebc4927..53938deb186e236a14c0d3afb306bdc4a95cf73e 100644
--- a/amadeus/src/app/pages/queue.rs
+++ b/amadeus/src/app/pages/queue.rs
@@ -1,14 +1,36 @@
 use crate::{
-    app::{kard, pages, Message},
+    app::{kard, pages::PageView, LektordCommand, Message},
     fl,
     store::Store,
 };
-use cosmic::{widget, Apply as _, Element};
+use cosmic::{prelude::*, style, widget};
 use lektor_payloads::{Priority, PRIORITY_VALUES};
 
-fn view_queue_level(store: &Store, level: Priority) -> Element<Message> {
-    // TODO: Add the controls
-    let header = widget::text::title1(format!("{} {level}", fl!("queue")));
+use super::{page_title, PageViewControl};
+
+fn view_queue_level(store: &Store, level: Priority) -> impl IntoIterator<Item = Element<Message>> {
+    let title = page_title(
+        widget::text::title2(format!("{} {level}", fl!("queue"))).style(style::Text::Default),
+        false,
+        vec![
+            PageViewControl::new(
+                crate::icons::SHUFFLE,
+                Message::SendCommand(LektordCommand::QueueLevelShuffle(level)),
+            )
+            .need_content(),
+            PageViewControl::new(
+                crate::icons::RETRY,
+                Message::SendCommand(LektordCommand::QueueLevelGet(level)),
+            ),
+            PageViewControl::new(
+                crate::icons::METEOR,
+                Message::SendCommand(LektordCommand::QueueLevelClear(level)),
+            )
+            .need_content()
+            .destructive(),
+        ],
+        store.iter_queue_level(level).is_empty(),
+    );
 
     let content = store
         .iter_queue_level(level)
@@ -16,20 +38,42 @@ fn view_queue_level(store: &Store, level: Priority) -> Element<Message> {
             list.add(kard::view(store.get(kid)))
         });
 
-    vec![header.into(), content.into()]
-        .apply(widget::column::with_children)
-        .into()
+    [title.into(), content.into()]
 }
 
-pub fn view(store: &Store) -> Element<Message> {
-    match store.iter_queue().count() == 0 {
-        true => pages::empty_page_label("The queue is empty").apply(Element::<Message>::from),
-        false => (PRIORITY_VALUES.iter().rev().copied())
-            .flat_map(|level| {
-                (store.iter_queue_level(level).count() != 0).then(|| view_queue_level(store, level))
-            })
-            .collect::<Vec<Element<Message>>>()
-            .apply(widget::column::with_children)
-            .into(),
-    }
+/// Displays the page with the queue.
+pub fn view(store: &Store) -> impl Into<Element<Message>> {
+    PageView::default()
+        .titles(fl!("queue"), fl!("empty-queue"))
+        .controls([
+            PageViewControl::new(
+                crate::icons::SHUFFLE,
+                Message::SendCommand(LektordCommand::QueueShuffle),
+            )
+            .need_content(),
+            PageViewControl::new(
+                crate::icons::RETRY,
+                Message::SendCommand(LektordCommand::QueueGet),
+            ),
+            PageViewControl::new(
+                crate::icons::CROP,
+                Message::SendCommand(LektordCommand::QueueCrop),
+            )
+            .need_content()
+            .destructive(),
+            PageViewControl::new(
+                crate::icons::METEOR,
+                Message::SendCommand(LektordCommand::QueueClear),
+            )
+            .need_content()
+            .destructive(),
+        ])
+        .extend(
+            (PRIORITY_VALUES.iter().rev())
+                .flat_map(|&lvl| {
+                    (!store.iter_queue_level(lvl).is_empty()).then(|| view_queue_level(store, lvl))
+                })
+                .flatten(),
+        )
+        .view()
 }
diff --git a/amadeus/src/app/pages/search.rs b/amadeus/src/app/pages/search.rs
index df9f906736a1e05ca5cae5446469ceb829e1ceb9..b427ed70c11b2b806b459eb81a05b4d3cccab1c0 100644
--- a/amadeus/src/app/pages/search.rs
+++ b/amadeus/src/app/pages/search.rs
@@ -1,7 +1,6 @@
-use crate::{
-    app::{kard, pages, AppModel, Message},
-    fl,
-};
+//! Contains everything to implement search from Amadeus.
+
+use crate::app::{kard, pages::PageView, AppModel, Message};
 use cosmic::{iced::Alignment, prelude::*, style, theme, widget};
 use lektor_payloads::KaraBy;
 use std::{
@@ -10,6 +9,8 @@ use std::{
     sync::atomic::{AtomicUsize, Ordering},
 };
 
+use super::PageViewControl;
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct FilterAtom(FilterAtomId, KaraBy);
 
@@ -19,10 +20,6 @@ pub struct FilterAtomId(usize);
 #[derive(Debug, Default)]
 pub struct Filter(String, Vec<FilterAtom>);
 
-fn vertical_space<'a>() -> Element<'a, Message> {
-    widget::vertical_space(theme::active().cosmic().space_m()).into()
-}
-
 macro_rules! icon {
     ($icon:ident) => {
         widget::icon::from_svg_bytes(crate::icons::$icon)
@@ -48,19 +45,19 @@ impl FilterAtomId {
 
 impl FilterAtom {
     fn view(&self) -> Element<Message> {
-        let (maybe_icon, text) = match &self.1 {
-            KaraBy::Id(id) => (Some(icon!(HASHTAG)), id.to_string()),
-            KaraBy::Query(query) => (None, query.clone()),
-            KaraBy::Tag((name, None)) => (Some(icon!(TAG)), name.clone()),
-            KaraBy::Tag((name, Some(value))) => (Some(icon!(TAGS)), format!("{name}:{value}")),
-            KaraBy::SongType(song_type) => (None, song_type.to_string()),
-            KaraBy::SongOrigin(song_origin) => (None, song_origin.to_string()),
-            KaraBy::Author(author) => (Some(icon!(USER)), author.clone()),
-            KaraBy::Playlist(playlist) => (Some(icon!(FOLDER)), playlist.clone()),
+        let (icon, text) = match &self.1 {
+            KaraBy::Id(id) => (icon!(HASHTAG), id.to_string()),
+            KaraBy::Query(query) => (icon!(FILTER), query.clone()),
+            KaraBy::Tag((name, None)) => (icon!(TAG), name.clone()),
+            KaraBy::Tag((name, Some(value))) => (icon!(TAGS), format!("{name}:{value}")),
+            KaraBy::SongType(song_type) => (icon!(HASHTAG), song_type.to_string()),
+            KaraBy::SongOrigin(song_origin) => (icon!(HASHTAG), song_origin.to_string()),
+            KaraBy::Author(author) => (icon!(USER), author.clone()),
+            KaraBy::Playlist(playlist) => (icon!(FOLDER), playlist.clone()),
         };
 
         widget::row::with_capacity(2)
-            .push_maybe(maybe_icon)
+            .push(icon)
             .push(widget::text(text))
             .align_items(Alignment::Center)
             .spacing(theme::active().cosmic().space_xxxs())
@@ -72,54 +69,24 @@ impl FilterAtom {
 }
 
 impl Filter {
-    fn can_be_clear(&self) -> bool {
-        !self.1.is_empty() || !self.0.is_empty()
-    }
-
-    fn can_be_submited(&self) -> bool {
-        !self.1.is_empty()
-    }
-
-    fn view(&self) -> Element<Message> {
-        let space_xxs = theme::active().cosmic().space_xxs();
-
-        let staging_text_input = widget::text_input(
+    fn view_staging_row(&self) -> impl Into<Element<Message>> {
+        widget::text_input(
             "@author | tag:value | tag: | #playlist | OP | anime | ...",
             &self.0,
         )
         .on_submit(Message::AddFilterAtomFromStageBuffer)
         .on_input(Message::ChangeFilterStageBuffer)
-        .on_clear(Message::ChangeFilterStageBuffer(String::default()));
+        .on_clear(Message::ChangeFilterStageBuffer(String::default()))
+    }
 
-        let button_commit_filters = widget::button::custom(icon!(FILTER))
-            .on_press_maybe(self.can_be_submited().then_some(Message::QueryWithFilters))
-            .padding(space_xxs)
-            .height(32)
-            .width(32);
+    fn view_filters_row(&self) -> impl Into<Element<Message>> {
+        (self.1.iter().map(FilterAtom::view).collect::<Vec<_>>())
+            .apply(widget::flex_row)
+            .spacing(theme::active().cosmic().space_xxs())
+    }
 
-        let button_clear_filters = widget::button::custom(icon!(METEOR))
-            .on_press_maybe(self.can_be_clear().then_some(Message::ClearFilters))
-            .style(style::Button::Destructive)
-            .padding(space_xxs)
-            .height(32)
-            .width(32);
-
-        let staging_row = widget::row::with_capacity(3)
-            .push(staging_text_input)
-            .push(button_commit_filters)
-            .push(button_clear_filters)
-            .align_items(Alignment::Center)
-            .spacing(space_xxs)
-            .into();
-
-        Element::from(widget::column::with_children(vec![
-            staging_row,
-            vertical_space(),
-            (self.1.iter().map(FilterAtom::view).collect::<Vec<_>>())
-                .apply(widget::flex_row)
-                .spacing(space_xxs)
-                .apply(Element::<Message>::from),
-        ]))
+    pub fn is_empty(&self) -> bool {
+        self.1.is_empty()
     }
 
     pub fn clear(&mut self) {
@@ -132,11 +99,17 @@ impl Filter {
     }
 
     pub fn commit(&mut self) {
-        let atom: FilterAtom = self.0.parse().expect("infallible");
-        if !(self.1.iter()).any(|commited| commited.0 == atom.0 || commited.1 == atom.1) {
-            self.1.push(atom);
+        if !self.0.is_empty() {
+            let atom: FilterAtom = self.0.parse().expect("infallible");
+            if !(self.1.iter()).any(|commited| commited.0 == atom.0 || commited.1 == atom.1) {
+                self.1.push(atom);
+            }
+            self.0.clear();
         }
-        self.0.clear();
+    }
+
+    pub fn iter_cloned(&self) -> impl Iterator<Item = KaraBy> + '_ {
+        self.1.iter().map(|FilterAtom(_, filter)| filter.clone())
     }
 
     pub fn remove(&mut self, id: FilterAtomId) {
@@ -148,15 +121,22 @@ impl Filter {
     }
 }
 
-pub fn view(app: &AppModel) -> Element<Message> {
-    widget::column::with_capacity(4)
-        .push(pages::page_label(fl!("search")))
-        .push(app.search_filter.view())
-        .push(vertical_space())
-        .push_maybe((!app.search_results.is_empty()).then(|| {
-            (app.search_results.iter()).fold(widget::list_column(), |list, kid| {
+/// Display the search page.
+pub fn view(app: &AppModel) -> impl Into<Element<Message>> {
+    PageView::default()
+        .custom_title(app.search_filter.view_staging_row())
+        .controls([
+            PageViewControl::new(crate::icons::FILTER, Message::QueryWithFilters).need_content(),
+            PageViewControl::new(crate::icons::METEOR, Message::ClearFilters)
+                .need_content()
+                .destructive(),
+        ])
+        .has_content(!app.search_filter.is_empty() || !app.search_results.is_empty())
+        .push_and_ignore_for_empty(app.search_filter.view_filters_row())
+        .push_when(!app.search_results.is_empty(), || {
+            (app.search_results.iter()).fold(widget::list_column(), |list, &kid| {
                 list.add(kard::view(app.store.get(kid)))
             })
-        }))
-        .into()
+        })
+        .view()
 }
diff --git a/amadeus/src/app/progress_bar.rs b/amadeus/src/app/progress_bar.rs
index 67ad1d345bb07d142656dc51db3238d1bb16b40c..2bfde63cd6d96455539ee7e10c2d0ac686820472 100644
--- a/amadeus/src/app/progress_bar.rs
+++ b/amadeus/src/app/progress_bar.rs
@@ -1,10 +1,15 @@
+//! Contains things to display the progress bar of the currently playing kara.
+
 use crate::app::{AppModel, Message};
-use cosmic::{iced::Alignment, style, theme, widget, Apply as _, Element};
+use cosmic::{font, iced::Alignment, prelude::*, style, theme, widget};
 
+/// Utility to format the time in a `MM:SS` way. Note that we can show more that 59 minutes, no
+/// kara should be an hour long anyway.
 fn format_time(secs: f32) -> String {
     format!("{}:{}", secs / 60.0, secs % 60.0)
 }
 
+/// View the progress bar for the currently playing kara.
 pub fn view(app: &AppModel) -> Element<Message> {
     let Some((time, duration)) = app.lektord_state.current_times() else {
         return widget::row().into();
@@ -13,7 +18,7 @@ pub fn view(app: &AppModel) -> Element<Message> {
     widget::row::with_children(vec![
         format_time(time)
             .apply(widget::text::monotext)
-            .font(cosmic::font::FONT_LIGHT)
+            .font(font::light())
             .style(style::Text::Accent)
             .into(),
         widget::progress_bar(0.0..=duration, time)
@@ -23,7 +28,7 @@ pub fn view(app: &AppModel) -> Element<Message> {
             .into(),
         format_time(duration)
             .apply(widget::text::monotext)
-            .font(cosmic::font::FONT_LIGHT)
+            .font(font::light())
             .style(style::Text::Accent)
             .into(),
     ])
diff --git a/amadeus/src/subscriptions.rs b/amadeus/src/app/subscriptions.rs
similarity index 100%
rename from amadeus/src/subscriptions.rs
rename to amadeus/src/app/subscriptions.rs
diff --git a/amadeus/src/subscriptions/playback.rs b/amadeus/src/app/subscriptions/playback.rs
similarity index 87%
rename from amadeus/src/subscriptions/playback.rs
rename to amadeus/src/app/subscriptions/playback.rs
index df1ad3b42bd62a44e5f3bcd918bf36f9df5f0efc..c5329911d580cea193f18199534d4bff18536c1a 100644
--- a/amadeus/src/subscriptions/playback.rs
+++ b/amadeus/src/app/subscriptions/playback.rs
@@ -23,12 +23,12 @@ impl Suscription {
             loop {
                 match requests::get_status(config.read().await.as_ref()).await {
                     Ok(state) => {
-                        _ = channel.send(Lektord(PlaybackUpdate(state))).await;
+                        _ = channel.send(LektordUpdate(PlaybackUpdate(state))).await;
                         tokio::time::sleep(Duration::from_secs(1)).await;
                     }
                     Err(err) => {
                         log::debug!("failed to connect to lektord: {err}");
-                        _ = channel.send(Lektord(Disconnected)).await;
+                        _ = channel.send(LektordUpdate(Disconnected)).await;
                         tokio::time::sleep(config.read().await.retry).await;
                     }
                 }
diff --git a/amadeus/src/subscriptions/updates.rs b/amadeus/src/app/subscriptions/updates.rs
similarity index 90%
rename from amadeus/src/subscriptions/updates.rs
rename to amadeus/src/app/subscriptions/updates.rs
index 5a4b77ed76a8931c0ec27af0fef1dddfc7cb573a..1ddc792d6a63dd6b6e8b801be59f9090a22e7d51 100644
--- a/amadeus/src/subscriptions/updates.rs
+++ b/amadeus/src/app/subscriptions/updates.rs
@@ -23,11 +23,11 @@ impl Suscription {
             'connect: loop {
                 log::debug!("try initial connection");
                 let Ok(infos) = requests::get_infos(config.read().await.as_ref()).await else {
-                    _ = channel.send(Lektord(Disconnected)).await;
+                    _ = channel.send(LektordUpdate(Disconnected)).await;
                     tokio::time::sleep(config.read().await.retry).await;
                     continue 'connect;
                 };
-                _ = channel.send(Lektord(Connected(infos))).await;
+                _ = channel.send(LektordUpdate(Connected(infos))).await;
 
                 loop {
                     log::debug!("here we want to query updates for the queue, history, etc…");
diff --git a/amadeus/src/config.rs b/amadeus/src/config.rs
index ba840ea87b70ada166361305d6c2d3f26a991437..24e21a14dcf0aeb7b4bbcf6dade2a797c672113b 100644
--- a/amadeus/src/config.rs
+++ b/amadeus/src/config.rs
@@ -8,6 +8,7 @@ use std::{
     time::Duration,
 };
 
+/// The configuration struct of our application.
 #[derive(Debug, Clone, CosmicConfigEntry, Eq, PartialEq)]
 #[version = 1]
 pub struct Config {
@@ -22,6 +23,7 @@ pub struct Config {
     kurisu_url: String,
 }
 
+/// The default URL for the Kurisu website.
 pub const KURISU_URL: &str = "https://kurisu.iiens.net";
 
 impl Config {
@@ -72,6 +74,7 @@ impl Default for Config {
     }
 }
 
+/// Wrapper to get a default address which is the localhost one.
 #[derive(Debug, Clone, Copy, Eq, PartialEq, Display, From, Into, Serialize, Deserialize)]
 #[display("{socket_addr}")]
 #[repr(transparent)]
@@ -88,6 +91,7 @@ impl Default for Host {
     }
 }
 
+/// Wrapper to get a default log level, and merge the warn and error ones.
 #[derive(Debug, Clone, Copy, Eq, PartialEq, Display, Into)]
 #[display("{_0}")]
 #[repr(transparent)]
diff --git a/amadeus/src/i18n.rs b/amadeus/src/i18n.rs
index 4769970a8688475ca4cc2d7a4df8930e696f5854..1b893e0c4f399e51d70c38f790cac8b5ca1576e6 100644
--- a/amadeus/src/i18n.rs
+++ b/amadeus/src/i18n.rs
@@ -14,16 +14,18 @@ pub fn init(requested_languages: &[LanguageIdentifier]) {
     }
 }
 
-// Get the `Localizer` to be used for localizing this library.
+/// Get the `Localizer` to be used for localizing this library.
 #[must_use]
 pub fn localizer() -> Box<dyn Localizer> {
     Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
 }
 
+/// Localizations for amadeus.
 #[derive(rust_embed::RustEmbed)]
 #[folder = "i18n/"]
 struct Localizations;
 
+/// The language loader for the application.
 pub static LANGUAGE_LOADER: LazyLock<FluentLanguageLoader> = LazyLock::new(|| {
     let loader: FluentLanguageLoader = fluent_language_loader!();
     loader
diff --git a/amadeus/src/icons.rs b/amadeus/src/icons.rs
index 26396513d82985105671467c664642371f5463fd..28320f56464f4f0bc2830b343c9d024d19322325 100644
--- a/amadeus/src/icons.rs
+++ b/amadeus/src/icons.rs
@@ -4,6 +4,7 @@
 macro_rules! icon {
     ($icon:ident : $file:literal) => {
         #[allow(unused)]
+        #[doc = concat!("The ", stringify!($icon), " icon, from 'rsc/icons/", $file, ".svg'")]
         pub const $icon: &[u8] = include_bytes!(concat!("../rsc/icons/", $file, ".svg"));
     };
 }
@@ -44,3 +45,5 @@ icon!(STOP:         "fontawesome/stop");
 icon!(SHUFFLE:      "fontawesome/shuffle");
 icon!(PLAY:         "fontawesome/play");
 icon!(PAUSE:        "fontawesome/pause");
+icon!(RETRY:        "fontawesome/retry");
+icon!(CROP:         "fontawesome/crop");
diff --git a/amadeus/src/lib.rs b/amadeus/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..eef7803a42993c99939c8b44993bd5ff490e75f7
--- /dev/null
+++ b/amadeus/src/lib.rs
@@ -0,0 +1,8 @@
+pub mod app;
+pub mod config;
+pub mod i18n;
+mod icons;
+mod playlist;
+mod store;
+
+include!(concat!(env!("OUT_DIR"), "/amadeus_build_infos.rs"));
diff --git a/amadeus/src/main.rs b/amadeus/src/main.rs
index 0978fba8cb5820c90f7bf5f728399af44bd924d4..973b25d780c40e74245d7a27c14b54bdcb7eeb36 100644
--- a/amadeus/src/main.rs
+++ b/amadeus/src/main.rs
@@ -1,17 +1,10 @@
-mod app;
-mod config;
-mod i18n;
-mod icons;
-mod store;
-mod subscriptions;
-
-include!(concat!(env!("OUT_DIR"), "/amadeus_build_infos.rs"));
-
+use amadeus::{app, config, i18n};
 use anyhow::Context as _;
 use cosmic::cosmic_config::{self, CosmicConfigEntry as _};
 use lektor_utils::{appimage, logger};
 
 fn main() -> anyhow::Result<()> {
+    // Read the config. Also keep the cosmic thing that will allow us to write said config.
     let config = cosmic_config::Config::new(app::APP_ID, config::Config::VERSION).map(|ctx| {
         match config::Config::get_entry(&ctx)
             .inspect_err(|(errs, _)| errs.iter().for_each(|e| log::error!("config error: {e}")))
@@ -20,6 +13,7 @@ fn main() -> anyhow::Result<()> {
         }
     })?;
 
+    // Setup the logger. We ignore specific things around libcosmic and reqwest.
     logger::Builder::default()
         .level(config.0.log_level())
         .filter_targets([
@@ -28,9 +22,11 @@ fn main() -> anyhow::Result<()> {
         .init()
         .context("failed to init logger")?;
 
+    // On linux, detect if we are inside an AppImage.
     #[cfg(target_os = "linux")]
     appimage::detect_appimage()?;
 
+    // Launch the application.
     i18n::init(&i18n_embed::DesktopLanguageRequester::requested_languages());
     cosmic::app::run::<app::AppModel>(cosmic::app::Settings::default(), config)?;
     Ok(())
diff --git a/amadeus/src/playlist.rs b/amadeus/src/playlist.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dcff1f075788e6e770ad8259945ac2b28842564a
--- /dev/null
+++ b/amadeus/src/playlist.rs
@@ -0,0 +1,27 @@
+use lektor_payloads::{KId, PlaylistInfo};
+use std::mem;
+
+#[derive(Debug, Default, PartialEq, Eq)]
+pub struct Playlist {
+    infos: Option<PlaylistInfo>,
+    content: Vec<KId>,
+}
+
+impl Playlist {
+    pub fn name(&self) -> Option<&str> {
+        let infos = self.infos.as_ref()?;
+        (!infos.name().is_empty()).then_some(infos.name())
+    }
+
+    pub fn content(&self) -> impl Iterator<Item = KId> + '_ {
+        self.content.iter().copied()
+    }
+
+    pub fn set_content(&mut self, new: Vec<KId>) {
+        _ = mem::replace(&mut self.content, new);
+    }
+
+    pub fn infos(&self) -> Option<&PlaylistInfo> {
+        self.infos.as_ref()
+    }
+}
diff --git a/amadeus/src/store.rs b/amadeus/src/store.rs
index c6df4c90dac5ec4384df6aa4426545349a323de8..6c1c7fba8dbc1360bd927ef70d43065b71a12a13 100644
--- a/amadeus/src/store.rs
+++ b/amadeus/src/store.rs
@@ -4,12 +4,12 @@
 mod history;
 mod playlist_content;
 mod playlists;
-mod queue;
 mod queue_level;
 
+use crate::playlist::Playlist;
 use hashbrown::HashMap;
-use lektor_payloads::{KId, Kara, Playlist, PlaylistName, Priority, PRIORITY_LENGTH};
-use std::{fmt, mem};
+use lektor_payloads::{KId, Kara, Priority, PRIORITY_LENGTH};
+use std::mem;
 
 /// Stores the kara or its id if the [Kara] struct was not already cached in the [Store].
 #[derive(Debug, Clone)]
@@ -30,7 +30,7 @@ pub struct Store {
     karas: HashMap<KId, Kara>,
 
     /// All the informations about the playlists.
-    playlists: HashMap<PlaylistName, Playlist>,
+    playlists: HashMap<KId, Playlist>,
 
     /// The history, insert at the begin, remove at the end.
     history: Vec<KId>,
@@ -52,22 +52,30 @@ impl Store {
 
     /// Set the content of a playlist. Insert it if it didn't exists. Update it if the entry
     /// existed in the store.
-    pub fn set_playlist(&mut self, name: PlaylistName, plt: Playlist) {
-        _ = self.playlists.insert(name, plt);
+    pub fn set_playlist_content(&mut self, id: KId, plt: Vec<KId>) {
+        self.playlists.entry(id).or_default().set_content(plt);
     }
 
-    /// Keep playlists only if their name is specified in the passed [Vec].
-    pub fn keep_playlists(&mut self, names: Vec<PlaylistName>) {
-        self.playlists.retain(|key, _| names.contains(key));
+    /// Keep playlists only if their name is specified in the passed [Vec]. Returns the new
+    /// playlists in a vector.
+    pub fn keep_playlists(&mut self, ids: Vec<KId>) -> Vec<KId> {
+        self.playlists.retain(|key, _| ids.contains(key));
+        Vec::from_iter(ids.into_iter().flat_map(|id| {
+            (!self.playlists.contains_key(&id)).then(|| {
+                self.playlists.insert(id, Default::default());
+                id
+            })
+        }))
     }
-}
 
-impl Store {
-    /// Get the iterator to query all the levels of the queue.
-    pub fn iter_queue(&self) -> queue::QueueIter {
-        queue::QueueIter::new(self)
+    /// Set the metadata informations about a kar in the [Store]. Any previous information is
+    /// overwritten.
+    pub fn set(&mut self, kara: Kara) {
+        let _ = self.karas.insert(kara.id, kara);
     }
+}
 
+impl Store {
     /// Get the iterator to query a specific level of the queue.
     pub fn iter_queue_level(&self, level: Priority) -> queue_level::QueueLevelIter {
         queue_level::QueueLevelIter::new(self, level)
@@ -84,27 +92,22 @@ impl Store {
     }
 
     /// Get the iterator to list the playlist's content.
-    pub fn iter_playlist_content<S>(&self, name: S) -> playlist_content::PlaylistContentIter
-    where
-        S: TryInto<PlaylistName>,
-        <S as TryInto<PlaylistName>>::Error: fmt::Display,
-    {
-        match name.try_into() {
-            Ok(name) => playlist_content::PlaylistContentIter::new(self, name),
-            Err(err) => {
-                log::error!("{err}");
-                playlist_content::PlaylistContentIter::empty(self)
-            }
-        }
+    pub fn iter_playlist_content(&self, id: KId) -> playlist_content::PlaylistContentIter {
+        playlist_content::PlaylistContentIter::new(self, id)
     }
 
     /// Get a kara from the store. If the [Kara] was not already cached in the store, returns its
     /// [KId]... Note that if the passed [KId] is not valid, then we return a [KId] that doesn't
     /// really exists as we can't know if it's valid or not…
-    pub fn get(&self, kid: &KId) -> KaraOrId {
+    pub fn get(&self, kid: KId) -> KaraOrId {
         self.karas
-            .get(kid)
+            .get(&kid)
             .map(KaraOrId::Kara)
-            .unwrap_or_else(|| KaraOrId::Id(kid.clone()))
+            .unwrap_or_else(|| KaraOrId::Id(kid))
+    }
+
+    /// Get the informations about a playlist.
+    pub fn playlist(&self, id: KId) -> Option<&Playlist> {
+        self.playlists.get(&id)
     }
 }
diff --git a/amadeus/src/store/history.rs b/amadeus/src/store/history.rs
index b7e18576bdb3dc70e0ad296dc75f31671f2479dd..eae05c48d85f23b13b9ffaa18de11a1c329d19ed 100644
--- a/amadeus/src/store/history.rs
+++ b/amadeus/src/store/history.rs
@@ -1,3 +1,5 @@
+//! Implements the iterator for the history.
+
 use super::Store;
 use lektor_payloads::KId;
 
@@ -10,19 +12,25 @@ impl<'a> HistoryIter<'a> {
     pub(super) fn new(store: &'a Store) -> Self {
         Self(store, 0)
     }
+
+    /// Tells if the iterator is empty without consuming it.
+    pub fn is_empty(&self) -> bool {
+        self.count() == 0
+    }
 }
 
 impl<'a> Iterator for HistoryIter<'a> {
-    type Item = &'a KId;
+    type Item = KId;
 
     fn next(&mut self) -> Option<Self::Item> {
         (self.0.history)
             .get(self.1)
             .inspect(|_| self.1 += 1)
+            .copied()
     }
 
     fn last(self) -> Option<Self::Item> {
-        self.0.history.last()
+        self.0.history.last().copied()
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
diff --git a/amadeus/src/store/playlist_content.rs b/amadeus/src/store/playlist_content.rs
index 8f389bf1366ee0d9b6d60119fa36a013db02d039..1b6c7a20e03aa4e5f47e6dd572e01c95a556268d 100644
--- a/amadeus/src/store/playlist_content.rs
+++ b/amadeus/src/store/playlist_content.rs
@@ -1,42 +1,42 @@
+//! Implements the iterator for the content of a playlist.
+
 use super::Store;
-use lektor_payloads::{KId, PlaylistName};
+use lektor_payloads::KId;
 
 /// Wrapper struct to get the content of a playlist.
 #[derive(Debug, Clone)]
-pub struct PlaylistContentIter<'a>(&'a Store, Option<PlaylistName>, usize);
+pub struct PlaylistContentIter<'a>(&'a Store, KId, usize);
 
 impl<'a> PlaylistContentIter<'a> {
-    pub(super) fn empty(store: &'a Store) -> Self {
-        Self(store, None, 0)
+    pub(super) fn new(store: &'a Store, id: KId) -> Self {
+        Self(store, id, 0)
     }
 
-    pub(super) fn new(store: &'a Store, name: PlaylistName) -> Self {
-        Self(store, Some(name), 0)
+    /// Tells if the iterator is empty without consuming it.
+    pub fn is_empty(&self) -> bool {
+        self.size_hint().1.unwrap() == 0
     }
 }
 
 impl<'a> Iterator for PlaylistContentIter<'a> {
-    type Item = &'a KId;
+    type Item = KId;
 
     fn next(&mut self) -> Option<Self::Item> {
         (self.0.playlists)
-            .get(self.1.as_ref()?)?
+            .get(&self.1)?
             .content()
-            .get(self.2)
+            .nth(self.2)
             .inspect(|_| self.2 += 1)
     }
 
     fn last(self) -> Option<Self::Item> {
-        (self.0.playlists).get(self.1.as_ref()?)?.content().last()
+        (self.0.playlists).get(&self.1)?.content().last()
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
-        let Some(name) = self.1.as_ref() else {
-            return (0, Some(0));
-        };
         let size = (self.0.playlists)
-            .get(name)
-            .map(|plt| plt.content().len() - self.2)
+            .get(&self.1)
+            .map(|plt| plt.content().count() - self.2)
             .unwrap_or_default();
         (size, Some(size))
     }
diff --git a/amadeus/src/store/playlists.rs b/amadeus/src/store/playlists.rs
index 8734b713fd80781d5fb96e1f41026b669748d566..2d51c8c28475afc1b286b91fb7333c5578147c98 100644
--- a/amadeus/src/store/playlists.rs
+++ b/amadeus/src/store/playlists.rs
@@ -1,10 +1,12 @@
-use super::Store;
+//! Implements the iterator for all the playlists and their meta-informations.
+
+use crate::{playlist::Playlist, store::Store};
 use hashbrown::hash_map;
-use lektor_payloads::{Playlist, PlaylistName};
+use lektor_payloads::KId;
 
 /// Wrapper struct to get the playlists.
 #[derive(Debug, Clone)]
-pub struct PlaylistsIter<'a>(&'a Store, hash_map::Keys<'a, PlaylistName, Playlist>);
+pub struct PlaylistsIter<'a>(&'a Store, hash_map::Keys<'a, KId, Playlist>);
 
 impl<'a> PlaylistsIter<'a> {
     pub(super) fn new(store: &'a Store) -> Self {
@@ -13,16 +15,14 @@ impl<'a> PlaylistsIter<'a> {
 }
 
 impl<'a> Iterator for PlaylistsIter<'a> {
-    type Item = (PlaylistName, &'a Playlist);
+    type Item = &'a Playlist;
 
     fn next(&mut self) -> Option<Self::Item> {
-        let key = self.1.next()?;
-        self.0.playlists.get(key).map(|plt| (key.clone(), plt))
+        self.0.playlists.get(self.1.next()?)
     }
 
     fn last(self) -> Option<Self::Item> {
-        let key = self.1.last()?;
-        self.0.playlists.get(key).map(|plt| (key.clone(), plt))
+        self.0.playlists.get(self.1.last()?)
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
diff --git a/amadeus/src/store/queue.rs b/amadeus/src/store/queue.rs
deleted file mode 100644
index 9805720eac5b66d49d7a46d4810250efdc1d9ef2..0000000000000000000000000000000000000000
--- a/amadeus/src/store/queue.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use super::Store;
-use lektor_payloads::{KId, Priority};
-
-/// Wrapper struct to get the content of the queue, any level. Implements [Iterator], use the
-/// [Iterator::next] functions to query the state. Returns kara from the one closest to being
-/// played to the farest.
-#[derive(Debug, Clone, Copy)]
-pub struct QueueIter<'a>(&'a Store, usize);
-
-impl<'a> QueueIter<'a> {
-    pub(super) fn new(store: &'a Store) -> Self {
-        Self(store, 0)
-    }
-}
-
-impl<'a> Iterator for QueueIter<'a> {
-    type Item = &'a KId;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        (self.0.queue.iter().rev().flatten())
-            .nth(self.1)
-            .inspect(|_| self.1 += 1)
-    }
-
-    fn last(self) -> Option<Self::Item> {
-        self.0.queue[Priority::min().index()].last()
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        let size = self.0.queue.iter().map(|lvl| lvl.len()).sum::<usize>() - self.1;
-        (size, Some(size))
-    }
-
-    fn count(self) -> usize {
-        self.size_hint().1.unwrap()
-    }
-}
diff --git a/amadeus/src/store/queue_level.rs b/amadeus/src/store/queue_level.rs
index 2cabf0cb57762f0e80ee9663f11ef2ac4275f1ea..c90f995284b3ca53c632fbf4980196510a76c8a0 100644
--- a/amadeus/src/store/queue_level.rs
+++ b/amadeus/src/store/queue_level.rs
@@ -1,3 +1,5 @@
+//! Implements the iterator to iterate only over a single level of the queue at once.
+
 use super::Store;
 use lektor_payloads::{KId, Priority};
 
@@ -11,19 +13,24 @@ impl<'a> QueueLevelIter<'a> {
     pub(super) fn new(store: &'a Store, level: Priority) -> Self {
         Self(store, level, 0)
     }
+
+    pub fn is_empty(&self) -> bool {
+        self.count() == 0
+    }
 }
 
 impl<'a> Iterator for QueueLevelIter<'a> {
-    type Item = &'a KId;
+    type Item = KId;
 
     fn next(&mut self) -> Option<Self::Item> {
         (self.0.queue[self.1.index()])
             .get(self.2)
             .inspect(|_| self.2 += 1)
+            .copied()
     }
 
     fn last(self) -> Option<Self::Item> {
-        (self.0.queue[self.1.index()]).last()
+        (self.0.queue[self.1.index()]).last().copied()
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
diff --git a/kurisu_api/Cargo.toml b/kurisu_api/Cargo.toml
index b2d25cc338deef8e870c6bd83e7c6680d2fbc2e6..5c6996e5213885eeb14acdf1041e64f7af8b63de 100644
--- a/kurisu_api/Cargo.toml
+++ b/kurisu_api/Cargo.toml
@@ -1,19 +1,22 @@
 [package]
 name = "kurisu_api"
+description = "Crate used to deserialize what Kurisu returns"
+rust-version.workspace = true
+
 version.workspace = true
 edition.workspace = true
 authors.workspace = true
 license.workspace = true
-rust-version.workspace = true
-description = "Crate used to deserialize what Kurisu returns"
 
 [lib]
 doctest = false
 
 [dependencies]
+log.workspace = true
 serde.workspace = true
 sha256.workspace = true
 hashbrown.workspace = true
+derive_more.workspace = true
 lektor_utils = { path = "../lektor_utils" }
 
 [dev-dependencies]
diff --git a/kurisu_api/src/error.rs b/kurisu_api/src/error.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dad7ece09c64eb66f71e532d148e73f1adbbfb51
--- /dev/null
+++ b/kurisu_api/src/error.rs
@@ -0,0 +1,20 @@
+use derive_more::Display;
+use std::borrow::Cow;
+
+#[derive(Debug, Clone, Display)]
+#[display("{_0}")]
+pub struct Error(pub(crate) Cow<'static, str>);
+
+impl std::error::Error for Error {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        None
+    }
+
+    fn description(&self) -> &str {
+        self.0.as_ref()
+    }
+
+    fn cause(&self) -> Option<&dyn std::error::Error> {
+        self.source()
+    }
+}
diff --git a/kurisu_api/src/lib.rs b/kurisu_api/src/lib.rs
index 44ce6a88c9099670fcc8d3451e3ce6e0abbc9960..6f2839a76d52f4946936419dafc20572fb841f52 100644
--- a/kurisu_api/src/lib.rs
+++ b/kurisu_api/src/lib.rs
@@ -2,6 +2,7 @@
 
 #![forbid(unsafe_code)]
 
+pub mod error;
 mod hash;
 pub mod v2;
 pub use hash::*;
diff --git a/kurisu_api/src/v2.rs b/kurisu_api/src/v2.rs
index ca9f93e77fc35d6d6bbde5bb95baa94a1e3c14c3..35c9cae826034fac453bf272975f104feb17bcba 100644
--- a/kurisu_api/src/v2.rs
+++ b/kurisu_api/src/v2.rs
@@ -1,10 +1,10 @@
 //! Object rules for the Kurisu's V2 API
 
-use std::str::FromStr;
-
-use crate::SHA256;
-use hashbrown::HashSet;
+use crate::{error::Error, SHA256};
+use derive_more::Display;
+use hashbrown::{HashMap, HashSet};
 use serde::{Deserialize, Serialize};
+use std::{borrow, cmp, collections::BTreeSet, str::FromStr};
 
 /// We can have multiple kurisu repos.
 #[derive(Debug, PartialEq, Eq)]
@@ -14,24 +14,150 @@ pub struct Repo {
     pub infos: Infos,
 }
 
+/// Some infos about criteras. This is just an API specific thing, won't be accessible in other
+/// ways than getters.
+#[derive(Debug, Deserialize, PartialEq, Eq, Default)]
+struct Criterias {
+    /// Authors present in the database.
+    #[serde(default)]
+    author: HashSet<String>,
+
+    /// People that have favorites, will be imported as playlists.
+    #[serde(default)]
+    favorite_haver: HashSet<String>,
+
+    /// List of languages possible. The first item is the code, the second the name.
+    #[serde(default)]
+    language: Vec<(String, String)>,
+
+    /// Available song origins.
+    #[serde(default)]
+    song_origin: HashSet<SongOrigin>,
+
+    /// Available song types.
+    #[serde(default)]
+    song_type: HashSet<SongType>,
+}
+
+impl Criterias {
+    /// Merges two database [Criterias], can show warnings if some bizare things happens…
+    pub fn merge(mut self, mut other: Criterias) -> Self {
+        (self.language.iter())
+            .flat_map(|(c, name)| {
+                let idx = (other.language.iter().enumerate())
+                    .find_map(|(idx, (code, _))| (c.as_str() == code.as_str()).then_some(idx))?;
+                let (_, other) = other.language.remove(idx);
+                Some((c, name, other))
+            })
+            .for_each(|(c, name, other)| {
+                log::warn!("language with code '{c}' have multiple names: '{name}', '{other}'")
+            });
+
+        self.language.extend(other.language);
+        self.author.extend(other.author);
+        self.favorite_haver.extend(other.favorite_haver);
+        self.song_origin.extend(other.song_origin);
+        self.song_type.extend(other.song_type);
+        self
+    }
+}
+
 /// Some infos about the repo and its tags.
 #[derive(Debug, Deserialize, PartialEq, Eq, Default)]
 pub struct Infos {
     /// The current epoch of the database.
-    pub dbepoch: u64,
+    #[serde(default)]
+    dbepoch: u64,
 
     /// The tags with keys.
-    #[serde(rename = "TagKeys")]
-    pub tag_keys: HashSet<String>,
+    #[serde(rename = "valuefullTagKeys")]
+    #[serde(default)]
+    valuefull_tag_keys: HashSet<String>,
 
     /// The tags without keys.
-    #[serde(rename = "ValuelessTagKeys")]
-    pub valueless_tag_keys: HashSet<String>,
+    #[serde(rename = "valuelessTagKeys")]
+    #[serde(default)]
+    valueless_tag_keys: HashSet<String>,
+
+    /// The criterias.
+    #[serde(default)]
+    criterias: Criterias,
+}
+
+impl Infos {
+    /// Merges two database [Infos], can show warnings if some bizare things happens…
+    pub fn merge(mut self, other: Infos) -> Self {
+        if !(self.valueless_tag_keys).is_disjoint(&other.valuefull_tag_keys)
+            || !(self.valuefull_tag_keys).is_disjoint(&other.valueless_tag_keys)
+        {
+            log::warn!("valuefull and valueless tags share some keys…")
+        }
+
+        self.valueless_tag_keys.extend(other.valueless_tag_keys);
+        self.valuefull_tag_keys.extend(other.valuefull_tag_keys);
+        Self {
+            dbepoch: cmp::max(self.dbepoch, other.dbepoch),
+            valuefull_tag_keys: self.valuefull_tag_keys,
+            valueless_tag_keys: self.valueless_tag_keys,
+            criterias: self.criterias.merge(other.criterias),
+        }
+    }
+
+    /// Warn when there is intersection between valuefull and valueless tags.
+    pub fn warn_on_conflicting_tags(&self) {
+        if !(self.valuefull_tag_keys).is_disjoint(&self.valueless_tag_keys) {
+            log::warn!("valueless and valuefull tags have some shared keys…")
+        }
+    }
+
+    pub fn epoch(&self) -> u64 {
+        self.dbepoch
+    }
+
+    pub fn song_types(&self) -> impl Iterator<Item = SongType> + '_ {
+        self.criterias.song_type.iter().copied()
+    }
+
+    pub fn song_origins(&self) -> impl Iterator<Item = SongOrigin> + '_ {
+        self.criterias.song_origin.iter().copied()
+    }
+
+    pub fn languages(&self) -> impl Iterator<Item = &str> {
+        (self.criterias.language.iter()).map(|(code, _)| code.as_str())
+    }
+
+    pub fn language_code(&self, code: &str) -> Option<&str> {
+        (self.criterias.language.iter())
+            .find_map(|(c, n)| (code == c.as_str()).then_some(n.as_str()))
+    }
+
+    pub fn authors(&self) -> impl Iterator<Item = &str> {
+        self.criterias.author.iter().map(String::as_str)
+    }
+
+    pub fn favorites(&self) -> impl Iterator<Item = &str> {
+        self.criterias.favorite_haver.iter().map(String::as_str)
+    }
+
+    pub fn tags(&self) -> impl Iterator<Item = &str> {
+        self.valueless_tag_keys
+            .union(&self.valuefull_tag_keys)
+            .map(String::as_str)
+    }
+
+    pub fn valuefull_tags(&self) -> impl Iterator<Item = &str> {
+        self.valuefull_tag_keys.iter().map(String::as_str)
+    }
+
+    pub fn valueless_tags(&self) -> impl Iterator<Item = &str> {
+        self.valueless_tag_keys.iter().map(String::as_str)
+    }
 }
 
 /// The type of a song. One the the following, one per kara.
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash)]
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash, Display)]
 #[serde(rename_all = "UPPERCASE")]
+#[display("{}", self.as_str())]
 pub enum SongType {
     OP,
     ED,
@@ -41,8 +167,9 @@ pub enum SongType {
 }
 
 /// The origin of a song's source. One the the following, one per kara.
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash)]
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Hash, Display)]
 #[serde(rename_all = "lowercase")]
+#[display("{}", self.as_str())]
 pub enum SongOrigin {
     Anime,
     VN,
@@ -76,7 +203,7 @@ impl SongOrigin {
 }
 
 impl FromStr for SongType {
-    type Err = String;
+    type Err = Error;
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         match s {
             "OP" => Ok(Self::OP),
@@ -84,13 +211,13 @@ impl FromStr for SongType {
             "IS" => Ok(Self::IS),
             "MV" => Ok(Self::MV),
             "OT" => Ok(Self::OT),
-            _ => Err(format!("unknown song type: {s}")),
+            _ => Err(Error(format!("unknown song type: {s}").into())),
         }
     }
 }
 
 impl FromStr for SongOrigin {
-    type Err = String;
+    type Err = Error;
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         match s {
             "anime" => Ok(Self::Anime),
@@ -98,35 +225,52 @@ impl FromStr for SongOrigin {
             "game" => Ok(Self::Game),
             "music" => Ok(Self::Music),
             "other" => Ok(Self::Other),
-            _ => Err(format!("unknown song origin: {s}")),
+            _ => Err(Error(format!("unknown song origin: {s}").into())),
         }
     }
 }
 
-impl AsRef<str> for SongType {
-    fn as_ref(&self) -> &str {
+impl borrow::Borrow<str> for SongType {
+    fn borrow(&self) -> &str {
         self.as_str()
     }
 }
 
-impl AsRef<str> for SongOrigin {
+impl AsRef<str> for SongType {
     fn as_ref(&self) -> &str {
         self.as_str()
     }
 }
 
-impl std::fmt::Display for SongType {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.as_str())
+impl borrow::Borrow<str> for SongOrigin {
+    fn borrow(&self) -> &str {
+        self.as_str()
     }
 }
 
-impl std::fmt::Display for SongOrigin {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.as_str())
+impl AsRef<str> for SongOrigin {
+    fn as_ref(&self) -> &str {
+        self.as_str()
     }
 }
 
+/// The struct returned by kurisu when downloading a group of karas.
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct Karas {
+    /// The epoch.
+    #[serde(default)]
+    dbepoch: u64,
+
+    /// The validity (in seconds) of this epoch.
+    #[serde(rename = "dbepochValidity")]
+    #[serde(default)]
+    dbepoch_validity: u64,
+
+    /// All of the karas returned.
+    #[serde(default)]
+    karas: BTreeSet<Kara>,
+}
+
 /// The struct returned by kurisu. Intended to be deserialized from json.
 #[derive(Debug, Deserialize, PartialEq, Eq)]
 pub struct Kara {
@@ -142,13 +286,14 @@ pub struct Kara {
 
     /// The list of authors that contributed to the kara.
     #[serde(rename = "author")]
-    pub kara_makers: Vec<String>,
+    pub kara_makers: HashSet<String>,
 
     /// Is the kara a new one?
     pub is_new: bool,
 
     /// Is the kara a virtual one?
     #[serde(rename = "virtual")]
+    #[serde(default)]
     pub is_virtual: bool,
 
     /// The type of the song. Is it an ending, opening, etc.
@@ -170,11 +315,75 @@ pub struct Kara {
     pub epoch: u64,
 
     /// The size of the file.
+    #[serde(default)]
     pub filesize: u64,
 
     /// The sha256 hash of the file.
-    pub file_hash: SHA256,
+    #[serde(default)]
+    pub file_hash: Option<SHA256>,
 
     /// The tags ised in the kara.
+    #[serde(default)]
     pub tags: Vec<[String; 2]>,
 }
+
+impl Ord for Kara {
+    fn cmp(&self, other: &Self) -> cmp::Ordering {
+        PartialOrd::partial_cmp(&self, &other).unwrap()
+    }
+}
+impl PartialOrd for Kara {
+    #[allow(clippy::non_canonical_partial_ord_impl)]
+    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+        PartialOrd::partial_cmp(&self.id, &other.id)
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Favorites(HashMap<String, HashSet<u64>>);
+
+impl Favorites {
+    /// Names of favorite lists.
+    pub fn lists(&self) -> impl Iterator<Item = &str> {
+        self.0.keys().map(String::as_str)
+    }
+
+    /// The content of a favorite list.
+    pub fn list_content(&self, user: &str) -> impl Iterator<Item = u64> + '_ {
+        (self.0.get(user))
+            .map(|list| list.iter().copied())
+            .into_iter()
+            .flatten()
+    }
+}
+
+impl IntoIterator for Favorites {
+    type Item = (String, HashSet<u64>);
+    type IntoIter = <HashMap<String, HashSet<u64>> as IntoIterator>::IntoIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.into_iter()
+    }
+}
+
+#[derive(Debug, Deserialize, PartialEq, Eq)]
+pub struct Playlist {
+    /// The ID of the playlist.
+    id: u64,
+
+    /// The title of the playlist.
+    title: String,
+
+    /// The optional description of the playlist, can be empty.
+    #[serde(default)]
+    description: String,
+
+    /// The owners of this playlist, can be empty.
+    #[serde(default)]
+    owners: Vec<String>,
+
+    /// The content of this playlist, can be empty. The order is important and there can be
+    /// repetitions of karas.
+    #[serde(default)]
+    karas: Vec<u64>,
+}
diff --git a/kurisu_api/tests/dbinfo.json b/kurisu_api/tests/dbinfo.json
index cd9a54919be7d3cfe93b95db95e59de0905a977d..8bea121236097ae32279f05ea0d30b681502789b 100644
--- a/kurisu_api/tests/dbinfo.json
+++ b/kurisu_api/tests/dbinfo.json
@@ -1,23 +1,199 @@
 {
-    "dbepoch": 1,
-    "TagKeys": [
-        "Version",
-        "Composer",
-        "VTuber",
-        "Version",
-        "Composer",
-        "VTuber",
-        "Version",
-        "Composer",
-        "VTuber",
-        "Version",
-        "Composer",
-        "VTuber"
+  "dbepoch": 95,
+  "criterias": {
+    "author": [ "Elliu", "fooPseudo", "TheOneAuthor", "TheOneAuthor2", "TTheOneAuthor2" ],
+    "language": [
+      [ "aa", "Afar" ],
+      [ "ab", "Abkhazian" ],
+      [ "af", "Afrikaans" ],
+      [ "ak", "Akan" ],
+      [ "sq", "Albanian" ],
+      [ "am", "Amharic" ],
+      [ "ar", "Arabic" ],
+      [ "an", "Aragonese" ],
+      [ "hy", "Armenian" ],
+      [ "as", "Assamese" ],
+      [ "av", "Avaric" ],
+      [ "ae", "Avestan" ],
+      [ "ay", "Aymara" ],
+      [ "az", "Azerbaijani" ],
+      [ "ba", "Bashkir" ],
+      [ "bm", "Bambara" ],
+      [ "eu", "Basque" ],
+      [ "be", "Belarusian" ],
+      [ "bn", "Bengali" ],
+      [ "bh", "Bihari languages" ],
+      [ "bi", "Bislama" ],
+      [ "bs", "Bosnian" ],
+      [ "br", "Breton" ],
+      [ "bg", "Bulgarian" ],
+      [ "my", "Burmese" ],
+      [ "ca", "Catalan" ],
+      [ "ch", "Chamorro" ],
+      [ "ce", "Chechen" ],
+      [ "zh", "Chinese" ],
+      [ "cu", "Old Slavonic" ],
+      [ "cv", "Chuvash" ],
+      [ "kw", "Cornish" ],
+      [ "co", "Corsican" ],
+      [ "cr", "Cree" ],
+      [ "cs", "Czech" ],
+      [ "da", "Danish" ],
+      [ "dv", "Maldivian" ],
+      [ "nl", "Dutch" ],
+      [ "dz", "Dzongkha" ],
+      [ "en", "English" ],
+      [ "eo", "Esperanto" ],
+      [ "et", "Estonian" ],
+      [ "ee", "Ewe" ],
+      [ "fo", "Faroese" ],
+      [ "fj", "Fijian" ],
+      [ "fi", "Finnish" ],
+      [ "fr", "French" ],
+      [ "fy", "Western Frisian" ],
+      [ "ff", "Fulah" ],
+      [ "ka", "Georgian" ],
+      [ "de", "German" ],
+      [ "gd", "Gaelic" ],
+      [ "ga", "Irish" ],
+      [ "gl", "Galician" ],
+      [ "gv", "Manx" ],
+      [ "el", "Greek" ],
+      [ "gn", "Guarani" ],
+      [ "gu", "Gujarati" ],
+      [ "ht", "Haitian" ],
+      [ "ha", "Hausa" ],
+      [ "he", "Hebrew" ],
+      [ "hz", "Herero" ],
+      [ "hi", "Hindi" ],
+      [ "ho", "Hiri Motu" ],
+      [ "hr", "Croatian" ],
+      [ "hu", "Hungarian" ],
+      [ "ig", "Igbo" ],
+      [ "is", "Icelandic" ],
+      [ "io", "Ido" ],
+      [ "ii", "Sichuan Yi" ],
+      [ "iu", "Inuktitut" ],
+      [ "ie", "Interlingue" ],
+      [ "ia", "Interlingua" ],
+      [ "id", "Indonesian" ],
+      [ "ik", "Inupiaq" ],
+      [ "it", "Italian" ],
+      [ "jv", "Javanese" ],
+      [ "ja", "Japanese" ],
+      [ "kl", "Kalaallisut" ],
+      [ "kn", "Kannada" ],
+      [ "ks", "Kashmiri" ],
+      [ "kr", "Kanuri" ],
+      [ "kk", "Kazakh" ],
+      [ "km", "Central Khmer" ],
+      [ "ki", "Kikuyu" ],
+      [ "rw", "Kinyarwanda" ],
+      [ "ky", "Kirghiz" ],
+      [ "kv", "Komi" ],
+      [ "kg", "Kongo" ],
+      [ "ko", "Korean" ],
+      [ "kj", "Kuanyama" ],
+      [ "ku", "Kurdish" ],
+      [ "lo", "Lao" ],
+      [ "la", "Latin" ],
+      [ "lv", "Latvian" ],
+      [ "li", "Limburgan" ],
+      [ "ln", "Lingala" ],
+      [ "lt", "Lithuanian" ],
+      [ "lb", "Luxembourgish" ],
+      [ "lu", "Luba-Katanga" ],
+      [ "lg", "Ganda" ],
+      [ "mk", "Macedonian" ],
+      [ "mh", "Marshallese" ],
+      [ "ml", "Malayalam" ],
+      [ "mi", "Maori" ],
+      [ "mr", "Marathi" ],
+      [ "ms", "Malay" ],
+      [ "mg", "Malagasy" ],
+      [ "mt", "Maltese" ],
+      [ "mn", "Mongolian" ],
+      [ "na", "Nauru" ],
+      [ "nv", "Navajo" ],
+      [ "nr", "South Ndebele" ],
+      [ "nd", "North Ndebele" ],
+      [ "ng", "Ndonga" ],
+      [ "ne", "Nepali" ],
+      [ "nn", "Norwegian Nynorsk" ],
+      [ "nb", "Norwegian Bokmål" ],
+      [ "no", "Norwegian" ],
+      [ "ny", "Chichewa" ],
+      [ "oc", "Occitan" ],
+      [ "oj", "Ojibwa" ],
+      [ "or", "Oriya" ],
+      [ "om", "Oromo" ],
+      [ "os", "Ossetian" ],
+      [ "pa", "Punjabi" ],
+      [ "fa", "Persian" ],
+      [ "pi", "Pali" ],
+      [ "pl", "Polish" ],
+      [ "pt", "Portuguese" ],
+      [ "ps", "Pashto" ],
+      [ "qu", "Quechua" ],
+      [ "rm", "Romansh" ],
+      [ "ro", "Romanian / Moldavian" ],
+      [ "rn", "Rundi" ],
+      [ "ru", "Russian" ],
+      [ "sg", "Sango" ],
+      [ "sa", "Sanskrit" ],
+      [ "si", "Sinhala" ],
+      [ "sk", "Slovak" ],
+      [ "sl", "Slovenian" ],
+      [ "se", "Northern Sami" ],
+      [ "sm", "Samoan" ],
+      [ "sn", "Shona" ],
+      [ "sd", "Sindhi" ],
+      [ "so", "Somali" ],
+      [ "st", "Southern Sotho" ],
+      [ "es", "Spanish" ],
+      [ "sc", "Sardinian" ],
+      [ "sr", "Serbian" ],
+      [ "ss", "Swati" ],
+      [ "su", "Sundanese" ],
+      [ "sw", "Swahili" ],
+      [ "sv", "Swedish" ],
+      [ "ty", "Tahitian" ],
+      [ "ta", "Tamil" ],
+      [ "tt", "Tatar" ],
+      [ "te", "Telugu" ],
+      [ "tg", "Tajik" ],
+      [ "tl", "Tagalog" ],
+      [ "th", "Thai" ],
+      [ "bo", "Tibetan" ],
+      [ "ti", "Tigrinya" ],
+      [ "to", "Tonga (Tonga Islands)" ],
+      [ "tn", "Tswana" ],
+      [ "ts", "Tsonga" ],
+      [ "tk", "Turkmen" ],
+      [ "tr", "Turkish" ],
+      [ "tw", "Twi" ],
+      [ "ug", "Uighur" ],
+      [ "uk", "Ukrainian" ],
+      [ "ur", "Urdu" ],
+      [ "uz", "Uzbek" ],
+      [ "ve", "Venda" ],
+      [ "vi", "Vietnamese" ],
+      [ "vo", "Volapük" ],
+      [ "cy", "Welsh" ],
+      [ "wa", "Walloon" ],
+      [ "wo", "Wolof" ],
+      [ "xh", "Xhosa" ],
+      [ "yi", "Yiddish" ],
+      [ "yo", "Yoruba" ],
+      [ "za", "Zhuang" ],
+      [ "zu", "Zulu" ],
+      [ "fx", "Fictional" ],
+      [ "ot", "Joker" ]
     ],
-    "ValuelessTagKeys": [
-        "VTuber",
-        "VTuber",
-        "VTuber",
-        "VTuber"
-    ]
+    "song_origin": [ "anime", "vn", "game", "music", "other" ],
+    "song_type": [ "OP", "ED", "IS", "MV", "OT" ],
+    "favorites_haver": [ "fooPseudo" ]
+  },
+  "valuefullTagKeys": [ "Number", "Composer", "ComposerButSuperLongToFaireChier" ],
+  "valuelessTagKeys": [ "VTuber" ]
 }
diff --git a/kurisu_api/tests/favorites.json b/kurisu_api/tests/favorites.json
new file mode 100644
index 0000000000000000000000000000000000000000..d755994c68638388fb45903680aaf2ad11bd028f
--- /dev/null
+++ b/kurisu_api/tests/favorites.json
@@ -0,0 +1,5 @@
+{
+  "fooPseudo": [ 6, 9544 ],
+  "zooBar": [ 5 ]
+}
+
diff --git a/kurisu_api/tests/karas.json b/kurisu_api/tests/karas.json
new file mode 100644
index 0000000000000000000000000000000000000000..4636710d2caf1b66ee20b3afd15b9d6fae51da75
--- /dev/null
+++ b/kurisu_api/tests/karas.json
@@ -0,0 +1,150 @@
+{
+  "dbepoch": 95,
+  "dbepochValidity": 2024072806,
+  "karas": [
+    {
+      "id": 9544,
+      "song_title": "Hyper",
+      "source_name": "Under Ninja",
+      "song_type": "OP",
+      "song_origin": "anime",
+      "is_new": false,
+      "created_at": 65974410587,
+      "updated_at": 65974410587,
+      "epoch": 0,
+      "file_hash": "01567e864bf255dab39245b5ab0c521ac960080f45b69406bba434dc5ba7985b",
+      "nextFilepath": "",
+      "filesize": 24693413,
+      "virtual": false,
+      "language": [ "ru" ],
+      "author": [ "Elliu" ],
+      "tags": [
+        [ "Number", "1" ],
+        [ "Composer", "xcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklak" ],
+        [ "Composer", "ejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzakl" ]
+      ]
+    },
+    {
+      "id": 5,
+      "song_title": "Shion",
+      "source_name": "niKu",
+      "song_type": "MV",
+      "song_origin": "vn",
+      "is_new": false,
+      "created_at": 1724741313,
+      "updated_at": 1724741320,
+      "epoch": 0,
+      "file_hash": "054c85b4e39b62897e24922af5c21593325100a5d75a57f5fa56c194e83eee67",
+      "nextFilepath": "",
+      "filesize": 44060958,
+      "virtual": false,
+      "language": [ "ab" ],
+      "author": [ "fooPseudo", "Elliu", "TheOneAuthor" ],
+      "tags": [
+        [ "VTuber", "" ],
+        [ "Composer", "xcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklaxcklak" ],
+        [ "Composer", "ejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzaklejzakl" ],
+        [ "ComposerButSuperLongToFaireChier", "aze" ]
+      ]
+    },
+    {
+      "id": 4,
+      "song_title": "Gif ni Ted",
+      "source_name": "Henjin no Salad Bowl",
+      "song_type": "OP",
+      "song_origin": "vn",
+      "is_new": false,
+      "created_at": 1724740583,
+      "updated_at": 1724740596,
+      "epoch": 0,
+      "file_hash": "91b3afcfba5d9022f9b72e0b94127d9f1a11cc047f8540e2393c702429de80ce",
+      "nextFilepath": "",
+      "filesize": 24201908,
+      "virtual": false,
+      "language": [ "af" ],
+      "author": [ "TheOneAuthor", "Elliu", "fooPseudo" ],
+      "tags": [
+        [ "Number", "1" ],
+        [ "VTuber", "" ],
+        [ "Composer", "WaouhSuperJ'adore" ]
+      ]
+    },
+    {
+      "id": 2,
+      "song_title": "Pre-Romance",
+      "source_name": "Natsu e no Tunnel, Sayonara no Deguchi",
+      "song_type": "MV",
+      "song_origin": "game",
+      "is_new": true,
+      "created_at": 1723986494,
+      "updated_at": 1723986500,
+      "epoch": 0,
+      "file_hash": "6f3df02ee1f000295deaab0fe89e6908ae1ca3a25a533bc99b3d59fa60caf497",
+      "nextFilepath": "",
+      "filesize": 9784315,
+      "virtual": false,
+      "language": [ "aa" ],
+      "author": [ "fooPseudo" ],
+      "tags": []
+    },
+    {
+      "id": 1,
+      "song_title": "Rap God ft. Momoi (46 Brian)",
+      "source_name": "Eminem",
+      "song_type": "MV",
+      "song_origin": "game",
+      "is_new": false,
+      "created_at": 1723986487,
+      "updated_at": 1723986496,
+      "epoch": 0,
+      "file_hash": "6b512e13388623a1aed0967a449320818d4eb4c91ffec20014cc0ee02f0fbd4f",
+      "nextFilepath": "",
+      "filesize": 5488980,
+      "virtual": false,
+      "language": [ "af" ],
+      "author": [ "fooPseudo" ],
+      "tags": []
+    },
+    {
+      "id": 3,
+      "song_title": "Every Second (Japanese Version)",
+      "source_name": "Hananoi-kun to Koi no Yamai",
+      "song_type": "ED",
+      "song_origin": "game",
+      "is_new": false,
+      "created_at": 1724143207,
+      "updated_at": 1724740456,
+      "epoch": 0,
+      "file_hash": "5c78cc58610da5ac5212e8d48a61eb2f8f5ff89c97e47372e0142d5f59de8039",
+      "nextFilepath": "",
+      "filesize": 8197649,
+      "virtual": false,
+      "language": [ "ak" ],
+      "author": [ "fooPseudo" ],
+      "tags": [
+        [ "Number", "1" ],
+        [ "VTuber", "" ]
+      ]
+    },
+    {
+      "id": 6,
+      "song_title": "Reversal",
+      "source_name": "Tsuki ga Michibiku Isekai Douchuu 2nd Season",
+      "song_type": "OP",
+      "song_origin": "game",
+      "is_new": false,
+      "created_at": 1725547250,
+      "updated_at": 1726757878,
+      "epoch": 0,
+      "file_hash": "19f0f6ab891c8a565f6483d6148782efd2d0e935b1e092951529cb59bcc35f34",
+      "nextFilepath": "",
+      "filesize": 13453030,
+      "virtual": false,
+      "language": [ "af" ],
+      "author": [ "fooPseudo" ],
+      "tags": [
+        [ "Number", "2" ]
+      ]
+    }
+  ]
+}
diff --git a/kurisu_api/tests/playlists.json b/kurisu_api/tests/playlists.json
new file mode 100644
index 0000000000000000000000000000000000000000..46af1f8bd71275f93d5d66cd3ff5d20a5c82f239
--- /dev/null
+++ b/kurisu_api/tests/playlists.json
@@ -0,0 +1,37 @@
+[
+  {
+    "id": 5,
+    "title": "Playlist 1",
+    "description": "This is an awesome playlist",
+    "owners": [ "fooPseudo" ],
+    "karas": [ 6, 9544, 5 ]
+  },
+  {
+    "id": 6,
+    "title": "Playlist 2",
+    "description": "This playlist is not yours",
+    "owners": [ "fooPseudo<wow>" ],
+    "karas": [ 9544, 5 ]
+  },
+  {
+    "id": 8,
+    "title": "Playlist with quite long name",
+    "description": "Short description",
+    "owners": [],
+    "karas": [ 6 ]
+  },
+  {
+    "id": 9,
+    "title": "Kinda The longest The longest The longest The longest The longest The longest The longest The longest The longest The longest",
+    "description": "aze",
+    "owners": [ "fooPseudo", "fooPseudo<wow>" ],
+    "karas": []
+  },
+  {
+    "id": 10,
+    "title": "aze",
+    "description": "aze",
+    "owners": [ "fooPseudo" ],
+    "karas": []
+  }
+]
diff --git a/kurisu_api/tests/sample.json b/kurisu_api/tests/sample.json
deleted file mode 100644
index 609d1bfb1e70ad648c4302342fb88ebfe7749cf6..0000000000000000000000000000000000000000
--- a/kurisu_api/tests/sample.json
+++ /dev/null
@@ -1,323 +0,0 @@
-[
-  {
-    "id": 9022,
-    "song_title": "hA HA honk2",
-    "source_name": "senzawa",
-    "song_type": "OP",
-    "song_origin": "anime",
-    "language": [
-      "br",
-      "de"
-    ],
-    "author": [
-      "Elliu"
-    ],
-    "tags": [
-      [
-        "Version",
-        "Short"
-      ],
-      [
-        "Composer",
-        "aze"
-      ]
-    ],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-27 20:12:55",
-    "file_hash": "d1d2033d760e93158fc64426e8b7ec3535fbf92d41e48bc16fca2f5fd8b2a926",
-    "filesize": 1465654,
-    "epoch": 1,
-    "updated_at": 1677967139,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9026,
-    "song_title": "Chikatto Chika Chika",
-    "source_name": "Kaguya-sama wa Kokurasetai",
-    "song_type": "ED",
-    "song_origin": "vn",
-    "language": [
-      "ca",
-      "ja"
-    ],
-    "author": [
-      "Kubat"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-27 20:16:20",
-    "file_hash": "def21dad50c0d4d9c477591cd9de039a7ab3c0d65d52b390417edbbb0776052f",
-    "filesize": 17122564,
-    "epoch": 1,
-    "updated_at": 1683208523,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9027,
-    "song_title": "last sparkle",
-    "source_name": "Pop Team Epic Special",
-    "song_type": "OP",
-    "song_origin": "anime",
-    "language": [
-      "zh",
-      "br"
-    ],
-    "author": [
-      "Salixor"
-    ],
-    "tags": [
-      [
-        "Composer",
-        "aze"
-      ]
-    ],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-10 13:11:48",
-    "file_hash": "3da80457a071047896c833a0ddde64a82b524e78cf33e2de0a312160ad0e40c2",
-    "filesize": 16883894,
-    "epoch": 8,
-    "updated_at": 1683208648,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9028,
-    "song_title": "Piyo",
-    "source_name": "Higepiyo",
-    "song_type": "OP",
-    "song_origin": "anime",
-    "language": [
-      "en"
-    ],
-    "author": [
-      "Elliu"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-10 13:07:20",
-    "file_hash": "c285aa8b1bd3c21bf158b6bbb2c8798febc43ed9d707fb6fd79a074d5f359b41",
-    "filesize": 463172,
-    "epoch": 0,
-    "updated_at": 1683209107,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9029,
-    "song_title": "Maka went gao",
-    "source_name": "Soul Eater",
-    "song_type": "IS",
-    "song_origin": "game",
-    "language": [
-      "la"
-    ],
-    "author": [
-      "Deurstann"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": false,
-    "author_year": "2023-09-10 13:08:02",
-    "file_hash": "0a0baa8de3696955a695fdf816abb42539bceadbfb4c79cde7d43c081ff6d9c0",
-    "filesize": 196032,
-    "epoch": 0,
-    "updated_at": 1683209132,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9030,
-    "song_title": "Chijou no Senshi",
-    "source_name": "Sakura Taisen New York, New York",
-    "song_type": "OP",
-    "song_origin": "anime",
-    "language": [
-      "ja"
-    ],
-    "author": [
-      "Bisquette"
-    ],
-    "tags": [
-      [
-        "VTuber",
-        ""
-      ]
-    ],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-24 14:48:30",
-    "file_hash": "974583d04d8fe2485b64ec8de97785cc34536cd8a69166d3be2cd907148154d2",
-    "filesize": 11905649,
-    "epoch": 0,
-    "updated_at": 1683209134,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9031,
-    "song_title": "Hare Hare Yukai",
-    "source_name": "Suzumiya Haruhi no Yuuutsu",
-    "song_type": "ED",
-    "song_origin": "vn",
-    "language": [
-      "ja"
-    ],
-    "author": [
-      "Sting"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-10 13:08:05",
-    "file_hash": "99a280ae6fde376dc6f933e7ff86e377863bc18a2c775265eba387b217c660fb",
-    "filesize": 10130414,
-    "epoch": 0,
-    "updated_at": 1683209135,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9032,
-    "song_title": "All Alone With You",
-    "source_name": "Psycho-Pass",
-    "song_type": "ED",
-    "song_origin": "vn",
-    "language": [
-      "fr"
-    ],
-    "author": [
-      "Colgate"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-10 13:07:50",
-    "file_hash": "47a5585c5fe4d1e5ea85c80f3a01d465aa393a1dbbd1bd38f2021df90dd44fec",
-    "filesize": 10044203,
-    "epoch": 0,
-    "updated_at": 1683209136,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9033,
-    "song_title": "Mes rêves",
-    "source_name": "Pokémon",
-    "song_type": "OP",
-    "song_origin": "anime",
-    "language": [
-      "fr"
-    ],
-    "author": [
-      "Deurstann"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-10 13:07:24",
-    "file_hash": "4036f7f60c08bf70741f118eca95dff61facfb66199351e9e40900071cd5322d",
-    "filesize": 10247536,
-    "epoch": 0,
-    "updated_at": 1683209137,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9035,
-    "song_title": "Shiny tale",
-    "source_name": "Danshi Koukousei no Nichijou",
-    "song_type": "OP",
-    "song_origin": "anime",
-    "language": [
-      "fr"
-    ],
-    "author": [
-      "Sting"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-10 13:07:17",
-    "file_hash": "c6d19655f65777e8bcae8394289bd1ba5e986d31cd53725ea590a22e1c285419",
-    "filesize": 22148477,
-    "epoch": 0,
-    "updated_at": 1683209138,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9036,
-    "song_title": "Wind",
-    "source_name": "Naruto",
-    "song_type": "ED",
-    "song_origin": "vn",
-    "language": [
-      "ru"
-    ],
-    "author": [
-      "Deurstann"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-09 19:35:48",
-    "file_hash": "be4f6b70ef0c3fce3744d397e7c8a4292f1e029a2b82512832ece0f632641976",
-    "filesize": 6533611,
-    "epoch": 0,
-    "updated_at": 1683209138,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9037,
-    "song_title": "Get Wild",
-    "source_name": "City Hunter",
-    "song_type": "ED",
-    "song_origin": "vn",
-    "language": [
-      "ja"
-    ],
-    "author": [
-      "Deurstann"
-    ],
-    "tags": [],
-    "upload_comment": "",
-    "is_new": true,
-    "author_year": "2023-09-09 19:22:00",
-    "file_hash": "d6bad8ec540853d71669912e2731c197bdb150cf05f00c3d7fb2aaecd55b9a6e",
-    "filesize": 6857963,
-    "epoch": 0,
-    "updated_at": 1683209227,
-    "created_at": 1677967139,
-    "virtual": false
-  },
-  {
-    "id": 9041,
-    "song_title": "hA HA honk",
-    "source_name": "senzawa",
-    "song_type": "IS",
-    "song_origin": "game",
-    "language": [
-      "ja"
-    ],
-    "author": [
-      "Elliu"
-    ],
-    "tags": [],
-    "upload_comment": "oops reupload cause <>",
-    "is_new": true,
-    "author_year": "2023-09-10 12:38:27",
-    "file_hash": "86c2eb4fe6a78afb0a3c6d5e769154fdf4e00cf4a6e0e0ff5bb2ee74d5f3a750",
-    "filesize": 1465654,
-    "epoch": 0,
-    "updated_at": 1694290141,
-    "created_at": 1677967139,
-    "virtual": false
-  }
-]
diff --git a/kurisu_api/tests/v2.rs b/kurisu_api/tests/v2.rs
index 88fcbffa6d072a9d89ccf56cb806fb986cfca547..1fc0c1412c16e0123414ccc505084545f89fc67b 100644
--- a/kurisu_api/tests/v2.rs
+++ b/kurisu_api/tests/v2.rs
@@ -3,8 +3,7 @@ use lektor_utils::{assert_err, assert_ok};
 
 #[test]
 fn test_kurisu_v2_dbinfos() {
-    const SAMPLE: &str = include_str!("dbinfo.json");
-    assert_ok!(serde_json::from_str::<Infos>(SAMPLE));
+    assert_ok!(serde_json::from_str::<Infos>(include_str!("dbinfo.json")));
 }
 
 #[test]
@@ -43,6 +42,19 @@ fn test_kurisu_v2_simple_enums() {
 
 #[test]
 fn test_kurisu_v2_get_karas() {
-    const SAMPLE: &str = include_str!("sample.json");
-    assert_ok!(serde_json::from_str::<Vec<Kara>>(SAMPLE));
+    assert_ok!(serde_json::from_str::<Karas>(include_str!("karas.json")));
+}
+
+#[test]
+fn test_kurisu_v2_get_favorites() {
+    assert_ok!(serde_json::from_str::<Favorites>(include_str!(
+        "favorites.json"
+    )));
+}
+
+#[test]
+fn test_kurisu_v2_get_playlists() {
+    assert_ok!(serde_json::from_str::<Vec<Playlist>>(include_str!(
+        "playlists.json"
+    )));
 }
diff --git a/lektor_lib/Cargo.toml b/lektor_lib/Cargo.toml
index c6f37da0a86fcb327b5519cec5f4388446ae47a9..e882fd53e5c990b9dfb2d375dbc3e19ec23aba32 100644
--- a/lektor_lib/Cargo.toml
+++ b/lektor_lib/Cargo.toml
@@ -8,17 +8,14 @@ rust-version.workspace = true
 description = "Client library for lektord, used to factorize the code between lkt and amadeus"
 
 [dependencies]
-serde.workspace = true
+serde.workspace      = true
 serde_json.workspace = true
 
-log.workspace = true
-url.workspace = true
-anyhow.workspace = true
-
+log.workspace     = true
+url.workspace     = true
+anyhow.workspace  = true
 reqwest.workspace = true
-
 futures.workspace = true
-async-trait.workspace = true
 
-lektor_utils = { path = "../lektor_utils" }
+lektor_utils    = { path = "../lektor_utils" }
 lektor_payloads = { path = "../lektor_payloads" }
diff --git a/lektor_lib/src/requests.rs b/lektor_lib/src/requests.rs
index 719f92626f02f22b43771893bcc524d94d0c04fd..fc66474a4417cea0a8ec2933c958a60a52ee3f29 100644
--- a/lektor_lib/src/requests.rs
+++ b/lektor_lib/src/requests.rs
@@ -1,5 +1,9 @@
 use crate::ConnectConfig;
 use anyhow::{bail, Context, Result};
+use futures::{
+    stream::{self, FuturesUnordered},
+    StreamExt,
+};
 use lektor_payloads::*;
 use lektor_utils::{encode_base64, encode_base64_value};
 use reqwest::{
@@ -66,14 +70,23 @@ pub async fn get_status(config: impl AsRef<ConnectConfig>) -> Result<PlayStateWi
     request!(config; GET @ "/playback/state" => PlayStateWithCurrent)
 }
 
-pub async fn get_kara_by_kid(config: impl AsRef<ConnectConfig>, kid: KId) -> Result<Kara> {
-    let (len, kid) = encode_base64(kid.as_str())?;
-    let kid = std::str::from_utf8(&kid[..len])?;
-    request!(config; GET @ "/get/kid/{kid}" => Kara)
+pub async fn get_karas_by_kid(
+    config: impl AsRef<ConnectConfig>,
+    kids: Vec<KId>,
+) -> Result<Vec<Kara>> {
+    let config = config.as_ref();
+    Ok(stream::iter(kids)
+        .then(|kid| async move { get_kara_by_kid(config, kid).await })
+        .collect::<FuturesUnordered<_>>()
+        .await
+        .into_iter()
+        .flatten()
+        .collect())
 }
 
-pub async fn get_kara_by_id(config: impl AsRef<ConnectConfig>, id: u64) -> Result<Kara> {
-    request!(config; GET @ "/get/id/{id}" => Kara)
+pub async fn get_kara_by_kid(config: impl AsRef<ConnectConfig>, kid: KId) -> Result<Kara> {
+    log::info!("try to get kara {kid}");
+    request!(config; GET @ "/get/kid/{}", encode_base64(kid.to_string())? => Kara)
 }
 
 pub async fn toggle_playback_state(config: impl AsRef<ConnectConfig>) -> Result<()> {
@@ -132,33 +145,25 @@ pub async fn remove_level_from_queue(
     config: impl AsRef<ConnectConfig>,
     level: Priority,
 ) -> Result<()> {
-    request!(config; DELETE @ "/queue/{level}")
+    request!(config; DELETE @ "/queue/level/{level}")
 }
 
 pub async fn remove_range_from_queue(
     config: impl AsRef<ConnectConfig>,
     range: impl Into<Range>,
 ) -> Result<()> {
-    let (len, range) = encode_base64(format!("{}", range.into()))?;
-    let range = std::str::from_utf8(&range[..len])?;
-    request!(config; DELETE @ "/queue?range={range}")
+    request!(config; DELETE @ "/queue?range={}", encode_base64(format!("{}", range.into()))?)
 }
 
 pub async fn shuffle_level_queue(config: impl AsRef<ConnectConfig>, prio: Priority) -> Result<()> {
-    request!(config; PUT @ "/queue/{prio}")
-}
-
-pub async fn shuffle_queue(config: impl AsRef<ConnectConfig>) -> Result<()> {
-    shuffle_queue_range(config, ..).await
+    request!(config; PUT @ "/queue/level/{prio}")
 }
 
 pub async fn shuffle_queue_range(
     config: impl AsRef<ConnectConfig>,
     range: impl Into<Range>,
 ) -> Result<()> {
-    let (len, range) = encode_base64(format!("{}", range.into()))?;
-    let range = std::str::from_utf8(&range[..len])?;
-    request!(config; POST(QueueUpdateAction::Shuffle) @ "/queue?range={range}")
+    request!(config; POST(QueueUpdateAction::Shuffle) @ "/queue?range={}", encode_base64(format!("{}", range.into()))?)
 }
 
 // ================================================== //
@@ -169,9 +174,7 @@ pub async fn remove_range_from_history(
     config: impl AsRef<ConnectConfig>,
     range: impl Into<Range>,
 ) -> Result<()> {
-    let (len, range) = encode_base64(format!("{}", range.into()))?;
-    let range = std::str::from_utf8(&range[..len])?;
-    request!(config; DELETE @ "/history?range={range}")
+    request!(config; DELETE @ "/history?range={}", encode_base64(format!("{}", range.into()))?)
 }
 
 // ================================================== //
@@ -183,10 +186,7 @@ pub async fn remove_kid_from_playlist(
     name: Arc<str>,
     id: KId,
 ) -> Result<()> {
-    let action = PlaylistUpdateAction::Remove(KaraFilter::KId(id));
-    let (len, name) = encode_base64(name)?;
-    let name = std::str::from_utf8(&name[..len])?;
-    request!(config; PATCH(action) @ "/playlist/{name}")
+    request!(config; PATCH(PlaylistUpdateAction::Remove(KaraFilter::KId(id))) @ "/playlist/{}", encode_base64(name)?)
 }
 
 pub async fn add_to_playlist(
@@ -194,9 +194,7 @@ pub async fn add_to_playlist(
     name: Arc<str>,
     what: KaraFilter,
 ) -> Result<()> {
-    let (len, name) = encode_base64(name)?;
-    let name = std::str::from_utf8(&name[..len])?;
-    request!(config; PATCH(PlaylistUpdateAction::Add(what)) @ "/playlist/{name}")
+    request!(config; PATCH(PlaylistUpdateAction::Add(what)) @ "/playlist/{}", encode_base64(name)?)
 }
 
 pub async fn remove_from_playlist(
@@ -204,41 +202,34 @@ pub async fn remove_from_playlist(
     name: Arc<str>,
     what: KaraFilter,
 ) -> Result<()> {
-    let (len, name) = encode_base64(name)?;
-    let name = std::str::from_utf8(&name[..len])?;
-    request!(config; PATCH(PlaylistUpdateAction::Remove(what)) @ "/playlist/{name}")
+    request!(config; PATCH(PlaylistUpdateAction::Remove(what)) @ "/playlist/{}", encode_base64(name)?)
 }
 
 pub async fn create_playlist(config: impl AsRef<ConnectConfig>, name: Arc<str>) -> Result<()> {
-    let (len, name) = encode_base64(name)?;
-    let name = std::str::from_utf8(&name[..len])?;
-    request!(config; PUT @ "/playlist/{name}")
+    request!(config; PUT @ "/playlist/{}", encode_base64(name)?)
 }
 
 pub async fn delete_playlist(config: impl AsRef<ConnectConfig>, name: Arc<str>) -> Result<()> {
-    let (len, name) = encode_base64(name)?;
-    let name = std::str::from_utf8(&name[..len])?;
-    request!(config; DELETE @ "/playlist/{name}")
+    request!(config; DELETE @ "/playlist/{}", encode_base64(name)?)
 }
 
 // ================================================== //
 //                  Search functions                  //
 // ================================================== //
 
-pub async fn get_queue(config: impl AsRef<ConnectConfig>) -> Result<Vec<(Priority, KId)>> {
-    get_queue_range(config, ..).await
-}
-
-pub async fn get_history(config: impl AsRef<ConnectConfig>) -> Result<Vec<KId>> {
-    get_history_range(config, ..).await
-}
-
 pub async fn get_queue_count(
     config: impl AsRef<ConnectConfig>,
 ) -> Result<[usize; PRIORITY_LENGTH]> {
     request!(config; GET @ "/queue/count" => [usize; PRIORITY_LENGTH])
 }
 
+pub async fn get_queue_level(
+    config: impl AsRef<ConnectConfig>,
+    prio: Priority,
+) -> Result<Vec<KId>> {
+    request!(config; GET @ "/queue/level/{prio}" => Vec<KId>)
+}
+
 pub async fn get_history_count(config: impl AsRef<ConnectConfig>) -> Result<usize> {
     request!(config; GET @ "/history/count" => usize)
 }
@@ -247,44 +238,41 @@ pub async fn get_queue_range(
     config: impl AsRef<ConnectConfig>,
     range: impl Into<Range>,
 ) -> Result<Vec<(Priority, KId)>> {
-    let (len, range) = encode_base64(format!("{}", range.into()))?;
-    let range = std::str::from_utf8(&range[..len])?;
-    request!(config; GET @ "/queue?range={range}" => Vec<(Priority, KId)>)
+    request!(config; GET @ "/queue?range={}", encode_base64(range.into().to_string())? => Vec<(Priority, KId)>)
 }
 
 pub async fn get_history_range(
     config: impl AsRef<ConnectConfig>,
     range: impl Into<Range>,
 ) -> Result<Vec<KId>> {
-    let (len, range) = encode_base64(format!("{}", range.into()))?;
-    let range = std::str::from_utf8(&range[..len])?;
-    request!(config; GET @ "/history?range={range}" => Vec<KId>)
+    request!(config; GET @ "/history?range={}", encode_base64(range.into().to_string())? => Vec<KId>)
 }
 
-pub async fn get_playlists(
-    config: impl AsRef<ConnectConfig>,
-) -> Result<Vec<(PlaylistName, PlaylistInfo)>> {
-    request!(config; GET @ "/playlist" => Vec<(PlaylistName, PlaylistInfo)>)
+pub async fn get_playlists(config: impl AsRef<ConnectConfig>) -> Result<Vec<(KId, String)>> {
+    request!(config; GET @ "/playlists" => Vec<(KId, String)>)
 }
 
-pub async fn get_playlist_content(
-    config: impl AsRef<ConnectConfig>,
-    name: Arc<str>,
-) -> Result<Vec<KId>> {
-    let (len, name) = encode_base64(name)?;
-    let name = std::str::from_utf8(&name[..len])?;
-    request!(config; GET @ "/playlist/{name}" => Vec<KId>)
+pub async fn get_playlist(config: impl AsRef<ConnectConfig>, id: KId) -> Result<Playlist> {
+    request!(config; GET @ "/playlist/{id}" => Playlist)
+}
+
+pub async fn get_playlist_info(config: impl AsRef<ConnectConfig>, id: KId) -> Result<PlaylistInfo> {
+    request!(config; GET @ "/playlist/{id}/info" => PlaylistInfo)
+}
+
+pub async fn get_playlist_content(config: impl AsRef<ConnectConfig>, id: KId) -> Result<Vec<KId>> {
+    request!(config; GET @ "/playlist/{id}/content" => Vec<KId>)
 }
 
 pub async fn search_karas(
     config: impl AsRef<ConnectConfig>,
     from: SearchFrom,
-    by: KaraBy,
+    by: impl IntoIterator<Item = KaraBy>,
 ) -> Result<Vec<KId>> {
-    let regex = vec![by];
-    let (len, obj) = encode_base64_value(SearchData { from, regex })?;
-    let get = std::str::from_utf8(&obj[..len])?;
-    request!(config; GET @ "/search/{get}" => Vec<KId>)
+    request!(config;
+        GET @ "/search/{}", encode_base64_value(SearchData { from, regex: by.into_iter().collect() })?
+        => Vec<KId>
+    )
 }
 
 pub async fn count_karas(
@@ -292,8 +280,8 @@ pub async fn count_karas(
     from: SearchFrom,
     by: KaraBy,
 ) -> Result<usize> {
-    let regex = vec![by];
-    let (len, obj) = encode_base64_value(SearchData { from, regex })?;
-    let get = std::str::from_utf8(&obj[..len])?;
-    request!(config; GET @ "/count/{get}" => usize)
+    request!(config;
+        GET @ "/count/{}", encode_base64_value(SearchData { from, regex: vec![by] })?
+        => usize
+    )
 }
diff --git a/lektor_nkdb/Cargo.toml b/lektor_nkdb/Cargo.toml
index 4e18c45e4bb79e62eb5f760230db063ead8155d0..e7eaef27edcae9ac72e80b8705ea277a0fdab5b7 100644
--- a/lektor_nkdb/Cargo.toml
+++ b/lektor_nkdb/Cargo.toml
@@ -1,30 +1,28 @@
 [package]
 name = "lektor_nkdb"
+description = "New database implementation for lektord (New Kara DataBase)"
+rust-version.workspace = true
+
 version.workspace = true
 edition.workspace = true
 authors.workspace = true
 license.workspace = true
-rust-version.workspace = true
-description = "New database implementation for lektord (New Kara DataBase)"
 
 [dependencies]
-serde.workspace = true
-serde_json.workspace = true
-
-log.workspace = true
-url.workspace = true
-rand.workspace = true
-regex.workspace = true
-chrono.workspace = true
-sha256.workspace = true
-anyhow.workspace = true
-hashbrown.workspace = true
-
-tokio.workspace = true
-futures.workspace = true
-async-trait.workspace = true
+serde.workspace        = true
+serde_json.workspace   = true
+log.workspace          = true
+url.workspace          = true
+rand.workspace         = true
+regex.workspace        = true
+chrono.workspace       = true
+sha256.workspace       = true
+anyhow.workspace       = true
+hashbrown.workspace    = true
+derive_more.workspace  = true
+tokio.workspace        = true
+futures.workspace      = true
 tokio-stream.workspace = true
-
-kurisu_api = { path = "../kurisu_api" }
-lektor_utils = { path = "../lektor_utils" }
-lektor_procmacros = { path = "../lektor_procmacros" }
+kurisu_api             = { path = "../kurisu_api" }
+lektor_utils           = { path = "../lektor_utils" }
+lektor_procmacros      = { path = "../lektor_procmacros" }
diff --git a/lektor_nkdb/src/database/epoch.rs b/lektor_nkdb/src/database/epoch.rs
index 36c63d2b3f4e7216ca02da0509d0cd341e0b9237..059a569ef38daf2097e89c4133c6a97eeffea665 100644
--- a/lektor_nkdb/src/database/epoch.rs
+++ b/lektor_nkdb/src/database/epoch.rs
@@ -6,7 +6,7 @@ use crate::*;
 /// The epoch contains all available karas at a certain point in time. It can be submitted and
 /// available to all readers of the database or unsubmited and only one writter can edit it.
 #[derive(Debug, Default)]
-pub(crate) struct Epoch(EpochData, u64);
+pub struct Epoch(EpochData, u64);
 
 /// Represent the data contained in an epoch.
 pub type EpochData = HashMap<KId, Kara>;
@@ -19,15 +19,8 @@ impl From<(EpochData, u64)> for Epoch {
 
 impl Epoch {
     /// Get all the tuples (KId, RemoteKId) for the kara present in the epoch.
-    pub fn ids(&self) -> impl Iterator<Item = (&KId, &RemoteKId)> + '_ {
-        self.0.values().map(|kara| (&kara.id, &kara.remote))
-    }
-
-    /// Get a [Kara] by its local id [u64]. The [KId] is the unique id [u64] for this epoch plus
-    /// the path from the data folder. A single [u64] can have two [KId] representations if a kara
-    /// was changed in an epoch and that epoch is loaded in memory.
-    pub fn get_kara_by_u64(&self, id: u64) -> Option<&Kara> {
-        self.0.values().find(|kara| id.eq(&kara.id.local_id()))
+    pub fn ids(&self) -> impl Iterator<Item = (KId, RemoteKId)> + '_ {
+        self.0.values().map(|kara| (kara.id, kara.remote.clone()))
     }
 
     /// Get the list of [Kara] corresponding to the [KId]. If we failed to find one kara we log the
@@ -45,11 +38,6 @@ impl Epoch {
         self.0.get(&id)
     }
 
-    /// Returns whever the [Epoch] contains the [KId] or not.
-    pub fn contains(&self, id: &KId) -> bool {
-        self.0.contains_key(id)
-    }
-
     /// Get access to the karas in the epoch.
     pub fn data(&self) -> &EpochData {
         &self.0
@@ -61,7 +49,7 @@ impl Epoch {
     }
 
     /// Get the number of the epoch
-    pub fn epoch_num(&self) -> u64 {
+    pub fn num(&self) -> u64 {
         self.1
     }
 }
diff --git a/lektor_nkdb/src/database/mod.rs b/lektor_nkdb/src/database/mod.rs
index aa7598d855980c465d9bd1fda50e4e2474b17c88..357442d3173d9733a06698169984b7d68e3dcc7d 100644
--- a/lektor_nkdb/src/database/mod.rs
+++ b/lektor_nkdb/src/database/mod.rs
@@ -1,15 +1,7 @@
 //! Base definition of how we store kara in different epochs. Even if it's called "database", it's
 //! only a small, but fundational, part of the database system.
 
-mod epoch;
-mod kara;
-mod pool;
-mod update;
-
-pub use kara::*;
-pub use kurisu_api::v2::{SongOrigin, SongType};
-pub use pool::{KId, RemoteKId};
-pub use update::UpdateHandler;
-
-pub(crate) use epoch::*;
-pub(crate) use pool::Pool;
+pub(crate) mod epoch;
+pub(crate) mod kara;
+pub(crate) mod pool;
+pub(crate) mod update;
diff --git a/lektor_nkdb/src/database/pool.rs b/lektor_nkdb/src/database/pool.rs
index 332db3f225b1fd8b93af22befa9ffc1d1fe0e81a..5af0585da9191f85acbbd22541b52a4ac8add26d 100644
--- a/lektor_nkdb/src/database/pool.rs
+++ b/lektor_nkdb/src/database/pool.rs
@@ -4,109 +4,65 @@
 //! representation for searching plus some mapping. A pool is common for all the epochs. For epoch
 //! specific things like the [u64] to [Kid] / [Kara] mappings, do that in the epoch struct.
 
-use crate::{EpochData, Playlist};
-use hashbrown::HashMap;
-use serde::{Deserialize, Serialize};
-use std::{collections::hash_map::DefaultHasher, hash::Hasher, sync::Arc};
+use crate::{EpochData, KId, RemoteKId};
+use derive_more::Into;
+use hashbrown::{HashMap, HashSet};
+use std::sync::{
+    atomic::{AtomicU64, Ordering},
+    Arc,
+};
 use tokio::sync::RwLock;
 
-/// A pool of all the available kara for every epochs
+/// A pool of all the available kara for every epochs. Contains the mapping for remote id to the
+/// local id representations.
 #[derive(Debug, Default)]
 pub(crate) struct Pool {
-    /// Cache for strings, to reduce memory load.
-    string_cache: RwLock<HashMap<u64, Arc<str>>>,
+    /// ID mapping from local to remote. Note that there is a one-to-one relation between
+    /// [RemoteKId] and [KId], even with metadata updates or file update.
+    id_mapping: RwLock<HashMap<RemoteKId, KId>>,
 
-    /// The mapping for remote id <-> local id
-    id_mapping: RwLock<Vec<(KId, RemoteKId)>>,
+    /// The next local ID.
+    next_id: AtomicU64,
+
+    /// Cache strings for authors and languages, and tags.
+    strings: RwLock<HashSet<Arc<str>>>,
 }
 
 impl Pool {
     /// Create the pool storage from an iterator of id/remote_id.
     pub(crate) async fn from_iter<T: IntoIterator<Item = (KId, RemoteKId)>>(iter: T) -> Self {
-        // Compress things if needed!
-        let this = Self::default();
-        let vec = futures::future::join_all(iter.into_iter().map(|(kid, rkid)| async {
-            let kid = this.get_str::<KId>(kid.0).await;
-            let rkid = this.get_str::<RemoteKId>(rkid.0).await;
-            (kid, rkid)
-        }))
-        .await;
-        let _ = std::mem::replace(&mut (*this.id_mapping.write().await), vec);
-        this
+        let id_mapping = iter
+            .into_iter()
+            .map(|(kid, rkid)| (rkid, kid))
+            .collect::<HashMap<_, _>>();
+        Self {
+            next_id: AtomicU64::new(
+                (id_mapping.values())
+                    .map(|KId(id)| *id + 1)
+                    .max()
+                    .unwrap_or(1),
+            ),
+            id_mapping: RwLock::new(id_mapping),
+            ..Default::default()
+        }
     }
 
-    /// Get other possible ids from an id, we may want to do that to get all the local ids for a
-    /// remote id, to get a more up-to-date version of a kara.
-    pub(crate) async fn get_other_locals(&self, id: KId) -> Vec<KId> {
-        let mapping = self.id_mapping.read().await;
-
-        let find_remote = |(kid, rkid): &_| id.eq(kid).then_some(rkid).cloned();
-        let remote = mapping.iter().find_map(find_remote).expect("no remote id");
-
-        let find_locals = |(kid, rkid): &(KId, _)| remote.eq(rkid).then_some(kid.clone());
-        mapping.iter().filter_map(find_locals).collect()
+    /// Get a new and unique [KId]
+    pub(crate) fn next_kid(&self) -> KId {
+        KId(self.next_id.fetch_add(1, Ordering::AcqRel))
     }
 
     /// Get the tuple (local, remote) from the string representation of the remote id. Id the local
     /// id is not present, returns none, else some(local_id).
     pub(crate) async fn get_from_remote(&self, rkid: impl AsRef<str>) -> (Option<KId>, RemoteKId) {
-        let rkid = self.get_str::<RemoteKId>(rkid).await;
-        let id = (self.id_mapping.read().await.iter())
-            .find_map(|(id, remote)| remote.eq(&rkid).then_some(id))
-            .cloned();
-        (id, rkid)
-    }
-
-    /// Get a pointer to the string by its value. Used to reduce the amount of memory allocated for
-    /// string representation. The heuristic is that we can share the song's origins and the tag
-    /// keys and values. This is the sync version where we don't need async because we have a mut
-    /// reference to the pool (thus exclusive).
-    fn get_str_sync<I: sealed::Id>(&mut self, id: Arc<str>) -> I {
-        let mut hash = DefaultHasher::new();
-        hash.write(id.as_ref().as_bytes());
-        (self.string_cache.get_mut())
-            .entry(hash.finish())
-            .or_insert(id)
-            .clone()
-            .into()
-    }
-
-    /// Get a pointer to the string by its value. Used to reduce the amount of memory allocated for
-    /// string representation. The heuristic is that we can share the song's origins and the tag
-    /// keys and values.
-    pub(crate) async fn get_str<I: sealed::Id>(&self, id: impl AsRef<str>) -> I {
-        let mut hash = DefaultHasher::new();
-        hash.write(id.as_ref().as_bytes());
-        let mut this = self.string_cache.write().await;
-        this.entry(hash.finish())
-            .or_insert_with(|| Arc::<str>::from(id.as_ref()))
-            .clone()
-            .into()
-    }
-
-    /// Same as [Pool::get_str], but don't create the id if not present to avoid being DOSed...
-    pub(crate) async fn try_get_str<I: sealed::Id>(&self, id: impl AsRef<str>) -> Option<I> {
-        let mut hash = DefaultHasher::new();
-        hash.write(id.as_ref().as_bytes());
-        let this = self.string_cache.write().await;
-        this.get(&hash.finish()).cloned().map(Into::into)
-    }
-
-    /// Get the maximal id present in the pool.
-    pub(crate) async fn maximal_id(&self) -> u64 {
-        let list = self.id_mapping.read().await;
-        list.iter()
-            .map(|(id, _)| id.local_id())
-            .max()
-            .unwrap_or_default()
+        let id = self.id_mapping.read().await.get(rkid.as_ref()).copied();
+        (id, RemoteKId(rkid.as_ref().into()))
     }
 
     /// Fatcorize the [Arc<str>] for an [EpochData] to have pointer equality. We do that at the
     /// loading time, thus we can have a mut referenceto the pool and skip some locks.
     pub(crate) fn factorize_epoch_data(&mut self, data: &mut EpochData) {
         let content = std::mem::take(data).into_iter().map(|(kid, mut kara)| {
-            kara.id = self.get_str_sync(kara.id.0);
-            kara.remote = self.get_str_sync(kara.remote.0);
             kara.language = kara
                 .language
                 .into_iter()
@@ -122,188 +78,20 @@ impl Pool {
                 let values = values.into_iter().map(|v| self.get_str_sync(v));
                 (key, values.collect())
             }));
-            (self.get_str_sync(kid.0), kara)
+            (kid, kara)
         });
         let _ = std::mem::replace(data, EpochData::from_iter(content));
     }
 
-    /// Fatcorize the [Arc<str>] for a [Playlist] to have pointer equality. We do that at the
-    /// loading time, thus we can have a mut referenceto the pool and skip some locks.
-    pub(crate) fn factorize_playlist(&mut self, plt: &mut Playlist) {
-        let content = std::mem::take(&mut plt.content)
-            .into_iter()
-            .map(|kid| self.get_str_sync(kid.0));
-        let _ = std::mem::replace(&mut plt.content, content.collect());
-    }
-}
-
-/// The string must respect the following format, you can assert this:
-///     "local_id:path"
-/// Note that the path can't contains slashes!
-#[derive(Serialize, Deserialize, Clone)]
-pub struct KId(Arc<str>);
-
-impl KId {
-    /// Create a raw string representation of the [KId], unsafe operation.
-    ///
-    /// ### Safety
-    /// Don't use this function appart from reusing it in the pool to get the pointer from the
-    /// string cache.
-    pub(super) unsafe fn new_raw(id: u64, path: impl std::fmt::Display) -> String {
-        format!("{id}:{path}")
-    }
-
-    /// Split the id in its different parts
-    fn split(&self) -> (&str, &str) {
-        self.0.split_once(':').expect("invalid kid found")
-    }
-
-    /// Get the local path of a kara. The path is relative to the $prefix from the database
-    /// structure.
-    pub fn path(&self) -> &str {
-        self.split().1
-    }
-
-    /// Get the numeric local id of a kara.
-    pub fn local_id(&self) -> u64 {
-        self.split()
-            .0
-            .parse::<u64>()
-            .expect("invalid id part in kid found")
-    }
-
-    /// Get the string representaiton of the [KId].
-    pub fn as_str(&self) -> &str {
-        self.0.as_ref()
-    }
-}
-
-impl From<Arc<str>> for KId {
-    fn from(value: Arc<str>) -> Self {
-        Self(value)
-    }
-}
-
-/// The string must respect the following format, you can assert this:
-///     "remote_id@remote_name"
-/// Note that the path can't contains slashes!
-#[derive(Serialize, Deserialize, Clone)]
-pub struct RemoteKId(Arc<str>);
-
-impl RemoteKId {
-    /// Create a raw string representation of the [RemoteKId], unsafe operation.
-    ///
-    /// ### Safety
-    /// Don't use this function appart from reusing it in the pool to get the pointer from the
-    /// string cache.
-    pub(super) unsafe fn new_raw(remote_id: u64, remote_name: impl std::fmt::Display) -> String {
-        format!("{remote_id}@{remote_name}")
-    }
-
-    /// Split the id in its different parts
-    fn split(&self) -> (&str, &str) {
-        self.0.split_once('@').expect("invalid kid found")
-    }
-
-    /// Get the name of the remote server
-    pub fn remote_name(&self) -> &str {
-        self.split().1
-    }
-
-    /// Get the id of the kara in the remote server
-    pub fn remote_id(&self) -> u64 {
-        self.split()
-            .0
-            .parse::<u64>()
-            .expect("invalid id part in kid found")
-    }
-
-    /// Get the string representaiton of the [RemoteKId].
-    pub fn as_str(&self) -> &str {
-        self.0.as_ref()
-    }
-}
-
-impl From<Arc<str>> for RemoteKId {
-    fn from(value: Arc<str>) -> Self {
-        Self(value)
-    }
-}
-
-/// Protect the types that we cache to do pointer comparaison instead of string comparaisons.
-mod sealed {
-    /// We can create the said id ([super::KId] or [super::RemoteKId] only) from the pointer to the
-    /// character slice.
-    pub trait Id: From<std::sync::Arc<str>> {}
-    impl Id for std::sync::Arc<str> {}
-    impl Id for super::KId {}
-    impl Id for super::RemoteKId {}
-}
-
-// We implement equality and partial equality with pointer equals, we take into account that the
-// strings where created correctly. With this trick we can implement hash just by hashing the
-// address of the pointer, not the underlying string.
-
-impl Eq for KId {}
-impl PartialEq for KId {
-    fn eq(&self, other: &Self) -> bool {
-        Arc::ptr_eq(&self.0, &other.0)
-    }
-}
-
-impl Eq for RemoteKId {}
-impl PartialEq for RemoteKId {
-    fn eq(&self, other: &Self) -> bool {
-        Arc::ptr_eq(&self.0, &other.0)
-    }
-}
-
-impl std::hash::Hash for KId {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        std::ptr::hash(Arc::as_ptr(&self.0), state)
-    }
-}
-
-impl std::hash::Hash for RemoteKId {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        std::ptr::hash(Arc::as_ptr(&self.0), state)
-    }
-}
-
-// We need to display the ids for urls...
-
-impl std::fmt::Display for KId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.0.as_ref())
-    }
-}
-
-impl std::fmt::Display for RemoteKId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.0.as_ref())
-    }
-}
-
-impl std::fmt::Debug for KId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(&self.0)
-    }
-}
-
-impl std::fmt::Debug for RemoteKId {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(&self.0)
-    }
-}
-
-impl AsRef<str> for KId {
-    fn as_ref(&self) -> &str {
-        self.as_str()
+    pub(crate) async fn get_str(&self, str: impl AsRef<str>) -> Arc<str> {
+        (self.strings.write().await)
+            .get_or_insert_with(str.as_ref(), |str| Arc::from(str))
+            .clone()
     }
-}
 
-impl AsRef<str> for RemoteKId {
-    fn as_ref(&self) -> &str {
-        self.as_str()
+    fn get_str_sync(&mut self, str: impl AsRef<str>) -> Arc<str> {
+        (self.strings.get_mut())
+            .get_or_insert_with(str.as_ref(), |str| Arc::from(str))
+            .clone()
     }
 }
diff --git a/lektor_nkdb/src/database/update.rs b/lektor_nkdb/src/database/update.rs
index d5bdcb642669d5bcdbab005f9ad053bdc2f46203..80eafe081529c31583f77d8e83cdc3434e5f105c 100644
--- a/lektor_nkdb/src/database/update.rs
+++ b/lektor_nkdb/src/database/update.rs
@@ -1,16 +1,11 @@
 //! The update handle is the only way to write into the database at runtime.
 
-use crate::{database::*, storage::*};
+use crate::*;
 use anyhow::Result;
 use futures::future::join_all;
 use hashbrown::HashMap;
 use kurisu_api::SHA256;
-use lektor_utils::pushvec::*;
-use std::cell::RefCell;
-use std::sync::{
-    atomic::{AtomicU64, Ordering},
-    Arc,
-};
+use std::{cell::RefCell, sync::Arc};
 
 /// A pool handle. Used to add new karas to the pool. The update logic follows the following:
 /// - if a kara was not present, we add it and we download the file.
@@ -34,7 +29,6 @@ pub struct UpdateHandler<'a, Storage: DatabaseStorage> {
     storage: &'a Storage,
     new_epoch: RefCell<PushVecMutNode<Epoch>>,
     last_epoch: Option<&'a Epoch>,
-    last_kid: AtomicU64,
 }
 
 impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> {
@@ -50,7 +44,6 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> {
             storage,
             new_epoch: RefCell::new(node),
             last_epoch: last,
-            last_kid: AtomicU64::new(pool.maximal_id().await + 1),
         }
     }
 
@@ -78,12 +71,9 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> {
         self.storage.submit_kara(file, id, hash).await
     }
 
-    /// Add a kara. Can error. On success, if the kara needs to be downloaded returns the couple of
-    /// IDs with the hash of the file.
-    ///
-    /// NOTE: if the file changed, then it's hash will be different, then its local [KId] will also
-    ///       will be different, thus we will download automatically and not re-add the old thing.
-    async fn add_kara<'b>(&'b self, kara: Kara) -> Result<Option<(KId, RemoteKId, SHA256)>>
+    /// Add a kara. On success, if the kara needs to be downloaded returns the couple of IDs with
+    /// the hash of the file.
+    async fn add_kara<'b>(&'b self, kara: Kara) -> Option<(KId, RemoteKId, SHA256)>
     where
         'b: 'a,
     {
@@ -94,93 +84,93 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> {
                 .borrow_mut()
                 .content()
                 .data_mut()
-                .insert(kara.id.clone(), kara.clone());
-            Ok(None)
+                .insert(kara.id, kara.clone());
+            None
         };
         let doit = |kara: Kara, new_epoch: &'a RefCell<PushVecMutNode<Epoch>>| async {
             let KaraStatus::Physical { hash, .. } = kara.kara_status else {
                 log::warn!("tried to download a virtual kara: {kara}");
                 return reuse(kara, new_epoch);
             };
-            let (kid, rkid) = (kara.id.clone(), kara.remote.clone());
+            let (kid, rkid) = (kara.id, kara.remote.clone());
             log::debug!("need to download kid {kid} / remote_kid {rkid}");
             new_epoch
                 .borrow_mut()
                 .content()
                 .data_mut()
-                .insert(kara.id.clone(), kara);
-            Ok(Some((kid, rkid, hash)))
+                .insert(kara.id, kara);
+            Some((kid, rkid, hash))
         };
 
         match self.last_epoch {
             None => doit(kara, &self.new_epoch).await, // No way the kara was here
             Some(last_epoch) => match last_epoch.data().get(&kara.id) {
-                Some(old_kara) if !kara.same_file_as(old_kara) => {
-                    doit(kara, &self.new_epoch).await // The file has changed
-                }
-
                 // Not present last time
                 None => doit(kara, &self.new_epoch).await,
 
+                // Present last time, but the file has changed. The KId is reused.
+                Some(old_kara) if !kara.same_file_as(old_kara) => doit(kara, &self.new_epoch).await,
+
                 // Present last time, but we use the new built kara because some informations might
-                // have been updated even if the epoch was not incremented.
+                // have been updated even if the epoch was not incremented. Note that the KId is
+                // reused here.
                 Some(_) => reuse(kara, &self.new_epoch),
             },
         }
     }
 
-    /// Add a kara from the V2 API. Can error. On success, if the kara needs to be downloaded
-    /// returns the couple of IDs with the sha256 hash of the file to download.
+    /// Add a kara from the V2 API. On success, if the kara needs to be downloaded returns the
+    /// couple of IDs with the sha256 hash of the file to download.
     pub async fn add_kara_v2<'b>(
         &'b self,
         repo: &str,
         kara: kurisu_api::v2::Kara,
-    ) -> Result<Option<(KId, RemoteKId, SHA256)>>
+    ) -> Option<(KId, RemoteKId, SHA256)>
     where
         'b: 'a,
     {
         // Convert data from V2 API. Create a local ID if needed.
-        let (id, rkid) = (self.pool)
-            .get_from_remote(unsafe { RemoteKId::new_raw(kara.id, repo) })
+        let (id, remote) = (self.pool)
+            .get_from_remote(RemoteKId::new(kara.id, repo))
             .await;
-        let id = match id {
-            Some(id) => id,
-            None => unsafe {
-                self.pool
-                    .get_str::<KId>(KId::new_raw(
-                        self.last_kid.fetch_add(1, Ordering::SeqCst),
-                        kara.file_hash,
-                    ))
-                    .await
-            },
-        };
 
-        let get_str = |str| self.pool.get_str::<Arc<str>>(str);
         let (language, kara_makers) = tokio::join!(
-            join_all(kara.language.into_iter().map(get_str)),
-            join_all(kara.kara_makers.into_iter().map(get_str))
+            join_all((kara.language.into_iter()).map(|str| self.pool.get_str(str))),
+            join_all((kara.kara_makers.into_iter()).map(|str| self.pool.get_str(str)))
         );
 
         let mut tags = HashMap::<Arc<str>, Vec<Arc<str>>>::default();
         for [key, value] in kara.tags {
             if value.is_empty() {
-                tags.entry(get_str(key).await).or_insert_with(Vec::new);
+                tags.entry(self.pool.get_str(key).await)
+                    .or_insert_with(Vec::new);
             } else {
-                let (key, value) = tokio::join!(get_str(key), get_str(value));
+                let (key, value) = tokio::join!(self.pool.get_str(key), self.pool.get_str(value));
                 tags.entry(key).or_insert_with(Vec::new).push(value);
             }
         }
 
-        let kurisu_api::v2::Kara {
-            file_hash: hash,
-            filesize,
-            ..
-        } = kara;
+        let kara_status = match (kara.is_virtual, kara.file_hash) {
+            (true, None) => KaraStatus::Virtual,
+            (true, Some(hash)) => {
+                log::warn!("kara {remote} has virtual flag but a file hash: {hash}");
+                KaraStatus::Virtual
+            }
+            (false, None) => {
+                log::error!("the kara {remote} is not virtual but don't have a file hash");
+                KaraStatus::Virtual
+            }
+            (false, Some(hash)) => KaraStatus::Physical {
+                filesize: kara.filesize,
+                hash,
+            },
+        };
 
         self.add_kara(Kara {
-            id,
+            id: id.unwrap_or_else(|| self.pool.next_kid()),
             tags,
-            remote: rkid,
+            remote,
+            kara_status,
             song_type: kara.song_type,
             song_title: kara.song_title,
             song_source: kara.song_source,
@@ -192,10 +182,6 @@ impl<'a, Storage: DatabaseStorage> UpdateHandler<'a, Storage> {
                 updated_at: kara.updated_at,
                 epoch: kara.epoch,
             },
-            kara_status: match kara.is_virtual {
-                true => KaraStatus::Virtual,
-                false => KaraStatus::Physical { filesize, hash },
-            },
         })
         .await
     }
diff --git a/lektor_nkdb/src/id.rs b/lektor_nkdb/src/id.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0dea9e690ffd916fa0ca0d437dbb6b8af4171c37
--- /dev/null
+++ b/lektor_nkdb/src/id.rs
@@ -0,0 +1,97 @@
+//! Contains ID definitions used for kara and playlists.
+
+use derive_more::{Display, Into};
+use serde::{Deserialize, Serialize};
+use std::{borrow, num, str::FromStr, sync::Arc};
+
+/// The string must respect the following format, you can assert this:
+///     "local_id:path"
+/// Note that the path can't contains slashes!
+#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, Hash, Debug, Into)]
+#[repr(transparent)]
+#[serde(transparent)]
+#[display("{_0}")]
+pub struct KId(pub(crate) u64);
+
+impl PartialEq<KId> for u64 {
+    fn eq(&self, other: &KId) -> bool {
+        other.0 == *self
+    }
+}
+
+impl PartialEq<u64> for KId {
+    fn eq(&self, other: &u64) -> bool {
+        self.0 == *other
+    }
+}
+
+impl FromStr for KId {
+    type Err = num::ParseIntError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        s.parse().map(Self)
+    }
+}
+
+impl From<u64> for KId {
+    fn from(value: u64) -> Self {
+        Self(value)
+    }
+}
+
+/// The string must respect the following format, you can assert this:
+///     "remote_id@remote_name"
+/// Note that the path can't contains slashes!
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Display, Debug)]
+#[repr(transparent)]
+#[serde(transparent)]
+#[display("{_0}")]
+pub struct RemoteKId(pub(crate) Arc<str>);
+
+impl RemoteKId {
+    /// Create a raw string representation of the [RemoteKId], unsafe operation.
+    pub(super) fn new(remote_id: u64, remote_name: impl std::fmt::Display) -> Self {
+        Self(format!("{remote_id}@{remote_name}").into())
+    }
+
+    /// Split the id in its different parts
+    fn split(&self) -> (&str, &str) {
+        self.0.split_once('@').expect("invalid kid found")
+    }
+
+    /// Get the name of the remote server
+    pub fn remote_name(&self) -> &str {
+        self.split().1
+    }
+
+    /// Get the id of the kara in the remote server
+    pub fn remote_id(&self) -> u64 {
+        self.split()
+            .0
+            .parse::<u64>()
+            .expect("invalid id part in kid found")
+    }
+
+    /// Get the string representaiton of the [RemoteKId].
+    pub fn as_str(&self) -> &str {
+        self.0.as_ref()
+    }
+}
+
+impl borrow::Borrow<str> for RemoteKId {
+    fn borrow(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl From<Arc<str>> for RemoteKId {
+    fn from(value: Arc<str>) -> Self {
+        Self(value)
+    }
+}
+
+impl AsRef<str> for RemoteKId {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
diff --git a/lektor_nkdb/src/lib.rs b/lektor_nkdb/src/lib.rs
index e0d164a2d1058e3df7c84d76d11a9a335d2f6515..f9e9434155e5d178271d5902fadf59033f6e7c99 100644
--- a/lektor_nkdb/src/lib.rs
+++ b/lektor_nkdb/src/lib.rs
@@ -1,65 +1,43 @@
-//! A database to store informations about kara, by conserving old versions.
+//! A new implementation of a database to store informations about karas, playlists and such.
 
 pub use crate::{
     database::{
-        KId, Kara, KaraStatus, KaraTimeStamps, RemoteKId, SongOrigin, SongType, UpdateHandler,
+        epoch::Epoch,
+        kara::{Kara, KaraStatus, KaraTimeStamps},
+        update::UpdateHandler,
     },
-    playlist::{Playlist, PlaylistInfo, PlaylistName},
-    queue::{Priority, PRIORITY_LENGTH, PRIORITY_VALUES},
+    id::{KId, RemoteKId},
+    playlists::playlist::{Playlist, PlaylistInfo},
     search::{KaraBy, SearchFrom},
-    storage::*,
+    storage::{DatabaseDiskStorage, DatabaseStorage},
 };
+pub use kurisu_api::v2::{SongOrigin, SongType};
 
-use crate::{database::*, queue::*, search::*};
-use anyhow::{anyhow, bail, Context, Result};
-use futures::{
-    stream::{self, FuturesUnordered},
-    StreamExt,
+use crate::{
+    database::{epoch::EpochData, pool::Pool},
+    search::*,
 };
+use anyhow::{anyhow, Context as _, Result};
 use hashbrown::HashMap;
 use lektor_utils::pushvec::*;
-use playlist::Playlists;
-use serde::{Deserialize, Serialize};
-use std::ops::{RangeBounds, RangeFull};
-use tokio::sync::RwLock;
+use playlists::{Playlists, PlaylistsHandle};
 
 mod database;
-mod playlist;
-mod queue;
+mod id;
+mod playlists;
 mod search;
 mod storage;
+mod strings;
 
 /// The database type, we use interior mutability to handle the MT things.
 #[derive(Debug)]
 pub struct Database<Storage: DatabaseStorage = DatabaseDiskStorage> {
     pool: Pool,
-    queue: Queue,
     playlists: Playlists,
-    playstate: RwLock<PlayState>,
     epochs: PushVec<Epoch>,
     storage: Storage,
 }
 
-/// Play state, stored in the database.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Default)]
-#[serde(rename_all = "lowercase")]
-pub enum PlayState {
-    #[default]
-    Stop,
-    Play,
-    Pause,
-}
-
-impl AsRef<str> for PlayState {
-    fn as_ref(&self) -> &str {
-        match self {
-            PlayState::Stop => "stopped",
-            PlayState::Play => "play",
-            PlayState::Pause => "paused",
-        }
-    }
-}
-
 impl<Storage: DatabaseStorage> Database<Storage> {
     /// Create a new database with the correspondig prefix.
     pub async fn new(prefix: impl Into<Storage::Prefix>) -> Result<Self> {
@@ -72,37 +50,34 @@ impl<Storage: DatabaseStorage> Database<Storage> {
         let mut last_epoch = epochs
             .push(storage.read_last_epoch().await?.unwrap_or_default().into())
             .await;
-        let mut pool = Pool::from_iter(
-            last_epoch
-                .content()
-                .ids()
-                .map(|(a, b)| (a.clone(), b.clone())),
-        )
-        .await;
+        let mut pool = Pool::from_iter(last_epoch.content().ids()).await;
 
         log::info!("fatcorize content in the last_epoch and in the playlists");
-        let mut playlists = storage
-            .read_playlists()
-            .await
-            .with_context(|| "failed to read playlists")?;
         pool.factorize_epoch_data(last_epoch.content().data_mut());
-        playlists
-            .iter_mut()
-            .for_each(|(_, plt)| pool.factorize_playlist(plt));
 
         log::info!("database loaded from disk");
         last_epoch.finished();
+
         Ok(Self {
+            playlists: Playlists::new(
+                storage
+                    .read_playlists()
+                    .await
+                    .with_context(|| "failed to read playlists")?,
+            ),
             pool,
-            queue: Default::default(),
-            playstate: Default::default(),
-            playlists: FromIterator::from_iter(playlists),
             epochs,
             storage,
         })
     }
 
+    /// Get the playlist handle of the database.
+    pub fn playlists(&self) -> PlaylistsHandle<Storage> {
+        PlaylistsHandle::new(&self.playlists, &self.storage)
+    }
+
     /// Get the update handler.
+    #[must_use]
     pub async fn update(&self) -> UpdateHandler<Storage> {
         UpdateHandler::new(
             &self.pool,
@@ -113,429 +88,25 @@ impl<Storage: DatabaseStorage> Database<Storage> {
         .await
     }
 
-    /// Refresh the [KId]s of the playlists to use a most up-to-date version of a kara if
-    /// available. Should be called after a successfull update process. In case of failure we log
-    /// the error and do nothing else...
-    pub async fn refresh_playlist_contents(&self) {
-        let Some(epoch) = self.last_epoch().await else {
-            log::error!("no epoch in database, can't refresh playlists");
-            return;
-        };
-        for name in self.playlists.list_names().await {
-            log::info!("refresh ids for playlist {name}");
-            let ids = stream::iter(self.playlists.get_content(&name).await.unwrap_or_default())
-                .then(|id| async {
-                    let locals = self.pool.get_other_locals(id).await;
-                    locals.into_iter().filter(|id| epoch.contains(id))
-                })
-                .collect::<FuturesUnordered<_>>();
-            self.playlists
-                .refresh(&name, ids.await.into_iter().flatten().collect::<Vec<KId>>())
-                .await
-                .map_err(|err| log::error!("failed to refresh ids of playlist {name}: {err}"))
-                .unwrap_or_default();
-        }
-    }
-
-    /// Get the [KId] out of its string representation. We query the pool to guaranty the pointer
-    /// unicity for faster checks.
-    pub async fn get_kid_from_str(&self, kid: impl AsRef<str>) -> Option<KId> {
-        self.pool.try_get_str(kid).await
-    }
-
-    /// Get a [Kara] by its [u64] representation in the last epoch.
-    pub async fn get_kara_by_id(&self, id: u64) -> Result<&Kara> {
-        self.last_epoch()
-            .await
-            .ok_or(anyhow!("empty epoch"))?
-            .get_kara_by_u64(id)
-            .ok_or(anyhow!("no kara {id}"))
-    }
-
     /// Get a [Kara] by its [KId] representation in the last epoch. Should be more efficient than
     /// the [Self::get_kara_by_id] because we dirrectly use the hash thing and we don't iterate
     /// over all the items in the [HashMap].
     pub async fn get_kara_by_kid(&self, id: KId) -> Result<&Kara> {
-        let local_id = id.local_id();
         self.last_epoch()
             .await
-            .ok_or(anyhow!("empty epoch"))?
+            .context("empty epoch")?
             .get_kara_by_kid(id)
-            .ok_or(anyhow!("no kara {local_id}"))
-    }
-
-    /// Search the database with a specific regex.
-    pub async fn search(&self, from: SearchFrom, regex: Vec<KaraBy>) -> Result<Vec<KId>> {
-        let regex = SearchBy::from_iter(
-            regex
-                .into_iter()
-                .map(SearchBy::new)
-                .collect::<Result<Vec<_>, _>>()?,
-        );
-        let kids = match from {
-            SearchFrom::Playlist(plt) => self.playlists.get_content(plt).await.unwrap_or_default(),
-            SearchFrom::History => self.history(RangeFull).await,
-            SearchFrom::Queue => {
-                let queue = self.queue(RangeFull).await;
-                queue.into_iter().map(|(_, kid)| kid).collect()
-            }
-            SearchFrom::Database => match self.last_epoch().await {
-                Some(epoch) => epoch.ids().map(|(kid, _)| kid.clone()).collect(),
-                None => vec![],
-            },
-        };
-        let mut karas: Vec<KId> = match self.last_epoch().await {
-            Some(epoch) => epoch
-                .get_karas_by_kid(kids)
-                .into_iter()
-                .filter_map(|kara| regex.matches(kara).then_some(kara.id.clone()))
-                .collect(),
-            None => bail!("no epoch to search kara from"),
-        };
-        let plts = regex.into_needed_playlists();
-        if !plts.is_empty() {
-            let filter: Vec<_> = stream::iter(plts.into_iter())
-                .then(|name| self.playlists.get_content(name))
-                .collect::<FuturesUnordered<_>>()
-                .await
-                .into_iter()
-                .flatten()
-                .flatten()
-                .collect();
-            karas.retain(|kara_id| filter.contains(kara_id));
-        }
-        Ok(karas)
-    }
-
-    /// Returns the kara count from the search set.
-    pub async fn count(&self, from: SearchFrom, regex: Vec<KaraBy>) -> usize {
-        log::error!("find a way to not allocate the search result buffer...");
-        self.search(from, regex)
-            .await
-            .map(|vec| vec.len())
-            .unwrap_or_default()
+            .with_context(|| format!("no kara {id}"))
     }
 
     /// Get the last epoch from the database.
-    pub(crate) async fn last_epoch(&self) -> Option<&Epoch> {
+    pub async fn last_epoch(&self) -> Option<&Epoch> {
         self.epochs.last().await
     }
 
-    /// Get the last epoch from the database.
-    pub async fn last_epoch_num(&self) -> Option<u64> {
-        self.epochs.last().await.map(|epoch| epoch.epoch_num())
-    }
-
-    /// Get the current kara with the play state.
-    ///
-    /// We have to do shenanigans. Because between karas we have an idle peridod and the playback
-    /// is stopped, we can't clear the current kara on each idle period because the next one could
-    /// have already be set in the current member.
-    pub async fn current(&self) -> (PlayState, Option<KId>) {
-        use PlayState::*;
-        let (current, playstate) = tokio::join!(self.queue.current(), self.playstate.read());
-        match *playstate {
-            Stop => (Stop, None),
-            sta @ Play | sta @ Pause => (sta, current),
-        }
-    }
-
-    /// Get the number of karas in each level of the queue.
-    pub async fn queue_count(&self) -> [usize; PRIORITY_LENGTH] {
-        self.queue.queue_count_per_level().await
-    }
-
-    /// Get the number of karas in the history.
-    pub async fn history_count(&self) -> usize {
-        self.queue.history_count().await
-    }
-
-    /// Get the play history from the most recent one to the most old one.
-    pub async fn history(&self, range: impl RangeBounds<usize> + Copy) -> Vec<KId> {
-        self.queue.history(range).await
-    }
-
-    /// Remove elements from the history, from the most recent ones to the most old ones.
-    pub async fn history_delete(&self, range: impl RangeBounds<usize> + Copy) -> Vec<KId> {
-        self.queue.history_delete(range).await
-    }
-
-    /// Get the play queue in the correct order.
-    pub async fn queue(&self, range: impl RangeBounds<usize> + Copy) -> Vec<(Priority, KId)> {
-        self.queue.queue(range).await
-    }
-
-    /// Get the play queue in the correct order, only the specified priority level.
-    pub async fn queue_get_level(&self, prio: Priority) -> Vec<KId> {
-        self.queue.queue_get_level(prio).await
-    }
-
-    /// Remove elements from the queue.
-    pub async fn queue_delete(&self, range: impl RangeBounds<usize> + Copy) {
-        self.queue.queue_delete(range).await;
-    }
-
-    /// Remove an entire level from the queue.
-    pub async fn queue_delete_level(&self, prio: Priority) {
-        self.queue.queue_delete_level(prio).await;
-    }
-
-    /// Shuffle the queue.
-    pub async fn queue_shuffle(&self, range: impl RangeBounds<usize> + Copy) {
-        self.queue.queue_shuffle(range).await
-    }
-
-    /// Shuffle the queue.
-    pub async fn queue_shuffle_level(&self, level: Priority) {
-        self.queue.queue_shuffle_level(level).await
-    }
-
-    /// Move a part of the queue after a kara.
-    pub async fn queue_move_after(&self, range: impl RangeBounds<usize> + Copy, after: usize) {
-        self.queue.queue_move_after(range, after).await
-    }
-
-    /// Get the next kara to play. Update the queue and history. It is up to the player to play the
-    /// next file.
-    #[must_use]
-    pub async fn next(&self) -> Option<KId> {
-        self.queue.next().await
-    }
-
-    /// Get the previous kara to play. Update the queue and history. It is up to the player to play
-    /// the previous file.
-    #[must_use]
-    pub async fn previous(&self) -> Option<KId> {
-        self.queue.previous().await
-    }
-
-    /// Play from a position in the queue, remove all previous karas. If the position doesn't
-    /// exists then we returns [None].
-    #[must_use]
-    pub async fn play_from_position(&self, position: usize) -> Option<KId> {
-        self.queue.play_from_position(position).await
-    }
-
-    /// Set the play state.
-    pub async fn set_playstate(&self, new_state: PlayState) {
-        let mut state = self.playstate.write().await;
-        let _ = std::mem::replace(&mut *state, new_state);
-    }
-
-    /// Toggle the play state.
-    pub async fn toggle_playstate(&self) {
-        let mut state = self.playstate.write().await;
-        let new_state = match *state {
-            PlayState::Stop | PlayState::Pause => PlayState::Play,
-            PlayState::Play => PlayState::Pause,
-        };
-        let _ = std::mem::replace(&mut *state, new_state);
-    }
-
     /// Get the path to a kara, try to get the absolute path, so here we append the relative path
     /// to the prefix of the database if possible.
     pub fn get_kara_uri(&self, id: KId) -> Result<url::Url> {
         self.storage.get_kara_uri(id)
     }
-
-    /// Insert a kara in the queue.
-    pub async fn queue_insert(&self, prio: Priority, id: KId) {
-        self.queue.insert(prio, id).await
-    }
-
-    /// Insert all karas in the queue in this order.
-    pub async fn queue_insert_all(&self, prio: Priority, ids: Vec<KId>) {
-        self.queue.insert_all(prio, ids).await
-    }
-
-    /// Insert all karas in the queue in this order.
-    pub async fn queue_insert_slice(&self, prio: Priority, ids: &[KId]) {
-        self.queue.insert_slice(prio, ids).await
-    }
-
-    /// Delete all karas from the queue with this id
-    pub async fn queue_delete_all(&self, id: KId) {
-        self.queue.queue_delete_all(id).await
-    }
-
-    /// Delete all karas from the queue with this id
-    pub async fn queue_delete_all_from_level(&self, prio: Priority, id: KId) {
-        self.queue.queue_delete_all_from_level(prio, id).await
-    }
-
-    /// Delete all karas from the queue with this id. The id was not already parsed, we try to
-    /// parse it here.
-    pub async fn queue_delete_all_maybe_id(&self, id: String) {
-        self.queue
-            .queue_delete_all(self.pool.get_str::<KId>(id).await)
-            .await
-    }
-
-    /// Delete all karas from the history with this id
-    pub async fn history_delete_all(&self, id: KId) {
-        self.queue.history_delete_all(id).await
-    }
-
-    /// Get a playlist by its name.
-    pub async fn playlist_get_content(&self, name: impl AsRef<str>) -> Option<Vec<KId>> {
-        self.playlists.get_content(name.as_ref()).await
-    }
-
-    /// Get a list of all the playlists
-    pub async fn playlists(&self) -> Vec<(PlaylistName, PlaylistInfo)> {
-        self.playlists.list().await
-    }
-
-    /// Rename a playlist.
-    pub async fn playlist_rename(
-        &self,
-        name: PlaylistName,
-        new_name: PlaylistName,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .rename(name.clone(), new_name.clone(), user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(new_name, &self.storage)
-            .await?;
-        self.storage.delete_playlist(name).await?;
-        Ok(())
-    }
-
-    /// Give a playlist to another user.
-    pub async fn playlist_give_to(
-        &self,
-        name: PlaylistName,
-        new_user: impl ToString,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .give_to(name.clone(), new_user.to_string(), user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(name, &self.storage)
-            .await
-    }
-
-    /// Create a new playlist. Returns whever the creation operation was successfull or not.
-    pub async fn playlist_new(
-        &self,
-        name: impl Into<PlaylistName>,
-        plt: impl Into<PlaylistInfo>,
-    ) -> Result<()> {
-        self.playlists.create(name, plt, &self.storage).await
-    }
-
-    /// Delete a playlist. Returns whever the operation was successfull or not.
-    pub async fn playlist_delete(
-        &self,
-        name: impl Into<PlaylistName>,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        let name = name.into();
-        self.storage.delete_playlist(name.as_ref()).await?;
-        self.playlists.delete(name, user, admin).await?;
-        Ok(())
-    }
-
-    /// Delete a kara from a playlist.
-    pub async fn playlist_delete_id(
-        &self,
-        name: impl AsRef<str>,
-        kid: KId,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .remove_id(name.as_ref(), kid, user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(name, &self.storage)
-            .await
-    }
-
-    /// Delete a multiple karas from a playlist.
-    pub async fn playlist_delete_ids(
-        &self,
-        name: impl AsRef<str>,
-        kids: Vec<KId>,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .remove_ids(name.as_ref(), &kids, user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(name, &self.storage)
-            .await
-    }
-
-    /// Add a kara to a playlist.
-    pub async fn playlist_add_id(
-        &self,
-        name: impl AsRef<str>,
-        kid: KId,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .add_id(name.as_ref(), kid, user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(name, &self.storage)
-            .await
-    }
-
-    /// Add multiple karas to a playlist.
-    pub async fn playlist_add_ids(
-        &self,
-        name: impl AsRef<str>,
-        kids: Vec<KId>,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .add_ids(name.as_ref(), &kids, user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(name, &self.storage)
-            .await
-    }
-
-    /// Add karas from a playlist to another playlist.
-    pub async fn playlist_add_playlist(
-        &self,
-        name: impl AsRef<str>,
-        from: impl AsRef<str>,
-        rand: bool,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .add_plt(name.as_ref(), from.as_ref(), rand, user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(name, &self.storage)
-            .await
-    }
-
-    /// Remove karas from a playlist from another playlist.
-    pub async fn playlist_delete_playlist(
-        &self,
-        name: impl AsRef<str>,
-        from: impl AsRef<str>,
-        user: impl AsRef<str>,
-        admin: bool,
-    ) -> Result<()> {
-        self.playlists
-            .remove_plt(name.as_ref(), from.as_ref(), user, admin)
-            .await?;
-        self.playlists
-            .write_playlist_with(name, &self.storage)
-            .await
-    }
 }
diff --git a/lektor_nkdb/src/playlist/infos.rs b/lektor_nkdb/src/playlist/infos.rs
deleted file mode 100644
index e08b0fc731dcef76f871e956bcf9b6d692a8573a..0000000000000000000000000000000000000000
--- a/lektor_nkdb/src/playlist/infos.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-/// Informations about a playlist.
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct PlaylistInfo {
-    /// The name of the user who created the playlist. If the user who created the playlist wasn't
-    /// identified, then it's an anonymous playlist.
-    pub user: Option<String>,
-
-    /// Creation time of the playlist.
-    pub created_at: i64,
-
-    /// Last update time of the playlist.
-    pub updated_at: i64,
-}
-
-impl PlaylistInfo {
-    pub fn new() -> Self {
-        Self {
-            user: None,
-            created_at: chrono::Utc::now().timestamp(),
-            updated_at: chrono::Utc::now().timestamp(),
-        }
-    }
-
-    pub fn user(self, user: impl ToString) -> Self {
-        Self {
-            user: Some(user.to_string()),
-            ..self
-        }
-    }
-
-    pub fn created_at(self, created_at: i64) -> Self {
-        Self { created_at, ..self }
-    }
-
-    pub fn updated_at(self, updated_at: i64) -> Self {
-        Self { updated_at, ..self }
-    }
-
-    /// Should a user be authorized to modify a playlist?
-    pub fn authorize_write(&self, name: impl AsRef<str>, admin: bool) -> bool {
-        let name = name.as_ref();
-        if admin {
-            log::warn!("bypass author on playlist request by admin {name}");
-        }
-        let ret = admin
-            || (self.user.as_ref())
-                .map(|user| user.eq(name))
-                .unwrap_or_default();
-        if ret {
-            log::info!("authorize {name} to edit playlist");
-        } else {
-            log::info!("authorizen't {name} to edit playlist");
-        }
-        ret
-    }
-}
-
-impl Default for PlaylistInfo {
-    fn default() -> Self {
-        Self::new()
-    }
-}
diff --git a/lektor_nkdb/src/playlist/mod.rs b/lektor_nkdb/src/playlist/mod.rs
deleted file mode 100644
index 1c8cd458ced67a3fafb87cb2abcfb38a1728df9b..0000000000000000000000000000000000000000
--- a/lektor_nkdb/src/playlist/mod.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-//! How to store playlists. Because playlists and epochs don't have the same lifespan we must do
-//! some shenanigans.
-
-mod infos;
-mod name;
-mod register;
-mod values;
-
-pub use self::{infos::*, name::*, values::*};
-pub(crate) use register::*;
diff --git a/lektor_nkdb/src/playlist/name.rs b/lektor_nkdb/src/playlist/name.rs
deleted file mode 100644
index 640379927c26e555dcf1db2018246d36a1d57cff..0000000000000000000000000000000000000000
--- a/lektor_nkdb/src/playlist/name.rs
+++ /dev/null
@@ -1,140 +0,0 @@
-//! We have some restrictions on playlists' name to have a usable thing...
-
-use anyhow::bail;
-use serde::{Deserialize, Serialize};
-use std::{borrow::Borrow, str::FromStr, sync::Arc};
-
-/// A playlist name, we excract it from the path.
-///
-/// ### Safety
-/// We check before builder the playlist name that the [`u8`] is a valide UTF-8 string.
-#[derive(Clone, PartialEq, Eq, Hash)]
-pub struct PlaylistName(Arc<str>);
-
-impl FromStr for PlaylistName {
-    type Err = anyhow::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        if s.is_empty() || s.len() > Self::MAX_LEN {
-            bail!("invalid playlist name lengh of {}", s.len())
-        } else if s.chars().filter(|c| !c.is_ascii_alphanumeric()).count() > 0 {
-            bail!("invalid playlist name: not ascii alphanumeric")
-        } else {
-            Ok(Self(s.into()))
-        }
-    }
-}
-
-impl PlaylistName {
-    /// The maximal length of the playlist's name.
-    pub const MAX_LEN: usize = 128;
-
-    pub fn as_str(&self) -> &str {
-        self.0.as_ref()
-    }
-}
-
-impl AsRef<str> for PlaylistName {
-    fn as_ref(&self) -> &str {
-        self.as_str()
-    }
-}
-
-impl std::fmt::Display for PlaylistName {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.as_ref())
-    }
-}
-
-impl std::fmt::Debug for PlaylistName {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let name = self.as_str();
-        f.debug_tuple("PlaylistName").field(&name).finish()
-    }
-}
-
-impl Borrow<str> for PlaylistName {
-    fn borrow(&self) -> &str {
-        self.as_ref()
-    }
-}
-
-impl Serialize for PlaylistName {
-    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
-        serializer.serialize_str(self.as_ref())
-    }
-}
-
-impl<'de> Deserialize<'de> for PlaylistName {
-    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
-        use serde::de::Visitor;
-        struct PltNameVisitor;
-        impl<'de> Visitor<'de> for PltNameVisitor {
-            type Value = PlaylistName;
-
-            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
-                formatter.write_str("string slice")
-            }
-
-            fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
-                PlaylistName::from_str(&v).map_err(serde::de::Error::custom)
-            }
-
-            fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
-                PlaylistName::from_str(v).map_err(serde::de::Error::custom)
-            }
-
-            fn visit_borrowed_str<E: serde::de::Error>(
-                self,
-                v: &'de str,
-            ) -> Result<Self::Value, E> {
-                PlaylistName::from_str(v).map_err(serde::de::Error::custom)
-            }
-        }
-
-        deserializer.deserialize_any(PltNameVisitor)
-    }
-}
-
-#[test]
-fn create_playlist_name() {
-    use lektor_utils::assert_ok;
-    assert_eq!(
-        assert_ok!(PlaylistName::from_str("foo42bar69")).as_ref(),
-        "foo42bar69"
-    );
-}
-
-#[test]
-fn invalid_playlist_name() {
-    use lektor_utils::assert_err;
-    assert_err!(PlaylistName::from_str(""));
-    assert_err!(PlaylistName::from_str("Zażółć gęślą jaźń"));
-    assert_err!(PlaylistName::from_str("@"));
-    assert_err!(PlaylistName::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
-}
-
-#[test]
-fn playlist_name_eq() {
-    use lektor_utils::assert_ok;
-    use std::{
-        hash::{DefaultHasher, Hash, Hasher},
-        ptr,
-    };
-    let a = assert_ok!(PlaylistName::from_str("abc"));
-    let b = assert_ok!(PlaylistName::from_str("abc"));
-    assert!(!ptr::addr_eq(a.0.as_ptr(), b.0.as_ptr()));
-    assert_eq!(a, b);
-    assert_eq!(
-        {
-            let mut s = DefaultHasher::new();
-            a.hash(&mut s);
-            s.finish()
-        },
-        {
-            let mut s = DefaultHasher::new();
-            b.hash(&mut s);
-            s.finish()
-        },
-    );
-}
diff --git a/lektor_nkdb/src/playlist/register.rs b/lektor_nkdb/src/playlist/register.rs
deleted file mode 100644
index 85664188391c33cd4acff05339c8844e94aa7dec..0000000000000000000000000000000000000000
--- a/lektor_nkdb/src/playlist/register.rs
+++ /dev/null
@@ -1,292 +0,0 @@
-//! A list of playlists. It's different from an epoch because inside an epoch the playlists can
-//! change.
-
-use crate::{DatabaseStorage, KId, Playlist, PlaylistInfo, PlaylistName};
-use anyhow::{anyhow, bail, Result};
-use hashbrown::{hash_map::OccupiedEntry, HashMap};
-use rand::{seq::SliceRandom, thread_rng};
-use std::ops::Deref;
-use tokio::sync::RwLock;
-
-/// This type is just a wrapper around the [PlaylistsContent] with a [RwLock]. For function
-/// documentation see [PlaylistsContent].
-#[derive(Debug)]
-pub(crate) struct Playlists(RwLock<PlaylistsContent>);
-
-/// All the data from the playlists.
-#[derive(Debug)]
-struct PlaylistsContent(HashMap<PlaylistName, Playlist>);
-
-impl Playlists {
-    /// Write the playlist to a storage.
-    pub async fn write_playlist_with(
-        &self,
-        name: impl AsRef<str>,
-        storage: &impl DatabaseStorage,
-    ) -> Result<()> {
-        match self.0.read().await.0.get(name.as_ref()) {
-            Some(plt) => storage.write_playlist(name.as_ref(), plt).await?,
-            None => bail!("failed to get playlist {}", name.as_ref()),
-        }
-        Ok(())
-    }
-
-    /// Add a new playlist if it didn't already exists. Returns whever the creation operation was
-    /// successfull or not.
-    pub async fn create(
-        &self,
-        name: impl Into<PlaylistName>,
-        plt: impl Into<PlaylistInfo>,
-        storage: &impl DatabaseStorage,
-    ) -> Result<()> {
-        let name = name.into();
-        let mut this = self.0.write().await;
-        if !this.0.contains_key(&name) {
-            let plt = Into::<PlaylistInfo>::into(plt).into();
-            storage.write_playlist(&name, &plt).await?;
-            log::info!("create playlist {name}: {plt:#?}");
-            this.0.insert(name, plt);
-            Ok(())
-        } else {
-            bail!("can't create playlist {name} as it already exits")
-        }
-    }
-}
-
-impl FromIterator<(PlaylistName, Playlist)> for PlaylistsContent {
-    fn from_iter<T: IntoIterator<Item = (PlaylistName, Playlist)>>(iter: T) -> Self {
-        Self(HashMap::from_iter(iter))
-    }
-}
-
-impl FromIterator<(PlaylistName, Playlist)> for Playlists {
-    fn from_iter<T: IntoIterator<Item = (PlaylistName, Playlist)>>(iter: T) -> Self {
-        Self(RwLock::new(FromIterator::from_iter(iter)))
-    }
-}
-
-impl PlaylistsContent {
-    /// Get the playlist in write access if allowed.
-    fn write_plt_if_authorizes<T>(
-        &mut self,
-        name: impl AsRef<str>,
-        user: impl AsRef<str>,
-        admin: bool,
-        cb: impl FnOnce(&mut Playlist) -> Result<T>,
-    ) -> Result<T> {
-        let (name, user) = (name.as_ref(), user.as_ref());
-        let Some(playlist) = self.0.get_mut(name) else {
-            bail!("playlist {name} asked by user {user} doesn't exist")
-        };
-        if playlist.authorize_write(user, admin) {
-            cb(playlist)
-        } else {
-            bail!("user {user} is not authorized to write playlist {name}")
-        }
-    }
-
-    /// Get the playlist in write access if allowed. Here we get the whole entry, not just the
-    /// value out of the hashmap.
-    fn write_plt_entry_if_authorizes<T>(
-        &mut self,
-        name: impl Into<PlaylistName>,
-        user: impl AsRef<str>,
-        admin: bool,
-        cb: impl FnOnce(OccupiedEntry<'_, PlaylistName, Playlist>) -> Result<T>,
-    ) -> Result<T> {
-        use hashbrown::hash_map::Entry;
-        let user = user.as_ref();
-        match self.0.entry(name.into()) {
-            Entry::Occupied(entry) if entry.get().authorize_write(user, admin) => cb(entry),
-            Entry::Occupied(entry) => {
-                bail!(
-                    "user {user} is not authorized to write playlist {}",
-                    entry.key()
-                )
-            }
-            Entry::Vacant(entry) => {
-                bail!("failed to get playlist {}", entry.key())
-            }
-        }
-    }
-}
-
-macro_rules! impl_playlists {
-    ($(
-        $(#[$doc:meta])*
-        $operation: ident fn $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)? $body: block
-    )+) => {
-        impl Playlists {
-            $(impl_playlists! {
-                $(#[$doc])*
-                bridge: $operation $name (&$this $(, $arg: $ty)*) $(-> $ret)?
-            })+
-        }
-
-        impl PlaylistsContent {
-            $(impl_playlists! {
-                $(#[$doc])*
-                original: $operation $name (&$this $(, $arg: $ty)*) $(-> $ret)? { $body }
-            })+
-        }
-    };
-
-    (
-        $(#[$doc:meta])*
-        bridge: $operation: ident $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)?
-    ) => {
-        #[allow(dead_code)]
-        $(#[$doc])*
-        pub async fn $name(&$this $(,$arg: $ty)*) $(-> $ret)? {
-            $this.0.$operation().await.$name($($arg),*)
-        }
-    };
-
-    (
-        $(#[$doc:meta])*
-        original: read $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)? { $body: block }
-    ) => {
-        $(#[$doc])*
-        pub fn $name(&$this $(,$arg: $ty)*) $(-> $ret)? { $body }
-    };
-
-    (
-        $(#[$doc:meta])*
-        original: write $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)? { $body: block }
-    ) => {
-        $(#[$doc])*
-        pub fn $name(&mut $this $(,$arg: $ty)*) $(-> $ret)? { $body }
-    };
-}
-
-impl_playlists! {
-    /// Get the total count of playlists
-    read fn count(&self) -> usize {
-        self.0.len()
-    }
-
-    /// Get the list of all the playlists.
-    read fn list(&self) -> Vec<(PlaylistName, PlaylistInfo)> {
-        self.0.iter().map(|(name, plt)| (name.clone(), plt.deref().clone())).collect()
-    }
-
-    /// Get the list of all the playlists.
-    read fn list_names(&self) -> Vec<PlaylistName> {
-        self.0.iter().map(|(name, _)| name.clone()).collect()
-    }
-
-    /// Refresh some ids of a playlist with the new ones. Here we skip the user check thing as this
-    /// function should not be called by a user but as part of the update process.
-    write fn refresh(&self, name: impl AsRef<str>, ids: Vec<KId>) -> Result<()> {
-        let (mut ids, name) = (ids, name.as_ref());
-        let plt = self.0.get_mut(name).ok_or_else(|| anyhow!("no playlist {name} to refresh"))?;
-        ids.retain(|id| !plt.content.contains(id));
-        if ids.is_empty() {
-            log::warn!("no ids to refresh the playlist {name} with...");
-        } else {
-            plt.content.append(&mut ids);
-        }
-        Ok(())
-    }
-
-    /// Rename a playlist.
-    write fn rename(&self, name: PlaylistName, new_name: PlaylistName, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        if self.0.contains_key(&new_name) {
-            bail!("can't rename {name} to {new_name} because the playlist already exists")
-        }
-        let entry = match self.0.entry(name) {
-            hashbrown::hash_map::Entry::Occupied(entry) if entry.get().authorize_write(user.as_ref(), admin) => entry.remove(),
-            hashbrown::hash_map::Entry::Occupied(entry) => bail!("user {} not authorized to write {}", user.as_ref(), entry.key().as_str()),
-            hashbrown::hash_map::Entry::Vacant(entry)   => bail!("failed to get playlist {}", entry.into_key())
-        };
-        match self.0.insert(new_name, entry) {
-            Some(_) => bail!("failed to rename the playlist"),
-            None => Ok(()),
-        }
-    }
-
-    /// Give a playlist to another user.
-    write fn give_to(&self, name: PlaylistName, new_user: impl ToString, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        self.write_plt_entry_if_authorizes(name, user, admin, |mut entry| {
-            entry.get_mut().user = Some(new_user.to_string());
-            Ok(())
-        })
-    }
-
-    /// Deletes a playlist, returns whever the operation was successfull or not.
-    write fn delete(&self, name: PlaylistName, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        self.write_plt_entry_if_authorizes(name, user, admin, |entry| {
-            log::info!("remove playlist {}", entry.key().as_str());
-            entry.remove();
-            Ok(())
-        })
-    }
-
-    /// Add a kara to a playlist. Returns true if it was successfull, false otherwise.
-    write fn add(&self, name: impl AsRef<str>, id: KId, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        self.write_plt_if_authorizes(name, user, admin, |playlist| {
-            playlist.content.push(id);
-            Ok(())
-        })
-    }
-
-    /// Remove a kara from a playlist. Returns true if it was successfull, false otherwise.
-    write fn remove_id(&self, name: impl AsRef<str>, id: KId, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        self.write_plt_if_authorizes(name, user, admin, |playlist| {
-            playlist.content.retain(|kid| id.ne(kid));
-            Ok(())
-        })
-    }
-
-    /// Remove karas from a playlist. Returns true if it was successfull, false otherwise.
-    write fn remove_ids(&self, name: impl AsRef<str>, ids: &[KId], user: impl AsRef<str>, admin: bool) -> Result<()> {
-        self.write_plt_if_authorizes(name, user, admin, |playlist| {
-            playlist.content.retain(|kid| !ids.contains(kid));
-            Ok(())
-        })
-    }
-
-    /// Remove a kara from a playlist. Returns true if it was successfull, false otherwise.
-    write fn add_id(&self, name: impl AsRef<str>, id: KId, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        self.write_plt_if_authorizes(name, user, admin, |playlist| {
-            playlist.extend(Some(id));
-            Ok(())
-        })
-    }
-
-    /// Remove karas from a playlist. Returns true if it was successfull, false otherwise.
-    write fn add_ids(&self, name: impl AsRef<str>, ids: &[KId], user: impl AsRef<str>, admin: bool) -> Result<()> {
-        self.write_plt_if_authorizes(name, user, admin, |playlist| {
-            playlist.extend(ids.iter().cloned());
-            Ok(())
-        })
-    }
-
-    /// Add the content of a playlist into another one.
-    write fn add_plt(&self, name: impl AsRef<str>, from: impl AsRef<str>, rand: bool, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        let Some(mut ids) = self.get_content(&from) else { bail!("playlist {} doesn't exists", from.as_ref()) };
-        if rand { ids[..].shuffle(&mut thread_rng()) }
-        self.add_ids(name, &ids, user, admin)
-    }
-
-    /// Remove from a playlist the content of another one.
-    write fn remove_plt(&self, name: impl AsRef<str>, from: impl AsRef<str>, user: impl AsRef<str>, admin: bool) -> Result<()> {
-        let Some(ids) = self.get_content(&from) else { bail!("playlist {} doesn't exists", from.as_ref()) };
-        self.remove_ids(name, &ids, user, admin)
-    }
-
-    /// Get the content of a playlist, we need to clone because of the lock...
-    read fn get_content(&self, name: impl AsRef<str>) -> Option<Vec<KId>> {
-        let res = self.0.get(name.as_ref()).map(|plt| plt.content().to_vec());
-        if res.is_none() { log::error!("playlist {} doesn't exist", name.as_ref()); }
-        res
-    }
-
-    /// Count the number of elements in the playlist. If the playlist didn't exist returns [None],
-    /// otherwise returns [Some] of the count.
-    read fn count_playlist(&self, name: impl AsRef<str>) -> Option<usize> {
-        let res = self.0.get(name.as_ref()).map(|plt| plt.content().len());
-        if res.is_none() { log::error!("playlist {} doesn't exist", name.as_ref()); }
-        res
-    }
-}
diff --git a/lektor_nkdb/src/playlist/values.rs b/lektor_nkdb/src/playlist/values.rs
deleted file mode 100644
index 67611570f61e366e0a3937b607fc44708ddd1f11..0000000000000000000000000000000000000000
--- a/lektor_nkdb/src/playlist/values.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-//! A single playlist.
-
-use crate::{KId, PlaylistInfo};
-use serde::{Deserialize, Serialize};
-
-/// The playlist, with the informations and its content.
-#[derive(Debug, Serialize, Deserialize, Clone, Default)]
-pub struct Playlist {
-    /// Metadata about the playlist.
-    pub(crate) infos: PlaylistInfo,
-
-    /// The content of a playlist. Here the [KId] are about all the epochs. When loading a new
-    /// epoch, if a new [KId] replaces an old one, then we also add it to the playlist. At loading
-    /// time we filter the [KId]s.
-    pub(crate) content: Vec<KId>,
-}
-
-impl From<PlaylistInfo> for Playlist {
-    fn from(infos: PlaylistInfo) -> Self {
-        Self {
-            infos,
-            ..Default::default()
-        }
-    }
-}
-
-impl std::ops::Deref for Playlist {
-    type Target = PlaylistInfo;
-    fn deref(&self) -> &Self::Target {
-        &self.infos
-    }
-}
-
-impl std::ops::DerefMut for Playlist {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.infos
-    }
-}
-
-impl Playlist {
-    pub fn content(&self) -> &[KId] {
-        &self.content
-    }
-}
-
-impl Extend<KId> for Playlist {
-    fn extend<T: IntoIterator<Item = KId>>(&mut self, iter: T) {
-        for kid in iter.into_iter() {
-            if !self.content.contains(&kid) {
-                self.content.push(kid);
-            }
-        }
-    }
-}
diff --git a/lektor_nkdb/src/playlists/mod.rs b/lektor_nkdb/src/playlists/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..290c47d1bca7025daf0ad1e19d3c0c1ae37b45ba
--- /dev/null
+++ b/lektor_nkdb/src/playlists/mod.rs
@@ -0,0 +1,112 @@
+//! Contains implementation about a collection of playlists.
+
+pub(crate) mod playlist;
+
+use crate::{playlists::playlist::Playlist, DatabaseStorage, KId};
+use anyhow::{bail, Context as _, Result};
+use hashbrown::{hash_map, HashMap};
+use std::sync::atomic::{AtomicU64, Ordering};
+use tokio::sync::RwLock;
+
+/// This type is just a wrapper around the [PlaylistsContent] with a [RwLock]. For function
+/// documentation see [PlaylistsContent].
+#[derive(Debug)]
+pub(crate) struct Playlists {
+    content: RwLock<HashMap<KId, Playlist>>,
+    epoch: AtomicU64,
+}
+
+pub struct PlaylistsHandle<'a, Storage: DatabaseStorage + Sized> {
+    playlists: &'a Playlists,
+    storage: &'a Storage,
+}
+
+impl<'a, Storage: DatabaseStorage + Sized> PlaylistsHandle<'a, Storage> {
+    pub(crate) fn new(playlists: &'a Playlists, storage: &'a Storage) -> Self {
+        Self { playlists, storage }
+    }
+
+    pub async fn epoch(&self) -> u64 {
+        self.playlists.epoch.load(Ordering::Acquire)
+    }
+
+    pub async fn is_empty(&self) -> bool {
+        self.playlists.content.read().await.is_empty()
+    }
+
+    pub async fn len(&self) -> usize {
+        self.playlists.content.read().await.len()
+    }
+
+    pub async fn read<T>(&self, plt: KId, cb: impl FnOnce(&Playlist) -> T) -> Result<T> {
+        Ok(cb((self.playlists.content.read().await)
+            .get(&plt)
+            .context("playlist not found")?))
+    }
+
+    pub async fn list(&self) -> Vec<(KId, String)> {
+        (self.playlists.content.read().await.iter())
+            .map(|(&id, playlist)| (id, playlist.name().to_string()))
+            .collect()
+    }
+
+    pub async fn write<T>(
+        &self,
+        plt: KId,
+        user: impl AsRef<str>,
+        admin: bool,
+        cb: impl FnOnce(&mut Playlist) -> T,
+    ) -> Result<T> {
+        let mut this = self.playlists.content.write().await;
+        let plt = this
+            .get_mut(&plt)
+            .context("playlist not found")?
+            .authorize_writes(user, admin)
+            .context("user not allowed to modify playlist")?;
+        let res = cb(plt);
+        self.storage.write_playlist(plt).await?;
+        self.playlists.epoch.fetch_add(1, Ordering::AcqRel);
+        Ok(res)
+    }
+
+    /// Add a new playlist if it didn't already exists. Returns whever the creation operation was
+    /// successfull or not.
+    pub async fn create(
+        &self,
+        name: impl ToString,
+        settings: impl FnOnce(Playlist) -> Playlist,
+    ) -> Result<()> {
+        let mut this = self.playlists.content.write().await;
+        let next_id = KId(this.keys().map(|KId(id)| id + 1).max().unwrap_or(1));
+        let plt = settings(Playlist::new(next_id, name)).updated_now();
+        self.playlists.epoch.fetch_add(1, Ordering::AcqRel);
+        self.storage.write_playlist(&plt).await?;
+        this.insert(next_id, plt);
+        Ok(())
+    }
+
+    pub async fn delete(&self, plt: KId, user: impl AsRef<str>, admin: bool) -> Result<()> {
+        match self.playlists.content.write().await.entry(plt) {
+            hash_map::Entry::Vacant(_) => bail!("playlist not found"),
+            hash_map::Entry::Occupied(mut entry) => {
+                (entry.get_mut())
+                    .authorize_writes(user, admin)
+                    .context("user not allowed to modify the playlist")?;
+                entry.remove();
+                if let Err(err) = self.storage.delete_playlist(plt).await {
+                    log::error!("failed to delete playlist '{plt}': {err}")
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+impl Playlists {
+    pub(crate) fn new(iter: impl IntoIterator<Item = Playlist>) -> Self {
+        Self {
+            content: RwLock::new(iter.into_iter().map(|plt| (plt.local_id(), plt)).collect()),
+            epoch: AtomicU64::new(0),
+        }
+    }
+}
diff --git a/lektor_nkdb/src/playlists/playlist.rs b/lektor_nkdb/src/playlists/playlist.rs
new file mode 100644
index 0000000000000000000000000000000000000000..784377506583fcf4f5e677a54435d89d15dc24e0
--- /dev/null
+++ b/lektor_nkdb/src/playlists/playlist.rs
@@ -0,0 +1,221 @@
+//! Contains implementation about a single playlist.
+
+use crate::{strings, KId, RemoteKId};
+use hashbrown::HashSet;
+use rand::seq::SliceRandom as _;
+use serde::{Deserialize, Serialize};
+use std::sync::Arc;
+
+/// Informations about a playlist.
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct Playlist {
+    /// The local ID.
+    local_id: KId,
+
+    /// The remote ID. Is [None] if the playlist was not imported.
+    remote_id: Option<RemoteKId>,
+
+    /// The name of the owners of the playlist. If the user who created the playlist wasn't
+    /// identified, then it's an anonymous playlist. The users can't be empty strings.
+    owners: HashSet<Arc<str>>,
+
+    /// The name of the playlist. Note that this is not a primary key, multiple playlists can have
+    /// the same name... Must be only in ascii characters.
+    name: String,
+
+    /// The description of the playlist. Must be only in ascii characters. If it is [Some], then
+    /// the [String] is not empty.
+    description: Option<String>,
+
+    /// Creation time of the playlist. This is a local time, Kurisu doesn't have this notion.
+    created_at: i64,
+
+    /// Last update time of the playlist. This is a local time, Kurisu doesn't have this notion.
+    updated_at: i64,
+
+    /// The content of the playlist.
+    content: Vec<KId>,
+}
+
+// Builder-lite interface
+impl Playlist {
+    pub(crate) fn new(id: KId, name: impl ToString) -> Self {
+        Self {
+            name: name.to_string(),
+            local_id: id,
+            remote_id: None,
+            description: None,
+            owners: Default::default(),
+            content: Default::default(),
+
+            // Be mindfull of the order…
+            created_at: chrono::Utc::now().timestamp(),
+            updated_at: chrono::Utc::now().timestamp(),
+        }
+    }
+
+    pub fn with_owner_sync(mut self, user: impl AsRef<str>) -> Self {
+        self.owners.insert(strings::CACHE.get_sync(user.as_ref()));
+        self.updated_now()
+    }
+
+    pub async fn with_owner(mut self, user: impl AsRef<str>) -> Self {
+        self.owners.insert(strings::CACHE.get(user.as_ref()).await);
+        self.updated_now()
+    }
+
+    pub fn with_description(mut self, desc: impl ToString) -> Self {
+        let description = desc.to_string();
+        self.description = (!description.is_empty()).then_some(description);
+        self.updated_now()
+    }
+
+    pub(super) fn updated_now(mut self) -> Self {
+        self.updated_at = chrono::Utc::now().timestamp();
+        self
+    }
+}
+
+// Setters
+impl Playlist {
+    pub fn set_name(&mut self, name: impl ToString) -> &mut Self {
+        self.name = name.to_string();
+        self
+    }
+
+    pub fn add_owner(&mut self, name: impl AsRef<str>) -> &mut Self {
+        self.owners.insert(Arc::from(name.as_ref()));
+        self
+    }
+
+    pub fn remove_owner(&mut self, name: impl AsRef<str>) -> &mut Self {
+        self.owners.remove(name.as_ref());
+        self
+    }
+
+    pub(crate) fn authorize_writes(
+        &mut self,
+        user: impl AsRef<str>,
+        admin: bool,
+    ) -> Option<&mut Self> {
+        (admin || self.owners.contains(user.as_ref())).then_some(self)
+    }
+
+    pub fn push(&mut self, id: KId) {
+        self.content.push(id)
+    }
+
+    pub fn remove(&mut self, id: KId) {
+        self.retain(|other| *other != id)
+    }
+
+    pub fn retain(&mut self, cb: impl FnMut(&KId) -> bool) {
+        self.content.retain(cb)
+    }
+
+    pub fn append(&mut self, ids: &mut Vec<KId>) {
+        self.content.append(ids)
+    }
+
+    pub fn shuffle(&mut self) {
+        self.content.shuffle(&mut rand::thread_rng())
+    }
+}
+
+// Getters
+impl Playlist {
+    pub fn local_id(&self) -> KId {
+        self.local_id
+    }
+
+    pub fn contains(&self, id: KId) -> bool {
+        self.content.contains(&id)
+    }
+
+    pub fn remote_id(&self) -> Option<RemoteKId> {
+        self.remote_id.clone()
+    }
+
+    pub fn owners(&self) -> impl Iterator<Item = &str> {
+        self.owners.iter().map(Arc::as_ref)
+    }
+
+    pub fn name(&self) -> &str {
+        self.name.as_ref()
+    }
+
+    pub fn description(&self) -> Option<&str> {
+        self.description.as_deref()
+    }
+
+    pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
+        chrono::DateTime::from_timestamp(self.created_at, 0).unwrap_or_default()
+    }
+
+    pub fn updated_at(&self) -> chrono::DateTime<chrono::Utc> {
+        chrono::DateTime::from_timestamp(self.updated_at, 0).unwrap_or_default()
+    }
+
+    pub fn iter_seq_content(&self) -> impl Iterator<Item = KId> + '_ {
+        self.content.iter().copied()
+    }
+
+    pub fn iter_uniq_content(&self) -> impl Iterator<Item = KId> + '_ {
+        HashSet::<KId>::from_iter(self.content.iter().copied()).into_iter()
+    }
+
+    pub fn get_infos(&self) -> PlaylistInfo {
+        PlaylistInfo {
+            local_id: self.local_id,
+            owners: self.owners.clone(),
+            name: self.name.clone(),
+            description: self.description.clone(),
+            created_at: self.created_at,
+            updated_at: self.updated_at,
+        }
+    }
+}
+
+impl Extend<KId> for Playlist {
+    fn extend<T: IntoIterator<Item = KId>>(&mut self, iter: T) {
+        self.content.extend(iter)
+    }
+}
+
+/// Informations relative to a playlist. We don't show everything here. For fields signification,
+/// see the ones in [Playlist].
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+pub struct PlaylistInfo {
+    local_id: KId,
+    owners: HashSet<Arc<str>>,
+    name: String,
+    description: Option<String>,
+    created_at: i64,
+    updated_at: i64,
+}
+
+impl PlaylistInfo {
+    pub fn local_id(&self) -> KId {
+        self.local_id
+    }
+
+    pub fn owners(&self) -> impl Iterator<Item = &str> {
+        self.owners.iter().map(Arc::as_ref)
+    }
+
+    pub fn name(&self) -> &str {
+        self.name.as_ref()
+    }
+
+    pub fn description(&self) -> Option<&str> {
+        self.description.as_deref()
+    }
+
+    pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
+        chrono::DateTime::from_timestamp(self.created_at, 0).unwrap_or_default()
+    }
+
+    pub fn updated_at(&self) -> chrono::DateTime<chrono::Utc> {
+        chrono::DateTime::from_timestamp(self.updated_at, 0).unwrap_or_default()
+    }
+}
diff --git a/lektor_nkdb/src/queue/mod.rs b/lektor_nkdb/src/queue/mod.rs
deleted file mode 100644
index 4ae27535b78c7f8fd4a7293e92cab7c8f9bc86ad..0000000000000000000000000000000000000000
--- a/lektor_nkdb/src/queue/mod.rs
+++ /dev/null
@@ -1,304 +0,0 @@
-//! Store the state of the queue in memory. For now we don't store it on disk, see latter how we do
-//! that thing.
-
-mod priority;
-
-pub use priority::*;
-
-use crate::*;
-use lektor_utils::{filter_range, BoundedBoundRange};
-use rand::{seq::SliceRandom, thread_rng};
-use std::ops::RangeInclusive;
-use tokio::sync::RwLock;
-
-/// The queue contains the playing kara and the following ones. This type is just a wrapper around
-/// the [QueueContent] with a [RwLock]. For function documentation see [QueueContent].
-#[derive(Debug, Default)]
-pub(crate) struct Queue(RwLock<QueueContent>);
-
-/// The content of the queue. Is protected by a RwLock from tokio.
-#[derive(Debug, Default)]
-struct QueueContent {
-    /// The current kara
-    current: Option<KId>,
-
-    /// The queue, stored as a priority list, stored in reverse order from the priority.
-    queue: [Vec<KId>; PRIORITY_LENGTH],
-
-    /// The history, we push back into it, so the most recent one is the one at the end of the
-    /// vector.
-    history: Vec<KId>,
-}
-
-macro_rules! impl_queue {
-    ($(
-        $(#[$doc:meta])*
-        $operation: ident fn $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)? $body: block
-    )+) => {
-        impl Queue {
-            $(impl_queue! {
-                $(#[$doc])*
-                bridge: $operation $name (&$this $(, $arg: $ty)*) $(-> $ret)?
-            })+
-        }
-
-        impl QueueContent {
-            $(impl_queue! {
-                $(#[$doc])*
-                original: $operation $name (&$this $(, $arg: $ty)*) $(-> $ret)? { $body }
-            })+
-        }
-    };
-
-    (
-        $(#[$doc:meta])*
-        bridge: $operation: ident $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)?
-    ) => {
-        #[allow(dead_code)]
-        $(#[$doc])*
-        pub async fn $name(&$this $(,$arg: $ty)*) $(-> $ret)? {
-            $this.0.$operation().await.$name($($arg),*)
-        }
-    };
-
-    (
-        $(#[$doc:meta])*
-        original: read $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)? { $body: block }
-    ) => {
-        $(#[$doc])*
-        pub fn $name(&$this $(,$arg: $ty)*) $(-> $ret)? { $body }
-    };
-
-    (
-        $(#[$doc:meta])*
-        original: write $name: ident (&$this: ident $(, $arg: ident: $ty: ty)*) $(-> $ret: ty)? { $body: block }
-    ) => {
-        $(#[$doc])*
-        pub fn $name(&mut $this $(,$arg: $ty)*) $(-> $ret)? { $body }
-    };
-}
-
-impl_queue! {
-    /// Get the current kara if it exists.
-    read fn current(&self) -> Option<KId> {
-        self.current.clone()
-    }
-
-    read fn split_range_per_queue_level(&self, range: impl RangeBounds<usize>) -> Vec<(Priority, RangeInclusive<usize>)> {
-        let (mut start, mut end) = (match range.start_bound() {
-            std::ops::Bound::Included(pos) => *pos,
-            std::ops::Bound::Excluded(pos) => pos + 1,
-            std::ops::Bound::Unbounded => 0,
-        }, match range.end_bound() {
-            std::ops::Bound::Included(pos) => *pos,
-            std::ops::Bound::Excluded(pos) => pos - 1,
-            std::ops::Bound::Unbounded => self.queue_count(),
-        });
-        self.queue.iter().enumerate().rev().flat_map(|(idx, lvl)| {
-            let ret = (start <= lvl.len()).then(|| (
-                Priority::from(idx + 1),
-                start..=(lvl.len() - 1).min(end)
-            ));
-            (start, end) = (start - lvl.len(), end - lvl.len());
-            ret
-        }).collect()
-    }
-
-    /// Clear the queue up to the position specified, returning it into the current kara.
-    write fn play_from_position(&self, position: usize) -> Option<KId> {
-        (position < self.queue_count()).then(|| {
-            self.split_range_per_queue_level(0..=position).into_iter().fold(None, |_, (prio, range)| {
-                self.queue[prio.index()].drain(range).last()
-            })
-        })
-        .flatten()
-    }
-
-    /// Shuffle the elemenst in the queue, the kara won't change priorities.
-    write fn queue_shuffle(&self, range: impl RangeBounds<usize> + Copy) {
-        self.split_range_per_queue_level(range).into_iter().for_each(|(prio, range)| {
-            self.queue[prio.index()][range]
-                .shuffle(&mut thread_rng());
-        });
-    }
-
-    /// Shuffle the elemenst in the level of the queue.
-    write fn queue_shuffle_level(&self, level: Priority) {
-        self.queue[level.index()]
-            .shuffle(&mut thread_rng());
-    }
-
-    /// Get the number of element in the range. Handles the out of bound stuff.
-    read fn queue_range_count(&self, range: impl RangeBounds<usize> + Copy) -> usize {
-        let (start, end) = (match range.start_bound() {
-            std::ops::Bound::Included(pos) => *pos,
-            std::ops::Bound::Excluded(pos) => pos + 1,
-            std::ops::Bound::Unbounded => 0,
-        }, match range.end_bound() {
-            std::ops::Bound::Included(pos) => *pos,
-            std::ops::Bound::Excluded(pos) => pos - 1,
-            std::ops::Bound::Unbounded => self.queue_count(),
-        });
-        end - start
-    }
-
-    /// Move a kara after a position.
-    write fn queue_move_after(&self, range: impl RangeBounds<usize> + Copy, after: usize) {
-        if !range.contains(&after) {
-            let start = match range.start_bound() {
-                std::ops::Bound::Included(start) => *start,
-                std::ops::Bound::Excluded(start) => start - 1,
-                std::ops::Bound::Unbounded => 0,
-            };
-            let mut after = if after < start { after } else { after - start };
-            let count = self.queue_range_count(range);
-            let ids = self.queue_delete_get(range).into_iter().map(|(_, id)| id);
-            for lvl in self.queue.iter_mut().rev() {
-                if after > lvl.len() {
-                    after -= lvl.len();
-                    continue;
-                } else {
-                    lvl.reserve(count);
-                    ids.rev().for_each(|id| lvl.insert(after, id));
-                    break;
-                }
-            }
-        } else {
-            log::error!("can't move a range after a position included in it");
-        }
-    }
-
-    /// Remove elements from a level of the queue.
-    write fn queue_delete_level(&self, level: Priority) {
-        self.queue[level.index()].clear()
-    }
-
-    /// Remove elements from the queue by their position.
-    write fn queue_delete(&self, range: impl RangeBounds<usize> + Copy) {
-        self.split_range_per_queue_level(range).into_iter().for_each(|(prio, range)| {
-            self.queue[prio.index()].drain(range);
-        })
-    }
-
-    /// Remove elements from the queue by their position and returns what was deleted.
-    write fn queue_delete_get(&self, range: impl RangeBounds<usize> + Copy) -> Vec<(Priority, KId)> {
-        self.split_range_per_queue_level(range).into_iter().flat_map(|(prio, range)| {
-            self.queue[prio.index()]
-                .drain(range)
-                .map(move |id| (prio, id))
-                .collect::<Vec<_>>()
-        }).collect()
-    }
-
-    /// Get the content of the level of the queue.
-    read fn queue_get_level(&self, prio: Priority) -> Vec<KId> {
-        self.queue[prio.index()].clone()
-    }
-
-    /// Iterate over the queue.
-    read fn queue(&self, range: impl RangeBounds<usize> + Copy) -> Vec<(Priority, KId)> {
-        let iter = self.queue.iter().enumerate().map(|(idx, list)| {
-            list.iter().map(move |id| (Priority::from(idx + 1), id.clone()))
-        }).rev().flatten();
-        filter_range(iter, range).collect::<Vec<_>>()
-    }
-
-    /// Returns the number of karas present in the queue.
-    read fn queue_count(&self) -> usize {
-        self.queue.iter().map(|lvl| lvl.len()).sum()
-    }
-
-    /// Returns the number of elements in each level of the queue
-    read fn queue_count_per_level(&self) -> [usize; PRIORITY_LENGTH] {
-        [ self.queue[Priority::Add    .index()].len()
-        , self.queue[Priority::Suggest.index()].len()
-        , self.queue[Priority::Insert .index()].len()
-        , self.queue[Priority::Enforce.index()].len()
-        ]
-    }
-
-    /// Insert something into one priority of the queue.
-    write fn insert(&self, prio: Priority, kara: KId) {
-        self.queue[prio.index()].insert(0, kara);
-    }
-
-    /// Insert something into one priority of the queue.
-    write fn insert_slice(&self, prio: Priority, kara: &[KId]) {
-        let level = &mut self.queue[prio.index()];
-        level.reserve(kara.len());
-        kara.iter().rev().for_each(|id| level.insert(0, id.clone()));
-    }
-
-    /// Insert something into one priority of the queue.
-    write fn insert_all(&self, prio: Priority, kara: Vec<KId>) {
-        let level = &mut self.queue[prio.index()];
-        level.reserve(kara.len());
-        kara.into_iter().rev().for_each(|id| level.insert(0, id.clone()));
-    }
-
-    /// Returns the number of karas present in the history.
-    read fn history_count(&self) -> usize {
-        self.history.len()
-    }
-
-    /// Remove elements from the history by their position.
-    write fn history_delete(&self, range: impl RangeBounds<usize> + Copy) -> Vec<KId> {
-        use std::ops::Bound::{*, self};
-        let count = self.history_count();
-        let inverse = |bound: Bound<&usize>| match bound {
-            Included(idx) => Included(count - idx - 1),
-            Excluded(idx) => Excluded(count - idx - 1),
-            Unbounded => Unbounded,
-        };
-        let range = BoundedBoundRange::from((
-            inverse(range.start_bound()),
-            inverse(range.end_bound())
-        ));
-        self.history.drain(range).collect()
-    }
-
-    /// Iterate over the history. We iterate from the most recent one to the most old one.
-    read fn history(&self, range: impl RangeBounds<usize> + Copy) -> Vec<KId> {
-        filter_range(self.history.iter().rev(), range).cloned().collect()
-    }
-
-    /// Get the next kara to play from the queue and insert the old-playing one into the history.
-    /// The returned kara is the new current one.
-    write fn next(&self) -> Option<KId> {
-        let mut iter = self.queue.iter_mut().rev();
-        let current = iter.find_map(|lvl| (!lvl.is_empty()).then(|| lvl.remove(0)))?;
-        if let Some(current) = self.current.replace(current.clone()) {
-            self.history.push(current);
-        }
-        Some(current)
-    }
-
-    /// Get the previous kara to play from the history and insert the old-playing one into the
-    /// queue at the same priority as the next one. The returned kara is the new current one. If
-    /// the queue is empty we add as the default priority.
-    write fn previous(&self) -> Option<KId> {
-        let current = self.history.pop()?;
-        if let Some(current) = self.current.replace(current.clone()) {
-            match self.queue.iter_mut().rev().find(|lvl| !lvl.is_empty()) {
-                Some(lvl) => lvl.insert(0, current),
-                None => self.insert(Default::default(), current),
-            }
-        }
-        Some(current)
-    }
-
-    /// Delete all karas by and their id in the queue
-    write fn queue_delete_all(&self, id: KId) {
-        self.queue.iter_mut().for_each(|lvl| lvl.retain(|kid| id.ne(kid)))
-    }
-
-    /// Delete all karas by and their id from a level in the queue
-    write fn queue_delete_all_from_level(&self, prio: Priority, id: KId) {
-        self.queue[prio.index()].retain(|kid| id.ne(kid))
-    }
-
-    /// Delete all karas by and their id in the history
-    write fn history_delete_all(&self, id: KId) {
-        self.history.retain(|kid| id.ne(kid))
-    }
-}
diff --git a/lektor_nkdb/src/search/mod.rs b/lektor_nkdb/src/search/mod.rs
index d93ecd576c0ea0b18ca61d767b188e76ef5114e1..6c7b416bc9477c4c5d9e35cef8603e139bab7797 100644
--- a/lektor_nkdb/src/search/mod.rs
+++ b/lektor_nkdb/src/search/mod.rs
@@ -5,7 +5,7 @@ mod kara_by;
 
 pub use kara_by::*;
 
-use crate::{playlist::PlaylistName, Kara};
+use crate::{KId, Kara};
 use anyhow::Result;
 use kurisu_api::v2::{SongOrigin, SongType};
 use regex::Regex;
@@ -17,7 +17,7 @@ pub enum SearchFrom {
     Queue,
     Database,
     History,
-    Playlist(PlaylistName),
+    Playlist(KId),
 }
 
 /// Structure used to tell how to do the search, either by a regex, or by applying another way
@@ -62,7 +62,7 @@ impl SearchBy {
     pub(crate) fn matches(&self, kara: &Kara) -> bool {
         match &self {
             SearchBy::Query(regex) => regex.is_match(&kara.to_title_string()),
-            SearchBy::Id(id) => kara.id.local_id().eq(id),
+            SearchBy::Id(id) => kara.id == *id,
             SearchBy::SongType(ty) => kara.song_type.eq(ty),
             SearchBy::SongOrigin(ori) => kara.song_origin.eq(ori),
             SearchBy::Author(author) => kara.kara_makers.contains(author.as_str()),
diff --git a/lektor_nkdb/src/storage/disk_storage.rs b/lektor_nkdb/src/storage/disk_storage.rs
index 85543be1ed98ee0d520645803f5a066338f0c041..7917e517cdb23ad14248eb5297bf3add6e2e3792 100644
--- a/lektor_nkdb/src/storage/disk_storage.rs
+++ b/lektor_nkdb/src/storage/disk_storage.rs
@@ -1,7 +1,7 @@
 //! We store things in the disk.
 
 use crate::*;
-use anyhow::{bail, Context, Result};
+use anyhow::{bail, ensure, Result};
 use futures::{
     stream::{self, FuturesUnordered},
     StreamExt,
@@ -11,7 +11,8 @@ use kurisu_api::SHA256;
 use regex::Regex;
 use std::{
     path::{Path, PathBuf},
-    sync::{atomic::AtomicU64, Arc, LazyLock},
+    str::FromStr,
+    sync::{atomic::AtomicU64, LazyLock},
 };
 use tokio::{
     fs::{create_dir_all, read, read_dir, write, OpenOptions},
@@ -42,8 +43,8 @@ pub struct DatabaseDiskStorage {
 }
 
 enum PlaylistWriteEvent {
-    Write(Arc<str>, Playlist),
-    Delete(Arc<str>),
+    Write(KId, Playlist),
+    Delete(KId),
 }
 
 macro_rules! regex {
@@ -52,26 +53,16 @@ macro_rules! regex {
     };
 }
 
-fn get_regex_epoch_ok() -> &'static Regex {
+fn get_regex_ok() -> &'static Regex {
     static REGEX: LazyLock<Regex> = regex!(r"^([0123456789]+).ok$");
     &REGEX
 }
 
-fn get_regex_epoch_json() -> &'static Regex {
+fn get_regex_json() -> &'static Regex {
     static REGEX: LazyLock<Regex> = regex!(r"^([0123456789]+).json$");
     &REGEX
 }
 
-fn get_regex_playlist_ok() -> &'static Regex {
-    static REGEX: LazyLock<Regex> = regex!(r"^([a-zA-Z0123456789]+).ok$");
-    &REGEX
-}
-
-fn get_regex_playlist_json() -> &'static Regex {
-    static REGEX: LazyLock<Regex> = regex!(r"^([a-zA-Z0123456789]+).json$");
-    &REGEX
-}
-
 impl DatabaseDiskStorage {
     // Get a path from the root. Note that it's not really safe as something can be crafter to add
     // `/../` strings to escape the root...
@@ -88,7 +79,7 @@ impl DatabaseDiskStorage {
     /// Create a new kara file with a specific extension. If a file with the same path exists it
     /// will be overwritten.
     async fn new_kara_file(&self, id: KId) -> Result<(PathBuf, tokio::fs::File)> {
-        let path = self.path_from_root(format!("data/{}.mkv", id.path()));
+        let path = self.path_from_root(format!("data/{id}.mkv"));
         if path.exists() {
             log::warn!("overwriting file {}", path.to_string_lossy())
         }
@@ -189,29 +180,25 @@ impl DatabaseDiskStorage {
     async fn pool_playlist_write_events(prefix: PathBuf, mut recv: Receiver<PlaylistWriteEvent>) {
         let prefix = prefix.as_path();
         let write = |name, plt| async move {
-            let data = match serde_json::to_string(&plt) {
-                Ok(data) => data,
-                Err(err) => {
-                    log::error!("failed to serialize playlist {name} to write it to disk: {err:?}");
-                    return;
+            match serde_json::to_string(&plt) {
+                Ok(data) => {
+                    if let Err(err) = Self::write_json_from_root(prefix, &name, data).await {
+                        log::error!("can't write playlist {name}: {err:?}")
+                    }
                 }
+                Err(e) => log::error!("failed to write playlist {name} to disk: {e:?}"),
             };
-            if let Err(err) = Self::write_json_from_root(prefix, &name, data).await {
-                log::error!("can't write playlist {name}: {err:?}")
-            }
         };
-        let delete = |name: Arc<str>| async move {
-            let mut path = prefix.join(name.as_ref());
-            path.set_extension("json");
-            if let Err(err) = tokio::fs::remove_file(path).await {
-                log::error!("failed to delete playlist {name}: {err:?}")
+        let delete = |id: KId| async move {
+            if let Err(err) = tokio::fs::remove_file(prefix.join(format!("{id}.json"))).await {
+                log::error!("failed to delete playlist {id}: {err:?}")
             }
         };
 
         while let Some(event) = recv.recv().await {
             match event {
-                PlaylistWriteEvent::Write(name, plt) => write(name, plt).await,
-                PlaylistWriteEvent::Delete(name) => delete(name).await,
+                PlaylistWriteEvent::Write(id, plt) => write(id, plt).await,
+                PlaylistWriteEvent::Delete(id) => delete(id).await,
             }
         }
     }
@@ -241,10 +228,8 @@ macro_rules! read_json_folder {
                     continue;
                 };
                 match what.as_str().parse() {
-                    Err(err) => anyhow::bail!("invalid regex, problem with file: {name}: {err}"),
-                    Ok(name) => {
-                        sets[i].insert(name);
-                    }
+                    Err(err) => bail!("invalid regex, problem with file: {name}: {err}"),
+                    Ok(name) => _ = sets[i].insert(name),
                 }
             }
         }
@@ -256,27 +241,24 @@ macro_rules! read_json_folder {
     };
 }
 
-#[async_trait::async_trait]
 impl DatabaseStorage for DatabaseDiskStorage {
     type Prefix = PathBuf;
     type File = (PathBuf, tokio::fs::File);
 
     async fn load_from_prefix(prefix: PathBuf) -> Result<Self> {
-        let regex = get_regex_epoch_ok();
+        let regex = get_regex_ok();
 
         // Prepare folders.
         for folder in ["data", "epoch", "playlists"] {
             let folder = prefix.join(folder);
             match create_dir_all(&folder).await {
                 Err(err) if err.kind() != std::io::ErrorKind::AlreadyExists => {
-                    return Err(err).with_context(|| {
-                        format!("failed to create folder {}", folder.to_string_lossy())
-                    })
+                    return Err(err)
+                        .with_context(|| format!("failed to create folder {}", folder.display()))
                 }
                 _ => {}
             }
-            let file = folder.join(".touch");
-            write(&file, chrono::Local::now().to_string()).await?;
+            write(folder.join(".touch"), chrono::Local::now().to_string()).await?;
         }
 
         // We find the maximal epoch number for this computer, to be sure to not override any
@@ -319,26 +301,29 @@ impl DatabaseStorage for DatabaseDiskStorage {
 
     async fn read_last_epoch(&self) -> Result<Option<(EpochData, u64)>> {
         read_json_folder! {
-            (self) "epoch" [get_regex_epoch_ok(), get_regex_epoch_json()] => u64;
+            (self) "epoch" [get_regex_ok(), get_regex_json()] => u64;
             valid_epochs => { match valid_epochs.into_iter().max() {
-                None => {
-                    log::warn!("no epoch to load, new install?");
-                    Ok(None)
-                }
+                None => Ok(None),
                 Some(id) => {
                     let path = self.path_from_root(format!("epoch/{id}.json"));
                     let mut data = serde_json::from_slice::<EpochData>(&read(&path).await?)?;
-                    stream::iter(data.keys()).then(|id| async move {
-                        let hash = id.path();
-                        let path = self.path_from_root(format!("data/{hash}.mkv"));
-                        Ok::<_, anyhow::Error>(sha256::try_async_digest(&path).await?.ne(hash).then_some(id.clone()))
+                    stream::iter(data.iter()).then(|(id, kara): (&KId, &Kara)| async move {
+                        let KaraStatus::Physical { hash, .. } = &kara.kara_status else {
+                            return None;
+                        };
+                        match sha256::try_async_digest(self.path_from_root(format!("data/{id}.mkv"))).await
+                            .context("failed to digest file")
+                            .map(|str| SHA256::from_str(&str).map_err(|err| anyhow!("{err}")))
+                        {
+                            Ok(Ok(file_hash)) => (file_hash != *hash).then_some(*id),
+                            Ok(Err(err)) | Err(err) => {
+                                log::error!("kara with id {id} is corrupted: {err}");
+                                Some(*id)
+                            }
+                        }
                     })
-                    .collect::<FuturesUnordered<_>>().await.into_iter()
-                    .filter_map(|res| res.map_err(|err| log::error!("{err}")).ok()).flatten()
-                    .for_each(|kid| {
-                        log::warn!("kara {kid} from epoch {id} is corrupted, remove it from epoch");
-                        data.remove(&kid);
-                    });
+                    .collect::<FuturesUnordered<_>>().await
+                    .into_iter().flatten().for_each(|kid: KId| { data.remove(&kid); });
                     log::info!("load epoch {id} with {} karas from path: {}", data.len(), path.to_string_lossy());
                     Ok(Some((data, *id)))
                 }
@@ -346,15 +331,16 @@ impl DatabaseStorage for DatabaseDiskStorage {
         }
     }
 
-    async fn read_playlists(&self) -> Result<Vec<(PlaylistName, Playlist)>> {
+    async fn read_playlists(&self) -> Result<Vec<Playlist>> {
         read_json_folder! {
-            (self) "playlists" [get_regex_playlist_ok(), get_regex_playlist_json()] => PlaylistName;
+            (self) "playlists" [get_regex_ok(), get_regex_json()] => KId;
             valid_playlists => {
-                stream::iter(valid_playlists).then(|name: &PlaylistName| async move {
-                    let path = self.path_from_root(format!("playlists/{name}.json"));
+                stream::iter(valid_playlists).then(|id: &KId| async move {
+                    let path = self.path_from_root(format!("playlists/{id}.json"));
                     log::info!("load playlist from path: {}", path.to_string_lossy());
                     let json: Playlist = serde_json::from_slice(&read(path).await?)?;
-                    Ok((name.clone(), json))
+                    ensure!(json.local_id() == *id, "");
+                    Ok(json)
                 })
                 .collect::<FuturesUnordered<_>>().await
                 .into_iter().collect::<Result<Vec<_>, _>>()
@@ -370,16 +356,21 @@ impl DatabaseStorage for DatabaseDiskStorage {
         Ok(())
     }
 
-    async fn write_playlist(&self, name: impl AsRef<str> + Send, data: &Playlist) -> Result<()> {
-        let event = PlaylistWriteEvent::Write(name.as_ref().into(), data.clone());
-        self.playlist_pipeline.send(event).await?;
-        Ok(())
+    async fn write_playlist(&self, playlist: &Playlist) -> Result<()> {
+        self.playlist_pipeline
+            .send(PlaylistWriteEvent::Write(
+                playlist.local_id(),
+                playlist.clone(),
+            ))
+            .await
+            .context("failed to send event")
     }
 
-    async fn delete_playlist(&self, name: impl AsRef<str> + Send) -> Result<()> {
-        let event = PlaylistWriteEvent::Delete(name.as_ref().into());
-        self.playlist_pipeline.send(event).await?;
-        Ok(())
+    async fn delete_playlist(&self, id: KId) -> Result<()> {
+        self.playlist_pipeline
+            .send(PlaylistWriteEvent::Delete(id))
+            .await
+            .context("failed to send event")
     }
 
     async fn prepare_kara(&self, id: KId) -> Result<Self::File> {
@@ -392,17 +383,19 @@ impl DatabaseStorage for DatabaseDiskStorage {
     }
 
     async fn submit_kara(&self, (path, _): Self::File, id: KId, hash: SHA256) -> Result<()> {
-        let digest = SHA256::try_from(sha256::try_async_digest(&path).await?).unwrap();
-        let path = path.display();
-        if digest != hash {
-            bail!("invalid digest for {path}, expected {hash}, got {digest}",)
-        }
-        log::info!("finished writing file for kara {id:?}: {path}");
+        let digest = SHA256::try_from(sha256::try_async_digest(&path).await?)
+            .map_err(|err| anyhow!("invalid sha256 computed: {err}"))?;
+        ensure!(
+            digest == hash,
+            "invalid digest for {}, expected {hash}, got {digest}",
+            path.display()
+        );
+        log::info!("finished writing file for kara {id:?}: {}", path.display());
         Ok(())
     }
 
     fn get_kara_uri(&self, id: KId) -> Result<url::Url> {
-        let path = self.path_from_root(format!("data/{}.mkv", id.path()));
+        let path = self.path_from_root(format!("data/{id}.mkv"));
         Ok(url::Url::parse(&format!("file://{}", path.display()))?)
     }
 }
diff --git a/lektor_nkdb/src/storage/mod.rs b/lektor_nkdb/src/storage/mod.rs
index e43480929b375eef8dcfbba98c61ae73603f5244..ad841439f587b293cb7d4435ce0887e5bd4cf0f1 100644
--- a/lektor_nkdb/src/storage/mod.rs
+++ b/lektor_nkdb/src/storage/mod.rs
@@ -12,12 +12,13 @@ pub use disk_storage::DatabaseDiskStorage;
 #[cfg(test)]
 mod test_storage;
 #[cfg(test)]
+#[allow(unused_imports)]
 pub use test_storage::DatabaseTestStorage;
 
 /// Describes needed interactions with the database storage. All interactions should only be done
 /// throu this trait. The [url::Url] returned by the [Self::get_kara_uri] function should be usable by mpv to
 /// read the file (file://, http://, etc...)
-#[async_trait::async_trait]
+#[allow(async_fn_in_trait)]
 pub trait DatabaseStorage: Sized + std::fmt::Debug {
     /// The type of prefix to use for this loaded.
     type Prefix;
@@ -44,13 +45,13 @@ pub trait DatabaseStorage: Sized + std::fmt::Debug {
     // =============== //
 
     /// Read the playlists from the storage.
-    async fn read_playlists(&self) -> Result<Vec<(PlaylistName, Playlist)>>;
+    async fn read_playlists(&self) -> Result<Vec<Playlist>>;
 
     /// Write a playlist to the storage.
-    async fn write_playlist(&self, name: impl AsRef<str> + Send, data: &Playlist) -> Result<()>;
+    async fn write_playlist(&self, playlist: &Playlist) -> Result<()>;
 
     /// Delete a playlist.
-    async fn delete_playlist(&self, name: impl AsRef<str> + Send) -> Result<()>;
+    async fn delete_playlist(&self, id: KId) -> Result<()>;
 
     // ========== //
     // Kara stuff //
diff --git a/lektor_nkdb/src/storage/test_storage.rs b/lektor_nkdb/src/storage/test_storage.rs
index 49b418476a9e92abf2d3d8d96aac749e1e263683..b9e30c60f8e2b0853ab2f5a63b5b1bacbc96d0a3 100644
--- a/lektor_nkdb/src/storage/test_storage.rs
+++ b/lektor_nkdb/src/storage/test_storage.rs
@@ -2,9 +2,14 @@
 //! tests and we don't depend on the fs to do things... Note that the [Uri] returned by this
 //! storage are in `void://` which is not something usable!
 
-use crate::{database::*, storage::DatabaseStorage, Playlist, PlaylistName};
+use crate::{
+    database::{epoch::*, kara::*},
+    id::*,
+    storage::DatabaseStorage,
+    Playlist,
+};
 use anyhow::{Context, Result};
-use futures::future::join_all;
+use futures::future;
 use hashbrown::HashMap;
 use kurisu_api::SHA256;
 use tokio::sync::RwLock;
@@ -14,7 +19,6 @@ use tokio::sync::RwLock;
 #[derive(Debug)]
 pub struct DatabaseTestStorage(RwLock<HashMap<KId, Kara>>);
 
-#[async_trait::async_trait]
 impl DatabaseStorage for DatabaseTestStorage {
     type Prefix = ();
     type File = ();
@@ -28,22 +32,22 @@ impl DatabaseStorage for DatabaseTestStorage {
     }
 
     async fn write_epoch(&self, data: &EpochData) -> Result<()> {
-        join_all(data.iter().map(|(id, kara)| async {
-            self.0.write().await.insert(id.clone(), (*kara).clone());
+        future::join_all(data.iter().map(|(id, kara)| async {
+            self.0.write().await.insert(*id, kara.clone());
         }))
         .await;
         Ok(())
     }
 
-    async fn read_playlists(&self) -> Result<Vec<(PlaylistName, Playlist)>> {
+    async fn read_playlists(&self) -> Result<Vec<Playlist>> {
         unimplemented!()
     }
 
-    async fn write_playlist(&self, _: impl AsRef<str> + Send, _: &Playlist) -> Result<()> {
+    async fn write_playlist(&self, _: &Playlist) -> Result<()> {
         Ok(())
     }
 
-    async fn delete_playlist(&self, _: impl AsRef<str> + Send) -> Result<()> {
+    async fn delete_playlist(&self, _: KId) -> Result<()> {
         Ok(())
     }
 
@@ -60,7 +64,6 @@ impl DatabaseStorage for DatabaseTestStorage {
     }
 
     fn get_kara_uri(&self, id: KId) -> Result<url::Url> {
-        url::Url::parse(&format!("void://{}", id.path()))
-            .with_context(|| format!("invalid url void://{}", id.path()))
+        url::Url::parse(&format!("void://{id}")).with_context(|| format!("invalid url void://{id}"))
     }
 }
diff --git a/lektor_nkdb/src/strings.rs b/lektor_nkdb/src/strings.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d5892eebbb3270f836f5ea4c2ec0195b94bda0be
--- /dev/null
+++ b/lektor_nkdb/src/strings.rs
@@ -0,0 +1,34 @@
+//! Contains things to store and cache strings to reduce memory footprint and have simpler
+//! comparaisons: string comparaisons will become pointer —e.g. usize— comparaisons.
+
+use hashbrown::HashSet;
+use std::sync::{Arc, LazyLock};
+use tokio::sync::RwLock;
+
+pub(crate) static CACHE: LazyLock<StringCache> = LazyLock::new(StringCache::default);
+
+/// The string cache. Used to cache strings as [Arc<str>] to reduce memory footprint.
+#[derive(Default)]
+pub(crate) struct StringCache(RwLock<HashSet<Arc<str>>>);
+
+impl StringCache {
+    /// Get a string, in an async way.
+    pub async fn get(&self, str: impl AsRef<str>) -> Arc<str> {
+        match self.0.read().await.get(str.as_ref()).cloned() {
+            Some(str) => str,
+            None => (self.0.write().await)
+                .get_or_insert_with(str.as_ref(), |str| Arc::from(str))
+                .clone(),
+        }
+    }
+
+    /// Get a string, in a sync way.
+    pub fn get_sync(&self, str: impl AsRef<str>) -> Arc<str> {
+        match self.0.blocking_read().get(str.as_ref()).cloned() {
+            Some(str) => str,
+            None => (self.0.blocking_write())
+                .get_or_insert_with(str.as_ref(), |str| Arc::from(str))
+                .clone(),
+        }
+    }
+}
diff --git a/lektor_payloads/Cargo.toml b/lektor_payloads/Cargo.toml
index 315a1e2e1011ec3d96eb7e53d13dbdc6e63a5942..bfebc83593c5a934c7f9129c03d260b0ad92add7 100644
--- a/lektor_payloads/Cargo.toml
+++ b/lektor_payloads/Cargo.toml
@@ -1,21 +1,24 @@
 [package]
 name = "lektor_payloads"
+description = "The payloads of the requests used to exchange messages between lektord and its client, this is a utility crate, you can build your own thing because they are all serialized to json under the hood"
+
+rust-version.workspace = true
+
 version.workspace = true
 edition.workspace = true
 authors.workspace = true
 license.workspace = true
-rust-version.workspace = true
-description = "The payloads of the requests used to exchange messages between lektord and its client, this is a utility crate, you can build your own thing because they are all serialized to json under the hood"
 
 [dependencies]
-serde.workspace = true
+serde.workspace      = true
 serde_json.workspace = true
 
 anyhow.workspace = true
 
-futures.workspace = true
-axum.workspace = true
+futures.workspace     = true
+axum.workspace        = true
 async-trait.workspace = true
 
-lektor_nkdb = { path = "../lektor_nkdb" }
-lektor_utils = { path = "../lektor_utils" }
+lektor_nkdb.workspace       = true
+lektor_utils.workspace      = true
+lektor_procmacros.workspace = true
diff --git a/lektor_payloads/src/lib.rs b/lektor_payloads/src/lib.rs
index 3ad79de1c00ba2d103273eaa0a281e319c52b150..8825f98a2cec6cce89c5217a5cf6a8460b40e14f 100644
--- a/lektor_payloads/src/lib.rs
+++ b/lektor_payloads/src/lib.rs
@@ -1,15 +1,22 @@
 //! Crate containing structs/enums that are used as payloads to communicate with the lektord
 //! daemon. Some things are re-exports.
 
-mod playlist_name;
+mod play_state;
+mod priority;
 mod range;
 mod search;
 mod userid;
 
-pub use crate::{playlist_name::*, range::*, search::*, userid::LektorUser};
+pub use crate::{
+    priority::{Priority, PRIORITY_LENGTH, PRIORITY_VALUES},
+    play_state::PlayState,
+    range::*,
+    search::*,
+    userid::LektorUser,
+};
 pub use lektor_nkdb::{
-    KId, Kara, KaraBy, KaraStatus, KaraTimeStamps, PlayState, Playlist, PlaylistInfo, Priority,
-    RemoteKId, SearchFrom, SongOrigin, SongType, PRIORITY_LENGTH, PRIORITY_VALUES,
+    KId, Kara, KaraBy, KaraStatus, KaraTimeStamps, Playlist, PlaylistInfo, RemoteKId, SearchFrom,
+    SongOrigin, SongType,
 };
 
 use anyhow::{anyhow, ensure};
@@ -73,7 +80,7 @@ pub enum PlaylistUpdateAction {
     GiveTo(Box<str>),
 
     /// Rename the playlist.
-    Rename(PlaylistName),
+    Rename(String),
 
     /// Add a kara to the playlist.
     Add(KaraFilter),
diff --git a/lektor_payloads/src/play_state.rs b/lektor_payloads/src/play_state.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2b2b2571690c9ad09a78cc0aef55e47c03ff570a
--- /dev/null
+++ b/lektor_payloads/src/play_state.rs
@@ -0,0 +1,23 @@
+//! The playstate of the player.
+
+use serde::{Deserialize, Serialize};
+
+/// Play state of the player.
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Default)]
+#[serde(rename_all = "lowercase")]
+pub enum PlayState {
+    #[default]
+    Stop = 0,
+    Play = 1,
+    Pause = 2,
+}
+
+impl AsRef<str> for PlayState {
+    fn as_ref(&self) -> &str {
+        match self {
+            PlayState::Stop => "stopped",
+            PlayState::Play => "play",
+            PlayState::Pause => "paused",
+        }
+    }
+}
diff --git a/lektor_payloads/src/playlist_name.rs b/lektor_payloads/src/playlist_name.rs
deleted file mode 100644
index 68c71ca4bdff309758b23e934e805aad130a71f7..0000000000000000000000000000000000000000
--- a/lektor_payloads/src/playlist_name.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use anyhow::anyhow;
-use axum::{
-    extract::FromRequestParts,
-    http::{request::Parts, StatusCode},
-};
-use lektor_nkdb::PlaylistName as NKDBPlaylistName;
-use lektor_utils::decode_base64;
-use serde::{Deserialize, Serialize};
-use std::str::FromStr;
-
-/// A playlist name, we excract it from the path. We wrap the thing from the database.
-///
-/// ### Safety
-/// We check before builder the playlist name that the [`u8`] is a valide UTF-8 string.
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash)]
-#[repr(transparent)]
-pub struct PlaylistName(NKDBPlaylistName);
-
-/// The prefix of the playlists requests.
-const PLAYLIST_PREFIX: &str = "/playlist/";
-
-impl AsRef<str> for PlaylistName {
-    fn as_ref(&self) -> &str {
-        self.0.as_ref()
-    }
-}
-
-impl FromStr for PlaylistName {
-    type Err = anyhow::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        NKDBPlaylistName::from_str(s).map(Self)
-    }
-}
-
-impl TryFrom<&str> for PlaylistName {
-    type Error = anyhow::Error;
-
-    fn try_from(value: &str) -> Result<Self, Self::Error> {
-        value.parse()
-    }
-}
-
-impl From<PlaylistName> for NKDBPlaylistName {
-    fn from(value: PlaylistName) -> Self {
-        value.0
-    }
-}
-
-impl From<NKDBPlaylistName> for PlaylistName {
-    fn from(value: NKDBPlaylistName) -> Self {
-        Self(value)
-    }
-}
-
-impl std::fmt::Display for PlaylistName {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_str(self.as_ref())
-    }
-}
-
-#[async_trait::async_trait]
-impl<S> FromRequestParts<S> for PlaylistName {
-    type Rejection = (StatusCode, String);
-
-    async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
-        let err = |err: anyhow::Error| (StatusCode::NOT_ACCEPTABLE, err.to_string());
-        let path = parts.uri.path();
-        if !path.starts_with(PLAYLIST_PREFIX) {
-            Err(err(anyhow!("invalid playlist path: {path}")))
-        } else {
-            decode_base64(&path[PLAYLIST_PREFIX.len()..])
-                .map_err(err)?
-                .parse()
-                .map_err(err)
-        }
-    }
-}
diff --git a/lektor_nkdb/src/queue/priority.rs b/lektor_payloads/src/priority.rs
similarity index 98%
rename from lektor_nkdb/src/queue/priority.rs
rename to lektor_payloads/src/priority.rs
index f8cd69ba0d5b4a5bddff6280c7f23145316da07b..1fd1894dc896520f474e8d51927019a011cb54ae 100644
--- a/lektor_nkdb/src/queue/priority.rs
+++ b/lektor_payloads/src/priority.rs
@@ -1,5 +1,5 @@
-use crate::*;
 use lektor_procmacros::{EnumVariantCount, EnumVariantIter};
+use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 
 /// Priorities to insert into the queue. We are one based because that's how humans thinks about
diff --git a/lektor_payloads/src/search.rs b/lektor_payloads/src/search.rs
index bc4567b564e74ad3abf86a0d882e657409b8043a..4468c9956eb5763662c468de7af5ef741a70dc2b 100644
--- a/lektor_payloads/src/search.rs
+++ b/lektor_payloads/src/search.rs
@@ -18,7 +18,7 @@ pub enum KaraFilter {
     List(bool, Vec<KId>),
 
     /// The content of a playlist.
-    Playlist(bool, PlaylistName),
+    Playlist(bool, KId),
 }
 
 #[cfg(test)]
diff --git a/lektor_payloads/src/userid.rs b/lektor_payloads/src/userid.rs
index 28ec6f3ef39306e1e71ada0bbd304aeeebecd84c..502483c6c9a41d44551c7aa3ac15a87cebb52c84 100644
--- a/lektor_payloads/src/userid.rs
+++ b/lektor_payloads/src/userid.rs
@@ -1,6 +1,7 @@
 //! Specify who is the person making the request in the header. Extractor for when an
 //! identification is needed or optional.
 
+use anyhow::{anyhow, bail};
 use axum::{
     extract::FromRequestParts,
     http::header,
@@ -17,32 +18,6 @@ pub enum LektorUser {
     Unverified(Box<str>, Box<str>),
 }
 
-pub struct Error(anyhow::Error);
-
-impl std::fmt::Debug for Error {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
-impl std::fmt::Display for Error {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.0)
-    }
-}
-
-impl<E: Into<anyhow::Error>> From<E> for Error {
-    fn from(err: E) -> Self {
-        Self(err.into())
-    }
-}
-
-impl IntoResponse for Error {
-    fn into_response(self) -> axum::response::Response {
-        (StatusCode::NOT_ACCEPTABLE, format!("{self}")).into_response()
-    }
-}
-
 impl LektorUser {
     /// Is the user an admin?
     pub fn is_admin(&self) -> bool {
@@ -93,6 +68,51 @@ impl LektorUser {
         let (name, token) = self.into_parts();
         Self::User(name, token)
     }
+
+    /// Returns [Some] user if it is an admin, [None] otherwise.
+    pub fn maybe_admin(self) -> anyhow::Result<Self> {
+        match self {
+            user @ LektorUser::Admin(..) => Ok(user),
+            LektorUser::User(n, _) | LektorUser::Unverified(n, _) => {
+                bail!("user '{n}' is not an admin")
+            }
+        }
+    }
+
+    /// Returns [Some] user if it is an admin or a simple user, [None] otherwise (i.e. the user is
+    /// not authentified.)
+    pub fn maybe_user(self) -> anyhow::Result<Self> {
+        match self {
+            user @ (LektorUser::Admin(..) | LektorUser::User(..)) => Ok(user),
+            LektorUser::Unverified(n, _) => bail!("user '{n}' is not authentified"),
+        }
+    }
+}
+
+pub struct Error(anyhow::Error);
+
+impl std::fmt::Debug for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl std::fmt::Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl<E: Into<anyhow::Error>> From<E> for Error {
+    fn from(err: E) -> Self {
+        Self(err.into())
+    }
+}
+
+impl IntoResponse for Error {
+    fn into_response(self) -> axum::response::Response {
+        (StatusCode::NOT_ACCEPTABLE, format!("{self}")).into_response()
+    }
 }
 
 #[async_trait::async_trait]
@@ -103,11 +123,11 @@ impl<S> FromRequestParts<S> for LektorUser {
         match parts.headers.get(header::AUTHORIZATION) {
             Some(header) => {
                 let Some((name, token)) = header.to_str()?.split_once('=') else {
-                    return Err(anyhow::Error::msg("invalid header").into());
+                    return Err(anyhow!("invalid header").into());
                 };
                 Ok(LektorUser::Unverified(name.into(), token.into()))
             }
-            None => Err(anyhow::Error::msg("headers not found").into()),
+            None => Err(anyhow!("headers not found").into()),
         }
     }
 }
diff --git a/lektor_repo/src/downloader.rs b/lektor_repo/src/downloader.rs
index 87dc8d63423a93ed587339e40957413767f99a6e..f02ee940278657a4cd7da27b53f29c70c87428d4 100644
--- a/lektor_repo/src/downloader.rs
+++ b/lektor_repo/src/downloader.rs
@@ -6,7 +6,6 @@ use futures::{
     stream::{self, FuturesUnordered},
     StreamExt,
 };
-use hashbrown::HashSet;
 use kurisu_api::{
     v2::{Infos, Kara},
     SHA256,
@@ -21,32 +20,14 @@ use serde::Deserialize;
 
 pub async fn download_dbinfos(config: &LektorRepoConfig) -> Result<Infos> {
     let name = config.name.as_str();
-    let (infos, epochs) = stream::iter(config.urls.iter().map(get_dbinfos))
+    let infos = stream::iter(config.urls.iter().map(get_dbinfos))
         .then(|url| async { reqwest::get(url).await?.json::<Infos>().await })
         .collect::<FuturesUnordered<_>>()
         .await
         .into_iter()
         .filter_map(|r| r.map_err(|e| log::error!("error for {name}: {e}")).ok())
-        .fold(
-            (Infos::default(), HashSet::new()),
-            |(mut res, mut epochs), info| {
-                epochs.insert(info.dbepoch);
-                res.dbepoch = std::cmp::max(res.dbepoch, info.dbepoch);
-                res.tag_keys.extend(info.tag_keys);
-                res.valueless_tag_keys.extend(info.valueless_tag_keys);
-                (res, epochs)
-            },
-        );
-    if epochs.len() >= 2 {
-        log::warn!("urls for {name} are not in syncs, epochs are: {epochs:?}")
-    }
-    let intersection: Vec<_> = infos
-        .valueless_tag_keys
-        .intersection(&infos.tag_keys)
-        .collect();
-    if !intersection.is_empty() {
-        log::warn!("tags are indicated as valueless and with value: {intersection:?}")
-    }
+        .fold(Infos::default(), |res, info| res.merge(info));
+    infos.warn_on_conflicting_tags();
     Ok(infos)
 }
 
@@ -91,7 +72,6 @@ pub async fn download_kara<Storage: DatabaseStorage>(
             .map(|b| get_kara_file(b, rkid.remote_id())),
     )
     .then(|url| {
-        let kid = kid.clone();
         async move {
             // Build the request
             let request = Request::new(
@@ -116,7 +96,7 @@ pub async fn download_kara<Storage: DatabaseStorage>(
             }
 
             // Download the file
-            let mut file = handle.prepare_kara(kid.clone()).await?;
+            let mut file = handle.prepare_kara(kid).await?;
             while let Some(chunk) = res.chunk().await? {
                 handle.write_kara(&mut file, &chunk).await?;
             }
diff --git a/lektor_repo/src/lib.rs b/lektor_repo/src/lib.rs
index 2d8d6f5e274788a5fdf564e0f3d3e26da922e832..5e0ccebcc515f0df0cd70491494859da01e35edd 100644
--- a/lektor_repo/src/lib.rs
+++ b/lektor_repo/src/lib.rs
@@ -55,14 +55,10 @@ impl Repo {
                 });
                 stream::iter(list).then(move |kara| async move {
                     log::trace!("got kara from {}: {kara:#?}", cfg.name);
-                    match handler.add_kara_v2(&cfg.name, kara).await {
-                        Ok(Some(to_dl)) => {
-                            if let Err(err) = download_kara(cfg, handler, to_dl).await {
-                                log::error!("{err}");
-                            }
+                    if let Some(to_dl) = handler.add_kara_v2(&cfg.name, kara).await {
+                        if let Err(err) = download_kara(cfg, handler, to_dl).await {
+                            log::error!("{err}");
                         }
-                        Err(err) => log::error!("failed to update kara: {err}"),
-                        _ => {}
                     }
                 })
             })
diff --git a/lektor_utils/src/base64.rs b/lektor_utils/src/base64.rs
index 39e341f6891bd8a2845dbebcfe09fe28a873dcdf..6e80a103e54da69138409cff35acc3f71ffe3c13 100644
--- a/lektor_utils/src/base64.rs
+++ b/lektor_utils/src/base64.rs
@@ -1,6 +1,8 @@
+use core::fmt;
+
 use anyhow::Result;
 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
-use serde::{Deserialize, Serialize};
+use serde::{ser::Error, Deserialize, Serialize};
 
 /// Re-export of the slice decode error
 pub use base64::EncodeSliceError;
@@ -10,19 +12,37 @@ pub use base64::EncodeSliceError;
 /// playlists urls to 128 ($2^7$) characters and the queue/history is limited to u64 integers.
 pub const BASE64_BUFFER_SIZE: usize = 1024;
 
+/// Struct to wrap the result of [encode_base64] and [encode_base64_value]. Can be displayed and
+/// turn into parts.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Base64Encoded(usize, [u8; BASE64_BUFFER_SIZE]);
+
+impl Base64Encoded {
+    pub fn into_parts(self) -> (usize, [u8; BASE64_BUFFER_SIZE]) {
+        (self.0, self.1)
+    }
+}
+
+impl fmt::Display for Base64Encoded {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str(
+            std::str::from_utf8(&self.1[..self.0]).map_err(|_| {
+                fmt::Error::custom("encoded buffer contains invalid utf8 characters")
+            })?,
+        )
+    }
+}
+
 /// Encode a string as a base64 string and store it into a buffer without allocatig any memory.
-pub fn encode_base64(input: impl AsRef<str>) -> Result<(usize, [u8; BASE64_BUFFER_SIZE])> {
+pub fn encode_base64(input: impl AsRef<str>) -> Result<Base64Encoded> {
     let mut buffer = [0_u8; BASE64_BUFFER_SIZE];
     let len = URL_SAFE_NO_PAD.encode_slice(input.as_ref(), &mut buffer)?;
-    Ok((len, buffer))
+    Ok(Base64Encoded(len, buffer))
 }
 
 /// Serialize to json and encode a value in base64.
-pub fn encode_base64_value(value: impl Serialize) -> Result<(usize, [u8; BASE64_BUFFER_SIZE])> {
-    let json = serde_json::to_string(&value)?;
-    let size = encode_base64(&json)?;
-    log::trace!("encoding json {json}");
-    Ok(size)
+pub fn encode_base64_value(value: impl Serialize) -> Result<Base64Encoded> {
+    encode_base64(&serde_json::to_string(&value)?)
 }
 
 /// Decode a base64 encoded string without allocation then deserialize the json into the approriate
diff --git a/lektord/Cargo.toml b/lektord/Cargo.toml
index 7bbd03c3ee0a6ab6aebcff89b5269db9d5c07034..9b51010e15138e46594b022bc71893f60b0696e5 100644
--- a/lektord/Cargo.toml
+++ b/lektord/Cargo.toml
@@ -17,7 +17,6 @@ anyhow.workspace = true
 hashbrown.workspace = true
 
 futures.workspace = true
-async-trait.workspace = true
 
 axum.workspace = true
 tokio.workspace = true
diff --git a/lektord/src/app/mod.rs b/lektord/src/app/mod.rs
index 15c32be3b48b91a698636db40dfa73b77dfe7f63..be5a06b9978fba346d4f0852630381c1f368675c 100644
--- a/lektord/src/app/mod.rs
+++ b/lektord/src/app/mod.rs
@@ -3,10 +3,12 @@
 #![forbid(unsafe_code)]
 
 mod mpris;
+mod play_state;
+mod queue;
 mod routes;
 
 use crate::LektorConfig;
-use anyhow::{anyhow, Context, Result};
+use anyhow::{Context, Result};
 use axum::{
     extract::Request,
     http::{response, StatusCode},
@@ -19,6 +21,8 @@ use lektor_nkdb::{Database, DatabaseDiskStorage};
 use lektor_payloads::LektorUser;
 use lektor_repo::Repo;
 use lektor_utils::config::LektorDatabaseConfig;
+use play_state::StoredPlayState;
+use queue::{Queue, QueueHandle};
 use std::sync::{Arc, Weak};
 use tokio::sync::{oneshot::Sender, RwLock};
 
@@ -79,33 +83,38 @@ pub async fn app(
                // Simple search things
                ; "/search/:b64" -> get: routes::search
                ; "/count/:b64"  -> get: routes::count
-               ; "/get/id/:id"  -> get: routes::get_kara_by_id
                ; "/get/kid/:id" -> get: routes::get_kara_by_kid
 
-               // Queue and history functions. You need to specify the range, like:
-               // `/queue?range=..`
-               ; "/queue"            -> get:    routes::get_queue_range
-                                      , post:   routes::add_to_queue
-                                      , patch:  routes::update_queue_range
-                                      , delete: routes::delete_queue_range
-               ; "/history"          -> get:    routes::get_history_range
-                                      , delete: routes::delete_history_range
-               ; "/queue/count"      -> get:    routes::get_queue_count
-               ; "/history/count"    -> get:    routes::get_history_count
-               ; "/queue/:prio/:kid" -> post:   routes::add_kid_to_queue
-                                      , delete: routes::delete_kid_from_queue
-               ; "/queue/:what"      -> delete: routes::delete_from_queue
-                                      , put:    routes::shuffle_queue_level
-                                      , get:    routes::get_from_queue
-               ; "/history/:kid"     -> delete: routes::delete_kid_from_history
+               // Queue functions. You need to specify the range, like: `/queue?range=..`
+               ; "/queue"                  -> get:    routes::get_queue_range
+                                            , post:   routes::add_to_queue
+                                            , patch:  routes::update_queue_range
+                                            , delete: routes::delete_queue_range
+               ; "/queue/count"            -> get:    routes::get_queue_count
+               ; "/queue/level/:prio/:kid" -> post:   routes::add_kid_to_queue
+                                            , delete: routes::delete_kid_from_queue
+               ; "/queue/level/:prio"      -> get:    routes::get_queue_level
+                                            , put:    routes::shuffle_queue_level
+                                            , delete: routes::delete_queue_level
+               ; "/queue/:kid"             -> delete: routes::delete_from_queue
+                                            , put:    routes::shuffle_queue_level
+                                            , get:    routes::get_from_queue
+
+               // History functions. You need to specify the range, like: `/history?range=..`
+               ; "/history"       -> get:    routes::get_history_range
+                                   , delete: routes::delete_history_range
+               ; "/history/count" -> get:    routes::get_history_count
+               ; "/history/:kid"  -> delete: routes::delete_kid_from_history
 
                // Playlists things, to modify or delete a playlist, the user must be the owner or
                // the super user...
-               ; "/playlist"       -> get:    routes::get_playlists
-               ; "/playlist/:name" -> get:    routes::get_playlist_content
-                                    , patch:  routes::update_playlist
-                                    , delete: routes::delete_playlist
-                                    , put:    routes::create_playlist
+               ; "/playlists"              -> get:    routes::get_playlists
+               ; "/playlist/:name/content" -> get:    routes::get_playlist_content
+               ; "/playlist/:name/info"    -> get:    routes::get_playlist_info
+               ; "/playlist/:name"         -> get:    routes::get_playlist
+                                            , patch:  routes::update_playlist
+                                            , delete: routes::delete_playlist
+                                            , put:    routes::create_playlist
 
                // Admin routes, requires to be the super user to do.
                ; "/adm/update"   -> post: routes::adm_update
@@ -143,7 +152,13 @@ async fn log_requests(request: Request, next: Next) -> Response {
 #[derive(Debug)]
 pub struct LektorState {
     /// The database.
-    pub(crate) database: Database,
+    database: Database,
+
+    /// The queue.
+    queue: Queue,
+
+    /// The playstate of the player.
+    playstate: StoredPlayState,
 
     /// The thing that will handle updates for us.
     pub(crate) repo: Repo,
@@ -188,6 +203,8 @@ impl LektorState {
             repo: Repo::new(repo),
             users: users.collect(),
             mpris: Default::default(),
+            queue: Default::default(),
+            playstate: Default::default(),
             shutdown: RwLock::new(Some(shutdown)),
         }));
         if config.mpris {
@@ -205,7 +222,7 @@ impl LektorState {
     }
 
     /// Check whever the user is valid or not.
-    pub fn verify_user(&self, user: LektorUser) -> LektorUser {
+    fn verify_user(&self, user: LektorUser) -> LektorUser {
         let (name, tok) = user.as_parts();
         for config in &self.users {
             match config {
@@ -217,30 +234,27 @@ impl LektorState {
         user
     }
 
+    /// Get a handle to read or write the queue, alongside the playstate.
+    pub(crate) fn queue(&self) -> QueueHandle {
+        QueueHandle::new(&self.queue, &self.playstate)
+    }
+
     /// Play the next kara in the queue.
-    pub async fn play_next(&self) -> Result<()> {
+    pub(crate) async fn play_next(&self) -> Result<()> {
         let id = self
-            .database
-            .next()
-            .await
-            .ok_or(anyhow!("no next kara to play"))?;
-        crate::c_wrapper::player_load_file(
-            self.database.get_kara_uri(id.clone())?.to_string(),
-            id.local_id(),
-        )
+            .queue()
+            .write(|queue, _| queue.next().context("no next kara to play"))
+            .await?;
+        crate::c_wrapper::player_load_file(self.database.get_kara_uri(id)?.as_str(), id)
     }
 
     /// Play the previous kara in the queue.
-    pub async fn play_previous(&self) -> Result<()> {
+    pub(crate) async fn play_previous(&self) -> Result<()> {
         let id = self
-            .database
-            .previous()
-            .await
-            .ok_or_else(|| anyhow!("no previous kara to play"))?;
-        crate::c_wrapper::player_load_file(
-            self.database.get_kara_uri(id.clone())?.to_string(),
-            id.local_id(),
-        )
+            .queue()
+            .write(|queue, _| queue.previous().context("no next kara to play"))
+            .await?;
+        crate::c_wrapper::player_load_file(self.database.get_kara_uri(id)?.as_str(), id)
     }
 }
 
diff --git a/lektord/src/app/mpris.rs b/lektord/src/app/mpris.rs
index 9f57e52f5f1092bf4864d166f57801ef76703129..b34e9deae6ef71d0da091525b2418fc7198430bc 100644
--- a/lektord/src/app/mpris.rs
+++ b/lektord/src/app/mpris.rs
@@ -4,7 +4,7 @@ use lektor_nkdb::Kara;
 use std::sync::Arc;
 
 /// The play state, convertible to the thing liked by MPRIS.
-pub struct PlayState(lektor_nkdb::PlayState);
+pub struct PlayState(lektor_payloads::PlayState);
 
 /// A thing used to convert a [Kara] into track metadatas for MPRIS.
 pub struct TrackMdt(Arc<Kara>);
@@ -95,15 +95,21 @@ impl AsMpris for LektorStateWeakPtr {
     }
 }
 
-impl From<PlayState> for lektor_nkdb::PlayState {
+impl From<PlayState> for lektor_payloads::PlayState {
     fn from(PlayState(value): PlayState) -> Self {
         value
     }
 }
 
+impl From<lektor_payloads::PlayState> for PlayState {
+    fn from(value: lektor_payloads::PlayState) -> Self {
+        Self(value)
+    }
+}
+
 impl From<lektor_mpris::types::PlaybackStatus> for PlayState {
     fn from(value: lektor_mpris::types::PlaybackStatus) -> Self {
-        use {lektor_mpris::types::PlaybackStatus, lektor_nkdb::PlayState};
+        use {lektor_mpris::types::PlaybackStatus, lektor_payloads::PlayState};
         match value {
             PlaybackStatus::Stopped => Self(PlayState::Stop),
             PlaybackStatus::Playing => Self(PlayState::Play),
@@ -115,9 +121,9 @@ impl From<lektor_mpris::types::PlaybackStatus> for PlayState {
 impl From<PlayState> for lektor_mpris::types::PlaybackStatus {
     fn from(PlayState(value): PlayState) -> Self {
         match value {
-            lektor_nkdb::PlayState::Stop => Self::Stopped,
-            lektor_nkdb::PlayState::Play => Self::Playing,
-            lektor_nkdb::PlayState::Pause => Self::Paused,
+            lektor_payloads::PlayState::Stop => Self::Stopped,
+            lektor_payloads::PlayState::Play => Self::Playing,
+            lektor_payloads::PlayState::Pause => Self::Paused,
         }
     }
 }
diff --git a/lektord/src/app/play_state.rs b/lektord/src/app/play_state.rs
new file mode 100644
index 0000000000000000000000000000000000000000..89c4ed3722373ce27a978f5bcdebcf475ead6803
--- /dev/null
+++ b/lektord/src/app/play_state.rs
@@ -0,0 +1,26 @@
+//! Contains things to store the playstate for the player, in a way that can be accessed in a
+//! thread-safe maner.
+
+use lektor_payloads::PlayState;
+use std::sync::atomic::{AtomicU8, Ordering};
+
+/// The playstate, but stored in the database. We take into account the fact that it can be
+/// accessed in a parallel context.
+#[derive(Debug, Default)]
+pub struct StoredPlayState {
+    state: AtomicU8,
+}
+
+impl StoredPlayState {
+    pub fn get(&self) -> PlayState {
+        match self.state.load(Ordering::Acquire) {
+            1 => PlayState::Play,
+            2 => PlayState::Pause,
+            _ => PlayState::Stop,
+        }
+    }
+
+    pub fn set(&self, state: PlayState) {
+        self.state.store(state as u8, Ordering::Release);
+    }
+}
diff --git a/lektord/src/app/queue.rs b/lektord/src/app/queue.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a368cba3d0b7e150be5113402f59e92bd788c58b
--- /dev/null
+++ b/lektord/src/app/queue.rs
@@ -0,0 +1,307 @@
+use crate::app::play_state::StoredPlayState;
+use lektor_payloads::{KId, PlayState, Priority, PRIORITY_LENGTH};
+use lektor_utils::{filter_range, BoundedBoundRange};
+use rand::{seq::SliceRandom, thread_rng};
+use std::{
+    ops::{RangeBounds, RangeInclusive},
+    sync::atomic::{AtomicU64, Ordering},
+};
+use tokio::sync::RwLock;
+
+/// The queue contains the playing kara and the following ones. This type is just a wrapper around
+/// the [QueueContent] with a [RwLock]. For function documentation see [QueueContent].
+#[derive(Debug, Default)]
+pub(crate) struct Queue {
+    content: RwLock<QueueContent>,
+    epoch: AtomicU64,
+}
+
+pub struct QueueHandle<'a> {
+    queue: &'a Queue,
+    state: &'a StoredPlayState,
+}
+
+impl<'a> QueueHandle<'a> {
+    pub(crate) fn new(queue: &'a Queue, state: &'a StoredPlayState) -> Self {
+        Self { queue, state }
+    }
+
+    /// Read informations about the queue.
+    pub async fn read<T>(&self, cb: impl FnOnce(&QueueContent, PlayState) -> T) -> T {
+        cb(&*self.queue.content.read().await, self.state.get())
+    }
+
+    /// Modifies the queue.
+    pub async fn write<T>(&self, cb: impl FnOnce(&mut QueueContent, &StoredPlayState) -> T) -> T {
+        self.queue.epoch.fetch_add(1, Ordering::AcqRel);
+        cb(&mut *self.queue.content.write().await, self.state)
+    }
+
+    pub fn epoch(&self) -> u64 {
+        self.queue.epoch.load(Ordering::Acquire)
+    }
+}
+
+/// The content of the queue. Is protected by a RwLock from tokio.
+#[derive(Debug, Default)]
+pub struct QueueContent {
+    /// The current kara
+    current: Option<KId>,
+
+    /// The queue, stored as a priority list, stored in reverse order from the priority.
+    queue: [Vec<KId>; PRIORITY_LENGTH],
+
+    /// The history, we push back into it, so the most recent one is the one at the end of the
+    /// vector.
+    history: Vec<KId>,
+}
+
+impl QueueContent {
+    /// Get the current kara if it exists.
+    pub fn current(&self) -> Option<KId> {
+        self.current
+    }
+
+    pub fn split_range_per_level(
+        &self,
+        range: impl RangeBounds<usize>,
+    ) -> Vec<(Priority, RangeInclusive<usize>)> {
+        let (mut start, mut end) = (
+            match range.start_bound() {
+                std::ops::Bound::Included(pos) => *pos,
+                std::ops::Bound::Excluded(pos) => pos + 1,
+                std::ops::Bound::Unbounded => 0,
+            },
+            match range.end_bound() {
+                std::ops::Bound::Included(pos) => *pos,
+                std::ops::Bound::Excluded(pos) => pos - 1,
+                std::ops::Bound::Unbounded => self.count(),
+            },
+        );
+        self.queue
+            .iter()
+            .enumerate()
+            .rev()
+            .flat_map(|(idx, lvl)| {
+                let ret = (start <= lvl.len())
+                    .then(|| (Priority::from(idx + 1), start..=(lvl.len() - 1).min(end)));
+                (start, end) = (start - lvl.len(), end - lvl.len());
+                ret
+            })
+            .collect()
+    }
+
+    /// Clear the queue up to the position specified, returning it into the current kara.
+    pub fn play_from_position(&mut self, position: usize) -> Option<KId> {
+        (position < self.count())
+            .then(|| {
+                self.split_range_per_level(0..=position)
+                    .into_iter()
+                    .fold(None, |_, (prio, range)| {
+                        self.queue[prio.index()].drain(range).last()
+                    })
+            })
+            .flatten()
+    }
+
+    /// Shuffle the elemenst in the queue, the kara won't change priorities.
+    pub fn shuffle(&mut self, range: impl RangeBounds<usize> + Copy) {
+        self.split_range_per_level(range)
+            .into_iter()
+            .for_each(|(prio, range)| {
+                self.queue[prio.index()][range].shuffle(&mut thread_rng());
+            });
+    }
+
+    /// Shuffle the elemenst in the level of the queue.
+    pub fn shuffle_level(&mut self, level: Priority) {
+        self.queue[level.index()].shuffle(&mut thread_rng());
+    }
+
+    /// Get the number of element in the range. Handles the out of bound stuff.
+    pub fn range_count(&self, range: impl RangeBounds<usize> + Copy) -> usize {
+        let (start, end) = (
+            match range.start_bound() {
+                std::ops::Bound::Included(pos) => *pos,
+                std::ops::Bound::Excluded(pos) => pos + 1,
+                std::ops::Bound::Unbounded => 0,
+            },
+            match range.end_bound() {
+                std::ops::Bound::Included(pos) => *pos,
+                std::ops::Bound::Excluded(pos) => pos - 1,
+                std::ops::Bound::Unbounded => self.count(),
+            },
+        );
+        end - start
+    }
+
+    /// Move a kara after a position.
+    pub fn move_after(&mut self, range: impl RangeBounds<usize> + Copy, after: usize) {
+        if !range.contains(&after) {
+            let start = match range.start_bound() {
+                std::ops::Bound::Included(start) => *start,
+                std::ops::Bound::Excluded(start) => start - 1,
+                std::ops::Bound::Unbounded => 0,
+            };
+            let mut after = if after < start { after } else { after - start };
+            let count = self.range_count(range);
+            let ids = self.delete_get(range).into_iter().map(|(_, id)| id);
+            for lvl in self.queue.iter_mut().rev() {
+                if after > lvl.len() {
+                    after -= lvl.len();
+                    continue;
+                } else {
+                    lvl.reserve(count);
+                    ids.rev().for_each(|id| lvl.insert(after, id));
+                    break;
+                }
+            }
+        } else {
+            log::error!("can't move a range after a position included in it");
+        }
+    }
+
+    /// Remove elements from a level of the queue.
+    pub fn delete_level(&mut self, level: Priority) {
+        self.queue[level.index()].clear()
+    }
+
+    /// Remove elements from the queue by their position.
+    pub fn delete(&mut self, range: impl RangeBounds<usize> + Copy) {
+        self.split_range_per_level(range)
+            .into_iter()
+            .for_each(|(prio, range)| {
+                self.queue[prio.index()].drain(range);
+            })
+    }
+
+    /// Remove elements from the queue by their position and returns what was deleted.
+    pub fn delete_get(&mut self, range: impl RangeBounds<usize> + Copy) -> Vec<(Priority, KId)> {
+        self.split_range_per_level(range)
+            .into_iter()
+            .flat_map(|(prio, range)| {
+                self.queue[prio.index()]
+                    .drain(range)
+                    .map(move |id| (prio, id))
+                    .collect::<Vec<_>>()
+            })
+            .collect()
+    }
+
+    /// Get the content of the level of the queue.
+    pub fn get_level(&self, prio: Priority) -> Vec<KId> {
+        self.queue[prio.index()].clone()
+    }
+
+    /// Iterate over the queue.
+    pub fn get(&self, range: impl RangeBounds<usize> + Copy) -> Vec<(Priority, KId)> {
+        let iter = self
+            .queue
+            .iter()
+            .enumerate()
+            .map(|(idx, list)| list.iter().map(move |&id| (Priority::from(idx + 1), id)))
+            .rev()
+            .flatten();
+        filter_range(iter, range).collect::<Vec<_>>()
+    }
+
+    /// Returns the number of karas present in the queue.
+    pub fn count(&self) -> usize {
+        self.queue.iter().map(|lvl| lvl.len()).sum()
+    }
+
+    /// Returns the number of elements in each level of the queue
+    pub fn count_per_level(&self) -> [usize; PRIORITY_LENGTH] {
+        [
+            self.queue[Priority::Add.index()].len(),
+            self.queue[Priority::Suggest.index()].len(),
+            self.queue[Priority::Insert.index()].len(),
+            self.queue[Priority::Enforce.index()].len(),
+        ]
+    }
+
+    /// Insert something into one priority of the queue.
+    pub fn insert(&mut self, prio: Priority, kara: KId) {
+        self.queue[prio.index()].insert(0, kara);
+    }
+
+    /// Insert something into one priority of the queue.
+    pub fn insert_slice(&mut self, prio: Priority, kara: &[KId]) {
+        let level = &mut self.queue[prio.index()];
+        level.reserve(kara.len());
+        kara.iter().rev().for_each(|&id| level.insert(0, id));
+    }
+
+    /// Insert something into one priority of the queue.
+    pub fn insert_all(&mut self, prio: Priority, kara: Vec<KId>) {
+        self.insert_slice(prio, &kara)
+    }
+
+    /// Get the next kara to play from the queue and insert the old-playing one into the history.
+    /// The returned kara is the new current one.
+    pub fn next(&mut self) -> Option<KId> {
+        let mut iter = self.queue.iter_mut().rev();
+        let current = iter.find_map(|lvl| (!lvl.is_empty()).then(|| lvl.remove(0)))?;
+        if let Some(current) = self.current.replace(current) {
+            self.history.push(current);
+        }
+        Some(current)
+    }
+
+    /// Get the previous kara to play from the history and insert the old-playing one into the
+    /// queue at the same priority as the next one. The returned kara is the new current one. If
+    /// the queue is empty we add as the default priority.
+    pub fn previous(&mut self) -> Option<KId> {
+        let current = self.history.pop()?;
+        if let Some(current) = self.current.replace(current) {
+            match self.queue.iter_mut().rev().find(|lvl| !lvl.is_empty()) {
+                Some(lvl) => lvl.insert(0, current),
+                None => self.insert(Default::default(), current),
+            }
+        }
+        Some(current)
+    }
+
+    /// Delete all karas by and their id in the queue
+    pub fn delete_all(&mut self, id: KId) {
+        self.queue
+            .iter_mut()
+            .for_each(|lvl| lvl.retain(|kid| id != *kid))
+    }
+
+    /// Delete all karas by and their id from a level in the queue
+    pub fn delete_all_from_level(&mut self, prio: Priority, id: KId) {
+        self.queue[prio.index()].retain(|kid| id.ne(kid))
+    }
+
+    /// Delete all karas by and their id in the history
+    pub fn history_delete_all(&mut self, id: KId) {
+        self.history.retain(|kid| id.ne(kid))
+    }
+
+    /// Returns the number of karas present in the history.
+    pub fn history_count(&self) -> usize {
+        self.history.len()
+    }
+
+    /// Remove elements from the history by their position.
+    pub fn history_delete(&mut self, range: impl RangeBounds<usize> + Copy) -> Vec<KId> {
+        use std::ops::Bound::{self, *};
+        let count = self.history_count();
+        let inverse = |bound: Bound<&usize>| match bound {
+            Included(idx) => Included(count - idx - 1),
+            Excluded(idx) => Excluded(count - idx - 1),
+            Unbounded => Unbounded,
+        };
+        let range =
+            BoundedBoundRange::from((inverse(range.start_bound()), inverse(range.end_bound())));
+        self.history.drain(range).collect()
+    }
+
+    /// Iterate over the history. We iterate from the most recent one to the most old one.
+    pub fn history(&self, range: impl RangeBounds<usize> + Copy) -> Vec<KId> {
+        filter_range(self.history.iter().rev(), range)
+            .cloned()
+            .collect()
+    }
+}
diff --git a/lektord/src/app/routes.rs b/lektord/src/app/routes.rs
index 7f7becf7afbf173b64ce693b31695a29b2c42422..c4f09ecdaca623717a134f42adac0effd2da5d9f 100644
--- a/lektord/src/app/routes.rs
+++ b/lektord/src/app/routes.rs
@@ -6,32 +6,41 @@
 #![forbid(unsafe_code)]
 
 use crate::*;
-use anyhow::{anyhow, Error};
+use anyhow::anyhow;
 use axum::{
     extract::{Path, State},
-    http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
+    http::{header::CONTENT_TYPE, HeaderValue},
     response::{IntoResponse, Response},
     Json,
 };
+use futures::prelude::*;
 use lektor_nkdb::*;
 use lektor_payloads::*;
 use lektor_utils::decode_base64_json;
 use rand::{seq::SliceRandom, thread_rng};
-use std::{ops::RangeBounds, str::FromStr};
+use std::ops::RangeBounds;
 use tokio::task::LocalSet;
 
 /// Get informations abount the lektord server.
+#[axum::debug_handler(state = LektorStatePtr)]
 pub(crate) async fn root(State(state): State<LektorStatePtr>) -> Json<Infos> {
     Json(Infos {
         version: crate::version().to_string(),
-        last_epoch: state.database.last_epoch_num().await,
+        last_epoch: state
+            .database
+            .last_epoch()
+            .map(|epoch| epoch.map(Epoch::num))
+            .await,
     })
 }
 
 /// Get the play state of the server. Be aware of race conditions...
 #[axum::debug_handler(state = LektorStatePtr)]
 pub(crate) async fn get_state(State(state): State<LektorStatePtr>) -> Json<PlayStateWithCurrent> {
-    let (state, current) = state.database.current().await;
+    let (state, current) = state
+        .queue()
+        .read(|content, state| (state, content.current()))
+        .await;
     let current = current.map(|id| {
         let elapsed = c_wrapper::player_get_elapsed()
             .map_err(|err| log::error!("{err}"))
@@ -64,14 +73,11 @@ pub(crate) async fn play_from_queue_pos(
     Path(position): Path<usize>,
 ) -> Result<(), LektordError> {
     let id = state
-        .database
-        .play_from_position(position)
+        .queue()
+        .write(|content, _| content.play_from_position(position))
         .await
-        .ok_or(anyhow!("position {position} doesn't exists in queue"))?;
-    crate::c_wrapper::player_load_file(
-        state.database.get_kara_uri(id.clone())?.to_string(),
-        id.local_id(),
-    )?;
+        .with_context(|| format!("position {position} doesn't exists in queue"))?;
+    crate::c_wrapper::player_load_file(state.database.get_kara_uri(id)?.to_string(), id)?;
     Ok(())
 }
 
@@ -82,17 +88,22 @@ pub(crate) async fn set_play_state(
     Json(to): Json<PlayState>,
 ) -> Result<(), LektordError> {
     if to == PlayState::Stop {
-        Ok(c_wrapper::player_stop()?)
-    } else {
-        match state.database.current().await {
-            (PlayState::Stop, _) if to == PlayState::Play => Ok(state.play_next().await?),
-            (PlayState::Stop, _) => {
-                log::trace!("playback is set to stopped, can't set it to {to:?}");
-                Ok(())
+        c_wrapper::player_stop()?
+    } else if state
+        .queue()
+        .write(|_, state| match state.get() {
+            PlayState::Stop if to == PlayState::Play => Ok(true),
+            stop @ PlayState::Stop => {
+                log::trace!("can't change playback from {stop:?} to {to:?}");
+                Ok(false)
             }
-            _ => Ok(c_wrapper::player_set_paused(to)?),
-        }
+            _ => c_wrapper::player_set_paused(to).map(|_| false),
+        })
+        .await?
+    {
+        state.play_next().await?
     }
+    Ok(())
 }
 
 /// Toggle the play state of the server, Be aware of race conditions...
@@ -101,35 +112,15 @@ pub(crate) async fn toggle_play_state() -> Result<(), LektordError> {
     Ok(crate::c_wrapper::player_toggle_pause()?)
 }
 
-/// Get all the informations about a kara by its id. Returns the kara as a json object to avoid
-/// cloning the kara structure and directly return the serialized object.
-#[axum::debug_handler(state = LektorStatePtr)]
-pub(crate) async fn get_kara_by_id(
-    State(state): State<LektorStatePtr>,
-    Path(id): Path<u64>,
-) -> Result<Response, LektordError> {
-    let mut kara = serde_json::to_string(state.database.get_kara_by_id(id).await?)
-        .map_err(|err| anyhow!("{err}"))?
-        .into_response();
-    kara.headers_mut()
-        .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
-    Ok(kara)
-}
-
 /// Get all the informations about a kara by its id. Returns the kara as a json object to avoid
 /// cloning the kara structure and directly return the serialized object.
 #[axum::debug_handler(state = LektorStatePtr)]
 pub(crate) async fn get_kara_by_kid(
     State(state): State<LektorStatePtr>,
-    Path(id): Path<String>,
+    Path(id): Path<KId>,
 ) -> Result<Response, LektordError> {
-    let id = state
-        .database
-        .get_kid_from_str(&decode_base64(&id)?)
-        .await
-        .ok_or_else(|| anyhow!("no kara found with id {id}"))?;
     let mut kara = serde_json::to_string(state.database.get_kara_by_kid(id).await?)
-        .map_err(|err| anyhow!("{err}"))?
+        .context("failed to find kara with id")?
         .into_response();
     kara.headers_mut()
         .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
@@ -144,7 +135,8 @@ pub(crate) async fn search(
     Path(uri): Path<String>,
 ) -> Result<Json<Vec<KId>>, LektordError> {
     let SearchData { from, regex } = decode_base64_json(uri)?;
-    Ok(Json(state.database.search(from, regex).await?))
+    // Ok(Json(state.database.search(from, regex).await?))
+    todo!()
 }
 
 /// Count karas available in a search set. We take the json argument as base64 encoded
@@ -155,21 +147,39 @@ pub(crate) async fn count(
     Path(uri): Path<String>,
 ) -> Result<Json<usize>, LektordError> {
     let SearchData { from, regex } = decode_base64_json(uri)?;
-    Ok(Json(state.database.count(from, regex).await))
+    // Ok(Json(state.database.count(from, regex).await))
+    todo!()
 }
 
 /// Get all playlists from the database with their associated informations.
 #[axum::debug_handler(state = LektorStatePtr)]
-pub(crate) async fn get_playlists(
+pub(crate) async fn get_playlists(State(state): State<LektorStatePtr>) -> Json<Vec<(KId, String)>> {
+    Json(state.database.playlists().list().await)
+}
+
+/// Get the informations relative to a specific playlist.
+#[axum::debug_handler(state = LektorStatePtr)]
+pub(crate) async fn get_playlist_info(
     State(state): State<LektorStatePtr>,
-) -> Json<Vec<(lektor_payloads::PlaylistName, PlaylistInfo)>> {
-    Json(Vec::from_iter(
-        state
-            .database
-            .playlists()
-            .await
-            .into_iter()
-            .map(|(n, info)| (n.into(), info)),
+    Path(id): Path<KId>,
+) -> Result<Json<PlaylistInfo>, LektordError> {
+    Ok(Json(
+        (state.database.playlists())
+            .read(id, |plt| plt.get_infos())
+            .await?,
+    ))
+}
+
+/// Get everything about a specific playlist.
+#[axum::debug_handler(state = LektorStatePtr)]
+pub(crate) async fn get_playlist(
+    State(state): State<LektorStatePtr>,
+    Path(id): Path<KId>,
+) -> Result<Json<Playlist>, LektordError> {
+    Ok(Json(
+        (state.database.playlists())
+            .read(id, |plt| plt.clone())
+            .await?,
     ))
 }
 
@@ -177,14 +187,13 @@ pub(crate) async fn get_playlists(
 #[axum::debug_handler(state = LektorStatePtr)]
 pub(crate) async fn get_playlist_content(
     State(state): State<LektorStatePtr>,
-    name: lektor_payloads::PlaylistName,
+    Path(id): Path<KId>,
 ) -> Result<Json<Vec<KId>>, LektordError> {
-    Ok(state
-        .database
-        .playlist_get_content(&name)
-        .await
-        .ok_or_else(|| Error::msg(format!("failed to get content of playlist {name}")))
-        .map(Json)?)
+    Ok(Json(
+        (state.database.playlists())
+            .read(id, |plt| plt.iter_seq_content().collect::<Vec<_>>())
+            .await?,
+    ))
 }
 
 /// Update the content of a playlist. For that the client must be the owner of the playlist or the
@@ -193,42 +202,60 @@ pub(crate) async fn get_playlist_content(
 pub(crate) async fn update_playlist(
     State(state): State<LektorStatePtr>,
     user_infos: LektorUser,
-    name: lektor_payloads::PlaylistName,
+    Path(id): Path<KId>,
     Json(action): Json<PlaylistUpdateAction>,
 ) -> Result<(), LektordError> {
-    let user_infos = state.verify_user(user_infos);
+    let user_infos = state.verify_user(user_infos).maybe_user()?;
     let (uin, uia) = (user_infos.name(), user_infos.is_admin());
-    let db = &state.database;
-    let name = name.into();
+    let db = &state.database.playlists();
 
-    Ok(match action {
-        PlaylistUpdateAction::Delete => db.playlist_delete(name, uin, uia).await,
+    match action {
+        PlaylistUpdateAction::Delete => db.delete(id, uin, uia).await?,
         PlaylistUpdateAction::GiveTo(new_user) => {
-            db.playlist_give_to(name, new_user, uin, uia).await
+            db.write(id, uin, uia, |plt| _ = plt.add_owner(new_user))
+                .await?
         }
         PlaylistUpdateAction::Rename(new_name) => {
-            db.playlist_rename(name, new_name.into(), uin, uia).await
+            db.write(id, uin, uia, |plt| _ = plt.set_name(new_name))
+                .await?
         }
         PlaylistUpdateAction::Remove(filter) => match filter {
-            KaraFilter::KId(kid) => db.playlist_delete_id(name, kid, uin, uia).await,
-            KaraFilter::List(_, kids) => db.playlist_delete_ids(name, kids, uin, uia).await,
+            KaraFilter::KId(kid) => db.write(id, uin, uia, |plt| plt.remove(kid)).await?,
+            KaraFilter::List(_, kids) => {
+                db.write(id, uin, uia, move |plt| plt.retain(|id| !kids.contains(id)))
+                    .await?
+            }
             KaraFilter::Playlist(_, from) => {
-                db.playlist_delete_playlist(name, from, uin, uia).await
+                let kids = db
+                    .read(from, |plt| plt.iter_seq_content().collect::<Vec<_>>())
+                    .await?;
+                db.write(id, uin, uia, move |plt| plt.retain(|id| !kids.contains(id)))
+                    .await?
             }
         },
 
         PlaylistUpdateAction::Add(filter) => match filter {
-            KaraFilter::KId(kid) => db.playlist_add_id(name, kid, uin, uia).await,
+            KaraFilter::KId(kid) => db.write(id, uin, uia, |plt| plt.push(kid)).await?,
             KaraFilter::Playlist(rand, from) => {
-                db.playlist_add_playlist(name, from, rand, uin, uia).await
+                let mut kids = db
+                    .read(from, |plt| plt.iter_seq_content().collect::<Vec<_>>())
+                    .await?;
+                if rand {
+                    kids[..].shuffle(&mut thread_rng());
+                }
+                db.write(id, uin, uia, move |plt| plt.append(&mut kids))
+                    .await?
             }
-            KaraFilter::List(false, kids) => db.playlist_add_ids(name, kids, uin, uia).await,
-            KaraFilter::List(true, mut kids) => {
-                kids[..].shuffle(&mut thread_rng());
-                db.playlist_add_ids(name, kids, uin, uia).await
+            KaraFilter::List(rand, mut kids) => {
+                if rand {
+                    kids[..].shuffle(&mut thread_rng());
+                }
+                db.write(id, uin, uia, move |plt| plt.append(&mut kids))
+                    .await?
             }
         },
-    }?)
+    }
+    Ok(())
 }
 
 /// Delete a playlist. For that the client must be the owner of the playlist or the super user of
@@ -237,13 +264,13 @@ pub(crate) async fn update_playlist(
 pub(crate) async fn delete_playlist(
     State(state): State<LektorStatePtr>,
     user_infos: LektorUser,
-    name: lektor_payloads::PlaylistName,
+    Path(id): Path<KId>,
 ) -> Result<(), LektordError> {
-    let user_infos = state.verify_user(user_infos);
-    Ok(state
-        .database
-        .playlist_delete(name, user_infos.name(), user_infos.is_admin())
-        .await?)
+    let user_infos = state.verify_user(user_infos).maybe_user()?;
+    (state.database.playlists())
+        .delete(id, user_infos.name(), user_infos.is_admin())
+        .await?;
+    Ok(())
 }
 
 /// Create an empty playlist. The user must be identified or the playlist will belong to anyone.
@@ -251,13 +278,19 @@ pub(crate) async fn delete_playlist(
 pub(crate) async fn create_playlist(
     State(state): State<LektorStatePtr>,
     user_infos: LektorUser,
-    name: lektor_payloads::PlaylistName,
+    Path(name): Path<String>,
 ) -> Result<(), LektordError> {
-    let info = match state.verify_user(user_infos) {
-        LektorUser::Unverified(_, _) => PlaylistInfo::new(),
-        LektorUser::User(n, _) | LektorUser::Admin(n, _) => PlaylistInfo::new().user(n),
-    };
-    Ok(state.database.playlist_new(name, info).await?)
+    let user = state
+        .verify_user(user_infos)
+        .maybe_user()
+        .map(|user| user.into_parts().0);
+    (state.database.playlists())
+        .create(name, |plt| match user {
+            Ok(user) => plt.with_owner_sync(user),
+            _ => plt,
+        })
+        .await?;
+    Ok(())
 }
 
 /// Update the database from the remotes. The user must be the super user to do that. We can't use
@@ -268,11 +301,7 @@ pub(crate) async fn adm_update(
     State(state): State<LektorStatePtr>,
     admin: LektorUser,
 ) -> Result<(), LektordError> {
-    let admin = match state.verify_user(admin) {
-        user @ LektorUser::Admin(_, _) => user,
-        user => return Err(anyhow!("user {user:?} is not an admin").into()),
-    };
-
+    let admin = state.verify_user(admin).maybe_admin()?;
     log::info!("launching update requested by {admin:?}");
     std::thread::spawn(move || {
         let local = LocalSet::new();
@@ -281,7 +310,6 @@ pub(crate) async fn adm_update(
                 let handle = state.database.update().await;
                 let count = state.repo.update_with(&handle).await?;
                 handle.finished().await;
-                state.database.refresh_playlist_contents().await;
                 Ok::<_, anyhow::Error>(count)
             })
             .await
@@ -295,7 +323,7 @@ pub(crate) async fn adm_update(
             .max_blocking_threads(1024)
             .enable_all()
             .build()
-            .expect("failed to build tokio runtime to update database from repo")
+            .expect("can't build tokio runtime")
             .block_on(local);
     });
     Ok(())
@@ -306,29 +334,35 @@ pub(crate) async fn adm_update(
 pub(crate) async fn adm_shutdown(
     State(state): State<LektorStatePtr>,
     admin: LektorUser,
-) -> StatusCode {
-    match state.verify_user(admin) {
-        admin @ LektorUser::Admin(..) => {
-            log::info!("shutdown requested by {admin:?}");
-            if let Some(shutdown) = state.shutdown.write().await.take() {
-                let _ = shutdown.send(());
-            }
-            StatusCode::OK
-        }
-        _ => StatusCode::FORBIDDEN,
-    }
+) -> Result<(), LektordError> {
+    let admin = state.verify_user(admin).maybe_admin()?;
+    log::info!("shutdown requested by {admin:?}");
+    (state.shutdown.write().await.take())
+        .map(|shutdown| _ = shutdown.send(()))
+        .context("failed to send shutdown signal")?;
+    Ok(())
 }
 
 #[axum::debug_handler(state = LektorStatePtr)]
 pub(crate) async fn get_queue_count(
     State(state): State<LektorStatePtr>,
 ) -> Json<[usize; PRIORITY_LENGTH]> {
-    Json(state.database.queue_count().await)
+    Json(
+        state
+            .queue()
+            .read(|content, _| content.count_per_level())
+            .await,
+    )
 }
 
 #[axum::debug_handler(state = LektorStatePtr)]
 pub(crate) async fn get_history_count(State(state): State<LektorStatePtr>) -> Json<usize> {
-    Json(state.database.history_count().await)
+    Json(
+        state
+            .queue()
+            .read(|content, _| content.history_count())
+            .await,
+    )
 }
 
 /// Returns the karas in the indicated range of the queue.
@@ -339,8 +373,8 @@ pub(crate) async fn get_queue_range(
 ) -> Json<Vec<(Priority, KId)>> {
     Json(
         state
-            .database
-            .queue((range.start_bound(), range.end_bound()))
+            .queue()
+            .read(|content, _| content.get((range.start_bound(), range.end_bound())))
             .await,
     )
 }
@@ -350,26 +384,41 @@ pub(crate) async fn get_queue_range(
 pub(crate) async fn add_to_queue(
     State(state): State<LektorStatePtr>,
     Json(QueueAddAction { priority, action }): Json<QueueAddAction>,
-) {
+) -> Result<(), LektordError> {
     match action {
-        KaraFilter::KId(id) => state.database.queue_insert(priority, id).await,
-        KaraFilter::List(false, ids) => state.database.queue_insert_all(priority, ids).await,
-        KaraFilter::List(true, mut ids) => {
-            ids[..].shuffle(&mut thread_rng());
-            state.database.queue_insert_all(priority, ids).await
+        KaraFilter::KId(id) => {
+            state
+                .queue()
+                .write(|content, _| content.insert(priority, id))
+                .await
         }
-        KaraFilter::Playlist(true, plt) => {
-            if let Some(mut plt) = state.database.playlist_get_content(plt).await {
-                plt[..].shuffle(&mut thread_rng());
-                state.database.queue_insert_all(priority, plt).await
+        KaraFilter::List(shuffle, mut ids) => {
+            if shuffle {
+                ids[..].shuffle(&mut thread_rng());
             }
+            state
+                .queue()
+                .write(|content, _| content.insert_all(priority, ids))
+                .await
         }
-        KaraFilter::Playlist(false, plt) => {
-            if let Some(plt) = state.database.playlist_get_content(plt).await {
-                state.database.queue_insert_all(priority, plt).await
-            }
+        KaraFilter::Playlist(shuffle, plt) => {
+            let karas = (state.database.playlists())
+                .read(plt, |plt| match shuffle {
+                    true => {
+                        let mut content: Vec<_> = plt.iter_uniq_content().collect();
+                        content[..].shuffle(&mut thread_rng());
+                        content
+                    }
+                    false => plt.iter_seq_content().collect(),
+                })
+                .await?;
+            state
+                .queue()
+                .write(|content, _| content.insert_all(priority, karas))
+                .await
         }
     }
+    Ok(())
 }
 
 /// Enqueue a kara in the queue, at a specific priority.
@@ -378,7 +427,10 @@ pub(crate) async fn add_kid_to_queue(
     State(state): State<LektorStatePtr>,
     Path((prio, kid)): Path<(Priority, KId)>,
 ) {
-    state.database.queue_insert(prio, kid).await
+    state
+        .queue()
+        .write(|content, _| content.insert(prio, kid))
+        .await
 }
 
 /// Remove a kara from the queue, only a specific priority.
@@ -387,7 +439,36 @@ pub(crate) async fn delete_kid_from_queue(
     State(state): State<LektorStatePtr>,
     Path((prio, kid)): Path<(Priority, KId)>,
 ) {
-    state.database.queue_delete_all_from_level(prio, kid).await
+    state
+        .queue()
+        .write(|content, _| content.delete_all_from_level(prio, kid))
+        .await
+}
+
+/// Get the queue level
+#[axum::debug_handler(state = LektorStatePtr)]
+pub(crate) async fn get_queue_level(
+    State(state): State<LektorStatePtr>,
+    Path(prio): Path<Priority>,
+) -> Json<Vec<KId>> {
+    Json(
+        state
+            .queue()
+            .read(|content, _| content.get_level(prio))
+            .await,
+    )
+}
+
+/// Delete the queue level
+#[axum::debug_handler(state = LektorStatePtr)]
+pub(crate) async fn delete_queue_level(
+    State(state): State<LektorStatePtr>,
+    Path(prio): Path<Priority>,
+) {
+    state
+        .queue()
+        .write(|content, _| content.delete_level(prio))
+        .await
 }
 
 /// Shuffle a level of the queue.
@@ -396,7 +477,10 @@ pub(crate) async fn shuffle_queue_level(
     State(state): State<LektorStatePtr>,
     Path(prio): Path<Priority>,
 ) {
-    state.database.queue_shuffle_level(prio).await
+    state
+        .queue()
+        .write(|content, _| content.shuffle_level(prio))
+        .await
 }
 
 /// Get informations from the queue.
@@ -405,20 +489,21 @@ pub(crate) async fn get_from_queue(
     State(state): State<LektorStatePtr>,
     Path(level): Path<Priority>,
 ) -> Json<Vec<KId>> {
-    Json(state.database.queue_get_level(level).await)
+    Json(
+        state
+            .queue()
+            .read(|content, _| content.get_level(level))
+            .await,
+    )
 }
 
 /// Remove something from the queue, can be a level, a kara, etc.
 #[axum::debug_handler(state = LektorStatePtr)]
-pub(crate) async fn delete_from_queue(
-    State(state): State<LektorStatePtr>,
-    Path(what): Path<String>,
-) {
-    match Priority::from_str(&what) {
-        Ok(prio) => state.database.queue_delete_level(prio).await,
-        // Not a priority, try to parse a KId and delete it.
-        _ => state.database.queue_delete_all_maybe_id(what).await,
-    }
+pub(crate) async fn delete_from_queue(State(state): State<LektorStatePtr>, Path(kid): Path<KId>) {
+    state
+        .queue()
+        .write(|content, _| content.delete_all(kid))
+        .await
 }
 
 /// Remove a kara from the history, each occurances.
@@ -427,7 +512,10 @@ pub(crate) async fn delete_kid_from_history(
     State(state): State<LektorStatePtr>,
     Path(id): Path<KId>,
 ) {
-    state.database.history_delete_all(id).await
+    state
+        .queue()
+        .write(|content, _| content.history_delete_all(id))
+        .await
 }
 
 /// Updates the karas in the indicated range of the queue.
@@ -437,18 +525,35 @@ pub(crate) async fn update_queue_range(
     range: Range,
     Json(action): Json<QueueUpdateAction>,
 ) {
-    use QueueUpdateAction::*;
     match action {
-        Shuffle => state.database.queue_shuffle(range).await,
-        Delete => state.database.queue_delete(range).await,
-        MoveAfter(after) => state.database.queue_move_after(range, after).await,
+        QueueUpdateAction::Shuffle => {
+            state
+                .queue()
+                .write(|content, _| content.shuffle(range))
+                .await
+        }
+        QueueUpdateAction::Delete => {
+            state
+                .queue()
+                .write(|content, _| content.delete(range))
+                .await
+        }
+        QueueUpdateAction::MoveAfter(after) => {
+            state
+                .queue()
+                .write(|content, _| content.move_after(range, after))
+                .await
+        }
     }
 }
 
 /// Removes the karas in the indicated range of the queue.
 #[axum::debug_handler(state = LektorStatePtr)]
 pub(crate) async fn delete_queue_range(State(state): State<LektorStatePtr>, range: Range) {
-    state.database.queue_delete(range).await;
+    state
+        .queue()
+        .write(|content, _| content.delete(range))
+        .await
 }
 
 /// Returns the karas in the indicated range of the history.
@@ -457,7 +562,12 @@ pub(crate) async fn get_history_range(
     State(state): State<LektorStatePtr>,
     range: Range,
 ) -> Json<Vec<KId>> {
-    Json(state.database.history(range).await)
+    Json(
+        state
+            .queue()
+            .write(|content, _| content.history(range))
+            .await,
+    )
 }
 
 /// Removes the karas in the indicated range of the history.
@@ -466,5 +576,10 @@ pub(crate) async fn delete_history_range(
     State(state): State<LektorStatePtr>,
     range: Range,
 ) -> Json<Vec<KId>> {
-    Json(state.database.history_delete(range).await)
+    Json(
+        state
+            .queue()
+            .write(|content, _| content.history_delete(range))
+            .await,
+    )
 }
diff --git a/lektord/src/c_wrapper/commands.rs b/lektord/src/c_wrapper/commands.rs
index 6132438b77931633a21745bde56736cf5ee58acc..c12fe75a376d7976f2862d0f76b06269a51dde1f 100644
--- a/lektord/src/c_wrapper/commands.rs
+++ b/lektord/src/c_wrapper/commands.rs
@@ -1,6 +1,6 @@
 use super::{PlayerEvent, STATE};
 use anyhow::{bail, Context, Error, Result};
-use lektor_nkdb::PlayState;
+use lektor_payloads::{KId, PlayState};
 use std::{ffi::*, path::Path, ptr::NonNull};
 
 /// Safe wrapper around `mod_stop_playback`. Stops the playback. Same as [player_toggle_pause], don't update
@@ -77,7 +77,7 @@ pub(crate) fn player_get_elapsed() -> Result<i64> {
 }
 
 /// Safe wrapper around `mod_load_file` Tell the player to load a file by its path.
-pub(crate) fn player_load_file(path: impl AsRef<Path>, id: u64) -> Result<()> {
+pub(crate) fn player_load_file(path: impl AsRef<Path>, id: KId) -> Result<()> {
     extern "C" {
         fn mod_load_file(_: NonNull<c_char>, _: u64) -> c_int;
     }
@@ -86,7 +86,7 @@ pub(crate) fn player_load_file(path: impl AsRef<Path>, id: u64) -> Result<()> {
         .to_str()
         .ok_or_else(|| Error::msg("path contained non-utf8 characters"))?;
     let cstr = CString::new(path)?;
-    (0 == unsafe { mod_load_file(NonNull::new_unchecked(cstr.as_ptr() as *mut _), id) })
+    (0 == unsafe { mod_load_file(NonNull::new_unchecked(cstr.as_ptr() as *mut _), id.into()) })
         .then_some(())
         .with_context(|| format!("failed load file {path}"))
 }
diff --git a/lektord/src/c_wrapper/mod.rs b/lektord/src/c_wrapper/mod.rs
index 23c595e596987ef82784757a8af6977d5ec160e5..97b4d3c79288753929436443d580475a9c14df34 100644
--- a/lektord/src/c_wrapper/mod.rs
+++ b/lektord/src/c_wrapper/mod.rs
@@ -2,7 +2,6 @@
 
 use crate::LektorStatePtr;
 use anyhow::{bail, ensure, Context, Result};
-use lektor_nkdb::PlayState;
 use lektor_utils::config::LektorPlayerConfig;
 use std::{
     ffi::*,
@@ -69,36 +68,51 @@ pub(crate) fn init_player_module(ptr: LektorStatePtr, config: LektorPlayerConfig
         force_x11,
     } = config;
 
-    unsafe {
-        // We must use non-async stuff to propagate things from the player module back to the tokio
-        // runtime... The code called form C/C++ will use `sender.blocking_send`.
-        let (sender, mut receiver) = tokio::sync::mpsc::channel::<PlayerEvent>(10);
-        let weak_ptr = Arc::downgrade(&ptr);
-        let handle = tokio::task::spawn(async move {
-            while let Some(event) = receiver.recv().await {
-                use {LktPlayState as LPS, PlayState as PS};
-                match event {
-                    PlayerEvent::SetPlayState(state) => match LktPlayState::try_from(state) {
-                        Ok(LPS::Stop) => ptr.database.set_playstate(PS::Stop).await,
-                        Ok(LPS::Play) => ptr.database.set_playstate(PS::Play).await,
-                        Ok(LPS::Pause) => ptr.database.set_playstate(PS::Pause).await,
-                        Ok(LPS::Toggle) => ptr.database.toggle_playstate().await,
-                        Err(e) => log::error!("invalid playstate: {e}"),
-                    },
-                    PlayerEvent::PlayNext => {
-                        if let Some(ptr) = weak_ptr.upgrade() {
-                            let _ = ptr.play_next().await.map_err(|e| log::error!("{e}"));
-                        }
+    // We must use non-async stuff to propagate things from the player module back to the tokio
+    // runtime... The code called form C/C++ will use `sender.blocking_send`.
+    let (sender, mut receiver) = tokio::sync::mpsc::channel::<PlayerEvent>(10);
+    let weak_ptr = Arc::downgrade(&ptr);
+    let handle = tokio::task::spawn(async move {
+        while let Some(event) = receiver.recv().await {
+            use {lektor_payloads::PlayState as PS, LktPlayState as LPS};
+            match event {
+                PlayerEvent::SetPlayState(state) => match LktPlayState::try_from(state) {
+                    Ok(state @ (LPS::Stop | LPS::Play | LPS::Pause)) => {
+                        (ptr.queue())
+                            .write(|_, stored| {
+                                stored.set(match state {
+                                    LPS::Pause => PS::Pause,
+                                    LPS::Play => PS::Play,
+                                    _ => PS::Stop,
+                                })
+                            })
+                            .await
+                    }
+                    Ok(LPS::Toggle) => {
+                        (ptr.queue())
+                            .write(|_, stored| match stored.get() {
+                                PS::Stop | PS::Pause => stored.set(PS::Play),
+                                PS::Play => stored.set(PS::Pause),
+                            })
+                            .await
+                    }
+                    Err(e) => log::error!("invalid playstate: {e}"),
+                },
+                PlayerEvent::PlayNext => {
+                    if let Some(ptr) = weak_ptr.upgrade() {
+                        let _ = ptr.play_next().await.map_err(|e| log::error!("{e}"));
                     }
-                    PlayerEvent::PlayPrev => {
-                        if let Some(ptr) = weak_ptr.upgrade() {
-                            let _ = ptr.play_previous().await.map_err(|e| log::error!("{e}"));
-                        }
+                }
+                PlayerEvent::PlayPrev => {
+                    if let Some(ptr) = weak_ptr.upgrade() {
+                        let _ = ptr.play_previous().await.map_err(|e| log::error!("{e}"));
                     }
                 }
             }
-        });
+        }
+    });
 
+    unsafe {
         // Set the state for the C/C++ code
         ensure!(
             STATE.set((sender, handle)).is_ok(),
diff --git a/lektord/src/c_wrapper/playstate.rs b/lektord/src/c_wrapper/playstate.rs
index d77d0fc511d11ea3f2398767ce92078cc216480c..9221061f7576fce455156664eeff90c2339e62cf 100644
--- a/lektord/src/c_wrapper/playstate.rs
+++ b/lektord/src/c_wrapper/playstate.rs
@@ -1,4 +1,4 @@
-use lektor_nkdb::PlayState;
+use lektor_payloads::PlayState;
 use std::ffi::c_int;
 
 /// The play state that can be send from/to the player module. They don't directly map to what is
diff --git a/lektord/src/lib.rs b/lektord/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fa39c033ffc7a7340ad04dc58fe984a69f2ae51c
--- /dev/null
+++ b/lektord/src/lib.rs
@@ -0,0 +1,106 @@
+mod app;
+mod c_wrapper;
+pub mod cmd;
+pub mod config;
+mod error;
+
+include!(concat!(env!("OUT_DIR"), "/lektord_build_infos.rs"));
+
+use crate::{app::*, config::*, error::*};
+use anyhow::{Context, Result};
+use futures::{stream::FuturesUnordered, TryStreamExt as _};
+use hyper::service::service_fn;
+use hyper_util::{
+    rt::{TokioExecutor, TokioIo},
+    server::conn::auto::Builder as ServerBuilder,
+};
+use std::net::SocketAddr;
+use tokio::{net::TcpListener, signal, sync::oneshot::Receiver};
+use tower::Service;
+
+/// Launches the server, for each socket to listen to we have one task. We do that to be able to
+/// handle each sockets in a concurrent way.
+pub async fn launch_server(config: LektorConfig) -> Result<()> {
+    // Write to apply any changes...
+    log::info!("starting the lektord daemon");
+    lektor_utils::config::write_config_async("lektord", config.clone()).await?;
+
+    // Init the application.
+    let addrs = config.listen.clone();
+    let (app, shutdown) = app(config).await.context("failed to build service")?;
+
+    // Launch an instance for each socket to listen to.
+    FuturesUnordered::from_iter(addrs.into_iter().map(|listen| async move {
+        TcpListener::bind(listen)
+            .await
+            .map(|socket| (listen, socket))
+            .with_context(|| format!("failed to bind to {listen}"))
+    }))
+    .try_collect::<Vec<_>>()
+    .await?
+    .into_iter()
+    .for_each(|(addr, socket)| {
+        tokio::spawn(server_instance(addr, socket, app.clone()));
+    });
+
+    // Wait for terminazon...
+    shutdown_signal(shutdown).await;
+    Ok(())
+}
+
+/// Have the instance of the server for one socket that we listen to. For each client we will
+/// create a new task to be able to handle clients concurrently for one socket.
+async fn server_instance(addr: SocketAddr, socket: TcpListener, app: axum::Router) {
+    loop {
+        let Ok((stream, client)) = socket.accept().await else {
+            return log::error!("failed to accept socket at {addr}");
+        };
+        let app = app.clone(); // One thread per client, they all share the same state!
+        tokio::spawn(async move {
+            ServerBuilder::new(TokioExecutor::new())
+                .serve_connection(TokioIo::new(stream), service_fn(|r| app.clone().call(r)))
+                .await
+                .inspect_err(|e| log::error!("failed to serve {client} from {addr}: {e}"))
+        });
+    }
+}
+
+/// Gracefull ctrl+c handling.
+async fn shutdown_signal(shutdown: Receiver<()>) {
+    let shutdown = async {
+        let _ = shutdown.await;
+        log::info!("shutdown signal!")
+    };
+
+    let ctrl_c = async {
+        signal::ctrl_c()
+            .await
+            .expect("failed to install Ctrl+C handler");
+        log::info!("ctrl+c signal!")
+    };
+
+    #[cfg(unix)]
+    let terminate = async {
+        signal::unix::signal(signal::unix::SignalKind::terminate())
+            .expect("failed to install signal handler")
+            .recv()
+            .await;
+        log::info!("terminate signal!")
+    };
+
+    #[cfg(not(unix))]
+    let terminate = std::future::pending::<()>();
+
+    tokio::select! {
+        _ = ctrl_c => {},
+        _ = terminate => {},
+        _ = shutdown => {},
+    }
+
+    log::info!("received termination signal shutting down, try to unregister the state from the player module");
+    let _ = c_wrapper::close_player_module()
+        .await
+        .map_err(|e| log::error!("{e}"));
+    log::info!("will now call exit(EXIT_SUCCESS)");
+    std::process::exit(0);
+}
diff --git a/lektord/src/main.rs b/lektord/src/main.rs
index 42035fdc16fa3c0dd06d1612ea33f38994deefc0..7d532034dcb3a54adc94711fb59e13f3fd320fae 100644
--- a/lektord/src/main.rs
+++ b/lektord/src/main.rs
@@ -1,28 +1,10 @@
-mod app;
-pub mod c_wrapper;
-mod cmd;
-mod config;
-mod error;
-
-include!(concat!(env!("OUT_DIR"), "/lektord_build_infos.rs"));
-
-use crate::{app::*, config::*, error::*};
 use anyhow::{Context, Result};
-use cmd::SubCommand;
-use futures::{stream::FuturesUnordered, TryStreamExt as _};
 use lektor_utils::*;
-
-// Server things
-
-use hyper::service::service_fn;
-use hyper_util::{
-    rt::{TokioExecutor, TokioIo},
-    server::conn::auto::Builder as ServerBuilder,
+use lektord::{
+    cmd::{self, SubCommand},
+    config::*,
 };
-use std::net::SocketAddr;
 use std::sync::atomic::{AtomicU64, Ordering};
-use tokio::{net::TcpListener, signal, sync::oneshot::Receiver};
-use tower::Service;
 
 fn main() -> Result<()> {
     logger::Builder::default()
@@ -62,95 +44,8 @@ fn main() -> Result<()> {
             .enable_all()
             .thread_stack_size(3 * 1024 * 1024) // 3Mio for each thread, should be enaugh
             .build()?
-            .block_on(launch_server(config)),
+            .block_on(lektord::launch_server(config)),
 
         args => unreachable!("{args:?}"),
     }
 }
-
-/// Launches the server, for each socket to listen to we have one task. We do that to be able to
-/// handle each sockets in a concurrent way.
-async fn launch_server(config: LektorConfig) -> Result<()> {
-    // Write to apply any changes...
-    log::info!("starting the lektord daemon");
-    lektor_utils::config::write_config_async("lektord", config.clone()).await?;
-
-    // Init the application.
-    let addrs = config.listen.clone();
-    let (app, shutdown) = app(config).await.context("failed to build service")?;
-
-    // Launch an instance for each socket to listen to.
-    FuturesUnordered::from_iter(addrs.into_iter().map(|listen| async move {
-        TcpListener::bind(listen)
-            .await
-            .map(|socket| (listen, socket))
-            .with_context(|| format!("failed to bind to {listen}"))
-    }))
-    .try_collect::<Vec<_>>()
-    .await?
-    .into_iter()
-    .for_each(|(addr, socket)| {
-        tokio::spawn(server_instance(addr, socket, app.clone()));
-    });
-
-    // Wait for terminazon...
-    shutdown_signal(shutdown).await;
-    Ok(())
-}
-
-/// Have the instance of the server for one socket that we listen to. For each client we will
-/// create a new task to be able to handle clients concurrently for one socket.
-async fn server_instance(addr: SocketAddr, socket: TcpListener, app: axum::Router) {
-    loop {
-        let Ok((stream, client)) = socket.accept().await else {
-            return log::error!("failed to accept socket at {addr}");
-        };
-        let app = app.clone(); // One thread per client, they all share the same state!
-        tokio::spawn(async move {
-            ServerBuilder::new(TokioExecutor::new())
-                .serve_connection(TokioIo::new(stream), service_fn(|r| app.clone().call(r)))
-                .await
-                .inspect_err(|e| log::error!("failed to serve {client} from {addr}: {e}"))
-        });
-    }
-}
-
-/// Gracefull ctrl+c handling.
-async fn shutdown_signal(shutdown: Receiver<()>) {
-    let shutdown = async {
-        let _ = shutdown.await;
-        log::info!("shutdown signal!")
-    };
-
-    let ctrl_c = async {
-        signal::ctrl_c()
-            .await
-            .expect("failed to install Ctrl+C handler");
-        log::info!("ctrl+c signal!")
-    };
-
-    #[cfg(unix)]
-    let terminate = async {
-        signal::unix::signal(signal::unix::SignalKind::terminate())
-            .expect("failed to install signal handler")
-            .recv()
-            .await;
-        log::info!("terminate signal!")
-    };
-
-    #[cfg(not(unix))]
-    let terminate = std::future::pending::<()>();
-
-    tokio::select! {
-        _ = ctrl_c => {},
-        _ = terminate => {},
-        _ = shutdown => {},
-    }
-
-    log::info!("received termination signal shutting down, try to unregister the state from the player module");
-    let _ = c_wrapper::close_player_module()
-        .await
-        .map_err(|e| log::error!("{e}"));
-    log::info!("will now call exit(EXIT_SUCCESS)");
-    std::process::exit(0);
-}
diff --git a/lkt/Cargo.toml b/lkt/Cargo.toml
index c97c8633a085fc71c1dff7bf628fb4d240f20aea..6924ce8f823cfdc0156d62c06c7fa1a6eed95efa 100644
--- a/lkt/Cargo.toml
+++ b/lkt/Cargo.toml
@@ -11,7 +11,6 @@ description = "Simple command line utility to interact with the lektord daemon"
 tokio.workspace = true
 reqwest.workspace = true
 futures.workspace = true
-async-trait.workspace = true
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/lkt/src/args/mod.rs b/lkt/src/args/mod.rs
index e4831946cb44c1bbef1e3aa2cc516eaa91fa6b06..a9814e26cbba14a7a010ab94fad58028370a81ac 100644
--- a/lkt/src/args/mod.rs
+++ b/lkt/src/args/mod.rs
@@ -2,6 +2,7 @@ mod parsers;
 
 use clap::{Parser, Subcommand};
 use clap_complete::Shell;
+use lektor_payloads::KId;
 
 #[derive(Parser, Debug)]
 #[command( author
@@ -206,7 +207,7 @@ pub enum SubCommand {
              , long       = "info"
              , value_name = "ID"
         )]
-        get: Option<u64>,
+        get: Option<KId>,
 
         /// Search kara in a playlist with a query. The first element is the name of the playlist.
         #[arg( action     = clap::ArgAction::Append
diff --git a/lkt/src/lib.rs b/lkt/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..db3d322c133be68427879458ecd07dc0906b0dcb
--- /dev/null
+++ b/lkt/src/lib.rs
@@ -0,0 +1,346 @@
+#![forbid(unsafe_code)]
+
+include!(concat!(env!("OUT_DIR"), "/lkt_build_infos.rs"));
+
+pub mod args;
+pub mod config;
+pub mod manpage;
+
+use crate::{args::*, config::*};
+use anyhow::{anyhow, Context as _, Result};
+use futures::{
+    stream::{self, FuturesUnordered},
+    StreamExt,
+};
+use lektor_lib::*;
+use lektor_payloads::*;
+use std::{borrow::Cow, ops::RangeBounds};
+
+pub async fn collect_karas(config: &ConnectConfig, ids: Vec<KId>) -> Result<Vec<Kara>> {
+    let count = ids.len();
+    let res: Vec<_> = stream::iter(ids.into_iter())
+        .then(|id| requests::get_kara_by_kid(config, id))
+        .filter_map(|res| async { res.map_err(|err| log::error!("{err}")).ok() })
+        .collect::<_>()
+        .await;
+    res.len()
+        .eq(&count)
+        .then_some(res)
+        .ok_or(anyhow!("got errors, didn't received all the karas' data"))
+}
+
+pub async fn simple_karas_print(config: &ConnectConfig, ids: Vec<KId>) -> Result<()> {
+    let res = collect_karas(config, ids).await?;
+    let count = res.len().to_string().len();
+    res.into_iter()
+        .enumerate()
+        .for_each(|(i, k)| println!("[{i:0>count$}] {k}"));
+    Ok(())
+}
+
+pub async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> {
+    // Write to apply changes...
+    lektor_utils::config::write_config_async("lkt", config.clone()).await?;
+    let config = ConnectConfig::from(config);
+    let config = &config;
+
+    use crate::args::SubCommand::*;
+    match cmd {
+        // ============= //
+        // Queue options //
+        // ============= //
+
+        // Display current kara in one line (for status lines...)
+        Playback { current: true, .. } => {
+            let PlayStateWithCurrent { state, current } = requests::get_status(config).await?;
+            match current {
+                None => println!("[{state:?}]"),
+                Some((kid, elapsed, duration)) => {
+                    let current = requests::get_kara_by_kid(config, kid).await?.to_string();
+                    println!("[{state:?}] {elapsed}/{duration} {current}");
+                }
+            }
+            Ok(())
+        }
+
+        // Display the status of lektord
+        Playback { status: true, .. } => {
+            let Infos {
+                version,
+                last_epoch,
+            } = requests::get_infos(config).await?;
+            let PlayStateWithCurrent { state, current } = requests::get_status(config).await?;
+            let current = match current {
+                None => None,
+                Some((kid, elapsed, duration)) => Some((
+                    requests::get_kara_by_kid(config, kid).await?,
+                    elapsed,
+                    duration,
+                )),
+            };
+            let queue_counts = requests::get_queue_count(config).await?;
+            let history_count = requests::get_history_count(config).await?;
+            let last_epoch = match last_epoch {
+                Some(num) => num.to_string().into(),
+                None => Cow::Borrowed("None"),
+            };
+
+            println!("Version:          {version}");
+            println!("Playback State:   {state:?}");
+            if let Some((current, elapsed, duration)) = current {
+                println!("Current Kara:     {current}");
+                println!("Kara elapsed:     {elapsed}s");
+                println!("Kara duration:    {duration}s");
+            }
+            println!("Karas in Queue:   {queue_counts:?}");
+            println!("Karas in Hustory: {history_count}");
+            println!("Database epoch:   {last_epoch}");
+
+            Ok(())
+        }
+
+        // Play from a position in the queue
+        Playback {
+            play: Some(play), ..
+        } => requests::play_from_position(config, play.unwrap_or_default()).await,
+
+        // Toggle the play state
+        Playback {
+            pause: Some(None), ..
+        } => requests::toggle_playback_state(config).await,
+
+        // Pause the playback
+        Playback {
+            pause: Some(Some(true)),
+            ..
+        } => requests::set_playback_state(config, PlayState::Pause).await,
+
+        // Force to resume the playback
+        Playback {
+            pause: Some(Some(false)),
+            ..
+        } => requests::set_playback_state(config, PlayState::Play).await,
+
+        Playback { next: true, .. } => requests::play_next(config).await,
+        Playback { prev: true, .. } => requests::play_previous(config).await,
+        Playback { stop: true, .. } => requests::set_playback_state(config, PlayState::Stop).await,
+
+        // ============= //
+        // Queue options //
+        // ============= //
+
+        // List kara in the queue
+        Queue { pos: Some(pos), .. } => {
+            let pos = pos.unwrap_or_default();
+            let ids = requests::get_queue_range(config, pos).await?;
+            let prios: Vec<_> = ids.iter().map(|(prio, _)| *prio).collect();
+            let karas: Vec<_> =
+                collect_karas(config, ids.into_iter().map(|(_, id)| id).collect()).await?;
+            let count = match pos {
+                Range::From(_) | Range::Full => prios.len().max(karas.len()),
+                Range::To(max) | Range::Bound(_, max) => max - 1,
+                Range::ToInclusive(max) | Range::BoundInclusive(_, max) => max,
+            };
+            let start = match pos.start_bound() {
+                std::ops::Bound::Included(start) => *start,
+                std::ops::Bound::Excluded(start) => start - 1,
+                std::ops::Bound::Unbounded => 0,
+            };
+            let count = count.to_string().len();
+            let space = PRIORITY_VALUES
+                .iter()
+                .map(|prio| prio.as_str().len())
+                .max()
+                .expect("oupsy daisy");
+            std::iter::zip(prios, karas)
+                .enumerate()
+                .for_each(|(i, (l, k))| println!("[{:0>count$}] {l:>space$}: {k}", start + i));
+            Ok(())
+        }
+
+        // Add karas to the queue
+        Queue { add: Some(add), .. } => {
+            let AddArguments { level: lvl, query } = add.join(" ").parse()?;
+            let ids = requests::search_karas(config, SearchFrom::Database, [query]).await?;
+            let add = QueueAddAction {
+                priority: lvl,
+                action: KaraFilter::List(true, ids),
+            };
+            requests::add_to_queue(config, add).await
+        }
+
+        // Remove kara from the queue by their position
+        Queue {
+            remove: Some(r), ..
+        } => requests::remove_range_from_queue(config, r.unwrap_or_default()).await,
+
+        // Shuffle the queue.
+        Queue {
+            shuffle: Some(shuffle),
+            ..
+        } => requests::shuffle_queue_range(config, shuffle.unwrap_or_default()).await,
+
+        // =============== //
+        // History options //
+        // =============== //
+        History { clear: true, .. } => requests::remove_range_from_history(config, ..).await,
+        History { pos: Some(pos), .. } => {
+            let ids = requests::get_history_range(config, pos.unwrap_or_default()).await?;
+            simple_karas_print(config, ids).await
+        }
+
+        // ============== //
+        // Search options //
+        // ============== //
+        Search {
+            database: Some(regex),
+            ..
+        } => {
+            let regex = regex.join(" ").parse()?;
+            let ids = requests::search_karas(config, SearchFrom::Database, [regex]).await?;
+            simple_karas_print(config, ids).await
+        }
+
+        Search {
+            count: Some(regex), ..
+        } => {
+            let regex = regex.join(" ");
+            let count = requests::count_karas(config, SearchFrom::Database, regex.parse()?).await?;
+            println!("Search in Database: {regex}");
+            println!("Matched Count:      {count}");
+            Ok(())
+        }
+
+        Search { get: Some(kid), .. } => {
+            let kara = requests::get_kara_by_kid(config, kid).await?;
+            dbg!(kara);
+            Ok(())
+        }
+
+        Search {
+            plt: Some(mut args),
+            ..
+        } => {
+            let name = args.remove(0).parse()?;
+            let regex = args.join(" ").parse()?;
+            let ids = requests::search_karas(config, SearchFrom::Playlist(name), [regex]).await?;
+            simple_karas_print(config, ids).await
+        }
+
+        Search {
+            queue: Some(args), ..
+        } => {
+            let regex = args.join(" ").parse()?;
+            let ids = requests::search_karas(config, SearchFrom::Queue, [regex]).await?;
+            simple_karas_print(config, ids).await
+        }
+
+        Search {
+            history: Some(args),
+            ..
+        } => {
+            let regex = args.join(" ").parse()?;
+            let ids = requests::search_karas(config, SearchFrom::History, [regex]).await?;
+            simple_karas_print(config, ids).await
+        }
+
+        // ================ //
+        // Playlist options //
+        // ================ //
+
+        // Create a playlist
+        Playlist {
+            create: Some(n), ..
+        } => requests::create_playlist(config, n.into()).await,
+
+        // Delete a playlist and all its content.
+        Playlist {
+            destroy: Some(n), ..
+        } => requests::delete_playlist(config, n.into()).await,
+
+        // List all the playlists
+        Playlist {
+            list: Some(None), ..
+        } => {
+            let plts = stream::iter(requests::get_playlists(config).await?)
+                .then(|(id, _)| async move { requests::get_playlist_info(config, id).await })
+                .collect::<FuturesUnordered<_>>()
+                .await
+                .into_iter()
+                .collect::<Result<Vec<_>, _>>()?;
+
+            let count = plts.len().to_string().len();
+            let padding: String = " ".repeat(count);
+            for (idx, playlist) in plts.into_iter().enumerate() {
+                if idx != 0 {
+                    println!();
+                }
+                println!("{idx:0>count$}: Playlist {}", playlist.name());
+                if playlist.owners().count() != 0 {
+                    println!(
+                        "{padding}  Owned by {}",
+                        playlist.owners().collect::<Vec<&str>>().join(",")
+                    );
+                }
+                println!(
+                    "{padding}  Created at {}",
+                    playlist.created_at().format("%Y-%m-%d %H:%M:%S")
+                );
+                println!(
+                    "{padding}  Last updated at {}",
+                    playlist.updated_at().format("%Y-%m-%d %H:%M:%S")
+                );
+            }
+            Ok(())
+        }
+
+        // List the content of a playlist
+        Playlist {
+            list: Some(Some(n)),
+            ..
+        } => {
+            let id = requests::get_playlists(config)
+                .await?
+                .into_iter()
+                .find_map(|(id, name)| (n == name).then_some(id))
+                .with_context(|| format!("failed to find a playlist with the name {n}"))?;
+            let ids = requests::get_playlist_content(config, id).await?;
+            simple_karas_print(config, ids).await
+        }
+
+        // Add something to a playlist.
+        Playlist {
+            add: Some(mut args),
+            ..
+        } => {
+            let name = args.remove(0).into();
+            let rgx = args.join(" ").parse()?;
+            let ids = requests::search_karas(config, SearchFrom::Database, [rgx]).await?;
+            let add = KaraFilter::List(true, ids);
+            requests::add_to_playlist(config, name, add).await
+        }
+
+        // Remove something from a playlist
+        Playlist {
+            remove: Some(mut args),
+            ..
+        } => {
+            let name = args.remove(0).into();
+            let rgx = args.join(" ").parse()?;
+            let ids = requests::search_karas(config, SearchFrom::Database, [rgx]).await?;
+            let remove = KaraFilter::List(true, ids);
+            requests::remove_from_playlist(config, name, remove).await
+        }
+
+        // ============= //
+        // Admin options //
+        // ============= //
+        Admin { kill: true, .. } => requests::shutdown_lektord(config).await,
+        Admin { update: true, .. } => requests::update_from_repo(config).await,
+
+        // ============================================== //
+        // Can't be there... unless no flag was passed... //
+        // ============================================== //
+        _ => unreachable!("{cmd:#?}"),
+    }
+}
diff --git a/lkt/src/main.rs b/lkt/src/main.rs
index 9afa21b2daa35d687df16e6fe43163c2fec07ecc..8bc0685ee44e68f46d3bf3c09c6b93ce25967375 100644
--- a/lkt/src/main.rs
+++ b/lkt/src/main.rs
@@ -1,19 +1,12 @@
 #![forbid(unsafe_code)]
 
-include!(concat!(env!("OUT_DIR"), "/lkt_build_infos.rs"));
-
-mod args;
-mod config;
-mod manpage;
-
-use crate::{args::*, config::*};
-use anyhow::{anyhow, Context as _, Result};
-use chrono::TimeZone;
-use futures::{stream, StreamExt};
-use lektor_lib::*;
-use lektor_payloads::*;
+use anyhow::{Context as _, Result};
 use lektor_utils::*;
-use std::{borrow::Cow, ops::RangeBounds};
+use lkt::{
+    args::{self, *},
+    config::*,
+    manpage,
+};
 
 fn main() -> Result<()> {
     logger::Builder::default()
@@ -46,326 +39,6 @@ fn main() -> Result<()> {
     tokio::runtime::Builder::new_current_thread()
         .enable_all()
         .build()?
-        .block_on(exec_lkt(config, args.action))?;
+        .block_on(lkt::exec_lkt(config, args.action))?;
     Ok(())
 }
-
-async fn collect_karas(config: &ConnectConfig, ids: Vec<KId>) -> Result<Vec<Kara>> {
-    let count = ids.len();
-    let res: Vec<_> = stream::iter(ids.into_iter())
-        .then(|id| requests::get_kara_by_kid(config, id))
-        .filter_map(|res| async { res.map_err(|err| log::error!("{err}")).ok() })
-        .collect::<_>()
-        .await;
-    res.len()
-        .eq(&count)
-        .then_some(res)
-        .ok_or(anyhow!("got errors, didn't received all the karas' data"))
-}
-
-async fn simple_karas_print(config: &ConnectConfig, ids: Vec<KId>) -> Result<()> {
-    let res = collect_karas(config, ids).await?;
-    let count = res.len().to_string().len();
-    res.into_iter()
-        .enumerate()
-        .for_each(|(i, k)| println!("[{i:0>count$}] {k}"));
-    Ok(())
-}
-
-async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> {
-    // Write to apply changes...
-    lektor_utils::config::write_config_async("lkt", config.clone()).await?;
-    let config = ConnectConfig::from(config);
-    let config = &config;
-
-    use crate::args::SubCommand::*;
-    match cmd {
-        // ============= //
-        // Queue options //
-        // ============= //
-
-        // Display current kara in one line (for status lines...)
-        Playback { current: true, .. } => {
-            let PlayStateWithCurrent { state, current } = requests::get_status(config).await?;
-            match current {
-                None => println!("[{state:?}]"),
-                Some((kid, elapsed, duration)) => {
-                    let current = requests::get_kara_by_kid(config, kid).await?.to_string();
-                    println!("[{state:?}] {elapsed}/{duration} {current}");
-                }
-            }
-            Ok(())
-        }
-
-        // Display the status of lektord
-        Playback { status: true, .. } => {
-            let Infos {
-                version,
-                last_epoch,
-            } = requests::get_infos(config).await?;
-            let PlayStateWithCurrent { state, current } = requests::get_status(config).await?;
-            let current = match current {
-                None => None,
-                Some((kid, elapsed, duration)) => Some((
-                    requests::get_kara_by_kid(config, kid).await?,
-                    elapsed,
-                    duration,
-                )),
-            };
-            let queue_counts = requests::get_queue_count(config).await?;
-            let history_count = requests::get_history_count(config).await?;
-            let last_epoch = match last_epoch {
-                Some(num) => num.to_string().into(),
-                None => Cow::Borrowed("None"),
-            };
-
-            println!("Version:          {version}");
-            println!("Playback State:   {state:?}");
-            if let Some((current, elapsed, duration)) = current {
-                println!("Current Kara:     {current}");
-                println!("Kara elapsed:     {elapsed}s");
-                println!("Kara duration:    {duration}s");
-            }
-            println!("Karas in Queue:   {queue_counts:?}");
-            println!("Karas in Hustory: {history_count}");
-            println!("Database epoch:   {last_epoch}");
-
-            Ok(())
-        }
-
-        // Play from a position in the queue
-        Playback {
-            play: Some(play), ..
-        } => requests::play_from_position(config, play.unwrap_or_default()).await,
-
-        // Toggle the play state
-        Playback {
-            pause: Some(None), ..
-        } => requests::toggle_playback_state(config).await,
-
-        // Pause the playback
-        Playback {
-            pause: Some(Some(true)),
-            ..
-        } => requests::set_playback_state(config, PlayState::Pause).await,
-
-        // Force to resume the playback
-        Playback {
-            pause: Some(Some(false)),
-            ..
-        } => requests::set_playback_state(config, PlayState::Play).await,
-
-        Playback { next: true, .. } => requests::play_next(config).await,
-        Playback { prev: true, .. } => requests::play_previous(config).await,
-        Playback { stop: true, .. } => requests::set_playback_state(config, PlayState::Stop).await,
-
-        // ============= //
-        // Queue options //
-        // ============= //
-
-        // List kara in the queue
-        Queue { pos: Some(pos), .. } => {
-            let pos = pos.unwrap_or_default();
-            let ids = requests::get_queue_range(config, pos).await?;
-            let prios: Vec<_> = ids.iter().map(|(prio, _)| *prio).collect();
-            let karas: Vec<_> =
-                collect_karas(config, ids.into_iter().map(|(_, id)| id).collect()).await?;
-            let count = match pos {
-                Range::From(_) | Range::Full => prios.len().max(karas.len()),
-                Range::To(max) | Range::Bound(_, max) => max - 1,
-                Range::ToInclusive(max) | Range::BoundInclusive(_, max) => max,
-            };
-            let start = match pos.start_bound() {
-                std::ops::Bound::Included(start) => *start,
-                std::ops::Bound::Excluded(start) => start - 1,
-                std::ops::Bound::Unbounded => 0,
-            };
-            let count = count.to_string().len();
-            let space = PRIORITY_VALUES
-                .iter()
-                .map(|prio| prio.as_str().len())
-                .max()
-                .expect("oupsy daisy");
-            std::iter::zip(prios, karas)
-                .enumerate()
-                .for_each(|(i, (l, k))| println!("[{:0>count$}] {l:>space$}: {k}", start + i));
-            Ok(())
-        }
-
-        // Add karas to the queue
-        Queue { add: Some(add), .. } => {
-            let AddArguments { level: lvl, query } = add.join(" ").parse()?;
-            let ids = requests::search_karas(config, SearchFrom::Database, query).await?;
-            let add = QueueAddAction {
-                priority: lvl,
-                action: KaraFilter::List(true, ids),
-            };
-            requests::add_to_queue(config, add).await
-        }
-
-        // Remove kara from the queue by their position
-        Queue {
-            remove: Some(r), ..
-        } => requests::remove_range_from_queue(config, r.unwrap_or_default()).await,
-
-        // Shuffle the queue.
-        Queue {
-            shuffle: Some(shuffle),
-            ..
-        } => requests::shuffle_queue_range(config, shuffle.unwrap_or_default()).await,
-
-        // =============== //
-        // History options //
-        // =============== //
-        History { clear: true, .. } => requests::remove_range_from_history(config, ..).await,
-        History { pos: Some(pos), .. } => {
-            let ids = requests::get_history_range(config, pos.unwrap_or_default()).await?;
-            simple_karas_print(config, ids).await
-        }
-
-        // ============== //
-        // Search options //
-        // ============== //
-        Search {
-            database: Some(regex),
-            ..
-        } => {
-            let regex = regex.join(" ").parse()?;
-            let ids = requests::search_karas(config, SearchFrom::Database, regex).await?;
-            simple_karas_print(config, ids).await
-        }
-
-        Search {
-            count: Some(regex), ..
-        } => {
-            let regex = regex.join(" ");
-            let count = requests::count_karas(config, SearchFrom::Database, regex.parse()?).await?;
-            println!("Search in Database: {regex}");
-            println!("Matched Count:      {count}");
-            Ok(())
-        }
-
-        Search { get: Some(id), .. } => {
-            let kara = requests::get_kara_by_id(config, id).await?;
-            dbg!(kara);
-            Ok(())
-        }
-
-        Search {
-            plt: Some(mut args),
-            ..
-        } => {
-            let name = args.remove(0).parse()?;
-            let regex = args.join(" ").parse()?;
-            let ids = requests::search_karas(config, SearchFrom::Playlist(name), regex).await?;
-            simple_karas_print(config, ids).await
-        }
-
-        Search {
-            queue: Some(args), ..
-        } => {
-            let regex = args.join(" ").parse()?;
-            let ids = requests::search_karas(config, SearchFrom::Queue, regex).await?;
-            simple_karas_print(config, ids).await
-        }
-
-        Search {
-            history: Some(args),
-            ..
-        } => {
-            let regex = args.join(" ").parse()?;
-            let ids = requests::search_karas(config, SearchFrom::History, regex).await?;
-            simple_karas_print(config, ids).await
-        }
-
-        // ================ //
-        // Playlist options //
-        // ================ //
-
-        // Create a playlist
-        Playlist {
-            create: Some(n), ..
-        } => requests::create_playlist(config, n.into()).await,
-
-        // Delete a playlist and all its content.
-        Playlist {
-            destroy: Some(n), ..
-        } => requests::delete_playlist(config, n.into()).await,
-
-        // List all the playlists
-        Playlist {
-            list: Some(None), ..
-        } => {
-            let plts = requests::get_playlists(config).await?;
-            let count = plts.len().to_string().len();
-            let padding: String = " ".repeat(count);
-            for (idx, (name, infos)) in plts.into_iter().enumerate() {
-                let PlaylistInfo {
-                    user,
-                    created_at,
-                    updated_at,
-                } = infos;
-                if idx != 0 {
-                    println!();
-                }
-                println!("{idx:0>count$}: Playlist {name}");
-                if let Some(user) = user {
-                    println!("{padding}  Created by {user}");
-                }
-                if let Some(time) = chrono::Local.timestamp_opt(created_at, 0).latest() {
-                    let time = time.format("%Y-%m-%d %H:%M:%S");
-                    println!("{padding}  Created at {time}");
-                };
-                if let Some(time) = chrono::Local.timestamp_opt(updated_at, 0).latest() {
-                    let time = time.format("%Y-%m-%d %H:%M:%S");
-                    println!("{padding}  Last updated at {time}");
-                };
-            }
-            Ok(())
-        }
-
-        // List the content of a playlist
-        Playlist {
-            list: Some(Some(n)),
-            ..
-        } => {
-            let ids = requests::get_playlist_content(config, n.into()).await?;
-            simple_karas_print(config, ids).await
-        }
-
-        // Add something to a playlist.
-        Playlist {
-            add: Some(mut args),
-            ..
-        } => {
-            let name = args.remove(0).into();
-            let rgx = args.join(" ").parse()?;
-            let ids = requests::search_karas(config, SearchFrom::Database, rgx).await?;
-            let add = KaraFilter::List(true, ids);
-            requests::add_to_playlist(config, name, add).await
-        }
-
-        // Remove something from a playlist
-        Playlist {
-            remove: Some(mut args),
-            ..
-        } => {
-            let name = args.remove(0).into();
-            let rgx = args.join(" ").parse()?;
-            let ids = requests::search_karas(config, SearchFrom::Database, rgx).await?;
-            let remove = KaraFilter::List(true, ids);
-            requests::remove_from_playlist(config, name, remove).await
-        }
-
-        // ============= //
-        // Admin options //
-        // ============= //
-        Admin { kill: true, .. } => requests::shutdown_lektord(config).await,
-        Admin { update: true, .. } => requests::update_from_repo(config).await,
-
-        // ============================================== //
-        // Can't be there... unless no flag was passed... //
-        // ============================================== //
-        _ => unreachable!("{cmd:#?}"),
-    }
-}