Implement and archive Phase 5 integration acceptance
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -607,6 +607,8 @@ name = "tb-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossterm",
|
||||
"ratatui",
|
||||
"tb-frontend",
|
||||
"tb-ide",
|
||||
"tb-runtime",
|
||||
|
||||
27
PLAN.md
27
PLAN.md
@@ -446,15 +446,15 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
|
||||
virtuelle Ereignisfolge reproduzierbar ein.
|
||||
|
||||
## Phase 5 — IDE (`tb-ide`)
|
||||
- [ ] IDE-Rahmen: Menüleiste, MDI-artige Fensterverwaltung, Statuszeile,
|
||||
- [x] IDE-Rahmen: Menüleiste, MDI-artige Fensterverwaltung, Statuszeile,
|
||||
klassisches Farbschema
|
||||
- [ ] Editor: Syntaxprüfung/-normalisierung pro Zeile (Keywords groß,
|
||||
- [x] Editor: Syntaxprüfung/-normalisierung pro Zeile (Keywords groß,
|
||||
Leerzeichen), Suchen/Ersetzen, Hilfe-Verweise
|
||||
- [ ] Projektverwaltung (`.MAK`): mehrere Module/Formulare
|
||||
- [ ] Formular-Designer: Steuerelemente platzieren/verschieben/skalieren,
|
||||
Eigenschaftenfenster
|
||||
- [ ] Ausführen aus der IDE: Start/Unterbrechen/Fortsetzen/Neustart
|
||||
- [ ] **Export-Bedienung der IDE:** Run → Make EXE File und Make Library
|
||||
- [x] Projektverwaltung (`.MAK`): mehrere Module/Formulare
|
||||
- [x] Formular-Designer: Steuerelemente platzieren/verschieben/skalieren,
|
||||
Properties Bar
|
||||
- [x] Ausführen aus der IDE: Start/Unterbrechen/Fortsetzen/Neustart
|
||||
- [x] **Export-Bedienung der IDE:** Run → Make EXE File und Make Library
|
||||
mit vollständigen Dialogen, Zielsystem/-architektur, Ausgabepfad,
|
||||
Eingabeprüfung, Status-/Fehleranzeige und Übergabe an den Exportpfad
|
||||
vorbereiten. Die UI gehört vollständig zu Phase 5. Solange der
|
||||
@@ -469,16 +469,23 @@ Maßgeblich ist seit 2026-09-03 das Inventar, nicht diese Liste.
|
||||
- [x] **`RUN`** — Programm neu starten bzw. ein anderes laden und starten.
|
||||
Nutzt denselben Zurücksetzpfad der VM wie „Neustart" oben.
|
||||
**1 Inventareintrag**
|
||||
- [ ] Debugger: Breakpoints, Einzelschritt/Prozedurschritt, Direktfenster,
|
||||
- [x] Debugger: Breakpoints, Einzelschritt/Prozedurschritt, Direktfenster,
|
||||
Überwachungsausdrücke
|
||||
- [ ] Hilfe-System: rendert die Markdown-Dokumentation aus `docs/`
|
||||
- [x] Hilfe-System: rendert die Markdown-Dokumentation aus `docs/`
|
||||
(Sprach- und Bibliotheksreferenz) im Help-Fenster — Konsolen-
|
||||
Markdown-Rendering mit Umbruch nach aktueller Fenstergröße,
|
||||
Hyperlink-Navigation wie im Vorbild (Tab/Enter/Alt+F1), F1 =
|
||||
kontextsensitiver Sprung zum Thema unter dem Cursor
|
||||
- [ ] Meilenstein: Programm komplett in der IDE schreiben, gestalten,
|
||||
- [x] Meilenstein: Programm komplett in der IDE schreiben, gestalten,
|
||||
debuggen und ausführen
|
||||
|
||||
Abgenommen am 2026-09-07 durch Change `phase-5-08-integration-und-phasenabnahme`.
|
||||
Die zugehörigen `verification.md` und `befehlsmatrix.md` im OpenSpec-Change
|
||||
(bei Abschluss unter `openspec/changes/archive/`) ordnen alle elf Planpunkte
|
||||
und 45 Referenzbereiche den bestandenen Tests zu. Workspace-Regressionen,
|
||||
headless IDE/CLI/TBC-Parität, lokaler Unix-PTY-Test und Release-Compile-Budget
|
||||
sind bestanden. Die vollständige Terminal-/OS-Matrix bleibt Phase 6.
|
||||
|
||||
## Phase 6 — Kompatibilität, Politur, Distribution
|
||||
- [ ] Kompatibilitäts-Testsuite ausbauen (Snapshot-Tests der Bildschirmausgabe)
|
||||
- [ ] Plattformtests: Windows Terminal, Linux (mind. 2 Emulatoren), macOS —
|
||||
|
||||
@@ -19,3 +19,5 @@ anyhow.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tb-ide = { path = "../tb-ide" }
|
||||
crossterm.workspace = true
|
||||
ratatui.workspace = true
|
||||
|
||||
804
crates/tb-cli/src/ide_acceptance.rs
Normal file
804
crates/tb-cli/src/ide_acceptance.rs
Normal file
@@ -0,0 +1,804 @@
|
||||
//! Phase-5 acceptance drives App through native input; CLI comparisons use run_chain itself.
|
||||
use super::*;
|
||||
use crossterm::event::{
|
||||
Event, KeyCode as K, KeyEvent, KeyModifiers as M, MouseButton, MouseEvent, MouseEventKind,
|
||||
};
|
||||
use ratatui::{backend::TestBackend, Terminal};
|
||||
use std::{
|
||||
fs,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
use tb_ide::{
|
||||
app::{App, DialogKind, Execution, Hit, Mode},
|
||||
commands::Command,
|
||||
export::{ExportStatus, ProjectStamp},
|
||||
};
|
||||
use tb_runtime::{snapshot, value::Value};
|
||||
struct Temp(PathBuf);
|
||||
impl Temp {
|
||||
fn new() -> Self {
|
||||
static N: AtomicUsize = AtomicUsize::new(0);
|
||||
let p = std::env::temp_dir().join(format!(
|
||||
"tb-accept-{}-{}",
|
||||
std::process::id(),
|
||||
N.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
fs::create_dir_all(&p).unwrap();
|
||||
Self(p.canonicalize().unwrap())
|
||||
}
|
||||
}
|
||||
impl Drop for Temp {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
fn key(a: &mut App, k: K, m: M) {
|
||||
a.handle(Event::Key(KeyEvent::new(k, m)));
|
||||
}
|
||||
fn plain(a: &mut App, k: K) {
|
||||
key(a, k, M::NONE);
|
||||
}
|
||||
fn text(a: &mut App, s: &str) {
|
||||
for c in s.chars() {
|
||||
plain(a, K::Char(c));
|
||||
}
|
||||
}
|
||||
fn field(a: &mut App, n: usize, s: &str) {
|
||||
for _ in 0..30 {
|
||||
if a.dialog.as_ref().unwrap().focus == n {
|
||||
break;
|
||||
}
|
||||
plain(a, K::Tab);
|
||||
}
|
||||
assert_eq!(a.dialog.as_ref().unwrap().focus, n);
|
||||
key(a, K::Char('a'), M::CONTROL);
|
||||
plain(a, K::Delete);
|
||||
text(a, s);
|
||||
}
|
||||
fn choice(a: &mut App, n: usize, s: &str) {
|
||||
for _ in 0..30 {
|
||||
if a.dialog.as_ref().unwrap().focus == n {
|
||||
break;
|
||||
}
|
||||
plain(a, K::Tab);
|
||||
}
|
||||
for _ in 0..200 {
|
||||
if a.dialog.as_ref().unwrap().fields[n]
|
||||
.string()
|
||||
.eq_ignore_ascii_case(s)
|
||||
{
|
||||
return;
|
||||
}
|
||||
plain(a, K::Right);
|
||||
}
|
||||
panic!("choice missing {s}: {:?}", a.dialog);
|
||||
}
|
||||
fn submit(a: &mut App) {
|
||||
plain(a, K::Enter);
|
||||
assert!(a.dialog.is_none(), "{:?} / {}", a.dialog, a.message);
|
||||
}
|
||||
fn menu(a: &mut App, c: Command) {
|
||||
let menus = a.menus();
|
||||
let (m, i) = menus
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(m, menu)| {
|
||||
menu.items
|
||||
.iter()
|
||||
.position(|i| i.command == Some(c))
|
||||
.map(|i| (m, i))
|
||||
})
|
||||
.unwrap_or_else(|| panic!("menu missing {c:?}"));
|
||||
key(a, K::Char(menus[m].mnemonic), M::ALT);
|
||||
for _ in 0..i {
|
||||
plain(a, K::Down);
|
||||
}
|
||||
plain(a, K::Enter);
|
||||
assert_eq!(a.last_command, Some(c), "{}", a.message);
|
||||
}
|
||||
fn draw(a: &mut App) -> String {
|
||||
let mut t = Terminal::new(TestBackend::new(a.size.0, a.size.1)).unwrap();
|
||||
t.draw(|f| a.render(f)).unwrap();
|
||||
t.backend()
|
||||
.buffer()
|
||||
.content
|
||||
.chunks(a.size.0 as usize)
|
||||
.map(|r| r.iter().map(|c| c.symbol()).collect::<String>())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
fn click_hit(a: &mut App, predicate: impl Fn(&Hit) -> bool) {
|
||||
draw(a);
|
||||
let r = a
|
||||
.hits
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|(_, h)| predicate(h))
|
||||
.unwrap_or_else(|| panic!("hit missing"))
|
||||
.0;
|
||||
a.handle(Event::Mouse(MouseEvent {
|
||||
kind: MouseEventKind::Down(MouseButton::Left),
|
||||
column: r.x,
|
||||
row: r.y,
|
||||
modifiers: M::NONE,
|
||||
}));
|
||||
}
|
||||
fn replace_code(a: &mut App, s: &str) {
|
||||
key(a, K::Char('a'), M::CONTROL);
|
||||
a.handle(Event::Paste(s.into()));
|
||||
}
|
||||
fn code(a: &App) -> String {
|
||||
a.project
|
||||
.document(a.active_document().unwrap())
|
||||
.unwrap()
|
||||
.code()
|
||||
.into()
|
||||
}
|
||||
fn cursor_line(a: &mut App, line: usize) {
|
||||
key(a, K::Home, M::CONTROL);
|
||||
for _ in 1..line {
|
||||
plain(a, K::Down);
|
||||
}
|
||||
plain(a, K::Home);
|
||||
}
|
||||
fn save_as(a: &mut App, path: &Path) {
|
||||
menu(a, Command::SaveAs);
|
||||
field(a, 0, &path.display().to_string());
|
||||
submit(a);
|
||||
}
|
||||
fn property(a: &mut App, name: &str, value: &str) {
|
||||
if !a.properties {
|
||||
plain(a, K::F(10));
|
||||
}
|
||||
click_hit(a, |h| matches!(h, Hit::DesignAction("property")));
|
||||
choice(a, 0, name);
|
||||
submit(a);
|
||||
plain(a, K::F(2));
|
||||
key(a, K::Char('a'), M::CONTROL);
|
||||
text(a, value);
|
||||
plain(a, K::Enter);
|
||||
assert!(a.dialog.is_none(), "{:?}", a.dialog);
|
||||
assert!(!a.value_focus);
|
||||
}
|
||||
fn tick(a: &mut App, now: u64) {
|
||||
for _ in 0..40 {
|
||||
a.tick_execution(now);
|
||||
if !matches!(a.execution, Execution::Running) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn open_code(a: &mut App, name: &str) {
|
||||
menu(a, Command::Code);
|
||||
if a.dialog.is_none() {
|
||||
menu(a, Command::Code);
|
||||
}
|
||||
choice(a, 0, name);
|
||||
submit(a);
|
||||
assert_eq!(a.mode, Mode::Environment);
|
||||
}
|
||||
fn create(a: &mut App, t: &Temp) -> PathBuf {
|
||||
assert_eq!(a.project.members().len(), 1);
|
||||
assert!(code(a).is_empty());
|
||||
assert!(draw(a).contains("Untitled"));
|
||||
menu(a, Command::NewProject);
|
||||
if a.dialog.is_some() {
|
||||
choice(a, 0, "Verwerfen");
|
||||
submit(a);
|
||||
}
|
||||
assert!(code(a).is_empty());
|
||||
menu(a, Command::SyntaxChecking);
|
||||
let include=format!("total=1\nCALL Bump(total)\nForm1.Hide\nCLS\nLINE INPUT input$\nPRINT total; input$; COMMAND$\nOPEN \"{}\" FOR OUTPUT AS #1\nPRINT #1, total\nCLOSE #1\nIF COMMAND$=\"error\" THEN ERROR 5\nEND\n",t.0.join("result.txt").display());
|
||||
replace_code(a, &include);
|
||||
menu(a, Command::SaveText);
|
||||
field(a, 0, &t.0.join("work.bi").display().to_string());
|
||||
submit(a);
|
||||
replace_code(a, "' Main module\n");
|
||||
save_as(a, &t.0.join("main.bas"));
|
||||
menu(a, Command::NewModule);
|
||||
field(a, 0, "Helper");
|
||||
submit(a);
|
||||
replace_code(a, "SUB Bump(n AS INTEGER)\nn=n+1\nEND SUB\n");
|
||||
save_as(a, &t.0.join("helper.bas"));
|
||||
menu(a, Command::NewForm);
|
||||
assert_eq!(a.mode, Mode::Designer);
|
||||
property(a, "CAPTION", "Abnahme");
|
||||
let designer_focus = a.active;
|
||||
plain(a, K::F(1));
|
||||
assert_eq!(a.help.page().path, "docs/forms-referenz.md");
|
||||
plain(a, K::Tab);
|
||||
plain(a, K::Enter);
|
||||
key(a, K::F(1), M::ALT);
|
||||
plain(a, K::Esc);
|
||||
assert_eq!(a.active, designer_focus);
|
||||
plain(a, K::F(12));
|
||||
plain(a, K::Enter);
|
||||
choice(a, 0, "Load");
|
||||
submit(a);
|
||||
let handler = code(a);
|
||||
assert!(handler.to_uppercase().contains("FORM_LOAD"), "{handler}");
|
||||
cursor_line(a, 1);
|
||||
key(a, K::End, M::CONTROL); // Replace body through the editor, retaining the generated event signature.
|
||||
replace_code(
|
||||
a,
|
||||
&format!(
|
||||
"DECLARE SUB Bump(n AS INTEGER)\nDIM SHARED total AS INTEGER\n{}",
|
||||
handler.replace("END SUB", "SHARED total\n'$INCLUDE: 'work.bi'\nEND SUB")
|
||||
),
|
||||
);
|
||||
key(a, K::F(12), M::SHIFT);
|
||||
assert_eq!(a.mode, Mode::Designer);
|
||||
// Toolbox Enter is the documented keyboard equivalent of double-click placement.
|
||||
menu(a, Command::Toolbox);
|
||||
for _ in 0..2 {
|
||||
plain(a, K::Down);
|
||||
}
|
||||
plain(a, K::Enter);
|
||||
assert!(a.designer.selections.values().any(|s| !s.is_empty()));
|
||||
property(a, "INDEX", "0");
|
||||
property(a, "CAPTION", "Start");
|
||||
menu(a, Command::Toolbox);
|
||||
plain(a, K::Down);
|
||||
plain(a, K::Down);
|
||||
plain(a, K::Enter);
|
||||
property(a, "INDEX", "1");
|
||||
property(a, "CTLNAME", "CommandButton1");
|
||||
menu(a, Command::MenuDesign);
|
||||
plain(a, K::Insert);
|
||||
plain(a, K::Enter);
|
||||
field(a, 1, "&Datei");
|
||||
submit(a);
|
||||
// Save all members and the chosen startup through actual dialog fields.
|
||||
open_code(a, "main.bas");
|
||||
menu(a, Command::Startup);
|
||||
choice(a, 0, "Form1.frm");
|
||||
submit(a);
|
||||
let path = t.0.join("project.mak");
|
||||
menu(a, Command::SaveProject);
|
||||
field(a, 0, &path.display().to_string());
|
||||
submit(a);
|
||||
assert_eq!(a.project.members().len(), 3);
|
||||
assert!(!a.project.is_dirty());
|
||||
let form = fs::read_to_string(t.0.join("Form1.frm")).unwrap();
|
||||
let parsed = tb_ui::frm::read_text("Form1.frm", &form).unwrap();
|
||||
let array: Vec<_> = parsed
|
||||
.root
|
||||
.children
|
||||
.iter()
|
||||
.filter(|n| n.class == tb_frontend::forms::ObjectClass::CommandButton)
|
||||
.map(tb_ide::designer::node_key)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
array,
|
||||
vec![
|
||||
("COMMANDBUTTON1".into(), Some(0)),
|
||||
("COMMANDBUTTON1".into(), Some(1))
|
||||
]
|
||||
);
|
||||
assert!(form.to_uppercase().contains("BEGIN MENU"));
|
||||
assert!(form.to_uppercase().contains("FORM_LOAD"));
|
||||
path
|
||||
}
|
||||
fn exports(a: &mut App, t: &Temp) {
|
||||
let stamp = ProjectStamp::capture(&a.project);
|
||||
for c in [Command::MakeExe, Command::MakeLibrary] {
|
||||
menu(a, c);
|
||||
assert!(draw(a).contains("Erzeugen: Phase 6"));
|
||||
choice(a, 1, "linux");
|
||||
choice(a, 2, "x86_64");
|
||||
field(a, 3, "");
|
||||
plain(a, K::Enter);
|
||||
assert!(a.dialog.as_ref().unwrap().error.contains("Ausgabepfad"));
|
||||
field(a, 3, &t.0.join("bad.tbc").display().to_string());
|
||||
plain(a, K::Enter);
|
||||
assert!(a.dialog.as_ref().unwrap().error.contains("TBC"));
|
||||
let existing = t.0.join("existing");
|
||||
fs::write(&existing, "original").unwrap();
|
||||
field(a, 3, &existing.display().to_string());
|
||||
plain(a, K::Enter);
|
||||
assert!(a
|
||||
.dialog
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.error
|
||||
.contains("Überschreibentscheidung"));
|
||||
plain(a, K::Tab);
|
||||
plain(a, K::Char(' '));
|
||||
plain(a, K::Enter);
|
||||
assert_eq!(fs::read_to_string(existing).unwrap(), "original");
|
||||
let out = t.0.join("native-result");
|
||||
field(a, 3, &out.display().to_string());
|
||||
plain(a, K::Enter);
|
||||
let request = a.dialog.as_ref().unwrap().request.clone().unwrap();
|
||||
assert_eq!(request.project, stamp);
|
||||
for status in [
|
||||
ExportStatus::Running,
|
||||
ExportStatus::Failed("Kontrollierter Fehler".into()),
|
||||
ExportStatus::Cancelled,
|
||||
ExportStatus::Success,
|
||||
] {
|
||||
a.export_result(&request, status.clone()).unwrap();
|
||||
assert!(draw(a).contains(&status.text()));
|
||||
assert!(!out.exists());
|
||||
}
|
||||
plain(a, K::Esc);
|
||||
assert_eq!(ProjectStamp::capture(&a.project), stamp);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn create_save_debug_help_exports_and_reopen() {
|
||||
let t = Temp::new();
|
||||
let mut a = App::new(&t.0, t.0.join("options"), (80, 25)).unwrap();
|
||||
let path = create(&mut a, &t);
|
||||
let saved: Vec<_> = [
|
||||
"main.bas",
|
||||
"helper.bas",
|
||||
"Form1.frm",
|
||||
"work.bi",
|
||||
"project.mak",
|
||||
]
|
||||
.map(|p| fs::read(t.0.join(p)).unwrap())
|
||||
.into();
|
||||
exports(&mut a, &t);
|
||||
open_code(&mut a, "Form1.frm");
|
||||
let line = code(&a)
|
||||
.lines()
|
||||
.position(|l| l.contains("$INCLUDE"))
|
||||
.unwrap()
|
||||
+ 1;
|
||||
cursor_line(&mut a, line);
|
||||
menu(&mut a, Command::IncludedFile);
|
||||
assert!(a
|
||||
.project
|
||||
.document(a.active_document().unwrap())
|
||||
.unwrap()
|
||||
.source_path()
|
||||
.ends_with("work.bi"));
|
||||
cursor_line(&mut a, 2);
|
||||
plain(&mut a, K::F(9));
|
||||
let code_focus = a.active;
|
||||
cursor_line(&mut a, 6);
|
||||
plain(&mut a, K::F(1));
|
||||
assert!(a.help.page().title.contains("Index") || a.help.page().path.starts_with("docs/"));
|
||||
plain(&mut a, K::Tab);
|
||||
plain(&mut a, K::Enter);
|
||||
key(&mut a, K::F(1), M::ALT);
|
||||
plain(&mut a, K::Esc);
|
||||
assert_eq!(a.active, code_focus);
|
||||
menu(&mut a, Command::CommandLine);
|
||||
field(&mut a, 0, "!");
|
||||
submit(&mut a);
|
||||
key(&mut a, K::F(5), M::SHIFT);
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Paused, "{}", a.message);
|
||||
assert!(a
|
||||
.session
|
||||
.vm
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.current_file()
|
||||
.ends_with("work.bi"));
|
||||
assert_eq!(a.session.vm.as_ref().unwrap().current_line(), 2);
|
||||
plain(&mut a, K::F(10));
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Paused);
|
||||
assert_eq!(a.session.vm.as_ref().unwrap().current_line(), 3);
|
||||
menu(&mut a, Command::AddWatch);
|
||||
field(&mut a, 0, "total");
|
||||
submit(&mut a);
|
||||
assert!(matches!(a.debugger.watches[0].value, Ok(Value::Int(2))));
|
||||
menu(&mut a, Command::Immediate);
|
||||
text(&mut a, "total=9");
|
||||
plain(&mut a, K::Enter);
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Paused);
|
||||
plain(&mut a, K::F(5));
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Waiting, "{}", a.message);
|
||||
menu(&mut a, Command::Output);
|
||||
a.handle(Event::Paste("abc\n".into()));
|
||||
tick(&mut a, 100);
|
||||
assert_eq!(a.execution, Execution::Ended, "{}", a.message);
|
||||
assert_eq!(snapshot::text(a.session.screen()), "? abc\n 9 abc!\n");
|
||||
assert!(fs::read_to_string(t.0.join("result.txt"))
|
||||
.unwrap()
|
||||
.contains('9'));
|
||||
for (p, bytes) in [
|
||||
"main.bas",
|
||||
"helper.bas",
|
||||
"Form1.frm",
|
||||
"work.bi",
|
||||
"project.mak",
|
||||
]
|
||||
.iter()
|
||||
.zip(saved.iter())
|
||||
{
|
||||
assert_eq!(&fs::read(t.0.join(p)).unwrap(), bytes);
|
||||
}
|
||||
menu(&mut a, Command::NewProject);
|
||||
if a.dialog.is_some() {
|
||||
choice(&mut a, 0, "Verwerfen");
|
||||
submit(&mut a);
|
||||
}
|
||||
menu(&mut a, Command::OpenProject);
|
||||
field(&mut a, 0, &path.display().to_string());
|
||||
plain(&mut a, K::Enter);
|
||||
choice(&mut a, 0, "Verwerfen");
|
||||
submit(&mut a);
|
||||
assert_eq!(a.project.members().len(), 3);
|
||||
assert!(a
|
||||
.project
|
||||
.document(a.project.startup().unwrap())
|
||||
.unwrap()
|
||||
.source_path()
|
||||
.ends_with("Form1.frm"));
|
||||
assert!(!a.project.is_dirty());
|
||||
for id in a.project.members() {
|
||||
let doc = a.project.document(id).unwrap();
|
||||
match doc.content() {
|
||||
tb_vm::project_io::Content::Form(form) => assert_eq!(
|
||||
form.as_ref(),
|
||||
&tb_ui::frm::read_text(
|
||||
&doc.source_path().display().to_string(),
|
||||
&fs::read_to_string(doc.source_path()).unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
_ => assert_eq!(doc.code(), fs::read_to_string(doc.source_path()).unwrap()),
|
||||
}
|
||||
}
|
||||
menu(&mut a, Command::CommandLine);
|
||||
field(&mut a, 0, "!");
|
||||
submit(&mut a);
|
||||
menu(&mut a, Command::Start);
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Waiting);
|
||||
menu(&mut a, Command::Output);
|
||||
a.handle(Event::Paste("abc\n".into()));
|
||||
tick(&mut a, 100);
|
||||
assert_eq!(a.execution, Execution::Ended);
|
||||
assert_eq!(snapshot::text(a.session.screen()), "? abc\n 2 abc!\n");
|
||||
}
|
||||
|
||||
struct ScriptHost {
|
||||
now: u64,
|
||||
sent: bool,
|
||||
events: std::collections::VecDeque<Ereignis>,
|
||||
}
|
||||
impl Host for ScriptHost {
|
||||
fn present(&mut self, _: &tb_runtime::screen::TextScreen) {}
|
||||
fn jetzt_ms(&mut self) -> u64 {
|
||||
self.now
|
||||
}
|
||||
fn next_event(&mut self, _: bool) -> Option<Ereignis> {
|
||||
self.events.pop_front()
|
||||
}
|
||||
fn warten(&mut self, _: Option<u64>) -> Option<Ereignis> {
|
||||
assert!(!self.sent, "Unexpected extra wait");
|
||||
self.sent = true;
|
||||
self.now = 100;
|
||||
self.events
|
||||
.extend("abc\r".chars().map(|c| Ereignis::Taste(c.to_string(), 0)));
|
||||
self.events.pop_front()
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn saved_project_ide_source_cli_and_tbc_have_identical_sessions() {
|
||||
let t = Temp::new();
|
||||
let mut a = App::new(&t.0, t.0.join("options"), (80, 25)).unwrap();
|
||||
let path = create(&mut a, &t);
|
||||
assert_eq!(
|
||||
super::cmd_build(&[path.display().to_string()]),
|
||||
ExitCode::SUCCESS
|
||||
);
|
||||
for command in ["!", "error"] {
|
||||
menu(&mut a, Command::CommandLine);
|
||||
field(&mut a, 0, command);
|
||||
submit(&mut a);
|
||||
fs::write(t.0.join("result.txt"), "initial").unwrap();
|
||||
menu(&mut a, Command::Start);
|
||||
let ide = a.session.vm.as_ref().unwrap();
|
||||
assert_eq!(ide.rt.command, command);
|
||||
assert_eq!((ide.rt.screen.cols(), ide.rt.screen.rows()), (80, 25));
|
||||
assert!(ide.forms.has_visible_forms());
|
||||
assert!(!ide.rt.dateien.ist_offen(1));
|
||||
assert!(a.session.host.events.is_empty());
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Waiting, "{}", a.message);
|
||||
// Same input and time boundary as ScriptHost::warten, through native App input.
|
||||
a.handle(Event::Paste("abc\n".into()));
|
||||
tick(&mut a, 100);
|
||||
assert_eq!(
|
||||
a.execution,
|
||||
if command == "!" {
|
||||
Execution::Ended
|
||||
} else {
|
||||
Execution::Error
|
||||
},
|
||||
"{}",
|
||||
a.message
|
||||
);
|
||||
if command == "error" {
|
||||
assert!(a.message.contains('5'));
|
||||
}
|
||||
let output = snapshot::text(a.session.screen());
|
||||
let file = fs::read(t.0.join("result.txt")).unwrap();
|
||||
assert_eq!(output, format!("? abc\n 2 abc{command}\n"));
|
||||
assert!(String::from_utf8_lossy(&file).contains('2'));
|
||||
for target in [path.clone(), path.with_extension("tbc")] {
|
||||
fs::write(t.0.join("result.txt"), "initial").unwrap();
|
||||
let input = super::compile(Some(&target.display().to_string()))
|
||||
.unwrap()
|
||||
.1;
|
||||
let initial = new_execution(input, command, Some((80, 25)), None).unwrap();
|
||||
assert!(initial.forms.has_visible_forms());
|
||||
assert_eq!(initial.rt.command, command);
|
||||
assert!(!initial.rt.dateien.ist_offen(1));
|
||||
let mut host = ScriptHost {
|
||||
now: 0,
|
||||
sent: false,
|
||||
events: Default::default(),
|
||||
};
|
||||
let (event, vm) = super::run_chain(
|
||||
&[target.display().to_string(), command.into()],
|
||||
&mut host,
|
||||
Some((80, 25)),
|
||||
)
|
||||
.unwrap();
|
||||
if command == "!" {
|
||||
assert_eq!(event, RunEvent::Ended);
|
||||
} else {
|
||||
assert!(
|
||||
matches!(event, RunEvent::Error { code: 5, .. }),
|
||||
"{event:?}"
|
||||
);
|
||||
}
|
||||
assert_eq!(snapshot::text(&vm.rt.screen), output);
|
||||
assert_eq!(fs::read(t.0.join("result.txt")).unwrap(), file);
|
||||
assert!(!vm.rt.dateien.ist_offen(1));
|
||||
assert_eq!(host.now, 100);
|
||||
}
|
||||
menu(&mut a, Command::Restart);
|
||||
assert!(a.session.host.events.is_empty());
|
||||
assert!(!a.session.vm.as_ref().unwrap().rt.dateien.ist_offen(1));
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Waiting);
|
||||
a.handle(Event::Paste("abc\n".into()));
|
||||
tick(&mut a, 100);
|
||||
assert_eq!(snapshot::text(a.session.screen()), output);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_menu_command_has_native_keyboard_and_mouse_routing() {
|
||||
let t = Temp::new();
|
||||
for designer in [false, true] {
|
||||
let menus = tb_ide::commands::menus(designer);
|
||||
for (m, group) in menus.iter().enumerate() {
|
||||
for (i, item) in group.items.iter().enumerate() {
|
||||
let Some(command) = item.command else {
|
||||
continue;
|
||||
};
|
||||
for mouse in [false, true] {
|
||||
let mut a = App::new(&t.0, t.0.join("options"), (120, 45)).unwrap();
|
||||
if designer {
|
||||
menu(&mut a, Command::NewForm);
|
||||
}
|
||||
if mouse {
|
||||
if designer {
|
||||
plain(&mut a, K::F(11));
|
||||
plain(&mut a, K::Esc);
|
||||
}
|
||||
click_hit(&mut a, |h| matches!(h,Hit::Menu(n) if *n==m));
|
||||
click_hit(&mut a, |h| matches!(h,Hit::MenuItem(n) if *n==i));
|
||||
assert_eq!(a.last_command, Some(command), "{command:?}");
|
||||
} else {
|
||||
menu(&mut a, command);
|
||||
}
|
||||
assert!(a.basic_events.is_empty(), "{command:?}");
|
||||
assert!(!a.message.contains("folgt in Phase-5"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_keys_conflict_contexts_and_errors_keep_a_single_input_owner() {
|
||||
let t = Temp::new();
|
||||
let mut a = App::new(&t.0, t.0.join("options"), (100, 30)).unwrap();
|
||||
menu(&mut a, Command::SyntaxChecking);
|
||||
replace_code(&mut a, "PRINT (\n");
|
||||
menu(&mut a, Command::Start);
|
||||
assert_eq!(a.execution, Execution::Error);
|
||||
assert!(a.session.vm.is_none());
|
||||
let invalid = code(&a);
|
||||
assert!(invalid.contains("PRINT ("));
|
||||
replace_code(&mut a, "LINE INPUT s$\nPRINT s$\nEND\n");
|
||||
plain(&mut a, K::F(5));
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Waiting);
|
||||
plain(&mut a, K::F(4));
|
||||
assert!(a.session.fullscreen);
|
||||
plain(&mut a, K::F(4));
|
||||
assert!(!a.session.fullscreen);
|
||||
plain(&mut a, K::F(11));
|
||||
assert!(a.menu.is_some());
|
||||
plain(&mut a, K::Esc);
|
||||
plain(&mut a, K::F(2));
|
||||
assert!(matches!(
|
||||
a.dialog.as_ref().unwrap().kind,
|
||||
DialogKind::ChooseCode
|
||||
));
|
||||
plain(&mut a, K::Esc);
|
||||
plain(&mut a, K::F(12));
|
||||
assert!(a.basic_events.is_empty());
|
||||
if a.dialog.is_some() {
|
||||
plain(&mut a, K::Esc);
|
||||
}
|
||||
key(&mut a, K::Pause, M::CONTROL);
|
||||
assert_eq!(a.execution, Execution::Paused);
|
||||
assert!(a.basic_events.is_empty());
|
||||
a.handle(Event::Resize(40, 10));
|
||||
assert!(draw(&mut a).contains("80×25"));
|
||||
a.handle(Event::Resize(100, 30));
|
||||
assert!(draw(&mut a).contains("Untitled"));
|
||||
plain(&mut a, K::F(5));
|
||||
menu(&mut a, Command::Output);
|
||||
text(&mut a, "one");
|
||||
plain(&mut a, K::Enter);
|
||||
tick(&mut a, 100);
|
||||
assert_eq!(snapshot::text(a.session.screen()), "? one\none\n");
|
||||
open_code(&mut a, "Untitled.bas");
|
||||
replace_code(&mut a, "RUN \"missing\"\n");
|
||||
menu(&mut a, Command::Start);
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Error);
|
||||
assert!(a.message.contains("nicht gefunden"));
|
||||
open_code(&mut a, "Untitled.bas");
|
||||
assert!(code(&a).contains("missing"));
|
||||
replace_code(&mut a, "PRINT SHELL(\"exit 7\")\nEND\n");
|
||||
menu(&mut a, Command::Start);
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(
|
||||
a.session.host.shell_request.take().as_deref(),
|
||||
Some("exit 7")
|
||||
);
|
||||
a.session.host.shell_result = Some(Ok(7));
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(snapshot::text(a.session.screen()), " 7 \n");
|
||||
menu(&mut a, Command::NewForm);
|
||||
plain(&mut a, K::F(2));
|
||||
assert!(a.value_focus);
|
||||
plain(&mut a, K::Esc);
|
||||
plain(&mut a, K::F(10));
|
||||
assert!(!a.properties && a.menu.is_some());
|
||||
plain(&mut a, K::F(10));
|
||||
assert!(a.properties && a.menu.is_none());
|
||||
plain(&mut a, K::F(11));
|
||||
assert!(a.menu.is_some());
|
||||
plain(&mut a, K::Esc);
|
||||
plain(&mut a, K::F(12));
|
||||
assert!(matches!(
|
||||
a.dialog.as_ref().unwrap().kind,
|
||||
DialogKind::DesignObject(_)
|
||||
));
|
||||
plain(&mut a, K::Esc);
|
||||
assert!(a.basic_events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_save_and_external_edit_recover_without_losing_documents() {
|
||||
let t = Temp::new();
|
||||
let mut a = App::new(&t.0, t.0.join("options"), (100, 30)).unwrap();
|
||||
replace_code(&mut a, "PRINT 1\n");
|
||||
menu(&mut a, Command::NewModule);
|
||||
field(&mut a, 0, "Second");
|
||||
submit(&mut a);
|
||||
replace_code(&mut a, "PRINT 2\n");
|
||||
let ids = a.project.members();
|
||||
menu(&mut a, Command::SaveProject);
|
||||
field(&mut a, 0, &t.0.join("p.mak").display().to_string());
|
||||
field(&mut a, 2, &t.0.join("first.bas").display().to_string());
|
||||
field(
|
||||
&mut a,
|
||||
4,
|
||||
&t.0.join("absent/second.bas").display().to_string(),
|
||||
);
|
||||
plain(&mut a, K::Enter);
|
||||
assert!(!a.dialog.as_ref().unwrap().error.is_empty());
|
||||
assert!(!a.project.document(ids[0]).unwrap().is_dirty());
|
||||
assert!(a.project.document(ids[1]).unwrap().is_dirty());
|
||||
assert!(a
|
||||
.project
|
||||
.document(ids[1])
|
||||
.unwrap()
|
||||
.code()
|
||||
.contains("PRINT 2"));
|
||||
assert!(!t.0.join("p.mak").exists());
|
||||
field(&mut a, 4, &t.0.join("second.bas").display().to_string());
|
||||
submit(&mut a);
|
||||
open_code(&mut a, "first.bas");
|
||||
replace_code(&mut a, "PRINT 3\n");
|
||||
fs::write(t.0.join("first.bas"), "' external\n").unwrap();
|
||||
menu(&mut a, Command::SaveFile);
|
||||
plain(&mut a, K::Enter);
|
||||
assert!(a
|
||||
.dialog
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.error
|
||||
.contains("Überschreibentscheidung"));
|
||||
assert_eq!(
|
||||
fs::read_to_string(t.0.join("first.bas")).unwrap(),
|
||||
"' external\n"
|
||||
);
|
||||
assert!(code(&a).contains("PRINT 3"));
|
||||
plain(&mut a, K::Tab);
|
||||
plain(&mut a, K::Char(' '));
|
||||
submit(&mut a);
|
||||
assert!(!a.project.is_dirty());
|
||||
menu(&mut a, Command::Start);
|
||||
tick(&mut a, 0);
|
||||
assert_eq!(a.execution, Execution::Ended);
|
||||
assert_eq!(snapshot::text(a.session.screen()), " 3 \n 2 \n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_matrix_is_complete_and_points_to_runnable_tests() {
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
|
||||
let changes = root.join("openspec/changes");
|
||||
let name = "phase-5-08-integration-und-phasenabnahme";
|
||||
let active = changes.join(name);
|
||||
let change = if active.exists() {
|
||||
active
|
||||
} else {
|
||||
fs::read_dir(changes.join("archive"))
|
||||
.unwrap()
|
||||
.map(|p| p.unwrap().path())
|
||||
.find(|p| p.file_name().unwrap().to_string_lossy().ends_with(name))
|
||||
.unwrap()
|
||||
};
|
||||
let overview = fs::read_to_string(
|
||||
changes
|
||||
.join("archive/2026-09-06-phase-5-01-projekt-und-dokumentmodell/phase-5-uebersicht.md"),
|
||||
)
|
||||
.unwrap();
|
||||
let overview = overview
|
||||
.split("## Vollständige Zuordnung der Referenzbedienung")
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.split("## Explizite")
|
||||
.next()
|
||||
.unwrap();
|
||||
let matrix = fs::read_to_string(change.join("befehlsmatrix.md")).unwrap();
|
||||
let rows = |s: &str| {
|
||||
s.lines()
|
||||
.filter(|l| l.starts_with('|'))
|
||||
.skip(2)
|
||||
.map(|l| {
|
||||
l.split('|')
|
||||
.skip(1)
|
||||
.map(|c| c.trim().to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let expected = rows(overview);
|
||||
let actual = rows(&matrix);
|
||||
assert_eq!(expected.len(), 45);
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for (expected, actual) in expected.iter().zip(actual) {
|
||||
assert_eq!(&actual[..3], &expected[..3]);
|
||||
let tests: Vec<_> = actual[3].split('`').skip(1).step_by(2).collect();
|
||||
assert!(!tests.is_empty());
|
||||
for test in tests {
|
||||
let (file, function) = test.split_once("::").unwrap();
|
||||
let source = fs::read_to_string(root.join(file)).unwrap();
|
||||
assert!(
|
||||
source.contains(&format!("#[test]\nfn {function}(")),
|
||||
"{test}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,3 +336,6 @@ impl Host for PipeHost {
|
||||
self.start.elapsed().as_millis() as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod ide_acceptance;
|
||||
|
||||
@@ -720,14 +720,14 @@ impl App {
|
||||
for f in &input.forms {
|
||||
catalog.append(&f.catalog());
|
||||
}
|
||||
ensure!(
|
||||
!catalog
|
||||
.objects
|
||||
.iter()
|
||||
.any(|o| o.name.eq_ignore_ascii_case(new)),
|
||||
"Name {new} bereits vorhanden"
|
||||
);
|
||||
let form_name = self.design_form()?.root.name.clone();
|
||||
ensure!(
|
||||
catalog.objects.iter().filter(|o| o.name.eq_ignore_ascii_case(new)).all(|o| {
|
||||
node_key(&selected).1.is_some() && o.array && o.class == selected.class
|
||||
&& o.parent_form.as_deref().is_some_and(|name| name.eq_ignore_ascii_case(&form_name))
|
||||
}),
|
||||
"Name {new} bereits vorhanden; nur indizierte Controls derselben Klasse und Form können ein Array bilden"
|
||||
);
|
||||
let target = if selected.class == Class::Form {
|
||||
catalog.find(&old)
|
||||
} else {
|
||||
@@ -887,27 +887,52 @@ impl App {
|
||||
.join("; ")
|
||||
)
|
||||
})?;
|
||||
let mut candidate_catalog = forms::FormCatalog::default();
|
||||
for form in &candidate.forms {
|
||||
candidate_catalog.append(&form.catalog());
|
||||
}
|
||||
let bindings = |references: &[(tb_frontend::SourcePos, String, u16)],
|
||||
files: &[tb_frontend::source::SourceFile],
|
||||
objects: &forms::FormCatalog,
|
||||
rename_old: bool| {
|
||||
let mut result = Vec::new();
|
||||
for (p, n, o) in references {
|
||||
let mut n = n.clone();
|
||||
let object = &objects.objects[*o as usize];
|
||||
let mut name = object.name.clone();
|
||||
let mut owner = object.parent_form.clone();
|
||||
if rename_old && *o == target {
|
||||
name = new.to_uppercase();
|
||||
if n == old {
|
||||
n = new.to_uppercase();
|
||||
} else if let Some(r) = handlers.get(&n) {
|
||||
n = r.to_uppercase();
|
||||
}
|
||||
}
|
||||
result.push((files[p.source as usize].path.clone(), n, *o));
|
||||
if rename_old && selected.class == Class::Form && owner.as_deref() == Some(&old) {
|
||||
owner = Some(new.to_uppercase());
|
||||
}
|
||||
// Merging arrays changes catalog indices. Compare semantic object identities.
|
||||
result.push((
|
||||
files[p.source as usize].path.clone(),
|
||||
n,
|
||||
name,
|
||||
owner,
|
||||
object.class.name(),
|
||||
object.array,
|
||||
));
|
||||
}
|
||||
result.sort();
|
||||
result
|
||||
};
|
||||
ensure!(
|
||||
bindings(&bound.objects, &source_files, true)
|
||||
== bindings(&candidate_bound.objects, &candidate_compiled.sources, false),
|
||||
bindings(&bound.objects, &source_files, &catalog, true)
|
||||
== bindings(
|
||||
&candidate_bound.objects,
|
||||
&candidate_compiled.sources,
|
||||
&candidate_catalog,
|
||||
false
|
||||
),
|
||||
"Unklare oder verdeckte Objektverweise; nichts geändert"
|
||||
);
|
||||
self.project
|
||||
|
||||
@@ -924,3 +924,63 @@ fn events_with_the_same_name_are_local_to_their_form() {
|
||||
.code()
|
||||
.contains("SUB Form_LOAD"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexed_controls_merge_atomically_with_bindings_and_undo() {
|
||||
let t = Temp::new();
|
||||
let mut a = t.app();
|
||||
let root = root(&mut a);
|
||||
let first = a
|
||||
.design_place(C::CommandButton, 0, root, Rect::new(1, 1, 8, 2))
|
||||
.unwrap();
|
||||
property(&mut a, "INDEX", "0");
|
||||
let target = node(&mut a, first).name;
|
||||
let second = a
|
||||
.design_place(C::CommandButton, 0, root, Rect::new(1, 4, 8, 2))
|
||||
.unwrap();
|
||||
let old = node(&mut a, second).name;
|
||||
// Scalar and duplicate-index merges must leave every document untouched.
|
||||
let before = a.design_form().unwrap().clone();
|
||||
assert!(a.design_rename(&target).is_err());
|
||||
assert_eq!(*a.design_form().unwrap(), before);
|
||||
property(&mut a, "INDEX", "0");
|
||||
let before = a.design_form().unwrap().clone();
|
||||
assert!(a.design_rename(&target).is_err());
|
||||
assert_eq!(*a.design_form().unwrap(), before);
|
||||
property(&mut a, "INDEX", "1");
|
||||
let unrelated = a
|
||||
.design_place(C::TextBox, 0, root, Rect::new(20, 1, 8, 2))
|
||||
.unwrap();
|
||||
let unrelated_name = node(&mut a, unrelated).name;
|
||||
property(&mut a, "INDEX", "2");
|
||||
let before = a.design_form().unwrap().clone();
|
||||
assert!(a.design_rename(&target).is_err());
|
||||
assert_eq!(*a.design_form().unwrap(), before);
|
||||
a.design_select(second, false).unwrap();
|
||||
let doc = a.design_document().unwrap();
|
||||
let source = format!("SUB {old}_Click(Index AS INTEGER)\n{old}(1).Caption = {target}(0).Caption\n{unrelated_name}(2).Text = \"kept\"\nEND SUB\n");
|
||||
a.project.replace_text(doc, 0..0, &source).unwrap();
|
||||
let before = a.design_form().unwrap().clone();
|
||||
a.design_rename(&target).unwrap();
|
||||
assert_eq!(node(&mut a, second).name, target);
|
||||
let code = &a.design_form().unwrap().code;
|
||||
assert!(code
|
||||
.to_uppercase()
|
||||
.contains(&format!("SUB {}_CLICK", target.to_uppercase())));
|
||||
assert!(code.contains(&format!("{target}(1).Caption")));
|
||||
assert!(code.contains(&format!("{unrelated_name}(2).Text")));
|
||||
a.execute(Command::Undo);
|
||||
assert_eq!(*a.design_form().unwrap(), before);
|
||||
// Two handlers for the merged event are ambiguous and cannot be committed.
|
||||
a.project
|
||||
.replace_text(
|
||||
doc,
|
||||
0..0,
|
||||
&format!("SUB {target}_Click(Index AS INTEGER)\nEND SUB\n"),
|
||||
)
|
||||
.unwrap();
|
||||
let before = a.design_form().unwrap().clone();
|
||||
a.design_select(second, false).unwrap();
|
||||
assert!(a.design_rename(&target).is_err());
|
||||
assert_eq!(*a.design_form().unwrap(), before);
|
||||
}
|
||||
|
||||
@@ -336,7 +336,10 @@ Position und Größe auch bei langen Namen/Werten. Boolean-Werte sind
|
||||
`true`/`false` beziehungsweise `-1`/`0`. Typen, Metadatenbereiche und
|
||||
Laufzeitregeln werden geprüft; schreibgeschützte Properties bleiben erhalten.
|
||||
Spin.Style wählt 0 (vertikal, 1×2) oder 1 (horizontal, 2×1); Width/Height
|
||||
sind bei Spin schreibgeschützt. Explizites Index=0 erzeugt ein Control-Array.
|
||||
sind bei Spin schreibgeschützt. Explizites Index=0 erzeugt ein Control-Array. Weitere Controls derselben
|
||||
Klasse und Form erhalten zuerst einen anderen eindeutigen Index und dann
|
||||
denselben CtlName. Indexkollisionen oder doppelte Ereignisprozeduren werden
|
||||
ohne Teiländerung abgewiesen; Undo stellt Namen und Codebindungen wieder her.
|
||||
|
||||
Cut/Copy/Paste/Clear und Undo wirken im Designer auf Controls; die
|
||||
Textzwischenablage des Editors bleibt getrennt. Container nehmen ihre
|
||||
|
||||
@@ -12,15 +12,22 @@ und mögliche Überschreibbestätigungen stehen im Speicherdialog.
|
||||
|
||||
Tools → Command Button wählt das Werkzeug. Ziehe auf der Form eine Fläche
|
||||
auf; alternativ platziert ein Doppelklick auf das Toolbox-Werkzeug ein Control.
|
||||
Wähle das Control und mit Page Up/Down die Eigenschaft Caption. F2 aktiviert
|
||||
Wähle das Control. Falls die Menüleiste statt der Properties Bar sichtbar ist,
|
||||
wechsle mit F10 zur Properties Bar. Page Up/Down wählt die Eigenschaft Caption. F2 aktiviert
|
||||
das Wertfeld, tippe `Hallo` und bestätige mit Enter. Pfeile verschieben,
|
||||
Shift+Pfeile verändern die Größe. Die tatsächlichen Klassennamen und
|
||||
Eigenschaften stehen in der [Forms-Referenz](forms-referenz.md).
|
||||
|
||||
Für ein Control-Array setze beim ersten Control Index auf `0`. Platziere
|
||||
ein zweites Control derselben Klasse, setze dessen Index auf `1` und
|
||||
CtlName auf den Namen des ersten Controls. Beide gehören nun zum selben
|
||||
Array. Jeder Index muss eindeutig sein; Undo nimmt die Umbenennung
|
||||
einschließlich gebundener Codeverweise zurück.
|
||||
|
||||
## Ereigniscode schreiben
|
||||
|
||||
F12 öffnet die Ereignisauswahl des gewählten Controls. Wähle Click und
|
||||
bestätige. Die IDE öffnet beziehungsweise erzeugt die passende Prozedur.
|
||||
F12 öffnet die Objektauswahl. Bestätige das gewählte Control mit Enter,
|
||||
wähle anschließend Click und bestätige erneut. Die IDE öffnet beziehungsweise erzeugt die passende Prozedur.
|
||||
Schreibe zwischen SUB und END SUB:
|
||||
|
||||
```basic
|
||||
|
||||
@@ -35,7 +35,7 @@ Besondere Risiken sind unverändert ausgeführte alte Kompilate nach einer Text
|
||||
| 05 | [Formular-Designer](../2026-09-06-phase-5-05-formular-designer/proposal.md) | 01, 02, 03 | Visuelles Gestalten und konsistenter Ereigniscode |
|
||||
| 06 | [Debugger und Direktfenster](../2026-09-06-phase-5-06-debugger-und-direktfenster/proposal.md) | 03, 04 | Quellgenaues Debuggen, Watches, Immediate, History und Fehlerhalte |
|
||||
| 07 | [Hilfesystem](../2026-09-07-phase-5-07-hilfesystem/proposal.md) | 02, 03 | Offline-Markdown-Hilfe und Kontextnavigation |
|
||||
| 08 | [Integration und Phasenabnahme](../../phase-5-08-integration-und-phasenabnahme/proposal.md) | 01–07 | Durchgängiger Arbeitsablauf, Befehlsabdeckung und nachgewiesener PLAN-Status |
|
||||
| 08 | [Integration und Phasenabnahme](../2026-09-07-phase-5-08-integration-und-phasenabnahme/proposal.md) | 01–07 | Durchgängiger Arbeitsablauf, Befehlsabdeckung und nachgewiesener PLAN-Status |
|
||||
|
||||
```text
|
||||
01 --> 02 --> 03 --> 04 --> 06 --+
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Phase 5: ausführbare Befehlsmatrix
|
||||
|
||||
Die ersten drei Spalten übernehmen alle 45 Zeilen der ursprünglichen Phase-5-Übersicht.
|
||||
Zuständigkeiten 01–07 bezeichnen die archivierten Featurechanges, 08 die Integration.
|
||||
Alle hier genannten Rust-Tests laufen mit `cargo test --workspace`.
|
||||
`reference_matrix_is_complete_and_points_to_runnable_tests` prüft Vollständigkeit,
|
||||
Zuordnung und vorhandene Testfunktionen auch nach Archivierung dieses Changes.
|
||||
|
||||
Der gemeinsame App-Nachweis `every_menu_command_has_native_keyboard_and_mouse_routing`
|
||||
speist für jeden Menüeintrag in Environment und Designer sowohl Alt/Mnemonic/Pfeile/Enter
|
||||
als auch gerenderte Maus-Hitbereiche ein. Verfügbarkeitsdiagnosen sind dort erlaubt;
|
||||
den tatsächlichen Facheffekt im zulässigen Kontext belegen die folgenden Fachtests.
|
||||
`function_keys_conflict_contexts_and_errors_keep_a_single_input_owner` und der Hauptablauf
|
||||
prüfen zusätzlich die kontextabhängigen Funktionstasten ohne doppelte BASIC-Zustellung.
|
||||
Kein interner Befehlsaufruf allein wird als Nachweis des nativen Eingaberoutings gewertet.
|
||||
File-Shell und BASIC-Shell werden zusätzlich durch `python3 tests/support/ide-execution-pty.py`
|
||||
im echten lokalen PTY geprüft. Die Emulator-/OS-Matrix bleibt Phase 6.
|
||||
|
||||
| Referenzbereich | Befehle / Bedienung | Zuständig | Ausführbare Verhaltensnachweise |
|
||||
|---|---|---|---|
|
||||
| File | New Project, Open Project, Save Project | 01, 02 | `crates/tb-cli/src/ide_acceptance.rs::create_save_debug_help_exports_and_reopen` |
|
||||
| File | New Form, New Module, Add File, Remove File | 01, 02 | `crates/tb-ide/tests/app.rs::file_picker_save_conflict_and_project_replacement_use_real_events`; `crates/tb-ide/tests/documents.rs::startup_removal_requires_explicit_replacement_and_never_deletes_a_file` |
|
||||
| File | Save File, Save File As, Load Text, Save Text | 01; Textbearbeitung 03 | `crates/tb-ide/tests/app.rs::file_picker_save_conflict_and_project_replacement_use_real_events`; `crates/tb-ide/tests/documents.rs::shared_views_text_import_and_undo_are_document_transactions`; `crates/tb-cli/src/ide_acceptance.rs::create_save_debug_help_exports_and_reopen` |
|
||||
| File | Print | 04 | `crates/tb-ide/tests/execution.rs::print_uses_utf8_protected_output_and_does_not_mutate_source` |
|
||||
| File | Shell | 04 | `crates/tb-ide/tests/execution.rs::shell_request_is_resumed_once_and_error_is_basic_error` |
|
||||
| File | Exit / Alt+F4 | 02; Dokumententscheidungen 01, Sitzungsende 04 | `crates/tb-ide/tests/app.rs::exit_with_partial_save_retains_dialog_and_unsaved_documents` |
|
||||
| Edit | Undo/Ctrl+Z, Cut/Ctrl+X, Copy/Ctrl+C, Paste/Ctrl+V, Clear/Del | Text 03, Controls 05 | `crates/tb-ide/tests/editor.rs::cua_unicode_overwrite_wordstar_and_shared_undo`; `crates/tb-ide/tests/designer.rs::clipboard_children_arrays_and_delete_preserve_code` |
|
||||
| Edit | New Sub, New Function | 03 | `crates/tb-ide/tests/editor.rs::procedures_and_shared_save_hook_preserve_drafts_and_frm_code` |
|
||||
| Edit | Event Procedures / F12 | 05, Codeoperationen 03 | `crates/tb-ide/tests/designer.rs::event_array_signature_twice_and_shared_form_identity`; `crates/tb-cli/src/ide_acceptance.rs::create_save_debug_help_exports_and_reopen` |
|
||||
| View | Code / F2, Form / Shift+F12 | 02, 03, 05 | `crates/tb-cli/src/ide_acceptance.rs::function_keys_conflict_contexts_and_errors_keep_a_single_input_owner`; `crates/tb-ide/tests/designer.rs::event_array_signature_twice_and_shared_form_identity` |
|
||||
| View | Next Statement | 06 | `crates/tb-ide/tests/debugger.rs::calls_procedure_step_cursor_history_and_restart` |
|
||||
| View | Output Screen / F4 | 04 | `crates/tb-ide/tests/execution.rs::output_viewports_resize_and_end_preserve_buffer_and_basic_colors` |
|
||||
| View | Included File, Included Lines | 03 | `crates/tb-ide/tests/editor.rs::includes_and_diagnostic_selection_use_original_physical_files` |
|
||||
| View (Designer) | Menu Bar / F10, Grid Lines | 05 auf 02 | `crates/tb-ide/tests/designer.rs::property_bar_types_readonly_and_no_status_line`; `crates/tb-ide/tests/designer.rs::toolbox_double_click_tool_drag_grid_and_window_form_selection` |
|
||||
| Search | Find, Selected Text/Ctrl+Backslash, Repeat Last Find/F3, Change | 03 | `crates/tb-ide/tests/editor.rs::search_scope_direction_wrap_and_shortening_replace_all` |
|
||||
| Run | Start/Shift+F5, Restart, Continue/F5, Modify COMMAND$ | 04 | `crates/tb-ide/tests/execution.rs::restart_command_run_targets_and_missing_target_preserve_documents`; `crates/tb-cli/src/ide_acceptance.rs::saved_project_ide_source_cli_and_tbc_have_identical_sessions` |
|
||||
| Run | Unterbrechen / Ctrl+Break | 04; zusätzlicher zugänglicher IDE-Befehl | `crates/tb-ide/tests/execution.rs::f5_input_pause_copy_and_single_delivery_keep_the_same_vm` |
|
||||
| Run | Set Start-up File | Persistenz 01, Ausführungswirkung 04 | `crates/tb-ide/tests/execution.rs::startup_selection_matches_shared_cli_pipeline_and_legacy_mak` |
|
||||
| Run | Make EXE File, Make Library | Vollständige UI 02, UI-Abnahme 08; native Erzeugung und reale Backend-Anbindung Phase 6 | `crates/tb-ide/tests/app.rs::export_dialogs_validate_and_render_all_results_without_creating_files`; `crates/tb-cli/src/ide_acceptance.rs::create_save_debug_help_exports_and_reopen` |
|
||||
| Debug | Add Watch, Instant Watch/Shift+F9, Watchpoint, Delete Watch, Delete All Watch | 06 | `crates/tb-ide/tests/debugger.rs::dispatcher_marks_edits_rebind_and_readonly_watches`; `crates/tb-ide/tests/debugger.rs::immediate_wait_run_transition_watchpoint_and_trace_are_session_owned`; `crates/tb-ide/tests/debugger.rs::calls_mouse_inspection_instant_key_delete_and_error_window` |
|
||||
| Debug | Trace On, History On, Shift+F8/Shift+F10 | 06 | `crates/tb-ide/tests/debugger.rs::calls_procedure_step_cursor_history_and_restart`; `crates/tb-ide/tests/debugger.rs::immediate_wait_run_transition_watchpoint_and_trace_are_session_owned` |
|
||||
| Debug | Toggle Breakpoint/F9, Clear All Breakpoints, Break on Errors, Set Next Statement | 06 | `crates/tb-ide/tests/debugger.rs::dispatcher_marks_edits_rebind_and_readonly_watches`; `crates/tb-vm/tests/debugger.rs::immediate_handler_resume_and_break_on_errors`; `crates/tb-vm/tests/debugger.rs::set_next_rejects_loop_gosub_handler_and_pending_operations_atomically` |
|
||||
| Debug-Tasten | F7 bis Cursor, F8 Single Step, F10 Procedure Step | 06 | `crates/tb-ide/tests/debugger.rs::calls_procedure_step_cursor_history_and_restart`; `crates/tb-vm/tests/debugger.rs::gosub_step_over_breakpoint_priority_and_temporary_target_cleanup` |
|
||||
| Options | Display, Set Paths, Right Mouse, Save, Syntax Checking | 02; Wirkung in 01/03/07 | `crates/tb-ide/tests/app.rs::options_apply_persist_across_bases_and_preserve_unsaved_values_on_error`; `crates/tb-ide/tests/editor.rs::line_leave_incomplete_invalid_disabled_and_current_revisions`; `crates/tb-ide/tests/help.rs::right_click_configuration_and_external_link_follow` |
|
||||
| Window | New Window, Arrange All, offene Codefensterliste | 02 | `crates/tb-ide/tests/app.rs::windows_restore_geometry_and_resize_without_document_loss` |
|
||||
| Window | Calls, Debug, Immediate | Fenster 02, Inhalt 06 | `crates/tb-ide/tests/debugger.rs::calls_mouse_inspection_instant_key_delete_and_error_window`; `crates/tb-ide/tests/debugger.rs::immediate_wait_run_transition_watchpoint_and_trace_are_session_owned` |
|
||||
| Window | Help, Output, Project | Fenster 02, Inhalt 07/04/01 | `crates/tb-ide/tests/app.rs::status_and_project_buttons_use_the_keyboard_command_path`; `crates/tb-ide/tests/execution.rs::output_viewports_resize_and_end_preserve_buffer_and_basic_colors`; `crates/tb-ide/tests/help.rs::links_history_resize_scroll_and_focus_use_real_events` |
|
||||
| Window (Designer) | Color Palette, Menu Design Window, Toolbox, Help, Formliste | 05, Hilfe 07 | `crates/tb-ide/tests/designer.rs::toolbox_double_click_tool_drag_grid_and_window_form_selection`; `crates/tb-ide/tests/designer.rs::property_menu_buttons_palette_drag_and_modal_mouse_are_connected`; `crates/tb-ide/tests/help.rs::help_preserves_fullscreen_and_does_not_modify_designer_or_source` |
|
||||
| Help | Index, Contents, Keyboard, Topic/F1, Using Help/Shift+F1, Tutorial, About | 07 | `crates/tb-ide/tests/help.rs::twenty_back_steps_end_of_contents_and_all_entries` |
|
||||
| Menüs | Alt/F11, Mnemonic, Esc, Dialogkennzeichnung „…“, Help rechts | 02 | `crates/tb-ide/tests/app.rs::start_snapshot_and_reference_menus_have_all_commands`; `crates/tb-cli/src/ide_acceptance.rs::every_menu_command_has_native_keyboard_and_mouse_routing` |
|
||||
| Fenster | F6/Ctrl+F6, Shift+F6, Ctrl+F4, Ctrl+F7/F8/F9/F10/F5, Alt+Minus | 02 | `crates/tb-ide/tests/app.rs::windows_restore_geometry_and_resize_without_document_loss` |
|
||||
| Projektfenster | Anfangs rechts, Form/Code-Buttons, Enter | 02 auf 01 | `crates/tb-ide/tests/app.rs::status_and_project_buttons_use_the_keyboard_command_path`; `crates/tb-ide/tests/app.rs::clicking_project_members_and_dialog_choices_transfers_focus_and_applies_values` |
|
||||
| Toolbox/Tools | Check Box, Combo Box, Command Button, Dir List, Drive List, File List, Frame, HScrollBar, Label, List Box, Option Button, Picture Box, Text Box, Timer, VScrollBar; zusätzlich bereits unterstützte Spinvarianten | 05 | `crates/tb-ide/tests/designer.rs::all_metadata_tools_persist_and_preview_matches_runtime_without_events` |
|
||||
| Designgesten | Toolbox-Doppelklick, Aufziehen, Drag, Sizing Handles, Pfeile, Ctrl+Pfeile, Shift+Pfeile, Tab/Shift+Tab, Ctrl+Klick | 05 | `crates/tb-ide/tests/designer.rs::container_multiselect_keyboard_drag_resize_and_undo`; `crates/tb-ide/tests/designer.rs::all_eight_handles_resize_transactionally_and_preserve_identity_after_structure_edits`; `crates/tb-ide/tests/designer.rs::toolbox_double_click_tool_drag_grid_and_window_form_selection` |
|
||||
| Properties Bar | Property/Value-Dropdown, Koordinaten/Größe, F2 Value, F10 Barwechsel, keine Statuszeile | 05 auf 02 | `crates/tb-ide/tests/designer.rs::property_bar_types_readonly_and_no_status_line`; `crates/tb-ide/tests/designer.rs::property_menu_buttons_palette_drag_and_modal_mouse_are_connected` |
|
||||
| Menüdesigner | Caption (&/-), CtlName, Tag, Index, Checked/Enabled/Visible/Separator, Shortcut, Einfügen/Löschen/Ebene/Reihenfolge/Done | 05 | `crates/tb-ide/tests/designer.rs::menu_hierarchy_arrays_shortcuts_and_palette_use_events`; `crates/tb-ide/tests/designer.rs::menu_indent_outdent_rename_and_field_changes_have_one_undo` |
|
||||
| Farbpalette | ForeColor/BackColor, Drag auf Form | 05 | `crates/tb-ide/tests/designer.rs::property_menu_buttons_palette_drag_and_modal_mouse_are_connected` |
|
||||
| Editor | Line-Leave-Prüfung/-Normalisierung, Syntax-Checking-Toggle, DECLARE, DEFtype | 03 | `crates/tb-ide/tests/editor.rs::line_leave_incomplete_invalid_disabled_and_current_revisions`; `crates/tb-ide/tests/editor.rs::external_include_revision_and_project_context_declarations` |
|
||||
| Editor-Tasten | CUA, Einfg, Pos1/Ende, WordStar Ctrl+Q, Ctrl+Y, Bookmarks Ctrl+K,0–3 / Ctrl+Q,0–3, Shift+F2/Ctrl+F2 | 03 | `crates/tb-ide/tests/editor.rs::cua_unicode_overwrite_wordstar_and_shared_undo`; `crates/tb-ide/tests/editor.rs::crlf_navigation_word_boundaries_readonly_and_normalization_keep_views` |
|
||||
| Immediate | PRINT, Zuweisungen, Prozeduraufrufe, ERROR n im Break-Modus | 06 | `crates/tb-vm/tests/debugger.rs::watches_immediate_byref_and_errors_preserve_continuation`; `crates/tb-vm/tests/debugger.rs::immediate_wait_interrupt_nested_error_end_and_run`; `crates/tb-ide/tests/debugger.rs::immediate_wait_run_transition_watchpoint_and_trace_are_session_owned` |
|
||||
| Hilfe | F1/Rechtsklick, Shift+F1, ◄Thema►, Tab/Shift+Tab, Enter, Alt+F1 (20), Ctrl+F1, Esc | 07 | `crates/tb-ide/tests/help.rs::links_history_resize_scroll_and_focus_use_real_events`; `crates/tb-ide/tests/help.rs::right_click_configuration_and_external_link_follow`; `crates/tb-ide/tests/help.rs::twenty_back_steps_end_of_contents_and_all_entries` |
|
||||
| Darstellung | DOS-Farben, Magenta-Titel, Desktopzeichen, Tabweite, klickbare Statuskürzel, Zeile/Spalte | 02 | `crates/tb-ide/tests/app.rs::start_snapshot_and_reference_menus_have_all_commands`; `crates/tb-ide/tests/app.rs::status_and_project_buttons_use_the_keyboard_command_path` |
|
||||
| Start | Begrüßung, Version/Copyright, Untitled-Code/Projekt, keine Easy/Full-Modi | 02, About 07 | `crates/tb-ide/tests/app.rs::start_snapshot_and_reference_menus_have_all_commands`; `crates/tb-ide/tests/help.rs::twenty_back_steps_end_of_contents_and_all_entries` |
|
||||
| Terminal | Dynamische Größe, physisches Minimum 80×25 | 02; Runtime-/Output-Abgrenzung 04 | `crates/tb-ide/tests/app.rs::windows_restore_geometry_and_resize_without_document_loss`; `crates/tb-ide/tests/execution.rs::output_viewports_resize_and_end_preserve_buffer_and_basic_colors`; `crates/tb-cli/src/ide_acceptance.rs::function_keys_conflict_contexts_and_errors_keep_a_single_input_owner` |
|
||||
| Laufzeitparität | Dieselbe TBVM in IDE, CLI und TBC | 04/06; Gesamtnachweis 08 | `crates/tb-cli/src/ide_acceptance.rs::saved_project_ide_source_cli_and_tbc_have_identical_sessions` |
|
||||
@@ -24,6 +24,6 @@ Keine bestehenden Anforderungen werden ersetzt; die neue IDE-Fähigkeit ergänzt
|
||||
|
||||
tb-ide-Integrationstests, vorhandene CLI-/VM-/Forms-Tests und Benchmarks, docs/ sowie PLAN.md. Featureimplementierung bleibt jeweils beim verantwortlichen Change; dieser Change schließt Integration und Nachweise.
|
||||
|
||||
**Abhängigkeiten:** [phase-5-01-projekt-und-dokumentmodell](../archive/2026-09-06-phase-5-01-projekt-und-dokumentmodell/proposal.md), [phase-5-02-ide-rahmen](../archive/2026-09-06-phase-5-02-ide-rahmen/proposal.md), [phase-5-03-editor-und-inkrementelle-uebersetzung](../archive/2026-09-06-phase-5-03-editor-und-inkrementelle-uebersetzung/proposal.md), [phase-5-04-ausfuehrung-und-output](../archive/2026-09-06-phase-5-04-ausfuehrung-und-output/proposal.md), [phase-5-05-formular-designer](../archive/2026-09-06-phase-5-05-formular-designer/proposal.md), [phase-5-06-debugger-und-direktfenster](../archive/2026-09-06-phase-5-06-debugger-und-direktfenster/proposal.md), [phase-5-07-hilfesystem](../archive/2026-09-07-phase-5-07-hilfesystem/proposal.md).
|
||||
**Abhängigkeiten:** [phase-5-01-projekt-und-dokumentmodell](../2026-09-06-phase-5-01-projekt-und-dokumentmodell/proposal.md), [phase-5-02-ide-rahmen](../2026-09-06-phase-5-02-ide-rahmen/proposal.md), [phase-5-03-editor-und-inkrementelle-uebersetzung](../2026-09-06-phase-5-03-editor-und-inkrementelle-uebersetzung/proposal.md), [phase-5-04-ausfuehrung-und-output](../2026-09-06-phase-5-04-ausfuehrung-und-output/proposal.md), [phase-5-05-formular-designer](../2026-09-06-phase-5-05-formular-designer/proposal.md), [phase-5-06-debugger-und-direktfenster](../2026-09-06-phase-5-06-debugger-und-direktfenster/proposal.md), [phase-5-07-hilfesystem](../2026-09-07-phase-5-07-hilfesystem/proposal.md).
|
||||
|
||||
**Gesamtplanung:** [Phase-5-Übersicht](../archive/2026-09-06-phase-5-01-projekt-und-dokumentmodell/phase-5-uebersicht.md). Die Nummern geben eine gültige Umsetzungsreihenfolge an; OpenSpec erzwingt Change-Abhängigkeiten nicht automatisch.
|
||||
**Gesamtplanung:** [Phase-5-Übersicht](../2026-09-06-phase-5-01-projekt-und-dokumentmodell/phase-5-uebersicht.md). Die Nummern geben eine gültige Umsetzungsreihenfolge an; OpenSpec erzwingt Change-Abhängigkeiten nicht automatisch.
|
||||
@@ -1,21 +1,21 @@
|
||||
## 1. Vollständiger Arbeitsablauf
|
||||
|
||||
- [ ] 1.1 Den headless App-Testpfad aus 02 für Datei-/Dialogaktionen und deterministische VM-Ereignisse verwenden; ein Einstiegstest startet die echte App und erstellt ein leeres Projekt ohne vorbereitete Dokumentobjekte.
|
||||
- [ ] 1.2 Den Hauptablauf mit zwei BAS-Modulen, FRM, Control-Array, Menü, Include, F12-Ereigniscode und Startdatei über UI-Ereignisse aufbauen; Save/Close/Reopen muss den gleichen Projekt-/Formularstand ergeben.
|
||||
- [ ] 1.3 Den Ablauf um Start, Include-Breakpoint, Prozedurschritt, Watch, Immediate-Wertänderung und Continue ergänzen; Sollausgabe und Variablenwerte belegen die erwartete Wirkung.
|
||||
- [ ] 1.4 F1 aus Editor und Designer sowie Tab/Enter/Alt+F1 in denselben Ablauf aufnehmen; Hilfeziel und wiederhergestellter Fokus müssen stimmen.
|
||||
- [x] 1.1 Den headless App-Testpfad aus 02 für Datei-/Dialogaktionen und deterministische VM-Ereignisse verwenden; ein Einstiegstest startet die echte App und erstellt ein leeres Projekt ohne vorbereitete Dokumentobjekte.
|
||||
- [x] 1.2 Den Hauptablauf mit zwei BAS-Modulen, FRM, Control-Array, Menü, Include, F12-Ereigniscode und Startdatei über UI-Ereignisse aufbauen; Save/Close/Reopen muss den gleichen Projekt-/Formularstand ergeben.
|
||||
- [x] 1.3 Den Ablauf um Start, Include-Breakpoint, Prozedurschritt, Watch, Immediate-Wertänderung und Continue ergänzen; Sollausgabe und Variablenwerte belegen die erwartete Wirkung.
|
||||
- [x] 1.4 F1 aus Editor und Designer sowie Tab/Enter/Alt+F1 in denselben Ablauf aufnehmen; Hilfeziel und wiederhergestellter Fokus müssen stimmen.
|
||||
|
||||
## 2. Grenzfälle und vollständige Bedienung
|
||||
|
||||
- [ ] 2.1 Die gesamte Befehlsmatrix der Phase-5-Übersicht mit konkreten Tests verknüpfen und auf fehlende Einträge prüfen; alle Phase-5-Aktionen müssen im zulässigen Kontext mehr als einen Platzhaltereffekt besitzen.
|
||||
- [ ] 2.2 Mehrfachbelegungen von F2/F4/F10/F11/F12 sowie Tastatur-/Menü-/Mausäquivalenz über native Eingabeevents prüfen; kein Event darf zugleich als IDE-Aktion und BASIC-Eingabe wirksam werden.
|
||||
- [ ] 2.3 Kurze Fehlerabläufe für Teilspeichern, externe Änderung, Compilefehler, Pause beim Eingabewarten, RUN-Zielfehler, Shell-Rückkehr und Shrink/Resize hinzufügen; Dokumenterhalt und anschließende Bedienbarkeit müssen belegt sein.
|
||||
- [x] 2.1 Die gesamte Befehlsmatrix der Phase-5-Übersicht mit konkreten Tests verknüpfen und auf fehlende Einträge prüfen; alle Phase-5-Aktionen müssen im zulässigen Kontext mehr als einen Platzhaltereffekt besitzen.
|
||||
- [x] 2.2 Mehrfachbelegungen von F2/F4/F10/F11/F12 sowie Tastatur-/Menü-/Mausäquivalenz über native Eingabeevents prüfen; kein Event darf zugleich als IDE-Aktion und BASIC-Eingabe wirksam werden.
|
||||
- [x] 2.3 Kurze Fehlerabläufe für Teilspeichern, externe Änderung, Compilefehler, Pause beim Eingabewarten, RUN-Zielfehler, Shell-Rückkehr und Shrink/Resize hinzufügen; Dokumenterhalt und anschließende Bedienbarkeit müssen belegt sein.
|
||||
|
||||
- [ ] 2.4 Beide nativen Exportdialoge vollständig über App-Ereignisse prüfen; gültige/ungültige Ziele, Überschreibentscheidung und kontrollierte Status-/Fehlerrückmeldungen funktionieren, während der Phase-5-Produktionspfad die fehlende Erzeugung erklärt und keine native Datei vortäuscht.
|
||||
- [x] 2.4 Beide nativen Exportdialoge vollständig über App-Ereignisse prüfen; gültige/ungültige Ziele, Überschreibentscheidung und kontrollierte Status-/Fehlerrückmeldungen funktionieren, während der Phase-5-Produktionspfad die fehlende Erzeugung erklärt und keine native Datei vortäuscht.
|
||||
|
||||
## 3. Parität, Leistung und Abschluss
|
||||
|
||||
- [ ] 3.1 Dasselbe gespeicherte Projekt als IDE-Lauf, CLI-Quelllauf und TBC-Lauf mit identischem Hostskript vergleichen; Ausgabe, Anfangszustand, Dateieffekte und Fehler müssen bis auf dokumentierte IDE-Halte übereinstimmen.
|
||||
- [ ] 3.2 Die vorhandenen relevanten Workspace-Regressionen und Release-Compile-Benchmarks einmal auf dem finalen Stand ausführen; Ergebnisse, Referenzhardware, Profil, Größen und Zeitgrenzen in verification.md festhalten.
|
||||
- [ ] 3.3 Die endgültige IDE-Bedienung mit Keyboard/Tutorial/Hilfe abgleichen und PLAN.md ausschließlich anhand bestandener Nachweise aktualisieren; die Zuordnung jeder Phase-5-Checkbox und die separat offene Phase-6-Plattformmatrix sind im Abschlussbericht prüfbar.
|
||||
- [ ] 3.4 Alle acht Changes zusammen gegen die Hauptspezifikation und Befehlsmatrix verifizieren; offene Befunde an ihrem zuständigen Change beheben und den betroffenen Nachweis erneut ausführen, bis keine Phase-5-Befunde übrig sind.
|
||||
- [x] 3.1 Dasselbe gespeicherte Projekt als IDE-Lauf, CLI-Quelllauf und TBC-Lauf mit identischem Hostskript vergleichen; Ausgabe, Anfangszustand, Dateieffekte und Fehler müssen bis auf dokumentierte IDE-Halte übereinstimmen.
|
||||
- [x] 3.2 Die vorhandenen relevanten Workspace-Regressionen und Release-Compile-Benchmarks einmal auf dem finalen Stand ausführen; Ergebnisse, Referenzhardware, Profil, Größen und Zeitgrenzen in verification.md festhalten.
|
||||
- [x] 3.3 Die endgültige IDE-Bedienung mit Keyboard/Tutorial/Hilfe abgleichen und PLAN.md ausschließlich anhand bestandener Nachweise aktualisieren; die Zuordnung jeder Phase-5-Checkbox und die separat offene Phase-6-Plattformmatrix sind im Abschlussbericht prüfbar.
|
||||
- [x] 3.4 Alle acht Changes zusammen gegen die Hauptspezifikation und Befehlsmatrix verifizieren; offene Befunde an ihrem zuständigen Change beheben und den betroffenen Nachweis erneut ausführen, bis keine Phase-5-Befunde übrig sind.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Verification Report: phase-5-08-integration-und-phasenabnahme
|
||||
|
||||
Stand: 2026-09-07. Geprüft wurden Proposal, Design, sämtliche Aufgaben, das
|
||||
Delta `ide-phasenabnahme` sowie die sieben synchronisierten Phase-5-Hauptspecs.
|
||||
Der zusätzliche Abgleich umfasst sämtliche 45 Zeilen der Referenzbedienung.
|
||||
|
||||
## Ergebnis
|
||||
|
||||
| Dimension | Ergebnis |
|
||||
| --- | --- |
|
||||
| Completeness | 12/12 Aufgaben; 5/5 Anforderungen des Changes; 45/45 Matrixzeilen |
|
||||
| Correctness | 5/5 neue Szenarien; zusammen mit 01–07 insgesamt 47 Anforderungen und 67 Szenarien |
|
||||
| Coherence | Bestehende App, Dokumenttransaktionen, Compiler, VM, CLI und TestBackend wiederverwendet |
|
||||
| Offene Befunde | 0 CRITICAL, 0 WARNING, 0 SUGGESTION |
|
||||
|
||||
## Anforderungen und neue Nachweise
|
||||
|
||||
Alle sechs neuen Tests liegen in `crates/tb-cli/src/ide_acceptance.rs`.
|
||||
Sie laufen über `cargo test -p tb-cli --bin tbc ide_acceptance` und im
|
||||
Workspace-Lauf. Als Testmodul des bestehenden CLI-Einstiegs können sie dessen
|
||||
wirkliche `cmd_build`, `compile` und `run_chain` verwenden. Die bereits im
|
||||
Workspace vorhandenen crossterm-/ratatui-Abhängigkeiten sind dafür zusätzlich
|
||||
als CLI-Testabhängigkeiten deklariert; es gibt keine neue Laufzeitabhängigkeit.
|
||||
|
||||
| Requirement / Scenario | Implementierung und bestandener Nachweis |
|
||||
| --- | --- |
|
||||
| Vollständiger IDE-Arbeitsablauf / Schreiben bis Wiederaufnahme | `create_save_debug_help_exports_and_reopen` startet `App::new` ohne vorbereitete Dokumente. Native Tastatur-, Paste- und Mausereignisse erzeugen main.bas, helper.bas, Form1.frm, ein zweielementiges CommandButton-Array, ein Menü, work.bi sowie project.mak. F12 erzeugt Form_Load über Objekt- und Ereignisauswahl. Nach Startup-/Save-Dialog, Projektwechsel und Öffnen stimmen geladene BAS-Texte, vollständiges FormFile und Startdatei mit den gespeicherten Dateien überein; der wieder geöffnete Stand läuft erneut. |
|
||||
| Arbeitsablauf: Debugger und Hilfe | Derselbe Test öffnet das Include über View → Included File, setzt F9 an Zeile 2, hält vor dem modulübergreifenden Bump-Aufruf und führt ihn mit F10 bis Zeile 3 aus. Add Watch zeigt total=2. Window → Immediate setzt total=9; F5 und Eingabe abc führen zu `? abc\n 9 abc!\n` und Dateiwert 9. Sämtliche gespeicherten Quellbytes bleiben unverändert; Wiederöffnen läuft wieder mit 2. F1 im Designer und auf PRINT im Editor, Tab/Enter/Alt+F1/Esc prüfen Hilfeziel und restaurierten Fokus. |
|
||||
| Nachgewiesene Befehlsabdeckung / Mehrfach belegte Funktionstasten | `every_menu_command_has_native_keyboard_and_mouse_routing` bedient jeden Menüeintrag beider Modi über Alt/Mnemonic/Pfeile/Enter sowie gerenderte Maus-Hitbereiche. `function_keys_conflict_contexts_and_errors_keep_a_single_input_owner` und der Hauptablauf prüfen F2/F4/F10/F11/F12 einschließlich Designer-Value, Menü, Code-/Ereignisauswahl, Output-Vollbild und Debug-Prozedurschritt. IDE-Kommandos gelangen nicht zusätzlich in die BASIC-Eingabe. Die Facheffekte und zulässigen Kontexte stehen in der separaten, vollständig ausgefüllten [Befehlsmatrix](befehlsmatrix.md). `reference_matrix_is_complete_and_points_to_runnable_tests` vergleicht alle drei Originalspalten der 45 Referenzzeilen und prüft jeden verlinkten Rust-Test auch nach Archivierung. |
|
||||
| CLI-Parität und robuste Übergänge / Vergleich derselben Sitzung | `saved_project_ide_source_cli_and_tbc_have_identical_sessions` erstellt dasselbe Projekt durch die UI, baut mit der tatsächlichen CLI-Funktion ein TBC und vergleicht IDE, Quell-CLI und TBC-CLI. Auf allen Wegen: 80×25, Uhrstart 0, Eingabe abc/Enter bei 100 ms, identische Anfangsdatei und COMMAND$ (`!` sowie `error`). Ausgabe, berechneter Wert 2, Dateibytes und Resultat END beziehungsweise Fehler 5 stimmen überein. Anfangs sind Form sichtbar, Datei 1 geschlossen und IDE-Eingaben leer; der zusätzliche IDE-Neustart liefert wieder dieselben Resultate. Der deterministische Host ersetzt ausschließlich das Terminal, nicht den CLI-Ausführungspfad. |
|
||||
| Robuste Übergänge | `partial_save_and_external_edit_recover_without_losing_documents` erzeugt zwei Module ausschließlich über die UI. Ein fehlendes Zielverzeichnis lässt nur das erste Dokument gespeichert, das zweite bearbeitet und das Manifest ungeschrieben; Reparatur im offenen Dialog gelingt. Eine externe Änderung bleibt bis zur expliziten Überschreibentscheidung erhalten; anschließend läuft der erhaltene Projektcode. Der Funktionstasten-/Fehlertest ergänzt Compilefehler, Pause bei INPUT, fehlendes RUN-Ziel, Shell-Callback und Shrink auf 40×10 mit Rückkehr auf 100×30. Reale Shell-Übergabe und Terminalwiederherstellung werden zusätzlich im PTY geprüft. |
|
||||
| Leistungs- und Dokumentationsabnahme / Abschlussbericht | Alle Workspace-Regressionen, Clippy, Formatprüfung, lokale PTY-Fälle und das vorhandene Release-Compile-Budget sind bestanden; Befehle, Ergebnisse, Hardware und Revision stehen unten. PLAN.md ordnet alle elf Phase-5-Punkte dieser Abnahme zu. Tutorial, Tastatur, Bedienungsreferenz und eingebettete Hilfe wurden auf die endgültigen Eingabewege abgeglichen. |
|
||||
| Export-UI / Exportvorbereitung ohne Scheinerfolg | Im Hauptablauf werden Make EXE und Make Library über Menüs geöffnet. System/Architektur, leerer Pfad, abgewiesenes TBC-Ziel, bestehende Datei mit ausdrücklicher Überschreibentscheidung und gültiges neues Ziel werden über App-Eingaben geprüft. Das vorhandene `export_dialogs_validate_and_render_all_results_without_creating_files` ergänzt weitere Pfad-/Projektkollisionen. Die Produktionsaktion erklärt Phase 6 und schreibt keine Datei. Kontrollierte Running/Failed/Cancelled/Success-Rückmeldungen prüfen die vorhandene Auftrags-/Ergebnisübergabe; sie sind ausschließlich UI-Tests. Esc erhält den Projektstempel und alle ursprünglichen Dateibytes. |
|
||||
|
||||
## Gesamtprüfung der acht Phase-5-Changes
|
||||
|
||||
Die folgenden synchronisierten Spec-Dateien wurden mit ihren Implementierungs-
|
||||
und Testpfaden abgeglichen. Die archivierten Berichte enthalten die detaillierte
|
||||
Zuordnung der Vorgängerszenarien; ihre Tests wurden auf dem jetzigen Stand erneut
|
||||
im Workspace ausgeführt. Die neue Befehlsmatrix ergänzt den gemeinsamen nativen
|
||||
Eingabepfad und verlinkt konkrete ausführbare Facheffekte jeder Referenzzeile.
|
||||
|
||||
| Change / Capability | Anforderungen / Szenarien | Zuständiger Code und erneuter Nachweis |
|
||||
| --- | --- | --- |
|
||||
| 01 / ide-projekte | 5 / 9 | `tb-ide/src/documents.rs`, `tb-vm/src/project_io.rs`; 14 Dokumenttests, CLI-Projekttests, nativer Hauptablauf und Save-Fehlerablauf |
|
||||
| 02 / ide-oberflaeche | 7 / 11 | `tb-ide/src/app.rs`, `commands.rs`, `render.rs`, `export.rs`; 12 App-Tests, alle Menürouten, F-Tastenkonflikte, Export-UI und PTY |
|
||||
| 03 / ide-editor | 7 / 10 | `tb-ide/src/editor.rs`, Frontend-Editing und Projektcompiler; 7 Editortests, VM-Inkrementaltests, Include-Debugablauf und Release-Benchmark |
|
||||
| 04 / ide-ausfuehrung | 6 / 9 | `tb-ide/src/execution.rs`, `tb-vm/src/project_io.rs`, CLI `run_chain`; 12 Ausführungstests, tatsächliche Dreiweg-Parität, Reset-/Fehlerabläufe und PTY |
|
||||
| 05 / ide-formulardesigner | 6 / 6 | `tb-ide/src/designer.rs`, gemeinsame FormFile-/Forms-Metadaten; 18 Designertests einschließlich neuer Array-Merge-Gegenproben, UI-Hauptablauf und gespeicherter CLI-Lauf |
|
||||
| 06 / ide-debugger | 7 / 11 | `tb-ide/src/debugger.rs`, VM-Debugger; 6 IDE- und 14 VM-Debuggertests, tatsächlicher F9/F10/Watch/Immediate/F5-Ablauf |
|
||||
| 07 / ide-hilfe | 4 / 6 | `tb-ide/src/help.rs`, native F1-Aufrufer, eingebettete docs; 11 Hilfetests, Offline-Kindprozess sowie Hauptablauf mit Editor-/Designerhilfe |
|
||||
| 08 / ide-phasenabnahme | 5 / 5 | Sechs neue CLI-Abnahmetests, Befehlsmatrix, PLAN-Zuordnung und dieser Bericht |
|
||||
|
||||
Keine Phase-5-Platzhalteraktion ist mehr vorhanden. Die einzige weiterhin
|
||||
vorbereitete, ausdrücklich noch nicht ausführbare Erzeugungsleistung betrifft
|
||||
das in Phase 6 geplante native Exportbackend.
|
||||
|
||||
## Behobene Befunde
|
||||
|
||||
1. **Control-Array über CtlName nicht herstellbar (Featurebereich 05).**
|
||||
Die gemeinsame Umbenennung lehnte jeden bereits vorhandenen Namen ab.
|
||||
Jetzt dürfen explizit indizierte Controls derselben Klasse und Form zu
|
||||
einer Arraygruppe zusammengeführt werden. Formvalidierung und vollständige
|
||||
Kandidatenübersetzung prüfen Index- und Ereigniskollisionen vor dem Commit.
|
||||
Die Bindungsprüfung verwendet die semantische Objektidentität statt des
|
||||
durch Zusammenführen verschobenen numerischen Katalogindex.
|
||||
`indexed_controls_merge_atomically_with_bindings_and_undo` prüft erfolgreiche
|
||||
Zusammenführung samt Ziel-/Quell-/unbeteiligten Codebindungen, vollständiges
|
||||
Undo sowie atomare Ablehnung von Skalar-, Index-, Klassen- und Handlerkonflikten.
|
||||
Alle bisherigen Umbenennungs-/Include-/Schattenbindungsregressionen bestehen.
|
||||
2. **Tutorial verkürzte die tatsächliche Bedienfolge (Featurebereich 07).**
|
||||
F12 zeigt zuerst die Objekt-, danach die Ereignisauswahl. Das Tutorial nennt
|
||||
jetzt beide Bestätigungen und den nötigen F10-Wechsel zur Properties Bar
|
||||
nach Menübedienung. Control-Array-Erzeugung und ihre Grenzen sind in Tutorial
|
||||
und Bedienungsreferenz beschrieben. Help-Katalog und native Aufrufer bestehen.
|
||||
|
||||
Die fachliche Korrektur liegt im bestehenden Designerpfad; die Integration
|
||||
führt keinen zweiten Designer und keinen alternativen Compiler-/VM-Pfad ein.
|
||||
Es wurden keine Fachanforderungen abgeschwächt und keine bisherigen Tests entfernt.
|
||||
|
||||
## PLAN-Zuordnung
|
||||
|
||||
Alle elf Checkboxen unter Phase 5 sind anhand folgender bestandener Nachweise
|
||||
abgehakt; die Bezeichnung „Eigenschaftenfenster“ wurde zu „Properties Bar“ präzisiert.
|
||||
|
||||
| Phase-5-Punkt | Nachweis |
|
||||
| --- | --- |
|
||||
| IDE-Rahmen | App-Tests, vollständige native Menürouten, Resize-/Fokusprüfung |
|
||||
| Editor | Editortests, Help-Aufrufer, Include-Navigation, Inkrementaltests und Compile-Budget |
|
||||
| Projektverwaltung | UI-Hauptablauf, drei persistierte Mitglieder, Save/Close/Reopen, CLI-/TBC-Parität |
|
||||
| Formular-Designer | Vollständige Metadaten-Toolbox, 18 Designerregressionen, Array/Menü/Form_Load im UI-Ablauf |
|
||||
| Ausführen aus der IDE | Start/Pause/Continue/Restart, Fehlerabläufe, deterministische Dreiweg-Parität |
|
||||
| Export-Bedienung | Beide vollständigen Dialoge im Hauptablauf und App-Exportregression |
|
||||
| `$INCLUDE` | Bestehende relative/Zyklus-/Overlayprüfungen; physischer Include-Breakpoint und Dreiweg-Parität |
|
||||
| `RUN` | Bestehende Ziel-/Resetprüfungen, fehlendes Ziel im nativen Fehlerablauf und sauberer Neustart |
|
||||
| Debugger | IDE-/VM-Debuggertests und tatsächliche Watch-Wertänderung mit F10/F5 im Hauptablauf |
|
||||
| Hilfe-System | Elf Hilfetests, Offline-Kindprozess, Kataloglinks und beide tatsächlichen F1-Aufrufer |
|
||||
| Vollständiges IDE-Programm | `create_save_debug_help_exports_and_reopen` und anschließender Dreiweg-Lauf derselben gespeicherten Dateien |
|
||||
|
||||
## Abschlussprüfungen und Messung
|
||||
|
||||
Geprüfter Code: Basisrevision `cc828308893b95fb614fbd2ec102a811358c7762`
|
||||
plus der uncommittete Implementierungsstand dieses Changes. Keine nachträgliche
|
||||
Produktcodeänderung nach dem finalen Workspace-/Clippy-/Benchmark-Lauf.
|
||||
Implementierungsfingerabdruck SHA-256: `b616a1b94ce60bb215b104e5c6b4fe0edde4033703dda7a2d6124fea5e9e38b2`
|
||||
(Pfade lexikografisch sortiert, jeweils UTF-8-Pfad, NUL, Dateibytes, NUL:
|
||||
`Cargo.lock`, `crates/tb-cli/Cargo.toml`, `crates/tb-cli/src/main.rs`,
|
||||
`crates/tb-cli/src/ide_acceptance.rs`, `crates/tb-ide/src/designer.rs`,
|
||||
`crates/tb-ide/tests/designer.rs`).
|
||||
|
||||
Die abschließenden PLAN-/Bericht-/Task-Änderungen wurden separat validiert;
|
||||
wegen des eingebetteten PLAN wurde die Hilfeprüfung danach erneut ausgeführt.
|
||||
|
||||
| Befehl | Ergebnis |
|
||||
| --- | --- |
|
||||
| `cargo test -p tb-cli --bin tbc ide_acceptance` | 6 bestanden |
|
||||
| `cargo test -p tb-ide --test designer` | 18 bestanden |
|
||||
| `cargo test --workspace` | 605 bestanden plus 1 erfolgreicher Offline-Kindprozess; 0 Fehler; 2 bestehende Ignore-Fälle |
|
||||
| `cargo clippy --workspace --all-targets -- -D warnings` | bestanden |
|
||||
| `cargo fmt --all -- --check` | bestanden |
|
||||
| `cargo build -p tb-ide` | bestanden |
|
||||
| `python3 tests/support/ide-execution-pty.py` | 3 bestanden: Shell Exit 7, Ctrl+C im Shell-Kind, File → Shell; Rückkehr und Terminalrestaurierung geprüft |
|
||||
| `cargo bench -p tb-vm --bench compile` | alle vier bestehenden Zeitbudgets eingehalten, einmaliger Abschlusslauf |
|
||||
| `cargo test -p tb-ide --test help` | 11 bestanden plus Offline-Kindprozess, nach PLAN-Abschluss |
|
||||
| `openspec validate --all --strict --no-interactive` | 23/23 gültig; bestehende INFO-Längenhinweise sind keine Fehler |
|
||||
| `git diff --check` | bestanden |
|
||||
|
||||
Referenzhardware: Apple M5 Max, aarch64, 18 CPU-Kerne, 128 GiB RAM;
|
||||
macOS 26.6.2 (25G83). rustc 1.97.1 (8bab26f4f, 2026-07-14),
|
||||
cargo 1.97.1 (c980f4866, 2026-06-30). Bestehender eigener Benchmark-Harness,
|
||||
Release mit opt-level=3, LTO=true, codegen-units=1. Kein neues Benchmarkverfahren.
|
||||
|
||||
| Messung | Größe / Verfahren | Ergebnis | Bestehendes Budget |
|
||||
| --- | --- | --- | --- |
|
||||
| Vollcompile Einzelmodul | 508 Zeilen; Aufwärmen, bester von 10 Läufen | 0,74 ms | <50 ms |
|
||||
| Vollcompile Projekt | 20 Module, 49.760 Zeilen, 335.421 Instruktionen; ein Lauf | 98 ms | <1.000 ms |
|
||||
| Cacheänderung Einzelmodul | Private Rumpfänderung; Median aus 7; 1 neu / 0 wiederverwendet | 0,77 ms | <50 ms |
|
||||
| Cacheänderung Projekt | Private Rumpfänderung; Median aus 7; 1 neu / 19 wiederverwendet; Invalidierung + Link | 21,94 ms | <1.000 ms |
|
||||
|
||||
Die beiden bestehenden Ignore-Fälle sind der explizite Golden-Output-Generator
|
||||
und die externe VBDOS-Referenzsuite (`TB_VBDOS_REPO`); kein Phase-5-Test ist
|
||||
ignoriert. Die normale vorhandene Workspace-Regression wurde vollständig ausgeführt.
|
||||
Die lokale Abnahme verwendet native crossterm-Ereignisse, TestBackend und einen
|
||||
realen Unix-PTY. Sie ersetzt nicht die separat offene Phase-6-Matrix für Windows
|
||||
Terminal, mehrere Linux-Emulatoren und macOS samt systematischen Maus-/Sondertasten.
|
||||
Auch native Executable-/Library-Erzeugung und deren Verbraucherprüfung bleiben
|
||||
unverändert offene Phase-6-Aufgaben. Kontrollierte Exportcallbacks behaupten
|
||||
keinen tatsächlichen Export.
|
||||
|
||||
Keine Prüfdimension dieses Changes wurde ausgelassen. Die erneute Verifizierung
|
||||
meldet keine offenen Befunde. Der Change ist zur separaten Synchronisierung und
|
||||
Archivierung bereit.
|
||||
|
||||
## Synchronisierung und Archivierung
|
||||
|
||||
Am 2026-09-07 wurden alle fünf Anforderungen einschließlich Purpose unverändert
|
||||
in `openspec/specs/ide-phasenabnahme/spec.md` synchronisiert. Der vollständige
|
||||
Vergleich mit dem Delta und die strikte Validierung aller 23 Hauptspecs bestanden
|
||||
vor dem Archivieren. Der Change liegt jetzt unter
|
||||
`openspec/changes/archive/2026-09-07-phase-5-08-integration-und-phasenabnahme/`.
|
||||
Abhängigkeits- und Übersichtslinks wurden an den Archivpfad angepasst.
|
||||
Der ausführbare Matrixnachweis besteht auch am Archivort; OpenSpec führt
|
||||
keine aktiven Changes mehr. Der oben dokumentierte Produktcode-Fingerabdruck
|
||||
ist unverändert, daher wurden die bereits bestandenen Produkt- und
|
||||
Leistungsprüfungen nicht erneut ausgeführt.
|
||||
42
openspec/specs/ide-phasenabnahme/spec.md
Normal file
42
openspec/specs/ide-phasenabnahme/spec.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# ide-phasenabnahme Specification
|
||||
|
||||
## Purpose
|
||||
|
||||
Sichert die vollständige Nutzbarkeit der Phase-5-IDE über zusammenhängende Arbeitsabläufe, gemeinsame Laufzeitsemantik und reproduzierbare Abnahmekriterien.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Vollständiger IDE-Arbeitsablauf
|
||||
Ein Benutzer SHALL ausschließlich über die IDE ein Projekt mit mindestens zwei BAS-Modulen, einem Formular, einem Control-Array, einem Menü und einem Include erstellen, bearbeiten, speichern, schließen und erneut öffnen können. Danach SHALL dasselbe Projekt mit Ereigniscode ausführbar und mit Breakpoints, Prozedurschritt, Watch und Direktfenster debugbar sein. Kontext-Hilfe SHALL aus Editor und Designer erreichbar sein.
|
||||
|
||||
#### Scenario: Schreiben bis Wiederaufnahme
|
||||
- **WHEN** dieser Arbeitsablauf durch Menüs, Tastatur und Maus durchgeführt wird und während eines Halts ein Wert im Direktfenster geändert wird
|
||||
- **THEN** läuft das Programm mit der Änderung weiter, die gespeicherten Quellen bleiben konsistent und nach erneutem Öffnen sind Projekt, Formularstruktur, Ereigniscode und Startdatei erhalten
|
||||
|
||||
### Requirement: Nachgewiesene Befehlsabdeckung
|
||||
Alle Phase-5-Funktionen und die ihnen zugeordneten Menü-, Tastatur- und Mauswege aus docs/ide-referenz.md SHALL einen zuständigen Change und einen ausführbaren Nachweis besitzen. Ein Test nur des internen Befehls SHALL keinen Nachweis für die tatsächliche Tasten-/Mausverteilung ersetzen. Noch nicht implementierte Phase-5-Aktionen MUST NOT als vollständige Phasenabnahme gelten.
|
||||
|
||||
#### Scenario: Mehrfach belegte Funktionstasten
|
||||
- **WHEN** die Abnahme F2, F4, F10, F11 und F12 in ihren verschiedenen Kontexten einspeist
|
||||
- **THEN** bestätigt sie die jeweils erwartete Aktion und den Ausschluss einer zweiten Zustellung an das BASIC-Programm
|
||||
|
||||
### Requirement: CLI-Parität und robuste Übergänge
|
||||
Gespeicherte Projekte SHALL über IDE, CLI-Quellstart und CLI-Kompilatstart bei gleichen Eingaben, Zeiten, Größen und COMMAND$ dieselben BASIC-Ausgaben, Datenwerte und Fehler liefern, abgesehen von den ausdrücklich spezifizierten IDE-Halten. Speichern, Projektwechsel, Reset, Shell-Rückkehr und Resize SHALL auch nach Fehlern die Dokumente und Terminalbedienbarkeit erhalten.
|
||||
|
||||
#### Scenario: Vergleich derselben Sitzung
|
||||
- **WHEN** ein Forms-/Include-Projekt mit identischer Ereignisfolge über die drei Ausführungswege gestartet wird
|
||||
- **THEN** stimmen Ausgabe, Dateieffekte und Laufzeitresultat überein und ein anschließender IDE-Neustart enthält keinen Zustand des vorherigen Laufs
|
||||
|
||||
### Requirement: Leistungs- und Dokumentationsabnahme
|
||||
Phase 5 SHALL erst als umgesetzt dokumentiert werden, wenn die Change-Szenarien, die relevanten bisherigen Regressionen und das bestehende Release-Compile-Budget nachgewiesen sind. PLAN.md und die IDE-Hilfe SHALL den verifizierten Stand wiedergeben. Prüfberichte SHALL konkrete Befehle, Ergebnisse, Revision und verbleibende Grenzen nennen; vollständige OpenSpec-Artefakte allein SHALL nicht als Implementierungsnachweis gelten.
|
||||
|
||||
#### Scenario: Abschlussbericht
|
||||
- **WHEN** Phase 5 abgenommen wird
|
||||
- **THEN** verweisen alle Phase-5-Checkboxen auf bestandene Nachweise und der Bericht unterscheidet die lokale headless Abnahme von der weiterhin separat geplanten Plattformmatrix aus Phase 6
|
||||
|
||||
### Requirement: Export-UI als eigene Phasenleistung
|
||||
Die Phase-5-Abnahme SHALL die vollständige Bedienung von Make EXE File und Make Library einschließlich Zielsystem/-architektur, Ausgabepfad, Validierung, Überschreibentscheidung sowie Status-/Fehler-/Abbruchdarstellung prüfen. Dabei SHALL die noch nicht verfügbare native Erzeugung aus Phase 6 ausdrücklich erkennbar sein. Die Abnahme SHALL Oberflächennachweise von der späteren tatsächlichen Erzeugung eigenständiger nativer Executables und nativer Systembibliotheken unterscheiden.
|
||||
|
||||
#### Scenario: Exportvorbereitung ohne Scheinerfolg
|
||||
- **WHEN** beide Exportdialoge im durchgängigen IDE-Ablauf mit gültigen und ungültigen Angaben bedient werden
|
||||
- **THEN** funktionieren Eingabe, Validierung und Rückkehr zum Projekt, die Erzeugungsaktion nennt die Phase-6-Abhängigkeit und weder eine falsche Erfolgsmeldung noch eine als natives Artefakt ausgegebene TBC-Datei entsteht
|
||||
Reference in New Issue
Block a user