Phase 6: Plattformprüfungen und absolute RGB-Farben, Change 05 archivieren

This commit is contained in:
2026-09-07 21:12:25 +02:00
parent 5aa920b768
commit 796564795a
55 changed files with 3772 additions and 200 deletions

View File

@@ -514,6 +514,11 @@ diese Ergänzung erweitert die Umsetzung auf acht Changes (0107 plus 05a).
05a ist am 07.09.2026 auf Benutzeranweisung mit lokal verifizierter
Implementierung archiviert; seine drei offenen visuellen Nachweise werden
in 05 weitergeführt und bleiben Voraussetzung der Phasenabnahme.
05 ist am 07.09.2026 auf Benutzeranweisung synchronisiert und archiviert:
RGB-/Fensterflächenkorrektur vom Benutzer bestätigt, automatisierte macOS-
Prüfungen bestanden. Acht unvollständige Matrixaufgaben bleiben als
Nachweisbedarf bei 06/07; Windows-Ausführung ist vorerst zurückgestellt.
Nächster Umsetzungschange ist 06 (Gitea Actions und Releases).
Release-Pakete: Windows als `.7z`, macOS und beide Linux-Architekturen
als `.tar.gz`, jeweils mit `tb`, `tbc` und der passenden `tbrt`-Vorlage

View File

@@ -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)

View File

@@ -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}"
);
}
}
}

View File

@@ -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));
}

View File

@@ -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(())
}

View File

@@ -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ß
}
}

View File

@@ -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();
}
}

View File

@@ -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.
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.
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.
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.

193
docs/plattformmatrix.md Normal file
View File

@@ -0,0 +1,193 @@
# Plattformabnahme Phase 6, Change 05
Diese Matrix prüft die tatsächliche IDE und native Exporte auf den vier
Release-Zielen. Automatisierte Headless-/PTY-/Konsolentests und die manuelle
Emulatorbedienung sind getrennte Nachweise. Eine grüne Compilation oder ein
archivierter Fachchange schließt keine noch ungeprüfte Matrixzelle.
## Verbindliche Matrix
| ID | Tatsächliches Ziel | Emulator | OS-Version | Terminalversion | Revision / Executable-SHA-256 | Visueller Stand |
|---|---|---|---|---|---|---|
| W | Windows amd64 | Windows Terminal | offen | offen | offen | nicht ausgeführt |
| M | macOS arm64 | Terminal.app | 26.6.2 (25G83) | 2.15 | je Lauf eintragen | nicht ausgeführt |
| X64 | Linux amd64 | xterm | offen | offen | offen | nicht ausgeführt |
| V64 | Linux amd64 | VTE-Terminal, Produkt benennen | offen | offen | offen | nicht ausgeführt |
| XA | Linux arm64 | xterm | offen | offen | offen | nicht ausgeführt |
| VA | Linux arm64 | VTE-Terminal, Produkt benennen | offen | offen | offen | nicht ausgeführt |
Je Zelle zusätzlich Datum/Prüfer, `TERM`, `COLORTERM`, `NO_COLOR`, Profil,
Transparenz/Dimmung, Größe und OS-/Terminal-Shortcutbelegungen protokollieren.
Unbekannte Werte sind offen, keine Defaultannahmen. Ein Prüfcontainer auf
Linux ersetzt weder Windows/macOS noch einen echten xterm-/VTE-Lauf.
## Automatisierte Zielprüfungen
Kandidaten im passenden Checkout bauen:
```sh
cargo build --locked --release -p tb-ide -p tb-cli -p tb-runner -p tb-export --bins
python3 tests/support/platform-abnahme.py --bin-dir target/release --output /tmp/tb-platform-proof
```
Unter Windows `python` und ein neues, absolutes Ausgabeverzeichnis verwenden;
zusätzlich `--windows-console-test <tb-ui-Testexecutable.exe>` übergeben.
Dieses Testexecutable entsteht mit
`cargo test --locked -p tb-ui --features terminal --lib --no-run` auf Windows
oder wird als passendes Cross-Testartefakt bereitgestellt. Direkter Aufruf:
```text
<tb-ui-Testexecutable.exe> --exact terminal::tests::windows_console_restores_modes --ignored --nocapture
```
Der Test verwendet eine eigene Kindkonsole mit 30-Sekunden-Frist. Er prüft
nichtstandardmäßige Eingabeflags, Shell-Ergebnisse 0/7, Abbruch, Rückkehr zu
Raw Mode sowie Cleanup nach Render- und Initialisierungsfehlern. Die
Windows-Terminal-Bedienung der Matrix wird dadurch nicht abgedeckt. Der
fehlende Windows-Test wird vom Sammelaufruf als fehlend gewertet.
`platform-abnahme.py` sammelt Bericht, Logdateien und Unix-PTY-Transkripte in
einem neuen Verzeichnis; es überschreibt keine vorhandenen Nachweise. Es
verwendet die vorhandenen `native-abnahme.py`, `library-abnahme.py` und
`ide-execution-pty.py`. `report.json` enthält tatsächliches Host-Target,
OS-/Pythonversion, Checkout-Revision, Dirty-Status/Diff-Prüfsumme,
Executable-/Harness-Prüfsummen und Ergebnisse. Bei lokalen Änderungen gehört
`working-tree.patch` zum Nachweis. Binaries müssen aus genau diesem Stand
gebaut werden; eine Checkout-Revision allein belegt ihre Herkunft nicht.
Für 06 kommen Buildmanifest und exakte Paketprüfsumme hinzu.
`all_automated_passed` bezeichnet nur diese automatisierten Prüfungen.
`manual_terminal_status` bleibt `not_run` und `matrix_complete` bleibt
`false`. Ein erfolgreicher Skript-Exit ist keine Releasefreigabe.
`tb-template` wird hier als Prüf-/Buildhilfe benötigt; Anwenderprogramme
benötigen es nicht.
Die TBL-Probe verwendet auf allen vier Zielen unverändert
`tests/libraries/portable.tbl`, SHA-256
`5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df`.
Das jeweilige `library.log` muss quellfreies Linken nach TBC und EXE,
tatsächliche Ausführung und den gleichen RUN-Neustart belegen. Ein neues,
pro Target anders erzeugtes TBL ist kein Ersatz.
## Eingabeprobe vorbereiten
`tests/platform/input.frm` in ein eigenes Prüfverzeichnis kopieren. Die Probe
schreibt bei jedem Start dort `platform-events.log` neu; frühere Logs vorher
unter der jeweiligen Matrix-ID sichern. IDE mit der kopierten Form starten.
Als native Probe dieselbe Form über Run → Make EXE File exportieren (passende
vorbereitete tbrt-Vorlage), die IDE schließen und das Executable direkt aus
dem Prüfverzeichnis starten. Alternativ:
```text
tbc build input.frm --exe --target <Target-Triple> --template <vorbereitete-tbrt-Vorlage> -o <Probe-Executable>
```
Die Triples sind `x86_64-pc-windows-msvc`, `aarch64-apple-darwin`,
`x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`. Mac-Ausgaben müssen
mit `codesign --verify --strict <Probe-Executable>` geprüft und tatsächlich
gestartet werden. Fenster und Zähler sichtbar erfassen; Datei → Ende oder
der Ende-Button beenden die Probe.
Im Textfeld zählt die Probe KeyDown/KeyUp/KeyPress, am Zählen-Button Click,
auf freier Formfläche MouseDown/MouseUp/MouseMove. Ein einzelnes `a` ohne
Halten erzeugt genau Down 97, Press 97, Up 97. Ein zusätzlicher roher
Release darf keine zweite BASIC-Zustellung erzeugen. Erweiterte Tasten
führen im bestehenden Forms-Vertrag KeyCode 0 und kein KeyPress; ihre
Identität wird deshalb zusätzlich in der Rohdiagnose geprüft. Modifierbits
werden im Log mitgeschrieben. Repeat bei absichtlich gehaltenen Tasten ist
von ungewollter Doppelzustellung zu unterscheiden.
Rohdiagnose aus dem bestehenden Spike:
```sh
cargo build --locked --release -p tb-ui --features terminal --example spike
# Auf Windows: spike.exe; --log verlangt eine noch nicht existierende Datei.
target/release/examples/spike --log /tmp/tb-raw-events.txt
```
Die Diagnose protokolliert nummerierte Crossterm-Ereignisse einschließlich
Press/Release/Repeat, Modifier, Mauskoordinaten und Resize. Esc beendet sie.
Sie prüft den Eingang; das Formularlog prüft die BASIC-Wirkung. Beide müssen
zur jeweiligen physischen Einzelfolge passen. Diagnose und Formular werden
nacheinander mit derselben Belegung geprüft, nicht als vermeintlich identischer
Eventstream zweier gleichzeitiger Empfänger.
## Bedienfolge pro Matrixzelle
Für IDE-Tasten ein kleines Modul mit mehreren Anweisungen, `STOP`, einer
Prozedur und gesetztem Breakpoint bereithalten. Tests im jeweils angegebenen
Kontext ausführen. Die Rohdiagnose und die native Formularprobe mit F1 bis
F12 zusätzlich einzeln prüfen; im nativen Textfeld müssen je zugestellter
Funktionstaste genau ein Down/Up-Paar und kein KeyPress erscheinen.
| Taste | IDE-Kontext und sichtbares Soll |
|---|---|
| F1 | Codekontext: passende Hilfe; Shift+F1 Using Help; Alt+F1 zurück, Ctrl+F1 nächstes Thema |
| F2 | Form/Projekt: Code öffnen; im Designer Value-Box fokussieren |
| F3 | Nach vorbereiteter Suche: nächsten Treffer markieren |
| F4 | Output Screen öffnen/zurück; Ctrl+F4 Fenster schließen; Alt+F4 Exit-Dialog |
| F5 | STOP/Breakpoint: fortsetzen; Shift+F5 neu starten; Ctrl+F5 Fenster wiederherstellen |
| F6 | Nächstes Fenster; Shift+F6 vorheriges, Fokus sichtbar |
| F7 | Pausierter Code: bis Cursor laufen; Ctrl+F7 Fensterverschiebung |
| F8 | Einzelschritt; Ctrl+F8 Fenstergröße; Shift+F8 vorherige History-Position bei aktivierter History |
| F9 | Breakpoint einmal umschalten; Shift+F9 Instant Watch; Ctrl+F9 minimieren |
| F10 | Prozedurschritt; im Designer Properties/Menü wechseln; Ctrl+F10 maximieren; Shift+F10 nächste History-Position |
| F11 | Menü aktivieren und Eintrag mit Pfeilen/Enter ausführen |
| F12 | Event Procedures; Shift+F12 Form Designer |
Nach jeder IDE-Aktion bei laufender Formularprobe deren Log prüfen: Die
IDE-Taste darf nicht zusätzlich als BASIC-Ereignis auftauchen. Umgekehrt
muss ein normales Zeichen im aktiven Output genau einmal in der Probe
ankommen. Editor-Kopieren mit Ctrl+C verändert weder Programmzustand noch
Formularlog; im Output muss Ctrl+C den BASIC-Lauf unterbrechen. Ctrl+Break
prüft denselben Abbruchweg. Nach Break/Continue ein weiteres Zeichen senden:
es darf kein altes Ereignis erneut zugestellt werden.
Weitere Pflichtfolgen:
- Alt+F und Menümnemonics, Escape und Tab/Shift+Tab: Menü/Dialog öffnet,
genau eine Aktion, korrekt zurückkehrender Fokus; Formularlog ohne Leck.
- Ein Klick auf Zählen: Click-Zähler steigt genau um eins. Mausdruck und
Loslassen auf freier Formfläche: genau ein passendes Logpaar; Mausbewegung
erzeugt Move-Einträge ohne Click. Bewegung darf vom Emulator zusammengefasst
werden; Koordinaten und logische Ereignisse müssen konsistent sein.
- Unicode in Form und Editor (`Äöß`, `中界`, Rahmen): keine versetzten
Folgezeichen oder zerstörten Rahmen. Farben und Attribute mit Spike,
IDE-Zustände und Profil-Gegenprobe nach `ide-referenz.md`, Abschnitt 4a.
- Von 100×30 nach 79×24 und zurück: IDE zeigt nur den Größenhinweis und kehrt
ohne Dokument-/Cursorverlust zurück; native Probe bleibt konsistent und
bedienbar. Danach Mauskoordinaten und Formularzähler erneut prüfen.
- `INPUT`/`LINE INPUT`: auf Eingabe warten, Text eingeben, abbrechen,
fortsetzen beziehungsweise neu starten. IDE und native Ausgabe prüfen.
- Normaler Exit, `ERROR 6` und Shell-Rückkehr: Shell-Echo, Zeilenmodus,
sichtbarer Cursor und normale Anzeige erhalten. SHELL mit Exit 0 und 7,
nicht vorhandenem Kindkommando und unterbrochenem Kind prüfen. Anschließend
in der IDE Eingabe/Continue und abschließenden Exit prüfen.
- Für Shell-Abbruch einen interaktiven Kindprozess verwenden und Ctrl+C
dort auslösen; sein Abbruch darf nicht nochmals an BASIC zugestellt werden.
Unter Unix unterstützt `sleep 30`, unter Windows ein passendes Kindkommando
die Probe. Keine Unix-Kommandozeile unverändert als Windows-Soll verwenden.
## Befunde und Nachweisformat
Je Testfall ID, Kontext, Eingabe, erwarteten Effekt, tatsächlichen Effekt,
Zähler/Logbezug, Screenshot/Transkript und Status erfassen. Pro Shortcut, den
OS oder Emulator abfängt: genaue Belegung benennen und denselben vorhandenen
Menü-/Mausweg tatsächlich prüfen. Erst ein funktionierender Ersatzweg belegt
eine Terminalgrenze; ein Produktfehler bleibt ein Befund. Derzeit ist kein
solcher Ersatzweg durch eine echte Pflichtmatrixzelle verifiziert.
Offene Zugänge, Stand 07.09.2026: Computer Use hat Terminal.app ausdrücklich
gesperrt. Windows sowie Linux mit xterm/VTE sind noch nicht als Prüfsysteme
zugänglich. Der erreichbare Gitea-Host ist Linux aarch64 ohne die benötigten
GUI-Terminals; beim aktuellen Lesezugriff startet sein Runnercontainer
wiederholt neu. Die Runnerkonfiguration wurde nicht geändert. Das sind
fehlende Nachweise beziehungsweise Infrastrukturzustände, keine bestandenen
Zieltests und keine unterstellten Produktfehler.
Die aus 05a übernommenen offenen Aufgaben 1.2, 3.2 und 3.3 werden durch die
Profil-Gegenprobe, sämtliche visuellen Matrixzellen und die abschließende
befundfreie Verifizierung geschlossen. Für 06/07 müssen Prüfsystem,
Toolrevision und konkrete Artefakte zusammenpassen. Nach Änderungen an
Terminaladapter, Eingaberouting oder Theme sind die betroffenen Zielnachweise
auf der neuen Revision erneut zu erheben.

View File

@@ -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.

View File

@@ -0,0 +1,7 @@
Compiling tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Compiling tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Compiling tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Compiling tb-runner v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-runner)
Compiling tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Compiling tb-cli v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-cli)
Finished `release` profile [optimized] target(s) in 32.49s

View File

@@ -0,0 +1,5 @@
Checking tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Checking tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Checking tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Checking tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.81s

View File

@@ -0,0 +1 @@
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s

View File

@@ -0,0 +1,3 @@
PASS {'shell_exit': 7}
PASS {'abort': True}
PASS {'file_shell': True}

View File

@@ -0,0 +1 @@
{"target": "aarch64-apple-darwin", "tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df", "native_execution": true, "source_free": true, "run_reset": true, "output": " 1 \n 7 3 9 1 \nTB \n"}

View File

@@ -0,0 +1,13 @@
PASS native console
PASS native restart
PASS native stop
PASS native error
PASS native project
PASS native form
PASS native external
PASS PTY Eingabe, Abbruch und Wiederherstellung (CLI/native)
PASS Exportabbruch SIGINT/SIGTERM, Original und Bereinigung
PASS corrupted length
PASS corrupted version
PASS corrupted truncated
Native Abnahme bestanden: aarch64-apple-darwin

View File

@@ -0,0 +1,3 @@
PASS {'shell_exit': 7}
PASS {'abort': True}
PASS {'file_shell': True}

View File

@@ -0,0 +1,44 @@
{
"scope": "automated-target-checks",
"started_utc": "2026-09-07T19:04:25.039868+00:00",
"host_target": "aarch64-apple-darwin",
"os_version": "macOS-26.6.2-arm64-arm-64bit-Mach-O",
"python_version": "3.14.7",
"checkout_revision": "5aa920b768a2ad29ed17c5da137e54675a7292d6",
"working_tree_dirty": true,
"working_tree_diff_sha256": "0834248d5bd0643cf959a7e6d78f74b4401d541cf65bb7226026b7eac13a136c",
"program_sha256": {
"tb": "7472ced2e905da8e8389bb9dafb221e39d2cd465110e982328e7076dc18817a9",
"tbc": "52491c691507098f60524bbc078ef8e53a1883d284803427c422db42d95aa469",
"tbrt": "4095b5412cf7c0470068c3c0b7f5ab325d0d357cd8b8fe8bbb966fdef1898df0",
"tb-template": "a9d73ea8002b8f01bbb1fdc033a2595605ceb3b8c55d4531bf00da236351ccbf"
},
"tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df",
"manual_terminal_status": "not_run",
"matrix_complete": false,
"checks": {
"library": {
"status": "passed",
"exit_code": 0
},
"native": {
"status": "passed",
"exit_code": 0
},
"ide-pty": {
"status": "passed",
"exit_code": 0
},
"artifact-stability": {
"status": "passed"
}
},
"harness_sha256": {
"library-abnahme.py": "118de32bc5a9cbb254b234b420455f75961603afb16aa94333be8c4b5b17e962",
"platform-abnahme.py": "a5eadbd4bcabaae6c3c173ce933f83e331d03b1cf22998fe564519fe8f6b9696",
"native-abnahme.py": "fe9f34bcc581e6230909b6277bae4768dbf6be068e4616b47aaff4390424ca14",
"release-abnahme.py": "f77b1481913050faa321f9978d0f6cf31e8f884a1f626a861727b5393e1bea83",
"ide-execution-pty.py": "fdbde7bf84068fe1ec8cfa037a071644add947d04930b8c73770b59f3070c985"
},
"all_automated_passed": true
}

View File

@@ -0,0 +1,110 @@
- Validating...
✓ spec/bytecode-kompilat
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/datei-eio
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ereignissteuerung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/forms-dateiformat
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/forms-objektmodell
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/forms-steuerelemente
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-ausfuehrung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-debugger
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-editor
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-formulardesigner
✓ spec/ide-hilfe
✓ spec/ide-native-exporte
✓ spec/ide-oberflaeche
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-phasenabnahme
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-projekte
✓ spec/isam-datenbank
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/kompat-testkorpus
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/konsolen-basisbibliothek
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[9]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[11]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/laufzeitfehler
✓ spec/native-executables
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/pcode-bibliotheken
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ change/phase-6-05-plattformmatrix
✓ change/phase-6-06-gitea-actions-und-releases
✓ change/phase-6-07-dokumentation-und-phasenabnahme
✓ spec/release-kompatibilitaet
✓ spec/sprach-frontend
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[11]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[13]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/sprachinventar
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/textbildschirm
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[9]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/vm-ausfuehrung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/vm-fehlerbehandlung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
Totals: 30 passed, 0 failed (30 items)

View File

@@ -0,0 +1,18 @@
M crates/tb-ide/src/render.rs
M crates/tb-ide/tests/app.rs
M crates/tb-ide/tests/execution.rs
M crates/tb-ui/examples/spike.rs
M crates/tb-ui/src/screen.rs
M crates/tb-ui/src/terminal.rs
M docs/ide-referenz.md
M openspec/changes/phase-6-05-plattformmatrix/design.md
M openspec/changes/phase-6-05-plattformmatrix/proposal.md
M openspec/changes/phase-6-05-plattformmatrix/tasks.md
M tests/support/ide-execution-pty.py
?? docs/plattformmatrix.md
?? openspec/changes/phase-6-05-plattformmatrix/evidence/
?? openspec/changes/phase-6-05-plattformmatrix/specs/ide-oberflaeche/
?? openspec/changes/phase-6-05-plattformmatrix/specs/textbildschirm/
?? openspec/changes/phase-6-05-plattformmatrix/verification.md
?? tests/platform/
?? tests/support/platform-abnahme.py

View File

@@ -0,0 +1,12 @@
Compiling tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.46s
Running tests/app.rs (target/debug/deps/app-d3d6e33413d63d0f)
running 4 tests
test theme_color_commands_use_explicit_values_and_never_modify_terminal_palette ... ok
test theme_windows_erase_desktop_and_underlying_windows_on_every_redraw ... ok
test theme_designer_chrome_and_handles_preserve_program_palette ... ok
test theme_contrast_covers_rendered_states_and_rejects_bad_pairs ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 16 filtered out; finished in 0.42s

View File

@@ -0,0 +1,5 @@
Checking tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Checking tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Checking tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Checking tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.64s

View File

@@ -0,0 +1,19 @@
M crates/tb-ide/src/render.rs
M crates/tb-ide/tests/app.rs
M crates/tb-ide/tests/execution.rs
M crates/tb-ui/examples/spike.rs
M crates/tb-ui/src/screen.rs
M crates/tb-ui/src/terminal.rs
M docs/ide-referenz.md
M openspec/changes/phase-6-05-plattformmatrix/design.md
M openspec/changes/phase-6-05-plattformmatrix/proposal.md
M openspec/changes/phase-6-05-plattformmatrix/tasks.md
M tests/support/ide-execution-pty.py
?? docs/plattformmatrix.md
?? openspec/changes/phase-6-05-plattformmatrix/evidence/
?? openspec/changes/phase-6-05-plattformmatrix/specs/ide-oberflaeche/
?? openspec/changes/phase-6-05-plattformmatrix/specs/textbildschirm/
?? openspec/changes/phase-6-05-plattformmatrix/verification.md
?? tests/platform/
?? tests/support/__pycache__/
?? tests/support/platform-abnahme.py

View File

@@ -0,0 +1,943 @@
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 F1F12, 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 F1F12, 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()

View File

@@ -0,0 +1,899 @@
Compiling tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Compiling tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Compiling tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Compiling tb-runner v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-runner)
Compiling tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Compiling tb-cli v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-cli)
Finished `test` profile [unoptimized + debuginfo] target(s) in 8.84s
Running unittests src/main.rs (target/debug/deps/tbc-a3652d07eb423d84)
running 7 tests
test ide_acceptance::real_native_ide_exports_and_source_free_cli_library_consumer ... ignored, Echter Zielnachweis: TB_IDE_RUNTIME_DIR muss geprüfte Host-tbrt-Vorlage enthalten
test ide_acceptance::reference_matrix_is_complete_and_points_to_runnable_tests ... ok
test ide_acceptance::function_keys_conflict_contexts_and_errors_keep_a_single_input_owner ... ok
test ide_acceptance::partial_save_and_external_edit_recover_without_losing_documents ... ok
test ide_acceptance::saved_project_ide_source_cli_and_tbc_have_identical_sessions ... ok
test ide_acceptance::create_save_debug_help_exports_and_reopen ... ok
test ide_acceptance::every_menu_command_has_native_keyboard_and_mouse_routing ... ok
test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.98s
Running tests/compat.rs (target/debug/deps/compat-f89983ce5e40f582)
running 18 tests
test erzeuge_sollausgaben ... ignored, erzeugt Golden Files; nur bewusst aufrufen
test uhr_direktive_wird_gelesen ... ok
test zeilenanzahl_wird_benannt ... ok
test fehlende_und_unerwartete_attributebene_werden_benannt ... ok
test zeittrap_ohne_uhr_direktive_wird_abgewiesen - should panic ... ok
test textabweichung_nennt_zeile_soll_und_ist ... ok
test farbabweichung_nennt_zeile_spalte_soll_und_ist ... ok
test ereignisfolge_liefert_taste_maus_und_zeit_in_reihenfolge ... ok
test fehlende_groessenangabe_wird_abgewiesen ... ok
test release_formular_vergleicht_zeichen_und_attribute_unabhaengig ... ok
test prozessfrist::frist_beendet_blockierten_testprozess ... ok
test tbc_build_erzeugt_tbc ... ok
test tbc_run_hello ... ok
test tbc_run_stop_exitcode ... ok
test tbc_run_laufzeitfehler_exitcode ... ok
test tbc_loest_include_projekt_und_run_datei_auf ... ok
test tbc_isam_open_in_transaktion_hat_eine_harte_frist ... ok
test korpus_laeuft_mit_korrekter_ausgabe ... ok
test result: ok. 17 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.56s
Running tests/convert_frm.rs (target/debug/deps/convert_frm-cba5b52e462e1f23)
running 1 test
test conversion_failure_leaves_no_output ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/foreign.rs (target/debug/deps/foreign-92ee6191b667aed6)
running 3 tests
test oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar ... ignored, TB_VBDOS_REPO muss auf cout/vbdos in der dokumentierten Revision zeigen
test fehlender_oder_falscher_fremdbestand_wird_benannt ... ok
test prozessfrist::frist_beendet_blockierten_testprozess ... ok
test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.11s
Running tests/inventar.rs (target/debug/deps/inventar-4656d211fa731f53)
running 17 tests
test signatur_greift_ohne_laufzeitverhalten ... ok
test bislang_signaturlose_elemente_haben_signaturen ... ok
test dokumentierte_elemente_werden_namentlich_abgewiesen ... ok
test offene_elemente_erzeugen_keinen_syntaxfehler ... ok
test statusvokabular_ist_beschraenkt ... ok
test offene_elemente_haben_keine_fundstelle ... ok
test abdeckungsstand_wird_ausgewiesen ... ok
test non_features_nennen_eine_fundstelle_in_der_sprachreferenz ... ok
test implementiert_mit_unsupported_marker_faellt_durch ... ok
test falscher_status_und_fehlende_runtimebindung_werden_namentlich_erkannt ... ok
test falsch_gesetzte_eigenschaft_wird_erkannt ... ok
test forms_inventar_stimmt_mit_der_klassentabelle_ueberein ... ok
test forms_absenkung_muss_den_richtigen_member_erreichen ... ok
test gemeinsam_dispatchte_konverter_brauchen_den_richtigen_typselektor ... ok
test inventar_stimmt_mit_code_ueberein ... ok
test jeder_inventareintrag_erreicht_hir_und_laufzeitziel ... ok
test falsche_gueltige_builtinbindung_und_entfernte_absenkung_fallen_durch ... ok
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s
Running tests/project.rs (target/debug/deps/project-af9c39c0e4caf174)
running 8 tests
test designer_frm_is_accepted_by_the_standalone_cli_compiler ... ok
test native_build_optionen_sind_explizit_und_tbc_bleibt_standard ... ok
test release_projekt_prueft_anfangsdateien_dateieffekte_und_include_fehler ... ok
test explicit_startup_order_and_form_selection_survive_tbc ... ok
test cli_meldet_physische_quellorte_auch_nach_verschachtelten_includes ... ok
test library_cli_links_without_sources_and_protects_all_inputs_and_outputs ... ok
test projekt_und_formular_laufen_nach_entfernen_saemtlicher_quellen ... ok
test erlaubte_grafik_und_benannte_nonfeatures_werden_per_cli_geprueft ... ok
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s
Running unittests src/lib.rs (target/debug/deps/tb_export-740bf84213e12075)
running 3 tests
test tests::fehlgeschlagene_native_finalisierung_erhaelt_das_ziel ... ok
test tests::publication_preserves_originals_on_conflict_failure_race_and_cancel ... ok
test tests::targets_versions_integrity_and_payload_bounds_are_enforced ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
Running unittests src/bin/tb-template.rs (target/debug/deps/tb_template-158083ae68069508)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/tb_frontend-668b07f10bc30f2a)
running 52 tests
test lexer::tests::strings_mit_doppelten_anfuehrungszeichen ... ok
test lexer::tests::zeilenfortsetzung ... ok
test lexer::tests::bang_ist_suffix_oder_containeroperator_nach_folgetoken ... ok
test lexer::tests::suffix_macht_keyword_zum_bezeichner ... ok
test lexer::tests::bezeichner_mit_punkt_und_unterstrich ... ok
test lexer::tests::keywords_case_insensitiv ... ok
test parser::tests::operator_prioritaet ... ok
test lexer::tests::kommentare_und_rem ... ok
test parser::tests::forms_syntax ... ok
test parser::tests::schleifen ... ok
test lexer::tests::literal_typisierung ... ok
test parser::tests::select_case ... ok
test parser::tests::zuweisung_und_aufruf ... ok
test parser::tests::print_varianten ... ok
test parser::tests::prozeduren ... ok
test parser::tests::if_block_und_einzeilig ... ok
test parser::tests::fehlerbehandlung ... ok
test parser::tests::typen_und_deklarationen ... ok
test sema::tests::hir_select_wird_abgesenkt ... ok
test sema::tests::hir_intdiv_rundet_operanden ... ok
test sema::tests::hir_konvertierung_materialisiert ... ok
test sema::tests::dreizeiler_wird_namentlich_statt_falsch_abgewiesen ... ok
test sema::tests::datei_ea_und_events ... ok
test sema::tests::deftype_regeln ... ok
test sema::tests::funktionsaufruf_und_rueckgabe ... ok
test sema::tests::hir_byref_und_byval ... ok
test sema::tests::def_fn_blockform ... ok
test sema::tests::argumentanzahl ... ok
test sema::tests::byref_verlangt_exakten_typ ... ok
test sema::tests::hardware_features_zur_compilezeit_abgelehnt ... ok
test sema::tests::arrays_implizit_und_explizit ... ok
test sema::tests::dialoge_listindex_und_zeichenmessung_werden_abgesenkt ... ok
test sema::tests::ereignisprozedur_signatur_und_normale_unterstrich_sub ... ok
test sema::tests::hir_slots_aufgeloest ... ok
test sema::tests::createindex_ohne_obergrenze_der_spaltenzahl ... ok
test sema::tests::isam_argumentanzahl_nennt_das_element ... ok
test sema::tests::isam_anweisung_parst_ohne_diagnose ... ok
test sema::tests::builtins_typen ... ok
test sema::tests::konstantenfaltung ... ok
test sema::tests::label_pruefung ... ok
test sema::tests::mid_anweisung ... ok
test sema::tests::option_explicit ... ok
test sema::tests::eigenschaftstyp_schreibbarkeit_bang_und_hir_index ... ok
test sema::tests::rollback_all_wird_als_sentinel_abgesenkt ... ok
test sema::tests::isam_satzargument_wird_typgeprueft ... ok
test sema::tests::typeof_form_control_und_form_metabefehl ... ok
test sema::tests::objektpfade_werden_namentlich_geprueft_und_udt_bleibt_unveraendert ... ok
test sema::tests::typkonflikt_wird_erkannt ... ok
test sema::tests::unbekanntes_unterprogramm ... ok
test sema::tests::shared_und_common ... ok
test sema::tests::kein_isam_element_ist_unbekannter_bezeichner ... ok
test sema::tests::udt_feldtypen ... ok
test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/corpus.rs (target/debug/deps/corpus-0371f2991a3b061a)
running 1 test
test korpus_parst_und_wird_typgeprueft ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/editing.rs (target/debug/deps/editing-01467d91310d70f2)
running 4 tests
test incomplete_is_parser_context_not_a_second_grammar ... ok
test normalization_protects_tokens_and_is_idempotent ... ok
test deftype_context_does_not_leak_from_another_procedure ... ok
test declaration_maintenance_and_procedure_generation_are_transactional ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/tb_ide-43621e6718da8b87)
running 3 tests
test export::tests::delayed_result_is_published_only_after_ui_validation_and_success_stays_success ... ok
test export::tests::replaced_library_invalidates_pending_export_even_with_same_length ... ok
test export::tests::cancelled_superseded_changed_and_foreign_jobs_never_publish ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
Running unittests src/main.rs (target/debug/deps/tb-f285a33b6fdb6251)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/app.rs (target/debug/deps/app-d3d6e33413d63d0f)
running 20 tests
test theme_color_commands_use_explicit_values_and_never_modify_terminal_palette ... ok
test executable_target_validation_and_missing_template_are_honest ... ok
test start_snapshot_and_reference_menus_have_all_commands ... ok
test dispatcher_keeps_function_keys_and_copy_out_of_basic_input ... ok
test designer_form_list_and_context_availability_match_the_active_window ... ok
test windows_restore_geometry_and_resize_without_document_loss ... ok
test theme_windows_erase_desktop_and_underlying_windows_on_every_redraw ... ok
test modal_focus_and_dirty_project_cancel_preserve_document ... ok
test options_apply_persist_across_bases_and_preserve_unsaved_values_on_error ... ok
test clicking_project_members_and_dialog_choices_transfers_focus_and_applies_values ... ok
test theme_designer_chrome_and_handles_preserve_program_palette ... ok
test status_and_project_buttons_use_the_keyboard_command_path ... ok
test mouse_focus_is_modal_and_program_abort_is_not_editor_copy ... ok
test debugger_reports_absent_library_source_and_can_step_back_to_caller ... ok
test file_picker_save_conflict_and_project_replacement_use_real_events ... ok
test finalization_failure_is_reported_and_preserves_existing_output ... ok
test exit_with_partial_save_retains_dialog_and_unsaved_documents ... ok
test real_library_export_uses_dirty_include_and_mouse_without_runtime_template ... ok
test export_dialogs_validate_and_render_all_results_without_creating_files ... ok
test theme_contrast_covers_rendered_states_and_rejects_bad_pairs ... ok
test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s
Running tests/debugger.rs (target/debug/deps/debugger-18aeee8ea147e9f4)
running 6 tests
test form_handler_keys_and_old_revision_cursor_guard ... ok
test immediate_wait_run_transition_watchpoint_and_trace_are_session_owned ... ok
test includes_and_deleted_marks_do_not_bind_to_foreign_statements ... ok
test dispatcher_marks_edits_rebind_and_readonly_watches ... ok
test calls_procedure_step_cursor_history_and_restart ... ok
test calls_mouse_inspection_instant_key_delete_and_error_window ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
Running tests/designer.rs (target/debug/deps/designer-1f0dd9e883e87225)
running 18 tests
test menu_indent_outdent_rename_and_field_changes_have_one_undo ... ok
test property_bar_types_readonly_and_no_status_line ... ok
test imported_type_shadow_is_not_an_object_reference_during_rename ... ok
test clipboard_children_arrays_and_delete_preserve_code ... ok
test binary_designer_import_requires_text_target_and_compiles ... ok
test event_array_signature_twice_and_shared_form_identity ... ok
test bound_rename_across_documents_is_atomic_and_undo_restores_all ... ok
test container_multiselect_keyboard_drag_resize_and_undo ... ok
test toolbox_double_click_tool_drag_grid_and_window_form_selection ... ok
test menu_hierarchy_arrays_shortcuts_and_palette_use_events ... ok
test rename_preserves_shadowed_names_and_handles_form_aliases_and_include_calls ... ok
test indexed_controls_merge_atomically_with_bindings_and_undo ... ok
test events_with_the_same_name_are_local_to_their_form ... ok
test shared_include_ambiguity_aborts_rename_and_event_defaults_use_includes ... ok
test property_menu_buttons_palette_drag_and_modal_mouse_are_connected ... ok
test all_metadata_tools_persist_and_preview_matches_runtime_without_events ... ok
test all_eight_handles_resize_transactionally_and_preserve_identity_after_structure_edits ... ok
test existing_default_typed_event_and_all_metadata_signatures_compile ... ok
test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
Running tests/documents.rs (target/debug/deps/documents-6a65482da983e004)
running 15 tests
test unrepresentable_save_paths_fail_before_writing_or_changing_documents ... ok
test save_plan_prevents_same_destination_and_project_overwrite ... ok
test shared_views_text_import_and_undo_are_document_transactions ... ok
test failed_open_add_and_cancel_do_not_replace_existing_documents ... ok
test aliases_share_documents_and_form_code_offsets_match_saved_text ... ok
test binary_import_only_saves_to_explicit_text_target_and_preserves_original_forever ... ok
test edited_include_wins_in_both_modules_without_writing_the_expansion ... ok
test startup_removal_requires_explicit_replacement_and_never_deletes_a_file ... ok
test libraries_are_binary_members_with_relative_save_as_and_no_source_views ... ok
test project_save_as_rebases_members_and_keeps_include_targets ... ok
test external_changes_and_save_as_collisions_need_explicit_decisions ... ok
test module_save_as_rebases_includes_and_failed_save_does_not_change_identity_or_text ... ok
test cursor_after_shortening_uses_original_position_and_open_after_save_uses_fresh_disk ... ok
test partial_save_preserves_unsaved_data_and_does_not_finish_close ... ok
test project_create_save_reopen_preserves_forms_members_startup_and_cli_sources ... ok
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
Running tests/editor.rs (target/debug/deps/editor-b6dc665e95525030)
running 7 tests
test search_scope_direction_wrap_and_shortening_replace_all ... ok
test crlf_navigation_word_boundaries_readonly_and_normalization_keep_views ... ok
test cua_unicode_overwrite_wordstar_and_shared_undo ... ok
test line_leave_incomplete_invalid_disabled_and_current_revisions ... ok
test includes_and_diagnostic_selection_use_original_physical_files ... ok
test procedures_and_shared_save_hook_preserve_drafts_and_frm_code ... ok
test external_include_revision_and_project_context_declarations ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
Running tests/execution.rs (target/debug/deps/execution-bf913fdae7aacbc3)
running 13 tests
test shell_request_is_resumed_once_and_error_is_basic_error ... ok
test named_run_of_current_target_uses_edited_sources ... ok
test comment_only_source_change_also_requires_revision_choice ... ok
test ide_and_cli_run_wrapper_match_input_time_and_forms_script ... ok
test f5_input_pause_copy_and_single_delivery_keep_the_same_vm ... ok
test output_viewports_resize_and_end_preserve_buffer_and_basic_colors ... ok
test print_uses_utf8_protected_output_and_does_not_mutate_source ... ok
test changed_paused_source_requires_explicit_old_revision_or_restart ... ok
test explicit_end_and_system_end_visible_forms_but_natural_end_keeps_them_live ... ok
test restart_command_run_targets_and_missing_target_preserve_documents ... ok
test restart_drops_files_data_traps_and_queued_actions ... ok
test startup_selection_matches_shared_cli_pipeline_and_legacy_mak ... ok
test platform_probe_records_each_basic_key_once_and_keeps_ide_keys_out ... ok
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
Running tests/help.rs (target/debug/deps/help-ff16d5fb3c397804)
running 11 tests
test offline_help_child ... ok
test markdown_blocks_escapes_links_and_unicode_layout ... ok
running 1 test
test catalog_links_and_missing_targets_are_checked ... ok
test right_click_configuration_and_external_link_follow ... ok
test links_history_resize_scroll_and_focus_use_real_events ... ok
test every_command_and_forms_alias_resolves_and_invalid_alias_fails ... ok
test twenty_back_steps_end_of_contents_and_all_entries ... ok
test offline_help_child ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 10 filtered out; finished in 0.07s
test help_works_from_a_directory_without_checkout ... ok
test help_preserves_fullscreen_and_does_not_modify_designer_or_source ... ok
test context_functions_properties_commands_and_unknown_queries ... ok
test code_and_tables_remain_reachable_after_resize_and_horizontal_scroll ... ok
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s
Running unittests src/lib.rs (target/debug/deps/tb_runner-d3339a5a0df9c7dd)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/bin/tbrt.rs (target/debug/deps/tbrt-a6c3f62502f5f188)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/tb_runtime-6b2bc969f5367a62)
running 140 tests
test builtins::tests::benutzertaste_ueber_key_anweisung ... ok
test builtins::tests::nicht_getrappte_taste_bleibt_im_strom ... ok
test builtins::tests::getrappte_taste_verlaesst_den_eingabestrom ... ok
test builtins::tests::off_wirkt_auf_bereits_wartende_tasten ... ok
test builtins::tests::input_s_dispatch_bewahrt_codepointreste_auch_ohne_vm ... ok
test builtins::tests::signal_aus_dem_host_meldet_den_trap ... ok
test builtins::tests::zonenquelle_ausfall_und_erholung_gelten_fuer_alle_zeitfunktionen ... ok
test builtins::tests::benutzertaste_ohne_umschalt_bleibt_im_strom ... ok
test builtins::tests::builtin_ueber_tabelle ... ok
test builtins::tests::signal_stop_merkt_beide_quellen_und_off_verwirft ... ok
test console::tests::volle_zone_springt_zur_uebernaechsten ... ok
test console::tests::druckzone_folgt_dem_cursor_nach_locate ... ok
test builtins::tests::key_stop_merkt_einmal_und_laesst_alle_tasten_auch_nach_on_lesbar ... ok
test builtins::tests::mausereignisse_haeufen_sich_nicht_an ... ok
test builtins::tests::sigint_ohne_aktiven_trap_bleibt_abbruch ... ok
test console::tests::druckzonen ... ok
test builtins::tests::setuevent_meldet_das_benutzerereignis ... ok
test builtins::tests::stringfunktionen_randfaelle ... ok
test console::tests::zahlen_mit_vorzeichenspalte ... ok
test datetime::tests::fester_versatz_verschiebt_genau ... ok
test datetime::tests::gueltigkeitspruefung ... ok
test datetime::tests::programmversatz_kommt_hinzu ... ok
test datetime::tests::serienwert_und_rueckumwandlung ... ok
test datetime::tests::tageszeit ... ok
test datetime::tests::text_in_serienwert ... ok
test datetime::tests::wochentag_sonntag_ist_eins ... ok
test datetime::tests::zaehlweise_des_vorbilds ... ok
test errors::tests::bekannte_codes ... ok
test errors::tests::forms_und_isam_codes ... ok
test errors::tests::unbelegte_codes_sind_unprintable ... ok
test fileio::tests::felder_einer_input_zeile ... ok
test fileio::tests::modus_aus_kurzsyntax ... ok
test fileio::tests::muster_mit_stern_und_fragezeichen ... ok
test finanz::tests::abschreibungen ... ok
test finanz::tests::barwert_und_endwert_sind_gegenlaeufig ... ok
test fileio::tests::festes_feld_ist_inhaltsunabhaengig_gleich_lang ... ok
test fileio::tests::rset_schneidet_ab_und_richtet_rechts_aus ... ok
test finanz::tests::endwert_einer_rentenzahlung ... ok
test finanz::tests::irr_ohne_vorzeichenwechsel_ist_fehler ... ok
test finanz::tests::kapitalwert_und_interner_zinsfuss ... ok
test finanz::tests::mirr_liegt_zwischen_den_zinsen ... ok
test finanz::tests::nper_zaehlt_die_perioden ... ok
test finanz::tests::ungueltige_argumente ... ok
test finanz::tests::zins_und_tilgung_ergeben_die_zahlung ... ok
test finanz::tests::zinssatz_wird_zurueckgerechnet ... ok
test format::tests::currency_darstellung ... ok
test format::tests::double_darstellung ... ok
test format::tests::ganzzahlen ... ok
test format::tests::single_darstellung ... ok
test format::tests::val_parsen ... ok
test host::tests::capture_host_haelt_zeichen_und_attribut ... ok
test host::tests::maus_und_tasten_behalten_ihre_reihenfolge ... ok
test host::tests::virtuelle_uhr_laeuft_nur_wenn_gestellt ... ok
test host::tests::vorbereitete_zeile_wird_zu_tasten ... ok
test isam::tests::absteigende_spalte_kehrt_die_ordnung_um ... ok
test host::tests::groessenaenderung_und_abbruch_als_ereignis ... ok
test host::tests::verzoegertes_ereignis_kommt_nach_der_anzeige ... ok
test isam::tests::nullbyte_im_text_stoert_den_abschluss_nicht ... ok
test isam::tests::praefix_ende_zaehlt_hoch ... ok
test isam::tests::beschaedigte_datei_meldet_fehler_88 ... ok
test datetime::tests::versatz_liegt_im_gueltigen_bereich ... ok
test datetime::tests::versatz_gilt_je_zeitpunkt ... ok
test isam::tests::kodierung_erhaelt_die_ordnung_bei_zufallspaaren ... ok
test isam::tests::prozessfrist::frist_beendet_blockierten_testprozess ... ok
test isam::tests::close_beendet_keine_transaktion ... ok
test isam::tests::jede_satzoperation_erzeugt_einen_protokolleintrag ... ok
test isam::tests::textabschluss_ordnet_praefix_vor_laengerem_text ... ok
test isam::tests::neue_tabellen_und_aktive_indizes_verschwinden_bei_ruecknahme ... ok
test isam::tests::ungueltige_namen_werden_abgewiesen ... ok
test isam::tests::ohne_transaktion_kein_protokoll ... ok
test screen::tests::blink_wird_als_hell_simuliert ... ok
test isam::tests::saetze_nach_close_aller_bindungen_zuruecknehmen ... ok
test screen::tests::breites_zeichen_am_rechten_rand_bricht_ganz_um ... ok
test isam::tests::pfadaliase_teilen_transaktion_und_ruecknahme ... ok
test isam::tests::cursor_ueberlebt_eine_satzaenderung ... ok
test screen::tests::breites_zeichen_belegt_zwei_zellen ... ok
test isam::tests::geloeschte_satz_id_wird_nicht_neu_vergeben ... ok
test isam::tests::deletetable_loest_alle_betroffenen_bindungen ... ok
test isam::tests::gescheitertes_createindex_hinterlaesst_keinen_index ... ok
test screen::tests::dynamische_groesse_und_resize ... ok
test isam::tests::satz_roundtrip_ueber_alle_feldtypen ... ok
test screen::tests::locate_auf_fortsetzung_trifft_den_zeichenanfang ... ok
test isam::tests::delete_pufferfehler_erhaelt_saetze_indizes_und_cursor ... ok
test isam::tests::nicht_festgeschriebene_transaktion_verfaellt ... ok
test screen::tests::locate_prueft_grenzen ... ok
test screen::tests::print_schreibt_und_bewegt_cursor ... ok
test isam::tests::indexeintraege_stehen_in_schluesselreihenfolge ... ok
test isam::tests::struktur_undo_prueft_puffer_vor_dem_loeschen ... ok
test screen::tests::resize_folgt_vollem_scrollbereich ... ok
test isam::tests::hoehere_formatversion_wird_abgewiesen ... ok
test screen::tests::rueckschritt_loescht_ein_breites_zeichen_ganz ... ok
test screen::tests::resize_klemmt_nur_den_unteren_rand_wenn_der_bereich_bleibt ... ok
test screen::tests::resize_klemmt_cursor_und_scrollbereich ... ok
test screen::tests::scrollen_am_unteren_rand ... ok
test screen::tests::ueberschreiben_einer_haelfte_leert_die_andere ... ok
test screen::tests::ui_zeile_bleibt_am_rand_ohne_umbruch_oder_scrollen ... ok
test screen::tests::umbruch_am_rechten_rand ... ok
test screen::tests::unicode_zeichen_belegen_eine_zelle ... ok
test screen::tests::view_print_begrenzt_scrollen ... ok
test snapshot::tests::breites_zeichen_erscheint_einmal ... ok
test snapshot::tests::getrimmt_auf_die_belegte_flaeche ... ok
test snapshot::tests::leerer_bildschirm_ergibt_leeren_snapshot ... ok
test snapshot::tests::locate_erweitert_die_flaeche ... ok
test snapshot::tests::mit_farben_kommt_die_attributebene ... ok
test snapshot::tests::nachgestellte_leerzeichen_bleiben_erhalten ... ok
test snapshot::tests::ohne_farben_keine_attributebene ... ok
test traps::tests::aus_verwirft_auch_das_gemerkte ... ok
test traps::tests::aus_verwirft_kuenftige ... ok
test traps::tests::beide_umschaltbits_gelten_gleich ... ok
test traps::tests::benutzertaste_braucht_umschaltzustand_und_scancode ... ok
test traps::tests::event_off_verwirft_ereignisse_und_laesst_die_zustaende ... ok
test traps::tests::genauer_trap_geht_dem_sammeltrap_vor ... ok
test traps::tests::gestoppt_merkt_genau_eines ... ok
test traps::tests::gosub_null_schaltet_den_trap_ab ... ok
test traps::tests::kein_wiedereintritt_waehrend_der_handler_laeuft ... ok
test traps::tests::key_null_faengt_alle_tasten_der_tabelle ... ok
test traps::tests::off_im_handler_ueberlebt_das_return ... ok
test traps::tests::ohne_trap_kostet_nichts ... ok
test traps::tests::reihenfolge_key_vor_timer_vor_uevent_vor_signal ... ok
test traps::tests::return_schaltet_sonst_selbsttaetig_ein ... ok
test traps::tests::uebersprungene_fristen_zaehlen_als_eine ... ok
test traps::tests::vordefinierte_taste_wird_ueber_den_scancode_erkannt ... ok
test traps::tests::zeit_wird_erst_nach_der_frist_faellig ... ok
test using::tests::exponentialform ... ok
test using::tests::feldueberlauf_bekommt_prozentzeichen ... ok
test using::tests::format_wird_wiederverwendet ... ok
test using::tests::literale_und_unterstrich ... ok
test using::tests::numerisches_feld_mit_nachkommastellen ... ok
test using::tests::ohne_feld_aber_mit_werten_ist_fehler_5 ... ok
test using::tests::ohne_feld_und_ohne_werte_bleibt_der_text ... ok
test using::tests::stringfelder ... ok
test using::tests::tausendertrennung ... ok
test using::tests::vorzeichenfuehrung ... ok
test using::tests::waehrung_und_sterne ... ok
test value::tests::array_indexpruefung ... ok
test value::tests::banker_rounding ... ok
test value::tests::currency_rundung ... ok
test value::tests::overflow_bei_konvertierung ... ok
test isam::tests::savepoints_nehmen_index_und_tabellenstrukturen_zurueck ... ok
test isam::tests::symlink_neuanlage_behaelt_die_dateiidentitaet ... ok
test result: ok. 140 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.56s
Running unittests src/lib.rs (target/debug/deps/tb_ui-95ce53373c09fcd8)
running 66 tests
test forms::tests::hide_entlaedt_nicht_und_unload_ist_abbrechbar ... ok
test forms::tests::optionen_gruppieren_nach_unmittelbarem_container ... ok
test forms::tests::screen_folgt_resize ... ok
test forms::tests::menuregeln_weisen_separator_und_titel_shortcuts_ab ... ok
test forms::tests::combobox_dropdown_closes_on_focus_change_and_hide ... ok
test forms::tests::gleichzeitig_faellige_timer_laufen_nach_name ... ok
test forms::tests::fokus_tab_access_default_und_eingabeprioritaet ... ok
test forms::tests::screen_meldet_oberstes_formular_und_aktives_control ... ok
test forms::tests::modalfehler_400_bis_403 ... ok
test forms::tests::menues_zeichnen_und_navigieren_sechs_ebenen ... ok
test forms::tests::defaults_bereich_und_implizites_laden ... ok
test forms::tests::arrays_und_designzeitfehler ... ok
test forms::tests::freier_formularklick_und_doppelklick_werden_zugestellt ... ok
test forms::tests::simple_combo_zeichnet_listeninhalt_auswahl_und_begrenzung ... ok
test forms::tests::indexed_containers_preserve_parent_geometry_and_option_groups ... ok
test forms::tests::spin_zaehlt_zyklisch_und_wiederholt_nach_interval ... ok
test frm::tests::binary_layout_preserves_arrays_and_container_hierarchy ... ok
test forms::tests::timerarrays_holen_nur_aktive_zeit_nach_und_sortieren_nach_name_und_index ... ok
test frm::tests::binary_class_specific_properties_match_the_reference_exports ... ok
test forms::tests::zeichenbilder_aller_klassen_und_buttonhoehen ... ok
test forms::tests::timer_neustart_verwirft_alte_fristen_und_ereignisse ... ok
test frm::tests::binary_strings_are_decoded_from_cp437 ... ok
test forms::tests::listenauswahl_bleibt_beim_einfuegen_und_entfernen_konsistent ... ok
test frm::tests::binary_reader_rejects_magic_and_unknown_structure_with_offset ... ok
test frm::tests::binary_reader_rejects_out_of_range_property_with_offset ... ok
test frm::tests::reads_nested_form_and_preserves_source_exactly ... ok
test frm::tests::binary_strings_and_header_flags_map_to_the_named_properties ... ok
test frm::tests::spin_symbol_wird_als_bedienbares_control_erkannt ... ok
test frm::tests::text_form_control_arrays_share_one_catalog_object_and_keep_indices ... ok
test frm::tests::unsupported_binary_property_is_named_and_unclaimed_strings_still_fail ... ok
test frm::tests::writes_read_only_boolean_only_when_it_differs_from_false ... ok
test frm::tests::writes_no_properties_for_default_only_control ... ok
test frm::tests::writes_documented_d1_and_roundtrips_description ... ok
test frm::tests::rejects_version_class_property_value_and_open_block_with_location ... ok
test frm::tests::explicit_default_is_preserved_until_canonical_write ... ok
test frm_pcode::tests::decodes_defint_and_vbdos_isam_tokens ... ok
test frm_pcode::tests::decodes_vbdos_declarations_without_losing_type_or_bounds ... ok
test frm_pcode::tests::embedded_include_is_a_comment_but_literals_remain_unchanged ... ok
test frm::tests::canonical_index_zero_remains_an_array_and_array_parents_are_preserved ... ok
test frm::tests::unsupported_custom_control_is_named_and_not_emitted_as_screen ... ok
test frm_pcode::tests::next_trennt_eine_vorangehende_anweisung_auch_ohne_newline_token ... ok
test host::tests::ausserhalb_der_flaeche_wird_verworfen ... ok
test frm_pcode::tests::resets_block_local_default_types_before_the_next_block ... ok
test host::tests::backtab_ist_tab_mit_shift_auch_ohne_modifier ... ok
test frm::tests::reads_real_vbdos_form_with_complete_documented_description ... ok
test host::tests::letzte_zelle_liegt_noch_drin ... ok
test host::tests::maus_zaehlt_ab_eins ... ok
test host::tests::nach_groessenaenderung_gilt_die_neue_flaeche ... ok
test forms::tests::forms_dialoge_liefern_gruppen_eingabe_und_feste_groesse ... ok
test host::tests::rollrad_hat_keine_entsprechung ... ok
test host::tests::sondertaste_traegt_fuehrendes_nullzeichen ... ok
test host::tests::strg_c_ist_abbruch ... ok
test forms::tests::tabfolge_ueberspringt_und_laeuft_rueckwaerts ... ok
test host::tests::umschaltzustand_als_bitfeld ... ok
test host::tests::umschaltzustand_der_taste_wird_mitgefuehrt ... ok
test host::tests::zeichentaste_wird_zu_inkey_form ... ok
test screen::tests::farbabbildung ... ok
test signale::tests::marke_wird_zu_kennung_und_verfaellt_danach ... ok
test signale::tests::sigint_hat_vorrang_vor_sigterm ... ok
test signale::tests::echtes_signal_setzt_die_marke ... ok
test forms::tests::z_reihenfolge_dragdrop_und_timerreihenfolge ... ok
test forms::tests::text_liste_option_scroll_picture_und_dateisystem ... ok
test forms::tests::sorted_wechsel_erhaelt_auch_die_auswahl_zwischen_duplikaten ... ok
test forms::tests::zustandsfarben_laufen_fuer_alle_controls_ueber_denselben_zeichenpfad ... ok
test terminal::tests::terminal_cleanup_on_normal_error_and_partial_initialization ... ok
test forms::tests::listenrenderer_haelt_zellgrenzen_auch_an_bildschirmraendern_ein ... ok
test result: ok. 66 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.61s
Running unittests src/lib.rs (target/debug/deps/tb_vm-2a4538183acbb98c)
running 16 tests
test codegen::tests::fehlerbehandlung_emit ... ok
test codegen::tests::string_konkatenation ... ok
test codegen::tests::vergleich_und_logik ... ok
test codegen::tests::arrays_dim_und_zugriff ... ok
test bytecode::tests::instr_roundtrip ... ok
test codegen::tests::data_read_restore ... ok
test codegen::tests::tbc_roundtrip_ueber_codegen ... ok
test bytecode::tests::unbekannte_version_wird_abgelehnt ... ok
test bytecode::tests::tbc_roundtrip ... ok
test codegen::tests::ausdruck_monomorph_mit_conv ... ok
test codegen::tests::gosub_und_on_goto ... ok
test codegen::tests::for_mit_dynamischem_step ... ok
test codegen::tests::if_mit_fixup ... ok
test codegen::tests::prozedur_und_byref ... ok
test codegen::tests::for_mit_konstantem_step ... ok
test codegen::tests::function_liefert_wert ... ok
test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cooperative.rs (target/debug/deps/cooperative-acdd5e7adb1bd680)
running 7 tests
test sleep_deadline_survives_pause_and_does_not_pop_argument_twice ... ok
test finite_slices_preserve_events_without_clock_queries ... ok
test common_dialog_external_call_keeps_byref_arguments_until_completion ... ok
test partial_console_operations_keep_prompt_arguments_and_assignment ... ok
test pending_dialog_keeps_physical_resize_and_modeless_handler_finishes_after_hide ... ok
test dialogs_keep_partial_text_focus_and_caller_arguments ... ok
test modal_and_modeless_forms_resume_handlers_across_tiny_budgets ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/debugger.rs (target/debug/deps/debugger-2aaa51d59ed630d5)
running 14 tests
test same_line_calls_and_immediate_do_not_retrigger_a_line_breakpoint ... ok
test set_next_rejects_loop_gosub_handler_and_pending_operations_atomically ... ok
test break_continue_loop_and_statement_steps ... ok
test form_event_breakpoint_and_step_over_keep_the_event_frame ... ok
test step_over_recursive_frames_history_and_safe_jump ... ok
test immediate_handler_exit_and_cached_new_imports_are_safe ... ok
test set_next_rejects_else_and_case_entry_but_allows_peer_statements ... ok
test immediate_handler_resume_and_break_on_errors ... ok
test gosub_step_over_breakpoint_priority_and_temporary_target_cleanup ... ok
test watches_immediate_byref_and_errors_preserve_continuation ... ok
test include_identity_qualified_globals_udts_and_older_frames ... ok
test immediate_wait_interrupt_nested_error_end_and_run ... ok
test watches_never_initialize_arrays_or_run_user_functions_and_keep_selected_context ... ok
test watchpoint_history_ring_and_error_differential ... ok
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/events.rs (target/debug/deps/events-2f0766815a3e98d6)
running 33 tests
test lokaler_error_handler_nach_trap_verwirft_abgebrochene_teileingabe ... ok
test handler_eingabe_und_stop_bewahren_die_aeussere_eingabe ... ok
test fehlender_ereignispfad_wird_namentlich_erkannt ... ok
test randomize_eingabe_teilt_den_fortsetzbaren_konsolenpfad ... ok
test input_redo_verliert_weder_referenzen_noch_teiltext_beim_trap ... ok
test fokus_handler_laufen_geordnet_bis_zur_rueckkehr ... ok
test ohne_zeitbedarf_keine_uhrabfrage_und_keine_mausansammlung ... ok
test offenes_menu_sperrt_traps_und_wartende_forms_timer_an_allen_zustellpunkten ... ok
test doevents_verhindert_rekursion_derselben_forms_prozedur ... ok
test formular_tastatur_fallback_fuehrt_alle_drei_basic_handler_aus ... ok
test intervallaenderung_im_handler_verwirft_bereits_faellige_altintervalle ... ok
test dialog_erhaelt_stop_markierung_unverbrauchter_tasten ... ok
test eof_im_dialog_eines_formularhandlers_beendet_die_ereignispumpe ... ok
test beide_signale_werden_zugestellt_und_beginnen_in_quellreihenfolge ... ok
test eof_leert_angenommene_eingaben_bei_modalen_und_modellosen_forms ... ok
test review_gleichzeitig_key_und_uevent_behalten_prioritaet ... ok
test return_label_verwirft_abgebrochene_eingabe_und_ausdruck ... ok
test review_menu_schliessen_stellt_pending_trap_vor_erneutem_warten_zu ... ok
test input_fortsetzung_erhaelt_teiltext_ausdruck_und_byref_ziel ... ok
test timer_waehrend_input_wird_vor_erneutem_warten_zugestellt ... ok
test review_maus_vor_getrappter_taste ... ok
test review_modale_eingabe_vor_eof_wird_zugestellt ... ok
test review_signal_waehrend_blockierender_eingabe ... ok
test unload_aus_unload_handler_ruft_dieselbe_prozedur_nicht_rekursiv_auf ... ok
test spaete_timeraktivierung_nutzt_hostzeit_auch_ueber_control_parameter ... ok
test key_on_off_stop_gelten_fuer_alle_konsoleneingaben ... ok
test taste_maus_und_trap_behalten_die_annahmereihenfolge ... ok
test review_objektargument_darf_gotfocus_nicht_vorziehen ... ok
test shell_und_irr_funktionssignaturen_erreichen_die_runtime ... ok
test registrierte_form_ereignisse_erreichen_basic_aus_realen_quellen ... ok
test sleep_folgt_nur_der_hostfrist_und_forms_timer_beendet_es ... ok
test prozessfrist::frist_beendet_blockierten_testprozess ... ok
test registrierte_control_ereignisse_erreichen_basic_aus_realen_quellen ... ok
test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
Running tests/incremental.rs (target/debug/deps/incremental-ef01ad977c2204da)
running 4 tests
test unused_constants_do_not_invalidate_independent_units_and_declare_changes_do ... ok
test includes_source_ids_data_and_forms_relink_without_patching_old_products ... ok
test cached_private_edits_parse_and_compile_only_changed_modules ... ok
test constants_types_common_removed_symbols_and_order_match_full_compiler ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/library.rs (target/debug/deps/library-0b9e1b471b1e7297)
running 9 tests
test source_debug_context_keeps_module_ids_when_library_is_between_sources ... ok
test library_include_locations_keep_physical_lines_and_basic_erl ... ok
test library_source_locations_and_run_restart_are_preserved ... ok
test unused_declare_is_not_an_import_but_references_in_any_body_must_resolve ... ok
test contracts_reject_result_common_bounds_and_invalid_declarations ... ok
test source_and_library_have_identical_types_constants_common_data_byref_and_init_order ... ok
test two_source_free_libraries_with_typed_open_imports_and_diagnostics ... ok
test library_container_rejects_truncation_versions_lengths_and_references ... ok
test separate_form_libraries_remap_nested_objects_initials_arrays_and_events ... ok
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/project.rs (target/debug/deps/project-7ae350d75d21dcb2)
running 7 tests
test diagnose_im_include_und_echte_duplikate_bleiben_sichtbar ... ok
test typreferenzen_signaturen_und_option_base_ueber_modulgrenzen ... ok
test module_includes_erl_breakpoints_und_inspektion_behalten_ihren_ursprung ... ok
test lokale_prozeduren_shared_common_und_wiederholte_includes_bleiben_getrennt ... ok
test initialisierungen_def_fn_und_modulhandler_haben_eigene_quellorte ... ok
test beschaedigte_container_werden_vor_der_ausfuehrung_abgewiesen ... ok
test zwei_formulare_mit_gleichen_controls_und_arrays_laufen_aus_dem_kompilat ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/project_io.rs (target/debug/deps/project_io-76f82da2b295b346)
running 7 tests
test case_insensitive_parent_lookup_works_from_relative_base_without_changing_cwd ... ok
test overlays_keep_original_files_and_distinct_module_contexts ... ok
test documented_mak_example_is_accepted_by_the_shared_loader ... ok
test relative_include_precedes_search_paths_and_virtual_files_are_resolved ... ok
test cycle_missing_include_and_literal_directives_are_distinguished ... ok
test startup_metadata_roundtrip_and_invalid_contracts ... ok
test tbl_members_move_save_as_order_startup_and_same_size_cache_replacement ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/project_regressions.rs (target/debug/deps/project_regressions-88b074fd28a1a09a)
running 16 tests
test diagnostik_bei_literalen_im_include_hat_die_exakte_spalte ... ok
test spaetere_common_konflikte_und_doppelte_module_werden_am_ursprung_gemeldet ... ok
test v6_linkerdiagnose_nennt_den_urspruenglichen_dateiort ... ok
test debugger_unterscheidet_typisierte_scalar_und_arraynamen ... ok
test prozedurlokaler_deftype_veraendert_keine_nachfolgende_signatur ... ok
test v5_load_und_unload_loesen_den_expliziten_container_auf ... ok
test v3_form_metabefehle_erzeugen_getrennte_formularobjekte ... ok
test common_vertraege_und_linkfehler_haben_echte_quellorte ... ok
test v4_common_behaelt_typidentitaet_und_initialisiert_arrays_einmal ... ok
test gemischte_frm_und_form_module_haben_getrennte_ereignisbindungen ... ok
test modulbreakpoint_rundlauf_inspektion_und_numerische_erl_bleiben_getrennt ... ok
test v1_zweige_und_schleifen_behalten_eigene_quellorte ... ok
test v2_importierte_deklarationen_behalten_kontext_und_abhaengigkeiten ... ok
test common_arrays_behalten_dynamische_grenzen_und_kompatible_offene_deklarationen ... ok
test importkontext_bleibt_auch_bei_abweichenden_lokalen_definitionen_erhalten ... ok
test schleifengrenzen_tragen_breakpoint_include_und_resume ... ok
test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/vm.rs (target/debug/deps/vm-4bcfa272723580f5)
running 114 tests
test data_read_restore ... ok
test doevents_liefert_null ... ok
test data_typkonflikt_fehler_13 ... ok
test byref_wirkt_zurueck ... ok
test byref_auf_arrayelement ... ok
test default_button_hat_vorrang_und_beendet_modales_formular ... ok
test color_prueft_wertebereich ... ok
test banker_rounding_cint ... ok
test common_dialog_registrierung_setzt_byref_erfolg ... ok
test clipboard_und_printer_objekte_haben_laufzeitwirkung ... ok
test breakpoint_haelt_an_und_setzt_fort ... ok
test division_durch_null ... ok
test ausgabe_erscheint_vor_der_eingabe ... ok
test def_fn_im_modulkontext ... ok
test cls_setzt_cursor_zurueck ... ok
test einzelschritt ... ok
test do_loop_varianten ... ok
test eingabe_wird_geechot_und_rueckschritt_loescht ... ok
test doevents_stellt_zu ... ok
test erl_liefert_zeilennummer ... ok
test erl_null_ohne_zeilennummern ... ok
test end_und_system ... ok
test erl_nennt_die_fehlerzeile_nicht_die_handlerzeile ... ok
test error_anweisung ... ok
test exit_for_und_do ... ok
test fehlende_ereignisprozedur_verfaellt_und_typeof_prueft_klasse ... ok
test for_mit_dynamischem_step ... ok
test fester_versatz_verschiebt_die_uhr ... ok
test for_mit_negativem_step ... ok
test for_ohne_durchlauf ... ok
test fehler_im_handler_ist_fatal ... ok
test fehler_im_trap_handler_erreicht_den_modulweiten_handler ... ok
test event_off_klammert_die_zustellung ... ok
test form_load_laeuft_vor_dem_sichtbarsetzen ... ok
test grafik_line_paint_und_view_zeichnen_in_den_zellenpuffer ... ok
test inkey_ohne_taste_ist_leer ... ok
test ganzes_array_uebergeben ... ok
test inkey_liefert_zeichen_und_sondertaste ... ok
test form_load_laeuft_einmal_und_unload_cancel_verhindert_entladen ... ok
test gosub_return_und_fehler_3 ... ok
test input_mit_redo ... ok
test groessenaenderung_wirkt_zur_laufzeit ... ok
test gosub_null_schaltet_den_trap_ab ... ok
test input_s_liest_genau_n_zeichen_ohne_echo ... ok
test function_und_rekursion ... ok
test intdiv_rundet_operanden_vor ... ok
test gemischte_arithmetik ... ok
test kennung_ausserhalb_des_bereichs_ist_fehler_5 ... ok
test klammern_erzwingen_byval ... ok
test len_liefert_die_feste_satzbreite_eines_udt ... ok
test line_input_liest_ganze_zeile ... ok
test input_s_liest_aus_einer_binaerdatei ... ok
test input_s_teilt_sondertasten_und_bewahrt_rest_fuer_inkey ... ok
test klassischer_timer_trap_ruht_solange_das_menu_offen_ist ... ok
test logik_bitweise ... ok
test kontrollfluss_korpusdatei ... ok
test listenmethoden_und_indexeigenschaft_laufen_auf_control_arrays ... ok
test leere_warteschleife_hat_eine_anweisungsgrenze ... ok
test mid_anweisung_mutiert ... ok
test listenmethoden_indexeigenschaft_und_picture_messung_laufen_in_der_vm ... ok
test lokaler_handler_verdeckt_modulweiten ... ok
test locate_ausserhalb_loest_fehler_5_aus ... ok
test lokaler_handler_hat_vorrang ... ok
test mausargumente_kommen_in_zellen_an ... ok
test modales_show_setzt_nach_unload_fort ... ok
test modulweiter_handler_faengt_prozedurfehler ... ok
test ohne_handler_bricht_ab ... ok
test objektcode_erzeugt_keine_zusaetzlichen_zustellopcodes ... ok
test alle_mk_cv_paare_verlangen_exakte_bytezeichenbreite ... ok
test modulweites_on_error_aus_prozedur ... ok
test on_error_goto_0_deaktiviert ... ok
test objektzugriff_laeuft_ueber_objekt_und_eigenschaftsindex ... ok
test modelloses_formular_bleibt_nach_programmende_bedienbar ... ok
test on_goto_berechnet ... ok
test out_of_data_fehler_4 ... ok
test overflow_bei_zuweisung ... ok
test on_local_error_bleibt_prozedurlokal ... ok
test ohne_timer_on_kein_trap ... ok
test print_hallo_welt ... ok
test programmversatz_wirkt_auf_alle_zeitfunktionen ... ok
test printform_schreibt_das_textformular_in_den_druckerkanal ... ok
test restore_mit_label ... ok
test resume_ohne_fehler_20 ... ok
test print_zonen_und_tab ... ok
test resume_next_faehrt_fort ... ok
test datei_und_elternpfadfehler_sind_am_sprachzugang_unterscheidbar ... ok
test msgbox_und_inputbox_sind_keine_unsupported_opcodes_mehr ... ok
test resume_wiederholt_anweisung ... ok
test rueckfall_auf_utc_laeuft_fehlerfrei ... ok
test run_gibt_datei_oder_zeile_an_den_runner_weiter ... ok
test screen_controlpanel_hat_einen_indexierten_laufzeitwert ... ok
test select_case_bereiche ... ok
test setuevent_loest_den_uevent_trap_aus ... ok
test screen_anweisung_bildet_grafikmodi_auf_den_zellenpuffer_ab ... ok
test screen_funktion_ausserhalb_loest_fehler_5_aus ... ok
test sleep_endet_durch_ein_ereignis ... ok
test stop_liefert_zeile_und_ist_fortsetzbar ... ok
test sondertastenrest_wird_nicht_erneut_getrappt ... ok
test static_behaelt_werte ... ok
test stringvergleich_und_verkettung ... ok
test time_und_now_stimmen_ueberein ... ok
test steuerarray_ereignis_stellt_index_voran ... ok
test timer_zaehlt_ab_lokaler_mitternacht ... ok
test steuerarray_eigenschaften_und_objektparameter_funktionieren ... ok
test udt_wertsemantik_und_felder ... ok
test width_hebt_die_groesse_an ... ok
test timezoneknown_hat_integer_signatur_und_zeigt_aktuellen_status ... ok
test timer_off_im_handler_ueberlebt_das_return ... ok
test trap_aus_einer_sub_heraus_kehrt_dorthin_zurueck ... ok
test input_datei_bewahrt_felder_und_unterscheidet_leer_von_fehlend ... ok
test zeit_trap_laeuft_und_kehrt_zurueck ... ok
test input_datei_alle_lesearten_teilen_utf8_cursor_und_seek ... ok
test unicode_puffergrenzen_und_binaere_records_bleiben_getrennt ... ok
test open_for_isam_endet_nicht_mehr_mit_fehler_73 ... ok
test result: ok. 114 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
Doc-tests tb_export
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_frontend
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_ide
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_runner
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_runtime
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_ui
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_vm
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

View File

@@ -0,0 +1,3 @@
PASS {'shell_exit': 7}
PASS {'abort': True}
PASS {'file_shell': True}

View File

@@ -0,0 +1 @@
{"target": "aarch64-apple-darwin", "tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df", "native_execution": true, "source_free": true, "run_reset": true, "output": " 1 \n 7 3 9 1 \nTB \n"}

View File

@@ -0,0 +1,13 @@
PASS native console
PASS native restart
PASS native stop
PASS native error
PASS native project
PASS native form
PASS native external
PASS PTY Eingabe, Abbruch und Wiederherstellung (CLI/native)
PASS Exportabbruch SIGINT/SIGTERM, Original und Bereinigung
PASS corrupted length
PASS corrupted version
PASS corrupted truncated
Native Abnahme bestanden: aarch64-apple-darwin

View File

@@ -0,0 +1,44 @@
{
"scope": "automated-target-checks",
"started_utc": "2026-09-07T18:44:13.277828+00:00",
"host_target": "aarch64-apple-darwin",
"os_version": "macOS-26.6.2-arm64-arm-64bit-Mach-O",
"python_version": "3.14.7",
"checkout_revision": "5aa920b768a2ad29ed17c5da137e54675a7292d6",
"working_tree_dirty": true,
"working_tree_diff_sha256": "cd170b3c9923de226676c6e12a6644feaca40e048d3f750ac8935c8a2732d34d",
"program_sha256": {
"tb": "77b14be900039a72c2633f157c7deb2ea0c7c70d550e7b6a9490a38b996c5c5a",
"tbc": "a51b678bca795b88fe92a9f8ce0fcdd0f488931f85fd7f1d2721c17ed665a1e9",
"tbrt": "995a8e62454b7c4e6af2782007a4e1eda95deaca25cbacf4f3e01ac1c41ccce1",
"tb-template": "a9d73ea8002b8f01bbb1fdc033a2595605ceb3b8c55d4531bf00da236351ccbf"
},
"tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df",
"manual_terminal_status": "not_run",
"matrix_complete": false,
"checks": {
"library": {
"status": "passed",
"exit_code": 0
},
"native": {
"status": "passed",
"exit_code": 0
},
"ide-pty": {
"status": "passed",
"exit_code": 0
},
"artifact-stability": {
"status": "passed"
}
},
"harness_sha256": {
"library-abnahme.py": "118de32bc5a9cbb254b234b420455f75961603afb16aa94333be8c4b5b17e962",
"platform-abnahme.py": "a5eadbd4bcabaae6c3c173ce933f83e331d03b1cf22998fe564519fe8f6b9696",
"native-abnahme.py": "fe9f34bcc581e6230909b6277bae4768dbf6be068e4616b47aaff4390424ca14",
"release-abnahme.py": "f77b1481913050faa321f9978d0f6cf31e8f884a1f626a861727b5393e1bea83",
"ide-execution-pty.py": "751afaadc1f25639eab7d5f746bee9ad0a5c11446bab8dbd4ccdd6dfae6fe4f9"
},
"all_automated_passed": true
}

View File

@@ -0,0 +1,2 @@
1: Key(KeyEvent { code: Char('x'), modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })
2: Key(KeyEvent { code: Esc, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })

View File

@@ -0,0 +1,8 @@
M crates/tb-ide/tests/execution.rs
M crates/tb-ui/examples/spike.rs
M crates/tb-ui/src/terminal.rs
M tests/support/ide-execution-pty.py
?? docs/plattformmatrix.md
?? tests/platform/
?? tests/support/__pycache__/
?? tests/support/platform-abnahme.py

View File

@@ -0,0 +1,452 @@
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/terminal.rs b/crates/tb-ui/src/terminal.rs
index 77493b2..a8204d4 100644
--- a/crates/tb-ui/src/terminal.rs
+++ b/crates/tb-ui/src/terminal.rs
@@ -11,23 +11,44 @@ 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| {
+ #[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 +59,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 +115,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 +131,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 +229,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 +240,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 +270,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/tests/support/ide-execution-pty.py b/tests/support/ide-execution-pty.py
index b422070..d43c472 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"
@@ -84,6 +85,9 @@ def exercise(abort=False, file_shell=False):
assert transcript.count(b'?1049h') >= 2
assert transcript.count(b'?1049l') >= 2
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 +95,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()

View File

@@ -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

View File

@@ -0,0 +1,24 @@
## MODIFIED Requirements
### Requirement: Farben und Statusanzeige
Die IDE SHALL das anhand belegter VBDOS-Ansichten dokumentierte Farbschema aus docs/ide-referenz.md verwenden, insbesondere dunkelblaue Codefläche mit heller Schrift, weiße aktive Titel auf dunklem Magenta, graue Menüs und schwarze Statusschrift auf Cyan. Alle IDE-Farben SHALL als feste 24-Bit-RGB-Werte ausgegeben werden, unabhängig von TERM, COLORTERM, NO_COLOR und der ANSI-Palette. Ein Rückfall auf Index- oder Profilfarben MUST NOT erfolgen; die IDE MUST NOT die globale Terminalpalette verändern. Fensterflächen SHALL vor dem Zeichnen vorhandene Desktopzeichen und verdeckte Fensterinhalte löschen, sodass unbeschriebene Fläche und Texthintergrund dieselbe Farbe besitzen. Die Statuszeile SHALL kontextuelle klickbare Kürzel und Zeile/Spalte im Format 00001:001 zeigen. Display SHALL Vorder-/Hintergrund aus 16 Farben, Desktop-Füllzeichen und Tabweite ändern können. Bestehende gespeicherte Farbauswahlen SHALL gültig bleiben und MUST NOT stillschweigend überschrieben werden.
#### Scenario: Farben ändern
- **WHEN** der Benutzer Titelhintergrund, Desktopzeichen und Tabweite im Display-Dialog ändert
- **THEN** erscheinen die Änderungen in der IDE, während die Farbvorgaben des ausgeführten BASIC-Programms unverändert bleiben
#### Scenario: Abweichende ANSI-Palette
- **WHEN** die IDE mit Standardoptionen auf einem Terminal mit expliziter Farbausgabe und einer pastellfarbenen ANSI-Palette gestartet wird
- **THEN** bleiben Codefläche dunkelblau, Code lesbar und aktive Titel weiß auf dunklem Magenta; das Terminalprofil und die Farben nach Verlassen der IDE werden nicht verändert
#### Scenario: Bestehende Einstellungen laden
- **WHEN** eine vorhandene Optionsdatei mit angepassten DOS-Farbnummern geladen wird
- **THEN** bleiben die gewählten Nummern sowie alle weiteren Einstellungen erhalten und gelten über dieselbe definierte IDE-Farbabbildung
#### Scenario: Keine Farbfähigkeit gemeldet
- **WHEN** die IDE ohne COLORTERM, mit TERM=vt100 oder NO_COLOR=1 startet
- **THEN** gibt sie unverändert absolute RGB-Werte aus, insbesondere Codehintergrund #0000AA und Titelhintergrund #AA00AA
#### Scenario: Freie Fensterfläche
- **WHEN** der Desktop ein Füllzeichen enthält oder sich Fenster überdecken und im Editor Text eingegeben, markiert und die Auswahl aufgehoben wird
- **THEN** bleiben unbeschriebene Fensterzellen echte Leerzeichen in der jeweiligen Fensterfarbe ohne Desktopmuster oder verdeckte Inhalte

View File

@@ -0,0 +1,18 @@
## MODIFIED Requirements
### Requirement: Klassische Farbpalette und Blink-Simulation
Der Bildschirm SHALL die klassische Palette abbilden (Vordergrund 015,
Hintergrund 07) als feste 24-Bit-RGB-Werte ausgeben, unabhängig von Terminalprofil
und Farberkennung (klassisch 1 = #0000AA, 4 = #AA0000). Dieselbe
Abbildung SHALL für IDE, Formularvorschau und BASIC-Ausgabe gelten;
programmbestimmte Farbnummern SHALL unverändert bleiben. Blinkende Vordergrundfarben (1631) SHALL als „hell"
simuliert werden (Farbe 16, Intensitätsbit gesetzt) — kein echtes
Terminal-Blinken.
#### Scenario: Blink wird hell
- **WHEN** `COLOR 17, 0` gesetzt wird (blinkend Blau)
- **THEN** wird mit heller Vordergrundfarbe 9 gerendert
#### Scenario: Programmbestimmte absolute Farbe
- **WHEN** ein Programm COLOR 7, 1 oder entsprechende ForeColor-/BackColor-Eigenschaften verwendet
- **THEN** wird #AAAAAA auf #0000AA ausgegeben, auch bei abweichender ANSI-Palette und ohne COLORTERM

View File

@@ -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 F1F12, 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 F1F12, 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,13 @@
- [ ] 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.
Der Benutzer bestätigt den korrigierten Farbtest am 07.09.2026 mit „test is good“.
Archivierung ausdrücklich beauftragt; acht unvollständige Matrixaufgaben bleiben
als Nachweisbedarf an 06/07 übergeben, nicht als bestandene Prüfungen markiert.

View File

@@ -0,0 +1,163 @@
# Verifizierung: phase-6-05-plattformmatrix
Stand 07.09.2026. Basis ist der auf Origin bestätigte Commit
`5aa920b768a2ad29ed17c5da137e54675a7292d6` plus der lokale Change-05-Diff.
05a wurde auf Benutzeranweisung synchronisiert und mit den ausdrücklich
weiterhin offenen Nachweisen 1.2/3.2/3.3 archiviert. Diese Nachweise werden
hier weitergeführt; Archivierung wurde nicht als bestandene Zielprüfung gewertet.
## Ergebnis
| Dimension | Stand |
|---|---|
| Vollständigkeit | 2/10 Aufgaben abgeschlossen; acht Aufgaben mit fehlenden echten Zielnachweisen offen |
| Korrektheit | Lokale Workspace-, native macOS-/TBL-/PTY- und Eingabeprüfungen bestanden; Windows-Code einschließlich Konsolentest cross-geprüft, nicht auf Windows ausgeführt |
| Kohärenz | Bestehende Prüfskripte, Formular-Engine und TerminalGuard wiederverwendet; keine neue Abhängigkeit, keine Änderung an Runnerdiensten |
Der Change wird am 07.09.2026 auf ausdrückliche Benutzeranweisung archiviert.
Der Benutzer hat die RGB-/Fensterflächenkorrektur mit „test is good“ bestätigt.
Ein vollständiger Pflichtmatrixlauf ist weiterhin nicht belegt. Die acht
unvollständigen Aufgaben bleiben im Archiv sichtbar und werden für die
Paketfreigabe in 06 und die Phasenabnahme in 07 weitergeführt. Archivierung
und Benutzerbestätigung des Farbtests ersetzen diese Nachweise nicht.
## Umsetzung
- `docs/plattformmatrix.md`: sechs verbindliche Emulatorzellen für vier
Ziele, Versions-/Revisionsfelder, gesamte F1F12-/Modifierfolge, Eingabe,
Maus, Unicode, Resize, INPUT/Break, Shell und Theme-Gegenprobe aus 05a.
Nicht ausgeführte Fälle und fehlende Ersatzwege sind ausdrücklich offen.
- `tests/support/platform-abnahme.py`: führt vorhandene native-, Library-
und Unix-PTY-Prüfungen auf dem tatsächlich erkannten Ziel aus. Bericht
bindet Ziel, Checkout-/Diffstand, Skript- und Executable-Prüfsummen sowie
die unveränderte TBL-Prüfsumme. Fehlende Windows-Konsolenprobe verhindert
ein positives automatisiertes Gesamtergebnis. Manuelle Matrix bleibt
ausdrücklich `not_run` / `matrix_complete: false`.
- `tests/support/ide-execution-pty.py`: bestehende drei Bedienpfade erhalten;
jetzt mit explizitem Kandidatenpfad und gespeicherten ANSI-Transkripten.
- `tests/platform/input.frm`: interaktive Ereignis-/Zählerprobe für IDE und
nativen Export. `crates/tb-ide/tests/execution.rs` prüft eine normale Taste
einschließlich zusätzlichem rohem Release genau einmal, sowie F1 im
IDE-Codekontext ohne zusätzliche BASIC-Zustellung.
- `crates/tb-ui/examples/spike.rs`: vorhandene Rohdiagnose um nummeriertes
Ereignislog erweitert; keine Überschreibung bestehender Logs. Der gemeinsame
TerminalGuard übernimmt auch hier Cleanup bei Fehlern.
- `crates/tb-ui/src/terminal.rs`: Windows-Eingabe-/Ausgabemodi werden vor dem
Einstieg gesichert und bei Shell-Übergabe, Fehler und Ende wiederhergestellt.
Der vorhandene Crossterm-Code setzte Raw-Flags pauschal zurück. Zusätzlich
wird ANSI-Verarbeitung bei erneutem Eintritt aktiviert, weil Crossterm
die Fähigkeit zwischenspeichert. Der neue explizite Windows-Test verwendet
eine eigene Kindkonsole, Nichtstandard-Echozustand und begrenzte Laufzeit.
Er deckt Shell 0/7, Abbruch, Rückkehr, Renderfehler und Teilinitialisierung ab.
Die vorhandene Abbruchprüfung verlangt nicht länger einen Unix-spezifischen
Exitcode auf Windows und erzeugt dort keinen verbleibenden Ping-Unterprozess.
API-Grundlage der Windows-Korrektur:
[GetConsoleMode](https://learn.microsoft.com/en-us/windows/console/getconsolemode),
[SetConsoleMode](https://learn.microsoft.com/en-us/windows/console/setconsolemode).
Der tatsächliche Windows-Lauf bleibt Voraussetzung, um den Befund zu schließen.
## Ausgeführte Prüfungen
- `cargo test --locked --workspace`: **638 bestandene Testergebnisse,
0 Fehler, 3 bestehende ignorierte Tests**. Die zusätzliche Windows-Prüfung
ist auf macOS nicht enthalten und wurde nicht als bestanden gezählt.
- `cargo check --locked --target x86_64-pc-windows-msvc -p tb-ui --features terminal --tests`:
bestanden; ebenso Cross-Checks von IDE, CLI, Export und deren Targets.
- Clippy für tb-ui/tb-ide inklusive aller Targets sowie für die
Windows-Konsolentests mit `-D warnings`: bestanden.
- Release-Build von tb/tbc/tbrt/tb-template und Spike: bestanden.
- `platform-abnahme.py --bin-dir target/release`: auf macOS arm64 bestanden.
Aktuelle Einzelbelege nach RGB-Korrektur im Verzeichnis
`evidence/2026-09-07-macos-arm64-rgb`: native
Console-/Forms-/Fehler-/RUN-/Mehrmodul- und quellfreie Exporte,
CLI/native INPUT und Abbruch in Unix-PTY, Exportabbruch und Korruptionsfälle,
dieselbe portable.tbl mit quellfreier TBC-/EXE-Nutzung und RUN,
IDE-Shell 7, Kindabbruch und File→Shell mit Wiederherstellung.
- Die Probe wurde separat kompiliert; IDE-Ereignistest besteht.
Spike wurde headless über Unix-PTY mit x/Esc geprüft: genau zwei
nummerierte Rohereignisse und ursprüngliche Terminalattribute nach Ende.
Wiederverwendung desselben Logpfads wurde ohne Überschreiben abgelehnt.
- Negativprüfung des Sammelaufrufs mit fehlenden Binaries: Exit 2,
verständlicher Fehler, kein erzeugter Erfolgsreport.
Der erste ad-hoc Spike-Prüfversuch mit Python-preexec blieb beim Start hängen;
die eigenen Prüfprozesse wurden beendet. Der erfolgreiche Wiederholungslauf
verwendete dasselbe Fork-/Sitzungsbesitzmuster wie die bestehende IDE-PTY-Probe.
Dies ist kein visueller Emulatornachweis. Rohlogs und Testergebnisse werden
nur für die tatsächlich ausgeführten Pfade gewertet.
## RGB-Korrektur nach erneutem Benutzerbefund
Der Screenshot vom 07.09.2026 um 20:54 zeigte zwei Produktfehler:
Die Farbabbildung konnte weiterhin auf Terminalprofilfarben zurückfallen;
außerdem änderten Block/Paragraph nur Zellstile, sodass das Desktopzeichen
`▒` in unbeschriebenen Fensterflächen stehen blieb. Dieses Muster mischte
optisch Vorder-/Hintergrund und erklärte die hellere Fläche neben dem Text.
- IDE und BASIC verwenden jetzt die gemeinsame feste DOS-RGB-Palette in
`tb-ui::screen::basic_color`; keine Farberkennung oder Indexfarben mehr.
TerminalGuard aktiviert Farbausgabe auch bei `NO_COLOR`. Gespeicherte
DOS-Farbnummern und BASIC-ForeColor/BackColor bleiben erhalten.
- Jedes Fenster wird vor dem Rendern geleert. Der neue Regressionstest
scheiterte vor der Korrektur mit `Desktop in Fenster bei 9/2` und besteht
danach einschließlich Auswahlwechsel und überlappenden Fenstern.
- Vier Theme-Tests bestehen mit `TERM=vt100 COLORTERM= NO_COLOR=1`.
Der echte Release-Prozess besteht zusätzlich alle drei IDE-PTY-Pfade mit
`env -u COLORTERM TERM=vt100 NO_COLOR=1`; Transkripte enthalten explizit
`38;2;170;170;170`, `48;2;0;0;170`, `48;2;170;0;170`, keine Indexfarben
und keine OSC-Palettenänderungen. Auch Shell-Rückkehr besteht.
- Workspace: 638 bestanden, 0 Fehler, 3 bestehende ignorierte Tests.
Clippy, Windows-Cross-Check und OpenSpec (30/30) bestehen. Debug-tb und
Release-tb/tbc/tbrt/tb-template/Spike neu gebaut. Native-, TBL- und
IDE-PTY-Abnahme mit diesen Release-Artefakten erneut bestanden.
- Neue Delta-Spezifikationen in diesem Change ändern `ide-oberflaeche` und
`textbildschirm` entsprechend der verbindlichen Benutzerkorrektur.
Frühere ANSI-/256-Fallback-Verträge im Archiv dokumentieren nur den alten
Stand; die Synchronisierung der neuen Deltas erfolgt beim Abschluss von 05.
Belege: `evidence/2026-09-07-macos-arm64-rgb`. Der gespeicherte Diff stimmt
mit dem Hash des Zielberichts überein; spätere Berichtsänderungen betreffen
keinen geprüften Produktcode. Das ältere Verzeichnis ohne `-rgb` belegt den
Vorgängerstand und ist kein Nachweis für die aktuelle Farbausgabe.
Der Benutzer hat den Farbtest am 07.09.2026 als erfolgreich bestätigt.
Diese Bestätigung schließt den gemeldeten RGB-/Fensterflächenbefund; eine
vollständige versionsgebundene Emulatorfolge wurde damit nicht bestätigt.
Benutzerentscheidung: Windows-Ausführung vorerst offen lassen. DOSBox ist
kein Windows-PE-Prüfsystem. Linux nutzt denselben ANSI-/RGB-Pfad wie macOS;
das begründet eine Verhaltenserwartung, ersetzt aber keine xterm-/VTE-Zielprüfung.
## Offene Befunde
Jede der folgenden Aufgaben bleibt CRITICAL für die vollständige Abnahme:
| Aufgabe | Fehlender Nachweis / erforderliche Aktion |
|---|---|
| 1.2 | Windows-Konsolenprüfung auf echtem Windows ausführen; Code/Cross-Check vorhanden, die Wiederherstellung einschließlich Fehlerpfaden ist dort noch nicht bewiesen. |
| 2.1 | Windows amd64 in Windows Terminal: IDE, natives Formular, INPUT/Break, Shell und TBL tatsächlich ausführen und protokollieren. |
| 2.2 | Terminal.app auf macOS arm64 manuell prüfen. Native/PTY-/TBL-Läufe sind bestanden, die echte Emulatorbedienung und Theme-Gegenprobe fehlen. |
| 2.3 | Linux amd64 in xterm und VTE einschließlich derselben TBL tatsächlich prüfen. |
| 2.4 | Linux arm64 in xterm und VTE einschließlich derselben TBL tatsächlich prüfen. |
| 2.5 | Windows-Wiederherstellungsbefund durch den tatsächlichen Zieltest schließen. Code und lokale Regressionen bestehen. |
| 3.1 | Reservierte Shortcuts und gegebenenfalls funktionierende Ersatzwege in den echten Emulatoren belegen. Regeln sind dokumentiert, keine Ersatzwege ohne Versuch als erfolgreich eingetragen. |
| 3.2 | Alle Pflichtmatrixzellen und TBL-Ergebnisse vervollständigen, 05a-Profil-/Theme-Gegenprobe und abschließende Verifizierung schließen; danach aktuelle revisions-/artefaktgebundene Nachweise an 06/07 übergeben. |
### Verfügbare Systeme und konkrete Grenzen
- Lokal bestätigt: macOS 26.6.2 (25G83), arm64; Terminal.app 2.15 ist
installiert. Computer Use hat den Zugriff auf Terminal.app im früheren
Versuch ausdrücklich gesperrt. Ein automatisierter PTY-Lauf ersetzt den
erforderlichen manuellen Lauf nicht; die Sperre wird nicht umgangen.
- Windows sowie Linux mit xterm/VTE: bislang kein nutzbarer Zugang benannt.
- Gitea-Host: read-only als Linux aarch64 bestätigt, die benötigten
GUI-Terminals fehlen. Der Runnercontainer meldete beim Lesezugriff
`status=restarting`, `restarts=2319`, letzter Start
`2026-09-07T18:49:08.081383305Z`. Es wurden keine Dienste oder
Runnerkonfigurationen verändert. Das ist ein Infrastrukturzustand,
keine ausgeführte Zielabnahme.
Für die fehlenden Systeme sind Zugänge beziehungsweise ausgefüllte manuelle
Prüfprotokolle nötig. Nach deren Eingang die vorbereitete Folge ausführen,
Befunde beheben und betroffene Prüfungen wiederholen; bis dahin bleiben
Aufgaben und Matrixzellen offen.

View File

@@ -16,3 +16,8 @@
- [ ] 3.1 Alle neun PLAN-Punkte einer bestandenen Evidenz zuordnen und erst dann abhaken; Bericht muss Gitea-Run-/Releasebezug, zentralen Cross-Paketbau und davon getrennte echte Zielausführung, vier Targets und verbleibende Stufe-2-Abgrenzung zeigen.
- [ ] 3.2 Abschließende Doku-/Help-/OpenSpec-Konsistenz prüfen; keine unaufgelösten Links, widersprüchlichen Exporthinweise oder offenen Phase-6-Befunde dürfen verbleiben.
Übernahme aus dem auf Benutzeranweisung archivierten Change 05: dessen acht
unvollständige Matrixaufgaben einschließlich der restlichen 05a-Nachweise
bleiben für 2.22.4 und 3.1 nachzuweisen. Der erfolgreiche Benutzer-Farbtest
bestätigt die RGB-/Fensterflächenkorrektur, nicht die vollständige Plattformmatrix.

View File

@@ -36,7 +36,7 @@ Fenster SHALL Titelleiste, Control-Menü, Rahmen und bei Bedarf Scrollbalken erh
- **THEN** empfängt nur der Dialog die Eingaben und danach erhält das vorher aktive Fenster den Fokus zurück
### Requirement: Farben und Statusanzeige
Die IDE SHALL das anhand belegter VBDOS-Ansichten dokumentierte Farbschema aus docs/ide-referenz.md verwenden, insbesondere dunkelblaue Codefläche mit heller Schrift, weiße aktive Titel auf dunklem Magenta, graue Menüs und schwarze Statusschrift auf Cyan. Die Standardfarben SHALL auf Terminals mit Unterstützung expliziter Farbwerte unabhängig von der benutzerdefinierten ANSI-Palette ausgegeben werden. Für eingeschränkte Farbfähigkeiten SHALL ein dokumentierter, lesbarer Fallback bestehen; die IDE MUST NOT die globale Terminalpalette verändern. Die Statuszeile SHALL kontextuelle klickbare Kürzel und Zeile/Spalte im Format 00001:001 zeigen. Display SHALL Vorder-/Hintergrund aus 16 Farben, Desktop-Füllzeichen und Tabweite ändern können. Bestehende gespeicherte Farbauswahlen SHALL gültig bleiben und MUST NOT stillschweigend überschrieben werden.
Die IDE SHALL das anhand belegter VBDOS-Ansichten dokumentierte Farbschema aus docs/ide-referenz.md verwenden, insbesondere dunkelblaue Codefläche mit heller Schrift, weiße aktive Titel auf dunklem Magenta, graue Menüs und schwarze Statusschrift auf Cyan. Alle IDE-Farben SHALL als feste 24-Bit-RGB-Werte ausgegeben werden, unabhängig von TERM, COLORTERM, NO_COLOR und der ANSI-Palette. Ein Rückfall auf Index- oder Profilfarben MUST NOT erfolgen; die IDE MUST NOT die globale Terminalpalette verändern. Fensterflächen SHALL vor dem Zeichnen vorhandene Desktopzeichen und verdeckte Fensterinhalte löschen, sodass unbeschriebene Fläche und Texthintergrund dieselbe Farbe besitzen. Die Statuszeile SHALL kontextuelle klickbare Kürzel und Zeile/Spalte im Format 00001:001 zeigen. Display SHALL Vorder-/Hintergrund aus 16 Farben, Desktop-Füllzeichen und Tabweite ändern können. Bestehende gespeicherte Farbauswahlen SHALL gültig bleiben und MUST NOT stillschweigend überschrieben werden.
#### Scenario: Farben ändern
- **WHEN** der Benutzer Titelhintergrund, Desktopzeichen und Tabweite im Display-Dialog ändert
@@ -50,6 +50,14 @@ Die IDE SHALL das anhand belegter VBDOS-Ansichten dokumentierte Farbschema aus d
- **WHEN** eine vorhandene Optionsdatei mit angepassten DOS-Farbnummern geladen wird
- **THEN** bleiben die gewählten Nummern sowie alle weiteren Einstellungen erhalten und gelten über dieselbe definierte IDE-Farbabbildung
#### Scenario: Keine Farbfähigkeit gemeldet
- **WHEN** die IDE ohne COLORTERM, mit TERM=vt100 oder NO_COLOR=1 startet
- **THEN** gibt sie unverändert absolute RGB-Werte aus, insbesondere Codehintergrund #0000AA und Titelhintergrund #AA00AA
#### Scenario: Freie Fensterfläche
- **WHEN** der Desktop ein Füllzeichen enthält oder sich Fenster überdecken und im Editor Text eingegeben, markiert und die Auswahl aufgehoben wird
- **THEN** bleiben unbeschriebene Fensterzellen echte Leerzeichen in der jeweiligen Fensterfarbe ohne Desktopmuster oder verdeckte Inhalte
### Requirement: Beständige und validierte Optionen
Options SHALL Display, Set Paths, Right Mouse, Save und Syntax Checking anbieten. Syntax Checking SHALL anfangs aktiv sein. Rechtsklick SHALL konfigurierbar Kontext-Hilfe auslösen. Explizites Save SHALL die Optionen benutzerbezogen speichern; fehlerhafte oder unbekannte Konfiguration SHALL mit verständlicher Diagnose und benutzbaren Vorgaben behandelt werden.

View File

@@ -0,0 +1,40 @@
## Purpose
Belegt die tatsächliche Terminalbedienbarkeit der IDE und erzeugter Programme auf allen vereinbarten System-/Architektur-Kombinationen.
## Requirements
### Requirement: Vollständige Ziel- und Terminalmatrix
Die Plattformabnahme SHALL Windows amd64 mit Windows Terminal, macOS arm64 mit einem benannten Terminal sowie Linux amd64 und arm64 jeweils mit mindestens zwei verschiedenen Terminalemulatoren prüfen. Je Matrixzelle SHALL Ziel, Betriebssystem-, Terminal- und Programmversion sowie die geprüfte Revision dokumentiert sein. Reines Cross-Kompilieren MUST NOT als Ausführungsnachweis gelten.
#### Scenario: Ziel wurde nur gebaut
- **WHEN** ein Linux-arm64-Artefakt zwar kompiliert, aber auf keinem passenden Ziel ausgeführt wurde
- **THEN** bleibt dessen Ausführungsnachweis offen und die vollständige Plattformabnahme gilt nicht als bestanden
### Requirement: Reale Eingabe und Darstellung
Die Matrix SHALL F1F12, relevante Shift-/Ctrl-/Alt-Kombinationen, Menünavigation, Mausdruck/-loslassen/-bewegung, Unicode, Farben und Größenänderung für IDE und repräsentative erzeugte Programme prüfen. Die IDE-Farbprüfung SHALL das geltende VBDOS-Theme einschließlich Kontrast und Zuständen aus `ide-oberflaeche` mit dokumentiertem Farbprofil und tatsächlichen Ansichten abdecken. Eingaben SHALL genau einmal dem zuständigen IDE- oder BASIC-Kontext zugeordnet werden. Unterhalb der Mindestgröße SHALL die Anwendung bedienbar wiederherstellbar bleiben.
#### Scenario: Tastenkonflikt im Terminal
- **WHEN** der Emulator eine IDE-Tastenkombination selbst abfängt
- **THEN** dokumentiert der Nachweis diese Grenze und prüft einen erreichbaren alternativen Bedienweg, statt die nicht zugestellte Taste als erfolgreich zu werten
### Requirement: Wiederherstellung nach Unterbrechung
INPUT-Warten, Break/Continue, normaler Exit, Laufzeitfehler und Shell-Rückkehr SHALL Terminalmodus, Eingabe und Anzeige konsistent erhalten beziehungsweise wiederherstellen. Native Programme SHALL diese Regeln ebenso erfüllen wie die IDE.
#### Scenario: Shell-Kind abbrechen
- **WHEN** ein Shell-Kind während einer IDE- oder Programmsitzung unterbrochen wird
- **THEN** bleibt die übergeordnete Sitzung bedienbar und nach ihrem Ende sind Terminalmodus und Anzeige wiederhergestellt
### Requirement: Nachprüfbare Einschränkungen
Einschränkungen SHALL reproduzierbar, plattformspezifisch und mit Auswirkung sowie Ersatzweg beschrieben sein. Ein Produktfehler ohne funktionsgleichen erreichbaren Weg MUST als offener Befund gelten; reine TestBackend-Ergebnisse MUST NOT manuelle Emulatorprüfungen ersetzen.
#### Scenario: Fehlender Prüfrechner
- **WHEN** eine erforderliche System-/Emulatorkombination nicht verfügbar ist
- **THEN** nennt der Bericht die fehlende Kombination und führt sie bis zum tatsächlichen Nachweis offen
### Requirement: Identische Bibliothek auf vier Zielsystemen
Die Plattformabnahme SHALL dieselben unveränderten TBL-Bytes aus Change 03 auf Windows amd64, macOS arm64 und Linux amd64/arm64 mit separaten BASIC-Verbrauchern zu TBC und eigenständigem Executable verknüpfen und tatsächlich ausführen. Library-Quellen SHALL nicht benötigt werden; Resultate und RUN-Neustart SHALL dem gemeinsamen Soll entsprechen. Jeder Nachweis SHALL TBL-Prüfsumme, Toolrevision, tatsächliches Host-Target und Ergebnis enthalten. Der zentrale Cross-Build in Change 06 MUST NOT diese Zielausführung ersetzen; separate Prüfsysteme und dokumentierte manuelle Läufe sind zulässig.
#### Scenario: Cross-Bau erfolgreich, Bibliothekslauf fehlt
- **WHEN** alle vier Toolpakete gebaut sind, aber die gemeinsame TBL-Probe auf Windows noch nicht ausgeführt wurde
- **THEN** bleibt der Windows-Verbrauchernachweis offen und die Plattformabnahme ist nicht abgeschlossen

View File

@@ -56,8 +56,10 @@ Terminal sie nicht abbilden kann, folgenlos bleiben.
### Requirement: Klassische Farbpalette und Blink-Simulation
Der Bildschirm SHALL die klassische Palette abbilden (Vordergrund 015,
Hintergrund 07) und auf ANSI-Indexfarben mappen (klassisch 1 = Blau ↔
ANSI 4 usw.). Blinkende Vordergrundfarben (1631) SHALL als „hell"
Hintergrund 07) als feste 24-Bit-RGB-Werte ausgeben, unabhängig von Terminalprofil
und Farberkennung (klassisch 1 = #0000AA, 4 = #AA0000). Dieselbe
Abbildung SHALL für IDE, Formularvorschau und BASIC-Ausgabe gelten;
programmbestimmte Farbnummern SHALL unverändert bleiben. Blinkende Vordergrundfarben (1631) SHALL als „hell"
simuliert werden (Farbe 16, Intensitätsbit gesetzt) — kein echtes
Terminal-Blinken.
@@ -65,6 +67,10 @@ Terminal-Blinken.
- **WHEN** `COLOR 17, 0` gesetzt wird (blinkend Blau)
- **THEN** wird mit heller Vordergrundfarbe 9 gerendert
#### Scenario: Programmbestimmte absolute Farbe
- **WHEN** ein Programm COLOR 7, 1 oder entsprechende ForeColor-/BackColor-Eigenschaften verwendet
- **THEN** wird #AAAAAA auf #0000AA ausgegeben, auch bei abweichender ANSI-Palette und ohne COLORTERM
### Requirement: Unicode-Zellenmodell
Der Puffer SHALL Unicode-Zeichen speichern (keine CP437-Emulation).
Zeichen mit Darstellungsbreite 2 (u. a. Emoji, CJK) SHALL zwei

103
tests/platform/input.frm Normal file
View File

@@ -0,0 +1,103 @@
VERSION 1.00
Begin Form Probe
Caption = "Plattformprobe: Ereignisse genau einmal"
Width = 70
Height = 18
Begin Menu mnuDatei
Caption = "&Datei"
Begin Menu mnuEnde
Caption = "&Ende"
End
End
Begin Label Hint
Caption = "Textfeld: F1-F12 / Modifier. Datei > Ende beendet."
Left = 2
Top = 2
Width = 65
Height = 1
End
Begin TextBox Text1
Left = 2
Top = 4
Width = 50
Height = 1
TabIndex = 0
End
Begin Label Counts
Caption = "Down/Up/Press/Click/Move: 0/0/0/0/0"
Left = 2
Top = 6
Width = 65
Height = 1
End
Begin Label Unicode
Caption = "Unicode: ÄÖÜ äöü ß 中界 ┌─┐"
Left = 2
Top = 8
Width = 60
Height = 1
End
Begin CommandButton Count
Caption = "&Zählen"
Left = 2
Top = 10
Width = 14
Height = 1
TabIndex = 1
End
Begin CommandButton Done
Caption = "&Ende"
Default = -1
Left = 20
Top = 10
Width = 14
Height = 1
TabIndex = 2
End
End
DIM SHARED downs&, ups&, presses&, clicks&, moves&
OPEN "platform-events.log" FOR OUTPUT AS #1
CLOSE #1
Probe.Show
Text1.SetFocus
SUB RecordEvent(kind$, code%, modifiers%)
OPEN "platform-events.log" FOR APPEND AS #1
PRINT #1, kind$; ":"; code%; ":"; modifiers%
CLOSE #1
Counts.Caption = "Down/Up/Press/Click/Move:" + STR$(downs&) + "/" + STR$(ups&) + "/" + STR$(presses&) + "/" + STR$(clicks&) + "/" + STR$(moves&)
END SUB
SUB Text1_KeyDown(KeyCode AS INTEGER, Shift AS INTEGER)
downs& = downs& + 1
CALL RecordEvent("Down", KeyCode, Shift)
END SUB
SUB Text1_KeyUp(KeyCode AS INTEGER, Shift AS INTEGER)
ups& = ups& + 1
CALL RecordEvent("Up", KeyCode, Shift)
END SUB
SUB Text1_KeyPress(KeyAscii AS INTEGER)
presses& = presses& + 1
CALL RecordEvent("Press", KeyAscii, 0)
END SUB
SUB Probe_MouseDown(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)
CALL RecordEvent("MouseDown", Button, Shift)
END SUB
SUB Probe_MouseUp(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)
CALL RecordEvent("MouseUp", Button, Shift)
END SUB
SUB Probe_MouseMove(Button AS INTEGER, Shift AS INTEGER, X AS SINGLE, Y AS SINGLE)
moves& = moves& + 1
CALL RecordEvent("MouseMove", Button, Shift)
END SUB
SUB Count_Click()
clicks& = clicks& + 1
CALL RecordEvent("Click", 0, 0)
END SUB
SUB Done_Click()
CALL RecordEvent("End", 0, 0)
END
END SUB
SUB mnuEnde_Click()
CALL RecordEvent("End", 0, 0)
END
END SUB

View File

@@ -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()

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Automatisierte Zielabnahme mit Artefaktbindung; reale Emulatornachweise bleiben separat."""
import argparse
import datetime
import hashlib
import json
import os
from pathlib import Path
import platform
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[2]
TARGETS = {
('Windows', 'AMD64'): 'x86_64-pc-windows-msvc',
('Darwin', 'arm64'): 'aarch64-apple-darwin',
('Linux', 'x86_64'): 'x86_64-unknown-linux-gnu',
('Linux', 'aarch64'): 'aarch64-unknown-linux-gnu',
}
TBL_SHA256 = '5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df'
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--bin-dir', type=Path, default=ROOT / 'target/release')
parser.add_argument('--output', type=Path, required=True, help='neues Verzeichnis für Bericht und Logs')
parser.add_argument('--windows-console-test', type=Path, help='tb-ui-Testexecutable derselben Revision')
args = parser.parse_args()
target = TARGETS.get((platform.system(), platform.machine()))
if target is None:
parser.error(f'Kein vereinbartes Releaseziel: {platform.system()} {platform.machine()}')
binaries = args.bin_dir.resolve()
suffix = '.exe' if os.name == 'nt' else ''
programs = {name: binaries / (name + suffix) for name in ('tb', 'tbc', 'tbrt', 'tb-template')}
for path in programs.values():
if not path.is_file():
parser.error(f'Prüfwerkzeug fehlt: {path}')
library = ROOT / 'tests/libraries/portable.tbl'
if digest(library) != TBL_SHA256:
parser.error('Gemeinsame portable.tbl hat eine abweichende Prüfsumme')
revision = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT, text=True).strip()
changes = subprocess.check_output(['git', 'diff', '--binary', 'HEAD'], cwd=ROOT)
status = subprocess.check_output(['git', 'status', '--porcelain'], cwd=ROOT, text=True)
args.output.mkdir(parents=True) # vorhandene Nachweise nie überschreiben
report = {
'scope': 'automated-target-checks',
'started_utc': datetime.datetime.now(datetime.timezone.utc).isoformat(),
'host_target': target, 'os_version': platform.platform(),
'python_version': platform.python_version(), 'checkout_revision': revision,
'working_tree_dirty': bool(status),
'working_tree_diff_sha256': hashlib.sha256(changes).hexdigest(),
'program_sha256': {name: digest(path) for name, path in programs.items()},
'tbl_sha256': TBL_SHA256,
'manual_terminal_status': 'not_run', 'matrix_complete': False, 'checks': {},
}
(args.output / 'working-tree.patch').write_bytes(changes)
(args.output / 'working-tree-status.txt').write_text(status, encoding='utf-8')
# Neue Prüfscripte können noch untracked sein; ihre genauen Bytes festhalten.
report['harness_sha256'] = {p.name: digest(p) for p in Path(__file__).parent.glob('*abnahme.py')}
report['harness_sha256']['ide-execution-pty.py'] = digest(Path(__file__).with_name('ide-execution-pty.py'))
commands = {
'library': [sys.executable, str(Path(__file__).with_name('library-abnahme.py')), '--bin-dir', str(binaries)],
'native': [sys.executable, str(Path(__file__).with_name('native-abnahme.py')), '--bin-dir', str(binaries), '--target', target],
}
if os.name == 'posix':
commands['ide-pty'] = [sys.executable, str(Path(__file__).with_name('ide-execution-pty.py')),
'--binary', str(programs['tb']), '--transcript-dir', str(args.output.resolve() / 'pty')]
elif args.windows_console_test:
executable = args.windows_console_test.resolve(strict=True)
report['windows_console_test_sha256'] = digest(executable)
commands['windows-console'] = [str(executable), '--exact',
'terminal::tests::windows_console_restores_modes', '--ignored', '--nocapture']
else:
report['checks']['windows-console'] = {'status': 'missing', 'reason': '--windows-console-test fehlt'}
report['checks'].update({name: {'status': 'not_run'} for name in commands})
try:
for name, command in commands.items():
print('Prüfung:', name, flush=True)
with (args.output / (name + '.log')).open('wb') as log:
try:
result = subprocess.run(command, cwd=ROOT, stdout=log, stderr=subprocess.STDOUT, timeout=300)
report['checks'][name] = {'status': 'passed' if result.returncode == 0 else 'failed',
'exit_code': result.returncode}
except OSError as error:
report['checks'][name] = {'status': 'failed', 'reason': str(error)}
except subprocess.TimeoutExpired:
report['checks'][name] = {'status': 'failed', 'reason': 'Zeitlimit 300s'}
finally:
stable = all(digest(path) == report['program_sha256'][name] for name, path in programs.items()) and digest(library) == TBL_SHA256
report['checks']['artifact-stability'] = {'status': 'passed' if stable else 'failed'}
report['all_automated_passed'] = bool(report['checks']) and all(
check['status'] == 'passed' for check in report['checks'].values())
(args.output / 'report.json').write_text(json.dumps(report, indent=2, ensure_ascii=False) + '\n', encoding='utf-8')
print('Automatisierte Zielprüfung:', 'bestanden' if report['all_automated_passed'] else 'offen/fehlgeschlagen')
print('Reale Terminalmatrix: weiterhin separat nachzuweisen')
return 0 if report['all_automated_passed'] else 1
if __name__ == '__main__':
raise SystemExit(main())