diff --git a/crates/tb-ide/src/render.rs b/crates/tb-ide/src/render.rs
index 9c64b24..1ab5a4b 100644
--- a/crates/tb-ide/src/render.rs
+++ b/crates/tb-ide/src/render.rs
@@ -11,74 +11,9 @@ use ratatui::{
 };
 use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
 
-/// DOS-Farben der IDE. Die ersten 16 ANSI-Einträge gehören dem Terminalprofil.
-/// BASIC-Ausgabe verwendet weiterhin ihre eigene Farbabbildung in tb-ui.
+/// Feste DOS-RGB-Farben, identisch für IDE und BASIC-Ausgabe.
 pub fn dos(n: u8) -> Color {
-    static COLORS: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
-    dos_color(
-        n,
-        *COLORS.get_or_init(|| {
-            let detected = crossterm::style::available_color_count();
-            // Ein generisches COLORTERM (z.B. "yes") verdeckt bei crossterm TERM.
-            if detected < 256 && std::env::var("TERM").is_ok_and(|s| s.contains("256")) {
-                256
-            } else {
-                detected
-            }
-        }),
-    )
-}
-
-pub fn dos_color(n: u8, colors: u16) -> Color {
-    let n = n.min(15) as usize;
-    const RGB: [(u8, u8, u8); 16] = [
-        (0, 0, 0),
-        (0, 0, 170),
-        (0, 170, 0),
-        (0, 170, 170),
-        (170, 0, 0),
-        (170, 0, 170),
-        (170, 85, 0),
-        (170, 170, 170),
-        (85, 85, 85),
-        (85, 85, 255),
-        (85, 255, 85),
-        (85, 255, 255),
-        (255, 85, 85),
-        (255, 85, 255),
-        (255, 255, 85),
-        (255, 255, 255),
-    ];
-    if colors > 256 {
-        let (r, g, b) = RGB[n];
-        Color::Rgb(r, g, b)
-    } else if colors >= 256 {
-        // Fester 6x6x6-Farbwürfel / Graurampe statt profilabhängiger Systemfarben.
-        Color::Indexed(
-            [
-                16, 19, 34, 37, 124, 127, 130, 248, 240, 63, 83, 87, 203, 207, 227, 231,
-            ][n],
-        )
-    } else {
-        [
-            Color::Black,
-            Color::Blue,
-            Color::Green,
-            Color::Cyan,
-            Color::Red,
-            Color::Magenta,
-            Color::Yellow,
-            Color::Gray,
-            Color::DarkGray,
-            Color::LightBlue,
-            Color::LightGreen,
-            Color::LightCyan,
-            Color::LightRed,
-            Color::LightMagenta,
-            Color::LightYellow,
-            Color::White,
-        ][n]
-    }
+    tb_ui::screen::basic_color(n.min(15))
 }
 
 pub(crate) fn pair(fg: u8, bg: u8) -> Style {
@@ -160,6 +95,8 @@ impl App {
                     5
                 },
             );
+            // Block/Paragraph ändern Stile, löschen aber keine vorhandenen Zeichen.
+            f.render_widget(Clear, rect);
             f.render_widget(
                 Block::default()
                     .borders(Borders::ALL)
diff --git a/crates/tb-ide/tests/app.rs b/crates/tb-ide/tests/app.rs
index 6ef2fdd..8ccd8c9 100644
--- a/crates/tb-ide/tests/app.rs
+++ b/crates/tb-ide/tests/app.rs
@@ -930,21 +930,7 @@ fn theme_rgb(color: ratatui::style::Color) -> (u8, u8, u8) {
     use ratatui::style::Color;
     match color {
         Color::Rgb(r, g, b) => (r, g, b),
-        Color::Indexed(n @ 16..=231) => {
-            let n = n - 16;
-            let channel = |v: u8| if v == 0 { 0 } else { 55 + v * 40 };
-            (channel(n / 36), channel(n / 6 % 6), channel(n % 6))
-        }
-        Color::Indexed(n @ 232..=255) => {
-            let v = 8 + (n - 232) * 10;
-            (v, v, v)
-        }
-        _ => {
-            let n = (0..16)
-                .find(|n| tb_ide::render::dos_color(*n, 16) == color)
-                .unwrap_or_else(|| panic!("Unbestimmte IDE-Farbe: {color:?}"));
-            theme_rgb(tb_ide::render::dos_color(n, u16::MAX))
-        }
+        _ => panic!("Keine absolute RGB-Farbe: {color:?}"),
     }
 }
 fn theme_contrast(fg: ratatui::style::Color, bg: ratatui::style::Color) -> f64 {
@@ -983,23 +969,21 @@ fn assert_theme(app: &mut App, context: &str) {
 
 #[test]
 fn theme_contrast_covers_rendered_states_and_rejects_bad_pairs() {
-    use tb_ide::render::dos_color;
-    for colors in [256, u16::MAX] {
-        for (fg, bg) in tb_ide::options::Options::default().colors {
-            assert!(theme_contrast(dos_color(fg, colors), dos_color(bg, colors)) >= 4.5);
-        }
-        assert!(theme_contrast(dos_color(15, colors), dos_color(7, colors)) < 4.5);
-        for n in 0..16 {
-            let fg = if matches!(n, 0 | 1 | 4 | 5 | 6 | 8 | 9) {
-                15
-            } else {
-                0
-            };
-            assert!(
-                theme_contrast(dos_color(fg, colors), dos_color(n, colors)) >= 4.5,
-                "Palettenbeschriftung {n}"
-            );
-        }
+    use tb_ide::render::dos;
+    for (fg, bg) in tb_ide::options::Options::default().colors {
+        assert!(theme_contrast(dos(fg), dos(bg)) >= 4.5);
+    }
+    assert!(theme_contrast(dos(15), dos(7)) < 4.5);
+    for n in 0..16 {
+        let fg = if matches!(n, 0 | 1 | 4 | 5 | 6 | 8 | 9) {
+            15
+        } else {
+            0
+        };
+        assert!(
+            theme_contrast(dos(fg), dos(n)) >= 4.5,
+            "Palettenbeschriftung {n}"
+        );
     }
     let t = Temp::new();
     let mut app = t.app();
@@ -1086,34 +1070,23 @@ fn theme_color_commands_use_explicit_values_and_never_modify_terminal_palette()
         buffer::Cell,
         style::Color,
     };
-    use tb_ide::render::dos_color;
-    assert_eq!(dos_color(1, u16::MAX), Color::Rgb(0, 0, 170));
-    assert_eq!(dos_color(5, u16::MAX), Color::Rgb(170, 0, 170));
-    assert_eq!(dos_color(6, u16::MAX), Color::Rgb(170, 85, 0));
-    for colors in [16, 256, u16::MAX] {
-        for n in 0..16 {
-            let color = dos_color(n, colors);
-            if let Color::Indexed(index) = color {
-                assert!(index >= 16);
-            }
-            let mut cell = Cell::default();
-            cell.set_symbol("X")
-                .set_bg(color)
-                .set_fg(dos_color(15, colors));
-            let mut output = Vec::new();
-            CrosstermBackend::new(&mut output)
-                .draw(std::iter::once((0, 0, &cell)))
-                .unwrap();
-            let sequence = String::from_utf8(output).unwrap();
-            assert!(sequence.contains('X'));
-            if colors >= 256 {
-                assert!(sequence.contains("48;"));
-            }
-            if colors > 256 {
-                assert!(sequence.contains("48;2;"));
-            }
-            assert!(!sequence.contains("\x1b]")); // Keine globale Palettenänderung.
-        }
+    use tb_ide::render::dos;
+    assert_eq!(dos(1), Color::Rgb(0, 0, 170));
+    assert_eq!(dos(5), Color::Rgb(170, 0, 170));
+    assert_eq!(dos(6), Color::Rgb(170, 85, 0));
+    for n in 0..16 {
+        assert_eq!(dos(n), tb_ui::screen::basic_color(n));
+        let mut cell = Cell::default();
+        cell.set_symbol("X").set_bg(dos(n)).set_fg(dos(15));
+        let mut output = Vec::new();
+        CrosstermBackend::new(&mut output)
+            .draw(std::iter::once((0, 0, &cell)))
+            .unwrap();
+        let sequence = String::from_utf8(output).unwrap();
+        assert!(sequence.contains('X'));
+        assert!(sequence.contains("38;2;255;255;255"));
+        assert!(sequence.contains("48;2;"));
+        assert!(!sequence.contains("\x1b]")); // Keine globale Palettenänderung.
     }
 }
 
@@ -1149,3 +1122,61 @@ fn theme_designer_chrome_and_handles_preserve_program_palette() {
         assert_theme(&mut app, "Designer-Werkzeug");
     }
 }
+
+#[test]
+fn theme_windows_erase_desktop_and_underlying_windows_on_every_redraw() {
+    use ratatui::{
+        layout::Rect,
+        style::{Color, Modifier},
+    };
+    let t = Temp::new();
+    let mut app = t.app();
+    app.options.syntax_checking = false;
+    // Ein sichtbares Zeichen macht das Durchscheinen sicher erkennbar.
+    app.options.desktop = '@';
+    type_text(&mut app, "PRINT 42");
+    let mut term = Terminal::new(TestBackend::new(app.size.0, app.size.1)).unwrap();
+    for selected in [false, true, false] {
+        if selected {
+            key(&mut app, K::Char('a'), M::CONTROL);
+        } else {
+            plain(&mut app, K::Right);
+        }
+        term.draw(|f| app.render(f)).unwrap();
+        let b = term.backend().buffer();
+        let rect = app.rect(app.windows.iter().find(|w| w.id == app.active).unwrap());
+        let inner = Rect::new(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);
+        for y in inner.y..inner.bottom() {
+            for x in inner.x..inner.right() {
+                let c = &b[(x, y)];
+                assert_ne!(c.symbol(), "@", "Desktop in Fenster bei {x}/{y}");
+                assert_eq!(c.bg, Color::Rgb(0, 0, 170));
+                if y > inner.y {
+                    assert_eq!(c.symbol(), " ", "Restinhalt bei {x}/{y}");
+                    assert!(!c.modifier.contains(Modifier::REVERSED));
+                }
+            }
+        }
+        assert_eq!(b[(inner.x, inner.y)].symbol(), "P");
+        assert_eq!(
+            b[(inner.x, inner.y)].modifier.contains(Modifier::REVERSED),
+            selected
+        );
+    }
+    // Beim Aktivieren eines überlappenden Fensters müssen auch fremde Texte weg.
+    app.execute(Command::NewModule);
+    plain(&mut app, K::Enter);
+    assert!(app.dialog.is_none());
+    term.draw(|f| app.render(f)).unwrap();
+    let b = term.backend().buffer();
+    let rect = app.rect(app.windows.iter().find(|w| w.id == app.active).unwrap());
+    for y in rect.y + 1..rect.bottom() - 1 {
+        for x in rect.x + 1..rect.right() - 1 {
+            assert_eq!(
+                b[(x, y)].symbol(),
+                " ",
+                "Überdeckter Fensterinhalt bei {x}/{y}"
+            );
+        }
+    }
+}
diff --git a/crates/tb-ide/tests/execution.rs b/crates/tb-ide/tests/execution.rs
index 237ac71..e92a0b6 100644
--- a/crates/tb-ide/tests/execution.rs
+++ b/crates/tb-ide/tests/execution.rs
@@ -530,3 +530,35 @@ fn comment_only_source_change_also_requires_revision_choice() {
         DialogKind::ResumeRevision
     ));
 }
+
+#[test]
+fn platform_probe_records_each_basic_key_once_and_keeps_ide_keys_out() {
+    let t = Temp::new();
+    let log = t.0.join("platform-events.log");
+    let source = include_str!("../../../tests/platform/input.frm")
+        .replace("platform-events.log", &log.to_string_lossy());
+    let path = t.0.join("input.frm");
+    fs::write(&path, source).unwrap();
+    let mut a = t.app("");
+    a.load_initial_project(path).unwrap();
+    a.execute(Command::Start);
+    tick(&mut a);
+    key(&mut a, K::Char('a'), M::NONE);
+    tick(&mut a);
+    a.handle(Event::Key(KeyEvent::new_with_kind(
+        K::Char('a'),
+        M::NONE,
+        crossterm::event::KeyEventKind::Release,
+    )));
+    tick(&mut a);
+    let events = fs::read_to_string(&log).unwrap();
+    assert_eq!(
+        events.replace('\r', ""),
+        "Down: 97 : 0 \nPress: 97 : 0 \nUp: 97 : 0 \n"
+    );
+    edit_window(&mut a);
+    key(&mut a, K::F(1), M::NONE);
+    tick(&mut a);
+    assert_eq!(fs::read_to_string(&log).unwrap(), events);
+    assert!(matches!(a.active_window().unwrap().kind, WindowKind::Help));
+}
diff --git a/crates/tb-ui/examples/spike.rs b/crates/tb-ui/examples/spike.rs
index b6b5ff3..38196ed 100644
--- a/crates/tb-ui/examples/spike.rs
+++ b/crates/tb-ui/examples/spike.rs
@@ -1,15 +1,12 @@
 //! Phase-0-Spike: 80×25-Bildschirmpuffer, 16-Farben-Palette, Unicode,
 //! Tastatur- und Mausereignisse.
 //!
-//! Start: `cargo run -p tb-ui --example spike` — Beenden mit Esc.
+//! Start: `cargo run -p tb-ui --features terminal --example spike -- --log events.txt` — Beenden mit Esc.
 //! Getippte Zeichen erscheinen im Eingabebereich; Sondertasten und
 //! Mausereignisse werden in der Statuszeile angezeigt.
 
-use crossterm::event::{
-    self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
-};
-use crossterm::execute;
-use std::io::stdout;
+use crossterm::event::{self, Event, KeyCode, KeyEventKind};
+use std::io::{stdout, Write};
 use std::time::Duration;
 use tb_ui::screen::{ScreenWidget, TextScreen};
 
@@ -57,8 +54,24 @@ fn status(s: &mut TextScreen, text: &str) {
 }
 
 fn main() -> anyhow::Result<()> {
-    let mut terminal = ratatui::init();
-    execute!(stdout(), EnableMouseCapture)?;
+    let args: Vec<_> = std::env::args_os().skip(1).collect();
+    anyhow::ensure!(
+        args.is_empty() || (args.len() == 2 && args[0] == "--log"),
+        "Aufruf: spike [--log neue-datei]"
+    );
+    let mut log = if args.len() == 2 {
+        Some(
+            std::fs::OpenOptions::new()
+                .write(true)
+                .create_new(true)
+                .open(&args[1])?,
+        )
+    } else {
+        None
+    };
+    let _guard = tb_ui::terminal::TerminalGuard::enter(stdout())?;
+    let mut terminal = ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?;
+    let mut sequence = 0u64;
 
     let (tw, th) = crossterm::terminal::size()?;
     let mut screen = TextScreen::with_size(tw as usize, th as usize);
@@ -70,7 +83,12 @@ fn main() -> anyhow::Result<()> {
         if !event::poll(Duration::from_millis(100))? {
             continue;
         }
-        match event::read()? {
+        let received = event::read()?;
+        sequence += 1;
+        if let Some(log) = &mut log {
+            writeln!(log, "{sequence}: {received:?}")?;
+        }
+        match received {
             Event::Key(k) if k.kind != KeyEventKind::Release => match k.code {
                 KeyCode::Esc => break,
                 KeyCode::Char(c) => {
@@ -105,7 +123,5 @@ fn main() -> anyhow::Result<()> {
         }
     }
 
-    execute!(stdout(), DisableMouseCapture)?;
-    ratatui::restore();
     Ok(())
 }
diff --git a/crates/tb-ui/src/screen.rs b/crates/tb-ui/src/screen.rs
index 8727b91..8297812 100644
--- a/crates/tb-ui/src/screen.rs
+++ b/crates/tb-ui/src/screen.rs
@@ -12,11 +12,28 @@ use ratatui::layout::Rect;
 use ratatui::style::{Color, Style};
 use ratatui::widgets::Widget;
 
-/// Abbildung der klassischen 16-Farben-Palette auf ANSI-Indexfarben.
-/// (Klassisch: 1 = Blau, 4 = Rot — ANSI: 1 = Rot, 4 = Blau usw.)
+/// Klassische DOS-Farbnummern als absolute RGB-Werte, ohne Terminalprofil.
 pub fn basic_color(n: u8) -> Color {
-    const MAP: [u8; 16] = [0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15];
-    Color::Indexed(MAP[(n & 0x0F) as usize])
+    const RGB: [(u8, u8, u8); 16] = [
+        (0, 0, 0),
+        (0, 0, 170),
+        (0, 170, 0),
+        (0, 170, 170),
+        (170, 0, 0),
+        (170, 0, 170),
+        (170, 85, 0),
+        (170, 170, 170),
+        (85, 85, 85),
+        (85, 85, 255),
+        (85, 255, 85),
+        (85, 255, 255),
+        (255, 85, 85),
+        (255, 85, 255),
+        (255, 255, 85),
+        (255, 255, 255),
+    ];
+    let (r, g, b) = RGB[(n & 0x0F) as usize];
+    Color::Rgb(r, g, b)
 }
 
 /// Zeichenbares Gegenstück zu [`TextScreen`].
@@ -66,9 +83,9 @@ mod tests {
 
     #[test]
     fn farbabbildung() {
-        assert_eq!(basic_color(1), Color::Indexed(4)); // klassisch Blau
-        assert_eq!(basic_color(4), Color::Indexed(1)); // klassisch Rot
-        assert_eq!(basic_color(14), Color::Indexed(11)); // Gelb
-        assert_eq!(basic_color(15), Color::Indexed(15)); // Weiß
+        assert_eq!(basic_color(1), Color::Rgb(0, 0, 170)); // klassisch Blau
+        assert_eq!(basic_color(4), Color::Rgb(170, 0, 0)); // klassisch Rot
+        assert_eq!(basic_color(14), Color::Rgb(255, 255, 85)); // Gelb
+        assert_eq!(basic_color(15), Color::Rgb(255, 255, 255)); // Weiß
     }
 }
diff --git a/crates/tb-ui/src/terminal.rs b/crates/tb-ui/src/terminal.rs
index 77493b2..67a6c8d 100644
--- a/crates/tb-ui/src/terminal.rs
+++ b/crates/tb-ui/src/terminal.rs
@@ -11,23 +11,47 @@ use std::io::{self, Write};
 pub struct TerminalGuard<W: Write> {
     writer: W,
     raw: fn(bool) -> io::Result<()>,
+    #[cfg(windows)]
+    modes: Option<ConsoleModes>,
 }
 impl<W: Write> TerminalGuard<W> {
     pub fn enter(writer: W) -> io::Result<Self> {
-        Self::with_raw(writer, |on| {
+        // Farben tragen UI-/BASIC-Zustand und müssen auch ohne Farberkennung
+        // oder mit NO_COLOR als explizite RGB-Werte ausgegeben werden.
+        crossterm::style::force_color_output(true);
+        #[cfg(windows)]
+        let modes = ConsoleModes::capture()?;
+        #[cfg(windows)]
+        modes.enable_output()?;
+        #[allow(unused_mut)]
+        let mut guard = Self::with_raw(writer, |on| {
             if on {
                 enable_raw_mode()
             } else {
                 disable_raw_mode()
             }
-        })
+        })?;
+        #[cfg(windows)]
+        {
+            guard.modes = Some(modes);
+        }
+        Ok(guard)
     }
     fn with_raw(writer: W, raw: fn(bool) -> io::Result<()>) -> io::Result<Self> {
-        let mut guard = Self { writer, raw };
+        let mut guard = Self {
+            writer,
+            raw,
+            #[cfg(windows)]
+            modes: None,
+        };
         guard.resume()?;
         Ok(guard)
     }
     fn resume(&mut self) -> io::Result<()> {
+        #[cfg(windows)]
+        if let Some(modes) = &self.modes {
+            modes.enable_output()?;
+        }
         (self.raw)(true)?;
         execute!(
             self.writer,
@@ -38,15 +62,18 @@ impl<W: Write> TerminalGuard<W> {
         )
     }
     fn suspend(&mut self) -> io::Result<()> {
-        (self.raw)(false)?;
-        execute!(
+        let raw = (self.raw)(false);
+        let output = execute!(
             self.writer,
             ResetColor,
             Show,
             DisableMouseCapture,
             DisableBracketedPaste,
             LeaveAlternateScreen
-        )
+        );
+        #[cfg(windows)]
+        let output = output.and(self.modes.as_ref().map_or(Ok(()), ConsoleModes::restore));
+        raw.and(output)
     }
     /// Foreground child owns cooked terminal input. Always restore the IDE,
     /// including spawn/wait errors and a child interrupted by Ctrl+C.
@@ -91,6 +118,10 @@ impl<W: Write> TerminalGuard<W> {
 }
 impl<W: Write> Drop for TerminalGuard<W> {
     fn drop(&mut self) {
+        #[cfg(windows)]
+        if let Some(modes) = &self.modes {
+            let _ = modes.enable_output();
+        }
         let _ = (self.raw)(false);
         let _ = execute!(
             self.writer,
@@ -103,6 +134,58 @@ impl<W: Write> Drop for TerminalGuard<W> {
     }
 }
 
+// Crossterm stellt unter Windows Raw-/Mausflags pauschal zurück. Der Besitzer
+// muss zusätzlich die tatsächlichen Eingabe- und Ausgabemodi wiederherstellen.
+#[cfg(windows)]
+#[derive(Debug)]
+struct ConsoleModes([(std::os::windows::io::RawHandle, u32); 2]);
+#[cfg(windows)]
+#[link(name = "kernel32")]
+extern "system" {
+    fn GetStdHandle(kind: u32) -> std::os::windows::io::RawHandle;
+    fn GetConsoleMode(handle: std::os::windows::io::RawHandle, mode: *mut u32) -> i32;
+    fn SetConsoleMode(handle: std::os::windows::io::RawHandle, mode: u32) -> i32;
+}
+#[cfg(windows)]
+impl ConsoleModes {
+    fn capture() -> io::Result<Self> {
+        let mut modes = [(std::ptr::null_mut(), 0); 2];
+        for (slot, kind) in modes.iter_mut().zip([(-10i32) as u32, (-11i32) as u32]) {
+            // Borrowed standard handles: valid until the terminal owner exits.
+            slot.0 = unsafe { GetStdHandle(kind) };
+            if unsafe { GetConsoleMode(slot.0, &mut slot.1) } == 0 {
+                return Err(io::Error::last_os_error());
+            }
+        }
+        Ok(Self(modes))
+    }
+    fn enable_output(&self) -> io::Result<()> {
+        // Crossterm caches ANSI support, but a Shell handoff restores the old
+        // output flags. Re-enable VT for each ownership cycle, including re-entry.
+        if crossterm::ansi_support::supports_ansi()
+            && unsafe { SetConsoleMode(self.0[1].0, self.0[1].1 | 0x0004) } == 0
+        {
+            return Err(io::Error::last_os_error());
+        }
+        Ok(())
+    }
+    fn restore(&self) -> io::Result<()> {
+        let mut result = Ok(());
+        for (handle, mode) in self.0 {
+            if unsafe { SetConsoleMode(handle, mode) } == 0 && result.is_ok() {
+                result = Err(io::Error::last_os_error());
+            }
+        }
+        result
+    }
+}
+#[cfg(windows)]
+impl Drop for ConsoleModes {
+    fn drop(&mut self) {
+        let _ = self.restore();
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -149,7 +232,7 @@ mod tests {
             ("exit 7", false, 7),
             (
                 if cfg!(windows) {
-                    "ping -n 30 127.0.0.1 >nul"
+                    "for /L %i in (1,1,1000000000) do @rem"
                 } else {
                     "exec sleep 30"
                 },
@@ -160,10 +243,13 @@ mod tests {
             let out = Output(Rc::default());
             let copy = out.clone();
             let mut guard = TerminalGuard::with_raw(out, raw).unwrap();
-            assert_eq!(
-                guard.shell_with_interrupt(command, || abort).unwrap(),
-                expected
-            );
+            let started = std::time::Instant::now();
+            let code = guard.shell_with_interrupt(command, || abort).unwrap();
+            if !abort {
+                assert_eq!(code, expected);
+            }
+            // Windows TerminateProcess liefert einen Code; Unix ein Signal.
+            assert!(started.elapsed() < std::time::Duration::from_secs(5));
             assert_eq!(RAW.load(Ordering::SeqCst), 1);
             let text = String::from_utf8(copy.0.borrow().clone()).unwrap();
             assert_eq!(text.matches("?1049h").count(), 2);
@@ -187,4 +273,111 @@ mod tests {
             .unwrap()
             .contains("?25h"));
     }
+    /// Run explicitly on Windows; never counts as a Windows Terminal UI test.
+    #[cfg(windows)]
+    #[test]
+    #[ignore = "benötigt Windows mit echter Konsole; isolierter Kindprozess"]
+    fn windows_console_restores_modes() {
+        use std::os::windows::{io::AsRawHandle, process::CommandExt};
+        use std::process::{Command, Stdio};
+        use std::time::{Duration, Instant};
+        const CHILD: &str = "TB_CONSOLE_TEST_CHILD";
+        if std::env::var_os(CHILD).is_none() {
+            let mut child = Command::new(std::env::current_exe().unwrap())
+                .args([
+                    "--exact",
+                    "terminal::tests::windows_console_restores_modes",
+                    "--ignored",
+                    "--nocapture",
+                ])
+                .env(CHILD, "1")
+                .creation_flags(0x00000010) // CREATE_NEW_CONSOLE: never touches the caller's console.
+                .stdin(Stdio::null())
+                .stdout(Stdio::null())
+                .stderr(Stdio::null())
+                .spawn()
+                .unwrap();
+            let deadline = Instant::now() + Duration::from_secs(30);
+            loop {
+                if let Some(status) = child.try_wait().unwrap() {
+                    assert!(
+                        status.success(),
+                        "Isolierte Windows-Konsolenprüfung: {status}"
+                    );
+                    return;
+                }
+                if Instant::now() >= deadline {
+                    let _ = child.kill();
+                    let _ = child.wait();
+                    panic!("Windows-Konsolenprüfung hat die Frist überschritten");
+                }
+                std::thread::sleep(Duration::from_millis(20));
+            }
+        }
+        #[link(name = "kernel32")]
+        extern "system" {
+            fn SetStdHandle(kind: u32, handle: std::os::windows::io::RawHandle) -> i32;
+        }
+        let open = |name| {
+            std::fs::OpenOptions::new()
+                .read(true)
+                .write(true)
+                .open(name)
+                .unwrap()
+        };
+        let input = open("CONIN$");
+        let output = open("CONOUT$");
+        // Standard streams started as NUL; bind to this child's own new console.
+        assert_ne!(
+            unsafe { SetStdHandle((-10i32) as u32, input.as_raw_handle()) },
+            0
+        );
+        assert_ne!(
+            unsafe { SetStdHandle((-11i32) as u32, output.as_raw_handle()) },
+            0
+        );
+        let original = ConsoleModes::capture().unwrap();
+        // Non-default echo flags catch a reset-to-default masquerading as restore.
+        let mode = original.0[0].1 & !0x0004; // ENABLE_ECHO_INPUT
+        assert_ne!(unsafe { SetConsoleMode(input.as_raw_handle(), mode) }, 0);
+        let expected = ConsoleModes::capture().unwrap();
+        let assert_restored = || assert_eq!(ConsoleModes::capture().unwrap().0, expected.0);
+        for (command, abort, code) in [
+            ("exit 0", false, 0),
+            ("exit 7", false, 7),
+            ("for /L %i in (1,1,1000000000) do @rem", true, 0),
+        ] {
+            let mut guard = TerminalGuard::enter(io::stdout()).unwrap();
+            guard.suspend().unwrap();
+            assert_restored();
+            guard.resume().unwrap();
+            if crossterm::ansi_support::supports_ansi() {
+                assert_ne!(ConsoleModes::capture().unwrap().0[1].1 & 0x0004, 0);
+            }
+            let result = guard.shell_with_interrupt(command, || abort).unwrap();
+            if !abort {
+                assert_eq!(result, code);
+            }
+            assert!(crossterm::terminal::is_raw_mode_enabled().unwrap());
+            drop(guard);
+            assert_restored();
+        }
+        let error = (|| -> io::Result<()> {
+            let _guard = TerminalGuard::enter(io::stdout())?;
+            Err(io::Error::other("Erzwungener Renderfehler"))
+        })();
+        assert!(error.is_err());
+        assert_restored();
+        struct Broken;
+        impl Write for Broken {
+            fn write(&mut self, _: &[u8]) -> io::Result<usize> {
+                Err(io::Error::other("Initialisierung"))
+            }
+            fn flush(&mut self) -> io::Result<()> {
+                Ok(())
+            }
+        }
+        assert!(TerminalGuard::enter(Broken).is_err());
+        assert_restored();
+    }
 }
diff --git a/docs/ide-referenz.md b/docs/ide-referenz.md
index 6cfa05d..6104316 100644
--- a/docs/ide-referenz.md
+++ b/docs/ide-referenz.md
@@ -102,24 +102,23 @@ Laufzeit-Defaults kompilierter Programme weichen ab (Titel Weiß auf Schwarz).
 
 ## 4a. Farbabbildung und Kontrast (Phase 6)
 
-Die DOS-Farbnummern in Options→Display bleiben erhalten. Die IDE verwendet
-bei `COLORTERM=truecolor`/`24bit` explizite RGB-Werte der klassischen
-16-Farben-Palette; bei `TERM=…256color` feste Einträge im Farbwürfel und der
-Graurampe außerhalb der ersten 16 ANSI-Farben. Ein generisches `COLORTERM`
-verdeckt diese 256-Farben-Erkennung nicht. Auf Windows verwendet die IDE
-zusätzlich die ANSI-Fähigkeitserkennung des vorhandenen Terminaladapters.
-Die Erkennung erfolgt einmal pro Prozess; nach Profiländerungen neu starten.
-
-Damit hängt das Standardtheme bei expliziter Farbausgabe nicht von einer
-pastellfarbenen ANSI-Palette ab. Beispielwerte: Codehintergrund `#0000AA`,
-Codeschrift `#AAAAAA`, aktiver Titel `#FFFFFF` auf `#AA00AA`. DOS-Farbe 6
-ist Braun (`#AA5500`), nicht das frei konfigurierte ANSI-Gelb. Der
-256-Farben-Fallback nähert diese Werte an (`#0000AF`, `#A8A8A8`, `#AF00AF`).
-Die globale Terminalpalette wird nicht verändert. Unbekannte beziehungsweise
-reine 16-Farb-Terminals verwenden weiterhin die ANSI-Zuordnung; dort ist ein
-geeignetes Terminalprofil nötig. `NO_COLOR`, Transparenz, Farbfilter und
-Inaktivitätsdimmung des Emulators können die Darstellung zusätzlich verändern;
-die IDE überschreibt solche Benutzereinstellungen nicht.
+Die DOS-Farbnummern in Options→Display bleiben erhalten. IDE, Formularvorschau
+und BASIC-Ausgabe verwenden dieselbe feste klassische RGB-Palette. Jede
+Farbe wird als 24-Bit-Vorder-/Hintergrundwert ausgegeben, unabhängig von
+`TERM`, `COLORTERM`, `NO_COLOR` oder der ANSI-Palette des Terminalprofils.
+Es gibt keinen Rückfall auf Index- oder Profilfarben. Die globale
+Terminalpalette wird nicht verändert; gespeichert werden weiterhin
+DOS-Farbnummern, und BASIC bestimmt seine eigenen ForeColor/BackColor-Werte.
+
+Beispielwerte: Codehintergrund `#0000AA`, Codeschrift `#AAAAAA`, aktiver
+Titel `#FFFFFF` auf `#AA00AA`. DOS-Farbe 6 ist Braun (`#AA5500`).
+Fensterflächen werden vor dem Zeichnen geleert, damit Desktop-Füllzeichen
+und Inhalte verdeckter Fenster keine scheinbar helleren Hintergründe erzeugen.
+
+Die Ausgabe setzt einen Terminalemulator mit 24-Bit-Farbunterstützung voraus.
+Fehlende RGB-Unterstützung bleibt ein offener Zielbefund. Transparenz,
+Farbfilter und Inaktivitätsdimmung sind nachgelagerte Emulatorfunktionen;
+sie werden für die visuelle Farbprüfung getrennt protokolliert.
 
 ### Referenzfundstellen
 
@@ -144,7 +143,7 @@ Palette. Ansichten belegen Flächen und Rollen, keine heutige Terminalausgabe.
 | Deaktivierte Beschriftungen | Nicht ausreichend durch diese Ansichten belegt | Lesbares Schwarz auf Grau, Menüs zusätzlich `×` und kursiv |
 | Hilfe und Debuggermarkierungen | In diesen Ansichten nicht belegt | Dunkelblaue Hilfetitel/Links auf Grau; Fokus Weiß auf Blau; Breakpoints Weiß auf Rot, Ausführungszeile Schwarz auf Gelb |
 
-Die Standardpaare müssen bei RGB- und 256-Farben-Ausgabe mindestens 4,5:1
+Die Standardpaare müssen bei RGB-Ausgabe mindestens 4,5:1
 Textkontrast erreichen; notwendige nichttextuelle Fokusmarkierungen mindestens
 3:1. Die Zelltests prüfen auch Rahmen und Handles auf mindestens 4,5:1
 gegen ihren eigenen Hintergrund. Benutzerauswahl in Display bleibt frei;
@@ -154,16 +153,16 @@ Designer-Werkzeuge und Handles die IDE-Farben verwenden.
 
 Die sRGB-Kontrastberechnung ergibt für die wichtigsten Standardpaare:
 
-| Rolle | RGB | 256 Farben |
-|---|---|---|
-| Code / Hilfelink | 5,72:1 | 5,45:1 |
-| Aktiver Titel | 6,38:1 | 6,10:1 |
-| Menü / Dialog / deaktivierter Text | 9,04:1 | 8,83:1 |
-| Status / Werkzeuge | 7,33:1 | 7,75:1 |
-| Auswahl | 21,00:1 | 21,00:1 |
-| Linkfokus / Debugger | 13,29:1 | 12,97:1 |
-| Breakpoint | 7,75:1 | 7,44:1 |
-| Ausführung / Handle | 19,69:1 | 19,72:1 |
+| Rolle | RGB-Kontrast |
+|---|---|
+| Code / Hilfelink | 5,72:1 |
+| Aktiver Titel | 6,38:1 |
+| Menü / Dialog / deaktivierter Text | 9,04:1 |
+| Status / Werkzeuge | 7,33:1 |
+| Auswahl | 21,00:1 |
+| Linkfokus / Debugger | 13,29:1 |
+| Breakpoint | 7,75:1 |
+| Ausführung / Handle | 19,69:1 |
 
 ### Prüffolge für Change 05
 
@@ -188,10 +187,10 @@ Linux amd64/xterm, Linux amd64/VTE, Linux arm64/xterm und Linux arm64/VTE:
    Farbbeschriftungen und Handles; ForeColor/BackColor der Vorschau bleiben
    unabhängig vom IDE-Theme.
 6. Dieselben Ansichten mit absichtlich abweichender ANSI-Palette prüfen.
-   RGB-/256-Farben der IDE müssen stabil bleiben. Transparenz und
+   RGB-Farben von IDE und BASIC-Ausgabe müssen stabil bleiben. Transparenz und
    Inaktivitätsdimmung getrennt ein-/ausschalten und Abweichungen als
-   Emulatorverhalten dokumentieren. Für 16-Farb-Fallback das tatsächlich
-   geeignete Profil und verbleibende Unterschiede festhalten.
+   Emulatorverhalten dokumentieren. Auch ohne `COLORTERM`, mit `TERM=vt100`
+   und mit `NO_COLOR=1` muss die Anwendung dieselben RGB-Werte ausgeben.
 7. Options ändern, speichern und neu starten; Auswahl muss erhalten sein.
    IDE beenden: globale Palette und normale Shell-Anzeige müssen erhalten sein.
 
diff --git a/openspec/changes/phase-6-05-plattformmatrix/design.md b/openspec/changes/phase-6-05-plattformmatrix/design.md
index 22aa012..6e3fa4d 100644
--- a/openspec/changes/phase-6-05-plattformmatrix/design.md
+++ b/openspec/changes/phase-6-05-plattformmatrix/design.md
@@ -19,6 +19,8 @@ Phase 5 verwendete crossterm-Ereignisse, ratatui-TestBackend und `tests/support/
 
 7. Den ergänzenden Theme-Change 05a vor Abschluss der visuellen Matrix berücksichtigen. Dessen Referenzansichten und Kontrastprüfungen mit tatsächlicher Revision, Farbprofil und Standard-/abweichender ANSI-Palette in jeder Pflichtzelle prüfen; vor einer Theme-Korrektur erhobene betroffene Bilder erneut aufnehmen. Diese Nachweise gehen mit der bestehenden Matrix an 06/07.
 
+8. Benutzerbefund vom 07.09.2026: ANSI-/256-Fallback entfernen und die vorhandene Farbabbildung in tb-ui für IDE und BASIC gemeinsam nutzen. RGB-Ausgabe auch bei fehlenden Farbfähigkeitsvariablen und NO_COLOR erzwingen. Fenster vor Block/Paragraph leeren, da diese nur Stile setzen und sonst Desktopmuster oder verdeckte Texte durchscheinen. Regressionen prüfen tatsächliche Leerzeichen, Auswahl/Neuzeichnen, absolute Farbwerte und ausgegebene Escape-Sequenzen. Frühere visuelle Nachweise gelten nicht für diesen korrigierten Stand.
+
 ## Risks / Trade-offs
 
 - CI ohne interaktiven Desktop → automatisierte Zieltests plus zugeordnete manuelle Emulatornachweise, keine Gleichsetzung beider Kategorien.
diff --git a/openspec/changes/phase-6-05-plattformmatrix/proposal.md b/openspec/changes/phase-6-05-plattformmatrix/proposal.md
index 0e2efa2..ca383fa 100644
--- a/openspec/changes/phase-6-05-plattformmatrix/proposal.md
+++ b/openspec/changes/phase-6-05-plattformmatrix/proposal.md
@@ -18,7 +18,8 @@ Die bisherige headless Abnahme und der lokale Unix-PTY-Test beweisen nicht die e
 
 ### Modified Capabilities
 
-Keine. Bestehende IDE- und Host-Eingabeverträge bleiben maßgeblich.
+- `ide-oberflaeche`: Durchgehend absolute RGB-Farben und vollständig geleerte Fensterflächen; Benutzerkorrektur vom 07.09.2026.
+- `textbildschirm`: Dieselbe absolute DOS-RGB-Palette für programmbestimmte BASIC-Farben und Formularvorschau.
 
 ## Impact
 
diff --git a/openspec/changes/phase-6-05-plattformmatrix/tasks.md b/openspec/changes/phase-6-05-plattformmatrix/tasks.md
index da2b71e..5ab64d7 100644
--- a/openspec/changes/phase-6-05-plattformmatrix/tasks.md
+++ b/openspec/changes/phase-6-05-plattformmatrix/tasks.md
@@ -1,8 +1,8 @@
 ## 1. Prüfpfade vorbereiten
 
-- [ ] 1.1 Verbindliche Matrix und kurze Bediensequenzen für die vier Targets anlegen; Windows Terminal, Terminal.app sowie xterm/VTE auf beiden Linux-Architekturen müssen konkrete Versions-/Revisionsfelder und erwartete Resultate besitzen.
+- [x] 1.1 Verbindliche Matrix und kurze Bediensequenzen für die vier Targets anlegen; Windows Terminal, Terminal.app sowie xterm/VTE auf beiden Linux-Architekturen müssen konkrete Versions-/Revisionsfelder und erwartete Resultate besitzen.
 - [ ] 1.2 Bestehenden Unix-PTY-Test und passenden Windows-Konsolen-/ConPTY-Test für Shell, Abbruch und Wiederherstellung bereitstellen; positive Läufe und erzwungene Fehler dürfen keinen beschädigten Terminalmodus hinterlassen.
-- [ ] 1.3 Die gemeinsame TBL-Verbraucherprobe aus 03 samt fester Prüfsumme in jede der vier Zielabnahmen aufnehmen (quellfreies TBC/EXE, RUN, tatsächliches Host-Target); IDE-/Programmprüfungen für F1–F12, Modifikatoren, Maus, Unicode/Attribute und Resize nachvollziehbar auflisten; jeder Fall muss sichtbaren Effekt und Ausschluss doppelter BASIC-Zustellung prüfen.
+- [x] 1.3 Die gemeinsame TBL-Verbraucherprobe aus 03 samt fester Prüfsumme in jede der vier Zielabnahmen aufnehmen (quellfreies TBC/EXE, RUN, tatsächliches Host-Target); IDE-/Programmprüfungen für F1–F12, Modifikatoren, Maus, Unicode/Attribute und Resize nachvollziehbar auflisten; jeder Fall muss sichtbaren Effekt und Ausschluss doppelter BASIC-Zustellung prüfen.
 
 ## 2. Tatsächliche Zielabnahme
 
@@ -16,3 +16,11 @@
 
 - [ ] 3.1 Terminalgrenzen und verifizierte Ersatzwege dokumentieren; kein Produktfehler oder fehlender Prüfrechner darf als bestandene Matrixzelle erscheinen.
 - [ ] 3.2 Automatisierbare Zielprüfungen an 06 übergeben und Matrixbericht fertigstellen; alle Pflichtzellen, Revisionen, vier Ergebnisse derselben TBL-Verbraucherprobe und tatsächlichen Ergebnisse müssen vor Abschluss vorliegen. Die weiterhin offenen Archivaufgaben 05a/1.2 (konkretes Benutzerprofil, frische/gespeicherte Optionen, Transparenz/Dimmung), 05a/3.2 (visuelle Matrix) und 05a/3.3 (befundfreie Schlussverifizierung) müssen hier tatsächlich abgeschlossen werden. Die visuellen Nachweise müssen das umgesetzte Theme aus 05a einschließlich Profil-/Farbmodus und relevanter UI-Zustände abdecken. Native Prüfsysteme dürfen separat vom zentralen Buildrunner betrieben und Nachweise dokumentiert manuell erhoben werden.
+
+Arbeitsstand 07.09.2026: Matrix/Bedienfolgen in `docs/plattformmatrix.md`, Eingabeprobe `tests/platform/input.frm`, gemeinsamer Aufruf `tests/support/platform-abnahme.py`. 1.2 ist implementiert und für Windows cross-geprüft, bleibt bis zum tatsächlichen Windows-Konsolenlauf offen. Automatisierte macOS-arm64-Nachweise liegen unter `evidence/2026-09-07-macos-arm64`; sie schließen keine manuelle Terminalzelle. Details und offene Befunde: `verification.md`.
+
+RGB-Korrektur nach Benutzerbefund vom 07.09.2026 gehört zu 2.5: keine
+ANSI-/256-Fallbackfarben; gemeinsame feste RGB-Palette für IDE und BASIC;
+Fensterflächen ohne durchscheinendes Desktopmuster. Neue Delta-Spezifikationen
+in `ide-oberflaeche` und `textbildschirm` ersetzen den früheren Fallbackvertrag.
+Die betroffene visuelle Zielprüfung bleibt bis zur echten Wiederholung offen.
diff --git a/tests/support/ide-execution-pty.py b/tests/support/ide-execution-pty.py
index b422070..ee369fa 100644
--- a/tests/support/ide-execution-pty.py
+++ b/tests/support/ide-execution-pty.py
@@ -2,6 +2,7 @@
 """Headless Unix PTY smoke: real terminal handoff, child Ctrl+C and IDE resume.
 Run after cargo build -p tb-ide: python3 tests/support/ide-execution-pty.py
 """
+import argparse
 import fcntl
 import os
 from pathlib import Path
@@ -13,10 +14,10 @@ import tempfile
 import termios
 import time
 
-binary = Path(__file__).resolve().parents[2] / 'target/debug/tb'
 
 
-def exercise(abort=False, file_shell=False):
+
+def exercise(binary, transcript_dir=None, abort=False, file_shell=False):
     with tempfile.TemporaryDirectory(prefix='tb-pty-') as directory:
         source = Path(directory) / 'main.bas'
         command = "printf '\\nWAITING_CHILD\\n'; exec sleep 30" if abort else "printf '\\nSHELL_PROOF\\n'; exit 7"
@@ -32,7 +33,7 @@ def exercise(abort=False, file_shell=False):
                 os.dup2(slave, fd)
             os.close(master)
             os.close(slave)
-            os.environ['TERM'] = 'xterm-256color'
+            os.environ.setdefault('TERM', 'xterm-256color')
             os.environ['XDG_CONFIG_HOME'] = directory
             # Keep the controlling terminal alive while checking restoration.
             signal.signal(signal.SIGINT, lambda *_: None)
@@ -83,7 +84,17 @@ def exercise(abort=False, file_shell=False):
                     transcript.extend(os.read(master, 65536))
             assert transcript.count(b'?1049h') >= 2
             assert transcript.count(b'?1049l') >= 2
+            # Tatsächliche Ausgabe muss auch bei NO_COLOR/fehlender Erkennung
+            # dieselben absoluten IDE-Farben enthalten, ohne Palettenänderung.
+            assert b'38;2;170;170;170' in transcript
+            assert b'48;2;0;0;170' in transcript
+            assert b'48;2;170;0;170' in transcript
+            assert b'38;5;' not in transcript and b'48;5;' not in transcript
+            assert b'\x1b]' not in transcript
         finally:
+            if transcript_dir:
+                name = 'file-shell' if file_shell else 'shell-abort' if abort else 'shell-exit'
+                (transcript_dir / (name + '.ansi')).write_bytes(transcript)
             if not reaped:
                 os.killpg(pid, signal.SIGKILL)
                 os.waitpid(pid, 0)
@@ -91,6 +102,18 @@ def exercise(abort=False, file_shell=False):
             os.close(slave)
 
 
-for kwargs in ({}, {'abort': True}, {'file_shell': True}):
-    exercise(**kwargs)
-    print('PASS', kwargs or {'shell_exit': 7})
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('--binary', type=Path, default=Path(__file__).resolve().parents[2] / 'target/debug/tb')
+    parser.add_argument('--transcript-dir', type=Path)
+    args = parser.parse_args()
+    binary = args.binary.resolve(strict=True)
+    if args.transcript_dir:
+        args.transcript_dir.mkdir(parents=True, exist_ok=True)
+    for kwargs in ({}, {'abort': True}, {'file_shell': True}):
+        exercise(binary, args.transcript_dir, **kwargs)
+        print('PASS', kwargs or {'shell_exit': 7})
+
+
+if __name__ == '__main__':
+    main()
