diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
index 4ac8b0ed93150fb25aaf04c8b98700414147e201..192af230c62d961efb68792671a137820998d8c8 100644
--- a/src/rust/Cargo.lock
+++ b/src/rust/Cargo.lock
@@ -54,8 +54,10 @@ dependencies = [
  "clap",
  "commons",
  "getset",
+ "hashbrown 0.13.2",
  "iced",
  "serde",
+ "smallstring",
 ]
 
 [[package]]
diff --git a/src/rust/amadeus/Cargo.toml b/src/rust/amadeus/Cargo.toml
index 794c4105759dcf9e1bef2ed9e03c7ad8dcda888a..18ef4a4a4290b824d8c6bedfa079866852385e09 100644
--- a/src/rust/amadeus/Cargo.toml
+++ b/src/rust/amadeus/Cargo.toml
@@ -6,10 +6,12 @@ authors.workspace = true
 license.workspace = true
 
 [dependencies]
+hashbrown.workspace = true
 serde.workspace = true
 clap.workspace = true
 iced.workspace = true
 
+smallstring = { path = "../smallstring" }
 commons = { path = "../commons" }
 amalib = { path = "../amalib" }
 getset = { path = "../getset" }
diff --git a/src/rust/amadeus/src/amadeus/mod.rs b/src/rust/amadeus/src/amadeus/mod.rs
index a9b3ed4b94d740f9c176ce2ffb0c54ab154ec9b7..4ae3d60c60171d33891e8c99b6cf02de5dfde4ec 100644
--- a/src/rust/amadeus/src/amadeus/mod.rs
+++ b/src/rust/amadeus/src/amadeus/mod.rs
@@ -3,7 +3,8 @@ mod client;
 mod constants;
 mod queue_control;
 mod side_panel;
-mod window_content;
+mod tabs;
+mod window;
 
 use crate::AmadeusConfig;
 use iced::{
@@ -20,7 +21,7 @@ pub struct Amadeus {
     client: client::Client,
     queue_control: queue_control::QueueControl,
     side_panel: side_panel::SidePanel,
-    window_content: window_content::WindowContent,
+    window: window::Window,
 }
 
 #[derive(Debug, Clone)]
@@ -53,7 +54,7 @@ pub enum Message {
 
     /// Got a message for the different tabs inside the window / the main
     /// content returned a message.
-    WindowContent(window_content::Message),
+    Window(window::Message),
 }
 
 impl Application for Amadeus {
@@ -71,7 +72,7 @@ impl Application for Amadeus {
                 client: client::Client::new(),
                 queue_control: queue_control::QueueControl::new(),
                 side_panel: side_panel::SidePanel::new(),
-                window_content: window_content::WindowContent::new(),
+                window: window::Window::new(),
             },
             Command::none(),
         )
@@ -136,10 +137,7 @@ impl Application for Amadeus {
             Message::SidePanel(message) => self.side_panel.update(message).map(Message::SidePanel),
 
             // Dispatch to main window content.
-            Message::WindowContent(message) => self
-                .window_content
-                .update(message)
-                .map(Message::WindowContent),
+            Message::Window(message) => self.window.update(message).map(Message::Window),
 
             // On notifications we update only the queue controler for now, in
             // the future we may update something else.
@@ -169,7 +167,7 @@ impl Application for Amadeus {
             row![
                 column![self.side_panel.view().map(Message::SidePanel)].padding(10),
                 vertical_rule(2),
-                column![self.window_content.view().map(Message::WindowContent)].padding(10),
+                column![self.window.view().map(Message::Window)].padding(10),
             ]
         ]
         .into()
diff --git a/src/rust/amadeus/src/amadeus/side_panel.rs b/src/rust/amadeus/src/amadeus/side_panel.rs
index 4b58190a530076c427c05d9d4b4d97bb8dd93a1f..d9e45d8123637373a4637f0f6ae85f6468ec3cc3 100644
--- a/src/rust/amadeus/src/amadeus/side_panel.rs
+++ b/src/rust/amadeus/src/amadeus/side_panel.rs
@@ -1,4 +1,5 @@
 use super::constants::*;
+use commons::log;
 use iced::{widget::text, Command, Element};
 
 #[derive(Debug, Clone, Copy)]
@@ -16,6 +17,7 @@ impl SidePanel {
     }
 
     pub fn update(&mut self, message: Message) -> Command<Message> {
+        log::info!("got message {message:?}");
         Command::none()
     }
 
diff --git a/src/rust/amadeus/src/amadeus/tabs/mod.rs b/src/rust/amadeus/src/amadeus/tabs/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6546102b1d5d8e6ece8dfd368365546fe0f7e25a
--- /dev/null
+++ b/src/rust/amadeus/src/amadeus/tabs/mod.rs
@@ -0,0 +1,3 @@
+pub mod playlist;
+pub mod queue;
+pub mod search;
diff --git a/src/rust/amadeus/src/amadeus/tabs/playlist.rs b/src/rust/amadeus/src/amadeus/tabs/playlist.rs
new file mode 100644
index 0000000000000000000000000000000000000000..423c6948a5ead38ce5165e27bb966fa07e0c5929
--- /dev/null
+++ b/src/rust/amadeus/src/amadeus/tabs/playlist.rs
@@ -0,0 +1,29 @@
+use crate::amadeus::constants::*;
+use commons::log;
+use iced::{widget::text, Command, Element};
+use smallstring::SmallString;
+
+#[derive(Debug, Clone, Copy)]
+pub enum Message {}
+
+#[derive(Debug, Clone)]
+pub struct Playlist {
+    name: SmallString,
+}
+
+impl Playlist {
+    pub fn new(name: SmallString) -> Self {
+        Self { name }
+    }
+
+    pub fn update(&mut self, message: Message) -> Command<Message> {
+        log::info!("got message {message:?} for playlist {}", self.name);
+        Command::none()
+    }
+
+    pub fn view(&self) -> Element<Message> {
+        text(format!("playlist {}...", self.name))
+            .size(TEXT_SIZE)
+            .into()
+    }
+}
diff --git a/src/rust/amadeus/src/amadeus/window_content.rs b/src/rust/amadeus/src/amadeus/tabs/queue.rs
similarity index 66%
rename from src/rust/amadeus/src/amadeus/window_content.rs
rename to src/rust/amadeus/src/amadeus/tabs/queue.rs
index c8c29dee6e9edb48915d56a218255ca170ab536c..0a5612ff1c1cbfd9e31c226ab49dae631446f59a 100644
--- a/src/rust/amadeus/src/amadeus/window_content.rs
+++ b/src/rust/amadeus/src/amadeus/tabs/queue.rs
@@ -1,4 +1,5 @@
-use super::constants::*;
+use crate::amadeus::constants::*;
+use commons::log;
 use iced::{widget::text, Command, Element};
 
 #[derive(Debug, Clone, Copy)]
@@ -8,18 +9,19 @@ pub enum Message {}
 struct State {}
 
 #[derive(Debug, Clone)]
-pub struct WindowContent {}
+pub struct Queue {}
 
-impl WindowContent {
+impl Queue {
     pub fn new() -> Self {
         Self {}
     }
 
     pub fn update(&mut self, message: Message) -> Command<Message> {
+        log::info!("got message {message:?}");
         Command::none()
     }
 
     pub fn view(&self) -> Element<Message> {
-        text("window content...").size(TEXT_SIZE).into()
+        text("queue...").size(TEXT_SIZE).into()
     }
 }
diff --git a/src/rust/amadeus/src/amadeus/tabs/search.rs b/src/rust/amadeus/src/amadeus/tabs/search.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4951e02a58d7c6cc317a9bb017c22b7b4759a3ba
--- /dev/null
+++ b/src/rust/amadeus/src/amadeus/tabs/search.rs
@@ -0,0 +1,27 @@
+use crate::amadeus::constants::*;
+use commons::log;
+use iced::{widget::text, Command, Element};
+
+#[derive(Debug, Clone, Copy)]
+pub enum Message {}
+
+#[derive(Debug, Clone)]
+struct State {}
+
+#[derive(Debug, Clone)]
+pub struct Search {}
+
+impl Search {
+    pub fn new() -> Self {
+        Self {}
+    }
+
+    pub fn update(&mut self, message: Message) -> Command<Message> {
+        log::info!("got message {message:?}");
+        Command::none()
+    }
+
+    pub fn view(&self) -> Element<Message> {
+        text("search...").size(TEXT_SIZE).into()
+    }
+}
diff --git a/src/rust/amadeus/src/amadeus/window.rs b/src/rust/amadeus/src/amadeus/window.rs
new file mode 100644
index 0000000000000000000000000000000000000000..faab60a11fde4b421f5f52be46a4c8c49c38fed2
--- /dev/null
+++ b/src/rust/amadeus/src/amadeus/window.rs
@@ -0,0 +1,91 @@
+use crate::amadeus::{constants::*, tabs};
+use commons::log;
+use hashbrown::HashMap;
+use iced::{widget::text, Command, Element};
+use smallstring::SmallString;
+
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub enum View {
+    #[default]
+    Queue,
+    Search,
+    Playlist(SmallString),
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+    Playlist(SmallString, tabs::playlist::Message),
+    Search(tabs::search::Message),
+    Queue(tabs::queue::Message),
+
+    ChangeView(View),
+
+    LoadPlaylistAndRetry(SmallString, tabs::playlist::Message),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct State(View);
+
+#[derive(Debug, Clone)]
+pub struct Window {
+    state: State,
+
+    tab_search: tabs::search::Search,
+    tab_queue: tabs::queue::Queue,
+
+    tabs_playlists: HashMap<SmallString, tabs::playlist::Playlist>,
+}
+
+impl Window {
+    pub fn new() -> Self {
+        Self {
+            state: State(Default::default()),
+            tab_search: tabs::search::Search::new(),
+            tab_queue: tabs::queue::Queue::new(),
+            tabs_playlists: Default::default(),
+        }
+    }
+
+    pub fn update(&mut self, message: Message) -> Command<Message> {
+        match message {
+            Message::Playlist(name, message) => match self.tabs_playlists.get_mut(&name) {
+                Some(tab_playlist) => tab_playlist
+                    .update(message)
+                    .map(move |message| Message::Playlist(name.clone(), message)),
+                None => Command::perform(async { () }, move |()| {
+                    Message::LoadPlaylistAndRetry(name, message)
+                }),
+            },
+
+            Message::Search(message) => self.tab_search.update(message).map(Message::Search),
+            Message::Queue(message) => self.tab_queue.update(message).map(Message::Queue),
+
+            Message::ChangeView(view) => {
+                log::info!("change window view to {view:?}");
+                log::error!("handle playlist loading and queue refresh...");
+                self.state = State(view);
+                Command::none()
+            }
+
+            message => {
+                log::info!("ignored message {message:?}");
+                Command::none()
+            }
+        }
+    }
+
+    pub fn view(&self) -> Element<Message> {
+        match self.state.0 {
+            View::Queue => self.tab_queue.view().map(Message::Queue),
+            View::Search => self.tab_search.view().map(Message::Search),
+            View::Playlist(ref name) => match self.tabs_playlists.get(name) {
+                Some(tab_playlist) => tab_playlist
+                    .view()
+                    .map(move |message| Message::Playlist(name.clone(), message)),
+                None => text(format!("playlist {name} is not loaded..."))
+                    .size(TITLE_SIZE)
+                    .into(),
+            },
+        }
+    }
+}