Forms-Zustand und Darstellung korrigieren und Change archivieren
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
## Why
|
||||
|
||||
Listen und Timer erfüllen einfache gültige Zustandsübergänge nicht: ListIndex=-1 liefert Fehler 5; ein spät aktivierter Timer holt Zeit seit Programmstart nach. F12/F13 betreffen das Zustandsmodell unabhängig von dessen späterem Ereignisdispatch. Siehe [Review vom 05.09.2026](../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
Listen und Timer erfüllen einfache gültige Zustandsübergänge nicht: ListIndex=-1 liefert Fehler 5; ein spät aktivierter Timer holt Zeit seit Programmstart nach. F12/F13 betreffen das Zustandsmodell unabhängig von dessen späterem Ereignisdispatch. Siehe [Review vom 05.09.2026](../../../../docs/reviews/2026-09-05-openspec-code-review.md).
|
||||
|
||||
## What Changes
|
||||
|
||||
@@ -21,4 +21,10 @@ Keine neuen Capability-Pfade.
|
||||
|
||||
crates/tb-ui/src/forms.rs, Forms-Unit-/VM-Tests, Formular-Korpus und docs/forms-referenz.md. Baut auf der geordneten Ereigniszustellung auf.
|
||||
|
||||
Die Behebung der Darstellungsbefunde betrifft zusätzlich
|
||||
crates/tb-runtime/src/screen.rs: Forms-Zeilen verwenden den vorhandenen
|
||||
Zellenschreibpfad ohne Umbruch oder Scrollen am Bildschirmrand. `tb-ui`
|
||||
nutzt die bereits vorhandene Workspace-Abhängigkeit `unicode-width` für
|
||||
dieselbe Breitenberechnung wie die Runtime.
|
||||
|
||||
Nach ereigniszustellung-und-hostgrenzen. Ändert nur die Requirements zu Listen und Timer, nicht Fokus/Menü desselben Capability-Pfads.
|
||||
@@ -0,0 +1,154 @@
|
||||
//! Unabhängige Review-Proben: zwei behobene Vertragslücken, Timer und Mindestmaße.
|
||||
//! Ausführung über den temporären Cargo-Treiber aus review.md.
|
||||
use tb_frontend::forms::{self, FormCatalog, ObjectClass};
|
||||
use tb_runtime::{
|
||||
host::{CaptureHost, Host},
|
||||
screen::TextScreen,
|
||||
};
|
||||
use tb_ui::forms::{FormsModel, PropertyValue as V};
|
||||
use tb_vm::interp::{RunEvent, Vm};
|
||||
|
||||
fn model(class: ObjectClass) -> FormsModel {
|
||||
let mut c = FormCatalog::default();
|
||||
c.add("Form1", ObjectClass::Form, None, false);
|
||||
c.add("List1", class, Some("Form1"), false);
|
||||
let mut m = FormsModel::new(c.objects, 80, 25);
|
||||
m.set_initial(0, prop(ObjectClass::Form, "WIDTH"), V::Integer(40))
|
||||
.unwrap();
|
||||
m.set_initial(0, prop(ObjectClass::Form, "HEIGHT"), V::Integer(12))
|
||||
.unwrap();
|
||||
m
|
||||
}
|
||||
fn prop(class: ObjectClass, name: &str) -> u16 {
|
||||
forms::property(class, name).unwrap().0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sorted_nach_befuellen_ordnet_liste_und_erhaelt_auswahl() {
|
||||
let mut results = vec![];
|
||||
for class in [ObjectClass::ListBox, ObjectClass::ComboBox] {
|
||||
let mut m = model(class);
|
||||
m.add_item((1, None), "b".into(), None).unwrap();
|
||||
m.add_item((1, None), "a".into(), None).unwrap();
|
||||
m.set(1, prop(class, "LISTINDEX"), V::Integer(0)).unwrap();
|
||||
m.set(1, prop(class, "SORTED"), V::Boolean(true)).unwrap();
|
||||
m.add_item((1, None), "c".into(), None).unwrap();
|
||||
let list: Vec<_> = (0..3)
|
||||
.map(|i| m.get_indexed(1, prop(class, "LIST"), i).unwrap())
|
||||
.collect();
|
||||
println!(
|
||||
"{class:?}: {list:?}; Auswahl={:?}",
|
||||
m.get(1, prop(class, "LISTINDEX")).unwrap()
|
||||
);
|
||||
results.push((list, m.get(1, prop(class, "LISTINDEX")).unwrap()));
|
||||
}
|
||||
for (list, index) in results {
|
||||
assert_eq!(
|
||||
list,
|
||||
vec![
|
||||
V::String("a".into()),
|
||||
V::String("b".into()),
|
||||
V::String("c".into())
|
||||
]
|
||||
);
|
||||
assert_eq!(index, V::Integer(1));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combo_simple_zeigt_seine_listeneintraege() {
|
||||
let class = ObjectClass::ComboBox;
|
||||
let mut m = model(class);
|
||||
for (name, value) in [
|
||||
("STYLE", V::Integer(1)),
|
||||
("WIDTH", V::Integer(15)),
|
||||
("HEIGHT", V::Integer(5)),
|
||||
("LEFT", V::Integer(2)),
|
||||
("TOP", V::Integer(2)),
|
||||
("TEXT", V::String("frei".into())),
|
||||
] {
|
||||
m.set_initial(1, prop(class, name), value).unwrap();
|
||||
}
|
||||
m.add_item((1, None), "apfel".into(), None).unwrap();
|
||||
m.add_item((1, None), "birne".into(), None).unwrap();
|
||||
m.show(0, false).unwrap();
|
||||
let mut screen = TextScreen::new();
|
||||
m.render(&mut screen);
|
||||
let text = tb_runtime::snapshot::text(&screen);
|
||||
println!("{text}");
|
||||
assert!(
|
||||
text.contains("frei"),
|
||||
"Kontrolle: das Control muss sichtbar sein"
|
||||
);
|
||||
assert!(text.contains("apfel") && text.contains("birne"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timer_array_wird_beim_laden_an_aktueller_hostzeit_aktiviert() {
|
||||
let mut c = FormCatalog::default();
|
||||
c.add("Form1", ObjectClass::Form, None, false);
|
||||
c.add("Timer1", ObjectClass::Timer, Some("Form1"), true);
|
||||
let src="DIM SHARED s$\nForm1.Show\nTimer1(0).Interval=100\nSTOP\nLOAD Timer1(1)\nTimer1(0).Interval=0\nSTOP\nDOEVENTS\nEND\nSUB Timer1_Timer(Index AS INTEGER)\nSHARED s$\ns$=s$+LTRIM$(STR$(Index))\nEND SUB";
|
||||
let mut vm = Vm::new(tb_vm::compile_source_with_forms("FORM1", src, &c).unwrap());
|
||||
let mut h = CaptureHost::default();
|
||||
assert!(matches!(vm.run(&mut h), RunEvent::Stopped { .. }));
|
||||
h.uhr_setzen(1000);
|
||||
assert!(matches!(vm.run(&mut h), RunEvent::Stopped { .. }));
|
||||
assert_eq!(vm.forms.next_deadline(), Some(1100));
|
||||
h.uhr_setzen(1100);
|
||||
assert_eq!(vm.run(&mut h), RunEvent::Ended);
|
||||
assert_eq!(h.jetzt_ms(), 1100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listen_bleiben_auch_bei_minimalmassen_im_controlrechteck() {
|
||||
let mut failures = vec![];
|
||||
for (class, width, height) in [
|
||||
(ObjectClass::ComboBox, 15, 5),
|
||||
(ObjectClass::ListBox, 6, 4),
|
||||
(ObjectClass::ComboBox, 1, 5),
|
||||
(ObjectClass::ListBox, 1, 4),
|
||||
(ObjectClass::ListBox, 6, 1),
|
||||
] {
|
||||
let mut m = model(class);
|
||||
for (name, value) in [
|
||||
("WIDTH", width),
|
||||
("HEIGHT", height),
|
||||
("LEFT", 2),
|
||||
("TOP", 2),
|
||||
] {
|
||||
m.set_initial(1, prop(class, name), V::Integer(value))
|
||||
.unwrap();
|
||||
}
|
||||
if class == ObjectClass::ComboBox {
|
||||
m.set_initial(1, prop(class, "STYLE"), V::Integer(1))
|
||||
.unwrap();
|
||||
}
|
||||
m.add_item((1, None), "apfel".into(), None).unwrap();
|
||||
m.set(1, prop(class, "VISIBLE"), V::Boolean(false)).unwrap();
|
||||
m.show(0, false).unwrap();
|
||||
let mut screen = TextScreen::new();
|
||||
m.render(&mut screen);
|
||||
let before = tb_runtime::snapshot::text(&screen);
|
||||
m.set(1, prop(class, "VISIBLE"), V::Boolean(true)).unwrap();
|
||||
m.render(&mut screen);
|
||||
let after = tb_runtime::snapshot::text(&screen);
|
||||
let mut outside = vec![];
|
||||
for (row, (a, b)) in before.lines().zip(after.lines()).enumerate() {
|
||||
for (col, (a, b)) in a.chars().zip(b.chars()).enumerate() {
|
||||
if a != b
|
||||
&& !(row >= 2
|
||||
&& row < 2 + height as usize
|
||||
&& col >= 2
|
||||
&& col < 2 + width as usize)
|
||||
{
|
||||
outside.push((row + 1, col + 1, a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
if !outside.is_empty() {
|
||||
failures.push(format!("{class:?} {width}x{height}: {outside:?}"));
|
||||
}
|
||||
}
|
||||
assert!(failures.is_empty(), "{}", failures.join("\n"));
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
# Verifikation: forms-zustand-und-bedienung
|
||||
|
||||
Abnahme vom 05.09.2026, umgesetzt auf Basis von `644a862`.
|
||||
Nach erfolgreicher Verifikation am selben Tag synchronisiert und archiviert. **V1, V2 und V3 sind behoben. Keine
|
||||
offenen Befunde im Change.** Beide Delta-Requirements, alle acht Szenarien,
|
||||
die ergänzten Abnahmetasks und D1–D3 wurden erneut gegen Code und Tests geprüft.
|
||||
|
||||
| Dimension | Ergebnis |
|
||||
| --- | --- |
|
||||
| Vollständigkeit | 7/7 Tasks vollständig umgesetzt; beide Delta-Requirements mit allen acht Szenarien abgedeckt. Task 4.2 einschließlich Mindestmaßen und verwandten Zeichenpfaden erfüllt. |
|
||||
| Korrektheit | 428 Workspace-Tests und alle vier Review-Proben bestanden. Keine kritischen Befunde, Warnungen oder offenen Verbesserungsvorschläge. |
|
||||
| Kohärenz | D1–D3 umgesetzt. Die stabile Sortierung erhält die Auswahl; Simple-ComboBox und ListBox verwenden denselben Listenrenderer. |
|
||||
|
||||
## V3 — behoben; historischer Befund vor der Korrektur
|
||||
|
||||
Die folgenden Beobachtungen gelten für den vorherigen Stand. Die Korrektur
|
||||
und ihre erweiterten Nachweise stehen im nächsten Abschnitt.
|
||||
|
||||
### V3 (ursprünglich mittel): Listenrenderer überschreitet zulässige Mindestmaße
|
||||
|
||||
**Reproduktion:** Sichtbare Simple-ComboBox mit Width = 1, Height = 5,
|
||||
Left = 2, Top = 2 in einem ausreichend großen Formular. Gegenüber dem
|
||||
Bildschirm mit ausgeblendetem Control werden auch die Zellen in Spalte 4,
|
||||
Zeilen 4–7 überschrieben; die einzige erlaubte Controlspalte ist Spalte 3.
|
||||
Eine ListBox mit Width = 1 hat denselben horizontalen Überlauf. Bei einer
|
||||
ListBox mit Width = 6, Height = 1 wird zusätzlich die vollständige Zeile
|
||||
unterhalb des Controls gezeichnet. Benachbarte Inhalte können dadurch
|
||||
überschrieben werden.
|
||||
|
||||
**Ursache:** [list_lines](../../../../crates/tb-ui/src/forms.rs#L2047) verwendet
|
||||
`box_lines`, das Breite und Höhe auf mindestens 2 erhöht. Für Width < 3
|
||||
kehrt der Listenrenderer ohne Begrenzung zurück; im ListBox-Pfad fehlt
|
||||
auch eine abschließende Höhenbegrenzung. `write_at` begrenzt ausschließlich
|
||||
auf den Bildschirm. [Width und Height](../../../../crates/tb-frontend/src/forms.rs#L202)
|
||||
lassen ausdrücklich den Wert 1 zu.
|
||||
|
||||
**Vertrag:** Task 4.2 verlangt die Prüfung von Kürzung und Größenbegrenzung
|
||||
für die Simple-ComboBox. Der neue gemeinsame Renderer übernimmt die bereits
|
||||
vorhandene Mindestmaß-Lücke auch für die nun dargestellte Simple-Liste.
|
||||
Die bestehenden Regressionen prüfen Breite 6 und kleine Combo-Höhen,
|
||||
aber weder Breite 1 noch die ListBox-Höhe 1.
|
||||
|
||||
**Behebung:** Die gemeinsame Listenausgabe auf die tatsächlich angeforderte
|
||||
Breite und Höhe begrenzen. Mindestbreite 1 für beide Klassen und Mindesthöhe 1
|
||||
für ListBox durch Bildschirmprüfungen absichern; normale Maße beibehalten.
|
||||
|
||||
**Nachweis:** `listen_bleiben_auch_bei_minimalmassen_im_controlrechteck` in
|
||||
[review-probes.rs](review-probes.rs) prüft den tatsächlichen Bildschirm vor
|
||||
und nach Einblenden des Controls. Die Kontrollfälle ComboBox 15×5 und
|
||||
ListBox 6×4 bleiben innerhalb ihrer Grenzen; die drei Mindestmaßfälle
|
||||
schlugen vor der Behebung fehl und bestehen jetzt mit unveränderten Erwartungen.
|
||||
|
||||
## Behebung und Regressionen
|
||||
|
||||
- **V1:** Gemeinsame Sortierung für Laufzeit- und initiale Property-Zugriffe.
|
||||
Sortierung ohne Beachtung der Groß-/Kleinschreibung; ursprüngliche Indizes
|
||||
erhalten die Auswahl auch zwischen gleichen Texten. Regressionen prüfen
|
||||
beide Klassen, alle ComboBox-Stile, Basis-/Arrayindex 0 und dynamische
|
||||
Arrayelemente, wiederholtes Sortieren sowie anschließendes Einfügen/Entfernen.
|
||||
Der Formular-Korpus prüft jetzt das Einschalten von Sorted nach dem Befüllen
|
||||
mit unveränderter Sollausgabe.
|
||||
- **V2:** Simple verwendet den bestehenden Listenrenderer samt Rahmen und
|
||||
Auswahlmarkierung. Der Bildschirmtest prüft tatsächliche Einträge, Auswahl,
|
||||
Entfernen und erhaltenen Eingabetext. V3 ergänzt die Mindestmaßabnahme.
|
||||
- **V3:** `list_lines` begrenzt sämtliche Rahmenzeilen auf die angeforderte
|
||||
Breite und Höhe, auch vor dem frühen Rücksprung für schmale Controls.
|
||||
Alle Aufrufer (ListBox, Simple-ComboBox, DirListBox und FileListBox)
|
||||
verwenden diese Korrektur. Die gemeinsame Textkürzung zählt dieselben
|
||||
Zellenbreiten wie TextScreen und behandelt Steuerzeichen als Leerzeichen.
|
||||
`TextScreen::print_line` verwendet den vorhandenen Zellenschreibpfad ohne
|
||||
Umbruch oder Scrollen; `write_at` nutzt ihn für Forms-Ausgaben.
|
||||
Das normale BASIC-PRINT behält Umbruch und Scrollen.
|
||||
|
||||
Die zusätzliche Regression
|
||||
`listenrenderer_haelt_zellgrenzen_auch_an_bildschirmraendern_ein` vergleicht
|
||||
alle Bildschirmzellen außerhalb des jeweiligen Controlrechtecks einschließlich
|
||||
Farben und Unicode-Fortsetzungsmarkierung. **3.360 Kombinationen bestehen:**
|
||||
ListBox, Combo-Stile 0/1/2, DirListBox, FileListBox und DriveListBox;
|
||||
Basis- und dynamische Arrayelemente; Breiten 1/2/3/6/15/254 und Höhen
|
||||
1/2/3/5/254; vier Positionen einschließlich rechtem/unterem Bildschirmrand
|
||||
und vollständig außerhalb; leere und gefüllte Listen mit langen Texten,
|
||||
breiten Unicode-Zeichen und CR/LF. Vor der Korrektur schlug dieser Test
|
||||
bereits bei einer ListBox 1×1 fehl.
|
||||
|
||||
`ui_zeile_bleibt_am_rand_ohne_umbruch_oder_scrollen` prüft zusätzlich
|
||||
vollständig passende und nicht passende breite Zeichen, Farben, CR/LF
|
||||
sowie den rechten/unteren Rand und einen eigenen VIEW-PRINT-Bereich.
|
||||
Bestehende Zeichenbild-, Auswahl-, Timer-, VM- und Korpustests bestehen
|
||||
unverändert in ihren Erwartungen.
|
||||
|
||||
## Ursprüngliche Befunde — historischer Stand vor der Behebung
|
||||
|
||||
Die folgenden Ist-Beobachtungen dokumentieren den fehlgeschlagenen Review.
|
||||
Beide Befunde sind durch die Nachweise oben geschlossen.
|
||||
|
||||
### V1: Nachträgliches Sorted lässt den Listeninhalt unsortiert
|
||||
|
||||
**Reproduktion:** In ListBox oder ComboBox zuerst `b`, dann `a` hinzufügen,
|
||||
Eintrag 0 (`b`) auswählen, anschließend `Sorted = -1` setzen und `c`
|
||||
hinzufügen. **Ist:** `List = [b, a, c]`, ListIndex = 0.
|
||||
**Soll:** sortierte Liste `[a, b, c]` mit weiterhin ausgewähltem `b`, Index 1.
|
||||
|
||||
Der [Property-Setter](../../../../crates/tb-ui/src/forms.rs#L430) speichert
|
||||
SORTED ohne Sortierung des vorhandenen Inhalts. [ADDITEM](../../../../crates/tb-ui/src/forms.rs#L1025)
|
||||
verwendet anschließend `partition_point`, das bereits sortierte Daten
|
||||
voraussetzt. Der Fehler tritt bei beiden Klassen auf.
|
||||
|
||||
**Vertrag:** Delta „Steuerelemente mit Listeninhalt“, Sortierung bei
|
||||
Sorted = −1 und Konsistenz von List/ListIndex/Text; Task 1.1.
|
||||
Die Tests setzen Sorted ausschließlich vor dem Befüllen und decken den
|
||||
zulässigen Wechsel an einer gefüllten Liste nicht ab.
|
||||
|
||||
**Behebung:** Beim Einschalten der Sortierung vorhandene Einträge sortieren
|
||||
und die Auswahl ihrem bisherigen Eintrag zuordnen; anschließend Einfügen
|
||||
und Entfernen weiterhin konsistent halten. Regression für beide Klassen,
|
||||
einschließlich der Auswahl, ergänzen.
|
||||
|
||||
### V2: ComboBox.Style 1 zeichnet keine Listeneinträge
|
||||
|
||||
**Reproduktion:** Sichtbare ComboBox mit Style = 1, Width = 15, Height = 5,
|
||||
Text = `frei`; ADDITEM `apfel` und `birne`. **Ist:** Der Bildschirm zeigt
|
||||
`frei` und einen leeren eingerahmten Listenbereich. **Soll:** Die beiden
|
||||
Einträge erscheinen in diesem ausreichend großen Bereich.
|
||||
|
||||
Der [Renderer](../../../../crates/tb-ui/src/forms.rs#L2184) hängt für Style 1
|
||||
nur `box_lines` mit leerem Inhalt an. Er liest die vorhandenen Listeneinträge
|
||||
für diesen Bereich nicht. Die Probe prüft zuerst, dass das Control über den
|
||||
Text `frei` tatsächlich sichtbar ist, und danach die beiden Listeneinträge.
|
||||
|
||||
**Vertrag:** Delta „Steuerelemente mit Listeninhalt“ verlangt die Darstellung
|
||||
aller drei ComboBox-Stile; die [Forms-Referenz](../../../../docs/forms-referenz.md#L180)
|
||||
zeigt für Simple einen sichtbaren Listeneintrag. Task 1.1 verlangt die Prüfung
|
||||
aller Stile. Der bestehende Stiltest prüft nur Zeilenzahl und Präfix;
|
||||
die neuen Auswahltests prüfen nur die oberste Zeile.
|
||||
|
||||
**Behebung:** Den vorhandenen Listeninhalt samt Auswahl im Simple-Bereich
|
||||
darstellen und dessen Inhalt mit einem Bildschirmtest absichern. Die bereits
|
||||
festgelegten Zeichenformen beibehalten.
|
||||
|
||||
Beide Lücken bestanden bereits vor diesem Change. Es handelt sich um
|
||||
verbleibende Abweichungen im übernommenen Listen-Requirement, nicht um neu
|
||||
eingeführte Regressionen. Die Korrekturen wurden nach diesem Review umgesetzt.
|
||||
|
||||
## Szenarioabdeckung und positive Nachweise
|
||||
|
||||
| Delta-Szenario | Nachweis |
|
||||
| --- | --- |
|
||||
| Element hinzufügen | Bestehender Sorted-Listentest und neue Auswahlmatrix: a/b, ListCount und List(i). Nachträgliches Einschalten einschließlich stabiler Auswahl durch V1-Regressionen abgesichert. |
|
||||
| Keine Auswahl | ListIndex −1 bei leerer Liste und nach Entfernen der Auswahl. |
|
||||
| Auswahl ausdrücklich aufheben | Modellmatrix für ListBox und ComboBox-Stile 0/1/2; Formular-Korpus. |
|
||||
| Eintrag vor Auswahl einfügen | Sortierte/unsortierte Modellmatrix prüft Indexverschiebung und erhaltenen Text. |
|
||||
| Timer feuert im Abstand | Zwei Ereignisse nach 250 ms aktiver Zeit, mehrere Timer und Arrayelemente, identische Wiederholung. |
|
||||
| Interval 0 schaltet ab | Modelltest, Arrayindex-0-Reset und Änderung im VM-Handler. |
|
||||
| Späte Aktivierung | VM: direkte Property, Control-Parameter und Enabled; Aktivierung bei 1000, still bei 1099, einmal bei 1100 ms. Zusätzliche erfolgreiche Probe für spät geladenes Timer-Arrayelement. |
|
||||
| Abschalten verwirft anstehende Ereignisse | Interval/Enabled-Reset, Array-Entladen und Handlerwechsel mit altem Rückstau. |
|
||||
|
||||
Erneut ausgeführt:
|
||||
|
||||
- `cargo test --workspace`: **428 bestanden, 0 fehlgeschlagen, 1 ignoriert**.
|
||||
Ignoriert bleibt der Generator für Sollausgaben; keine Golden Files geändert.
|
||||
- `openspec validate forms-zustand-und-bedienung --strict`: gültig.
|
||||
- `git diff --check`: erfolgreich.
|
||||
- [Zusätzliche Review-Proben](review-probes.rs): **4 bestanden, 0 fehlgeschlagen**.
|
||||
|
||||
`cargo clippy -p tb-ui -p tb-vm -p tb-runtime --all-targets --all-features -- -D warnings`
|
||||
und `cargo fmt --all -- --check` wurden nach der Behebung erfolgreich ausgeführt. Kein interaktiver Terminal-/VBDOS-Vergleich und
|
||||
keine Windows-/Linux-Matrix. Die Darstellungsprobe prüft den echten Zellenpuffer
|
||||
ohne Terminal; Timerprüfungen verwenden ausschließlich virtuelle Hostzeit.
|
||||
|
||||
## Review-Proben reproduzieren
|
||||
|
||||
Im Repository-Wurzelverzeichnis ausführen; erzeugt einen separaten temporären
|
||||
Cargo-Treiber. Auf dem korrigierten Stand bestehen alle vier Proben.
|
||||
|
||||
```sh
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import json, subprocess, tempfile
|
||||
root = Path.cwd()
|
||||
p = Path(tempfile.mkdtemp(prefix='tb-forms-review-'))
|
||||
manifest = '[package]\nname="tb-forms-review"\nversion="0.0.0"\nedition="2021"\n[workspace]\n'
|
||||
manifest += '[[test]]\nname="probes"\npath=' + json.dumps(str(root / 'openspec/changes/archive/2026-09-05-forms-zustand-und-bedienung/review-probes.rs')) + '\n[dependencies]\n'
|
||||
for name in ['tb-ui', 'tb-vm', 'tb-runtime', 'tb-frontend']:
|
||||
manifest += name + ' = { path = ' + json.dumps(str(root / 'crates' / name)) + ' }\n'
|
||||
(p / 'Cargo.toml').write_text(manifest)
|
||||
subprocess.run(['cargo', 'test', '--manifest-path', str(p / 'Cargo.toml'), '--offline', '--test', 'probes', '--', '--nocapture'], check=True, timeout=120)
|
||||
PY
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
## 1. Listen
|
||||
|
||||
- [x] 1.1 ListIndex=-1 korrekt zulassen und Text/List/Count bei ADDITEM/REMOVEITEM konsistent halten; verifizieren mit leeren/sortierten Listen, Einfügen vor Auswahl, Entfernen der Auswahl und allen ComboBox-Stilen.
|
||||
|
||||
## 2. Timer
|
||||
|
||||
- [x] 2.1 Aktivierungs-/Änderungszeit über den Host erfassen und deaktivierte Queueeinträge entfernen; verifizieren mit Aktivierung bei 1000 ms, erster Fälligkeit bei 1100 ms, Änderung im Handler und Interval=0.
|
||||
- [x] 2.2 Fortlaufende Timer und gleichzeitige Fälligkeit erhalten; verifizieren mit exakt zwei Ereignissen bei 250 ms sowie mehreren Timer-/Arraynamen und Wiederholbarkeit.
|
||||
|
||||
## 3. Abnahme
|
||||
|
||||
- [x] 3.1 Formular-Korpus um Listenauswahl und späte Timeraktivierung ergänzen und Referenz dokumentieren; verifizieren mit cargo test -p tb-ui, cargo test -p tb-vm und cargo test -p tb-cli --test compat.
|
||||
- [x] 3.2 Delta-Specs und Diffs prüfen; verifizieren mit openspec validate forms-zustand-und-bedienung --strict und git diff --check.
|
||||
|
||||
Abnahmenachweise: [verification.md](verification.md).
|
||||
|
||||
## 4. Verifikationsbefunde
|
||||
|
||||
- [x] 4.1 V1: Gefüllte Listen beim Einschalten von Sorted stabil sortieren und die Auswahl einschließlich gleicher Texte erhalten; beide Klassen und Arrayzugriffe prüfen.
|
||||
- [x] 4.2 V2/V3: Simple-ComboBox mit dem bestehenden Listenrenderer darstellen; Einträge, Auswahl, Kürzung und Größenbegrenzung einschließlich Mindestmaßen, breiten Zeichen und Bildschirmrändern am Bildschirm prüfen. Alle Aufrufer des Listenrenderers berücksichtigen.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Implementierungsnachweis — 05.09.2026
|
||||
|
||||
Change: `forms-zustand-und-bedienung`, Ausgangscommit `644a862`.
|
||||
Alle sieben Tasks sind abgeschlossen; V1, V2 und V3 sind behoben. Die erneute
|
||||
[Verifikation](review.md) bestätigt beide Requirements und alle acht Szenarien
|
||||
ohne offene Befunde. Beide Delta-Requirements wurden am 05.09.2026 mit
|
||||
der Hauptspec synchronisiert und vollständig abgeglichen; anschließend
|
||||
wurde der Change unter `2026-09-05-forms-zustand-und-bedienung` archiviert.
|
||||
|
||||
## Umsetzung und Nachweise
|
||||
|
||||
| Task / Befund | Umsetzung | Ausführbarer Nachweis |
|
||||
| --- | --- | --- |
|
||||
| 1.1 / F12 | −1 ist bei leerer und gefüllter Liste zulässig. Einfügen/Entfernen verschiebt die Auswahl mit ihrem Eintrag; Entfernen der Auswahl hebt sie auf. Textabfrage und Combo-Anzeige verwenden denselben Auswahlzustand, editierbarer Text bleibt erhalten. | `listenauswahl_bleibt_beim_einfuegen_und_entfernen_konsistent`: ListBox und alle drei ComboBox-Stile, jeweils sortiert/unsortiert, Grenzen, Auswahl, List/Text/Count und Darstellung. Korpus `listenauswahl.frm`. |
|
||||
| 2.1 / F13 | Aktivierungszeit wird über die Hostuhr erfasst. Interval-Zuweisung setzt die aktive Phase zurück; unverändertes Enabled lässt sie weiterlaufen. Reset/Deaktivierung entfernt wartende Ereignisse einschließlich Arrayindex 0. Verstecken/Entladen beendet die Phase. | `timer_neustart_verwirft_alte_fristen_und_ereignisse`; VM-Test `spaete_timeraktivierung_nutzt_hostzeit_auch_ueber_control_parameter` für direkte/dynamische Property-Zuweisung und Enabled; bei 1000 ms aktiviert, bei 1099 ms still, bei 1100 ms einmal zugestellt. |
|
||||
| 2.1 | Intervalländerung im eigenen Handler verwirft bereits fällige Altintervalle. | `intervallaenderung_im_handler_verwirft_bereits_faellige_altintervalle`: erster Handler bei 100 ms, Änderung bei 450 ms, neue erste Frist bei 650 ms, dann Interval = 0. |
|
||||
| 2.2 | Durchgehend aktive Timer behalten die Nachholregel. Gleichzeitige Timer werden nach Name, Arrayelemente nach numerischem Index geordnet. | `timerarrays_holen_nur_aktive_zeit_nach_und_sortieren_nach_name_und_index`: zwei Ereignisse pro Timer nach 250 ms aktiver Zeit, identische Wiederholung, gezielte Deaktivierung/Entladung und Neustart nach HIDE/SHOW. Bestehende Menüsperr-/Timer-Reihenfolgetests. |
|
||||
| 3.1 | Zwei Formular-Korpusfälle mit ausdrücklich festgelegten Sollausgaben und ergänzte [Forms-Referenz](../../../../docs/forms-referenz.md). | `listenauswahl.frm`: −1, verschobene Auswahl und erhaltener Combo-Text. `timer-aktivierung.frm`: Aktivierung bei 1000 ms, Ereignis beendet SLEEP bei 1100 ms. Beide im normalen Korpus zweimal ausgeführt. |
|
||||
| 3.2 | Beide vollständigen Delta-Requirements mit acht Szenarien wurden nach erfolgreicher Abnahme in die Hauptspec übernommen. | OpenSpec-Validierung und Diff-Prüfung erfolgreich. |
|
||||
|
||||
Das Terminalbeispiel `tb-ui/examples/spike.rs` benötigt jetzt ausdrücklich
|
||||
das Feature `terminal`. Damit funktioniert auch der vorgeschriebene
|
||||
Einzelaufruf `cargo test -p tb-ui` ohne die Feature-Vereinigung des
|
||||
Workspace. `tb-ui` nutzt für die Zellenbreite jetzt dieselbe bereits im
|
||||
Workspace vorhandene Bibliothek `unicode-width` wie `tb-runtime`; kein
|
||||
zusätzliches Bibliothekspaket wurde eingeführt.
|
||||
|
||||
## Behebung der Verifikationsbefunde
|
||||
|
||||
V1, V2 und V3 sind geschlossen. Die gemeinsame stabile Sortierung erhält die
|
||||
Auswahl über ursprüngliche Eintragsindizes, auch bei Duplikaten. Laufzeit-
|
||||
und initiale Property-Zugriffe verwenden denselben Pfad. Die Simple-ComboBox
|
||||
nutzt den Listenrenderer der ListBox einschließlich Auswahlmarkierung und
|
||||
Begrenzung auf den verfügbaren Raum, einschließlich Mindestbreite und
|
||||
Mindesthöhe 1. Der gemeinsame Listenrenderer begrenzt Rahmen vor dem frühen
|
||||
Rücksprung für schmale Controls. `fit` berücksichtigt die Zellenbreite und
|
||||
ersetzt Steuerzeichen; `TextScreen::print_line` verhindert Umbruch und
|
||||
Scrollen bei Forms-Ausgaben am Bildschirmrand. BASIC-PRINT behält seinen
|
||||
bisherigen Zeichenpfad mit Umbruch und Scrollen.
|
||||
|
||||
Die neuen Modell-/Bildschirmtests heißen
|
||||
`sorted_wechsel_erhaelt_auch_die_auswahl_zwischen_duplikaten` und
|
||||
`simple_combo_zeichnet_listeninhalt_auswahl_und_begrenzung`. Die drei
|
||||
ursprünglichen Review-Proben bestehen unverändert in ihren Erwartungen.
|
||||
Der Korpus prüft nachträgliches Sorted bei unveränderter Sollausgabe.
|
||||
|
||||
`listenrenderer_haelt_zellgrenzen_auch_an_bildschirmraendern_ein` prüft
|
||||
3.360 Kombinationen: ListBox, alle drei Combo-Stile, DirListBox, FileListBox,
|
||||
DriveListBox; Basis- und dynamische Arrayelemente; Breiten 1/2/3/6/15/254,
|
||||
Höhen 1/2/3/5/254; normale, randnahe und außerhalb liegende Positionen;
|
||||
leere und gefüllte Listen mit breiten Zeichen und CR/LF. Alle Zellen
|
||||
außerhalb des Controlrechtecks werden einschließlich Farben und
|
||||
Fortsetzungsmarkierung mit dem Ausgangsbild verglichen.
|
||||
Der Test schlug vor der Korrektur fehl und besteht danach.
|
||||
`ui_zeile_bleibt_am_rand_ohne_umbruch_oder_scrollen` sichert zusätzlich
|
||||
den rechten/unteren Bildschirmrand und einen eigenen VIEW-PRINT-Bereich ab.
|
||||
|
||||
## Ausgeführte Prüfungen
|
||||
|
||||
Nach Behebung erneut ausgeführt:
|
||||
|
||||
- `cargo test --workspace`: **428 bestanden, 0 fehlgeschlagen, 1 ignoriert**.
|
||||
- Separater Treiber für `review-probes.rs`: **4 bestanden, 0 fehlgeschlagen**;
|
||||
die Erwartungen der ursprünglichen Mindestmaßprobe wurden nicht abgeschwächt.
|
||||
- `cargo clippy -p tb-ui -p tb-vm -p tb-runtime --all-targets --all-features -- -D warnings`: erfolgreich.
|
||||
- Format-, OpenSpec- und Diff-Prüfung: erfolgreich.
|
||||
|
||||
Die vorherige Implementierungsabnahme umfasste zusätzlich folgende Einzelaufrufe:
|
||||
|
||||
- `cargo test -p tb-ui`: 42 bestanden.
|
||||
- `cargo test -p tb-vm`: 149 bestanden, darunter 27 Ereignisregressionen.
|
||||
- Abschließend beide Pakete zusammen: `cargo test -p tb-ui -p tb-vm`,
|
||||
191 bestanden, 0 fehlgeschlagen.
|
||||
- `cargo test -p tb-cli --test compat`: 16 bestanden, 1 bewusst ignoriert
|
||||
(`erzeuge_sollausgaben`). Keine bestehenden Golden Files verändert oder
|
||||
neu aufgezeichnet.
|
||||
- `cargo clippy -p tb-ui -p tb-vm --all-targets --all-features -- -D warnings`:
|
||||
erfolgreich, ohne Warnungen.
|
||||
- `cargo fmt --all -- --check`: erfolgreich.
|
||||
- `openspec validate forms-zustand-und-bedienung --strict`: erfolgreich.
|
||||
- `git diff --check`: erfolgreich.
|
||||
|
||||
## Grenzen
|
||||
|
||||
Die neuen Nachweise prüfen den vereinbarten Vertrag mit virtueller Hostzeit.
|
||||
Kein neuer interaktiver VBDOS-/Terminalvergleich und keine Windows-/Linux-
|
||||
Ausführung. Bestehende Zeichenbilder bleiben unverändert; ihr Combo-Test
|
||||
wählt den darzustellenden Eintrag nun ausdrücklich aus. Einbetter mit
|
||||
direktem Forms-Modellzugriff müssen neue Timerphasen durch `sync_timers`
|
||||
mit ihrer Hostzeit beginnen lassen; die VM übernimmt das selbst.
|
||||
@@ -1,13 +0,0 @@
|
||||
## 1. Listen
|
||||
|
||||
- [ ] 1.1 ListIndex=-1 korrekt zulassen und Text/List/Count bei ADDITEM/REMOVEITEM konsistent halten; verifizieren mit leeren/sortierten Listen, Einfügen vor Auswahl, Entfernen der Auswahl und allen ComboBox-Stilen.
|
||||
|
||||
## 2. Timer
|
||||
|
||||
- [ ] 2.1 Aktivierungs-/Änderungszeit über den Host erfassen und deaktivierte Queueeinträge entfernen; verifizieren mit Aktivierung bei 1000 ms, erster Fälligkeit bei 1100 ms, Änderung im Handler und Interval=0.
|
||||
- [ ] 2.2 Fortlaufende Timer und gleichzeitige Fälligkeit erhalten; verifizieren mit exakt zwei Ereignissen bei 250 ms sowie mehreren Timer-/Arraynamen und Wiederholbarkeit.
|
||||
|
||||
## 3. Abnahme
|
||||
|
||||
- [ ] 3.1 Formular-Korpus um Listenauswahl und späte Timeraktivierung ergänzen und Referenz dokumentieren; verifizieren mit cargo test -p tb-ui, cargo test -p tb-vm und cargo test -p tb-cli --test compat.
|
||||
- [ ] 3.2 Delta-Specs und Diffs prüfen; verifizieren mit openspec validate forms-zustand-und-bedienung --strict und git diff --check.
|
||||
@@ -86,6 +86,8 @@ ListBox und ComboBox SHALL `ADDITEM` und `REMOVEITEM` unterstützen und
|
||||
`ListIndex = -1` SHALL „keine Auswahl" bedeuten. Die ComboBox SHALL die
|
||||
drei Stilarten (Dropdown, Simple, Dropdown List) darstellen.
|
||||
|
||||
Eine Zuweisung von ListIndex = -1 SHALL in leerer wie gefüllter Liste zulässig sein. Einfügen vor der Auswahl SHALL ihren Index verschieben, Entfernen der Auswahl SHALL sie aufheben. Eine ListBox ohne Auswahl SHALL Text als leeren String liefern; editierbare ComboBox-Stile SHALL ihren unabhängigen Eingabetext erhalten.
|
||||
|
||||
#### Scenario: Element hinzufügen
|
||||
- **WHEN** `List1.ADDITEM "b"` und `List1.ADDITEM "a"` bei `Sorted = -1` ausgeführt werden
|
||||
- **THEN** liefert `List1.List(0)` den Wert `a` und `List1.ListCount` den Wert 2
|
||||
@@ -94,6 +96,14 @@ drei Stilarten (Dropdown, Simple, Dropdown List) darstellen.
|
||||
- **WHEN** eine ListBox ohne Auswahl gelesen wird
|
||||
- **THEN** liefert `ListIndex` den Wert −1
|
||||
|
||||
#### Scenario: Auswahl ausdrücklich aufheben
|
||||
- **WHEN** nach ADDITEM die Eigenschaft ListIndex auf -1 gesetzt wird
|
||||
- **THEN** tritt kein Fehler auf und ListIndex ist -1
|
||||
|
||||
#### Scenario: Eintrag vor Auswahl einfügen
|
||||
- **WHEN** vor einem ausgewählten Eintrag ein Element eingefügt wird
|
||||
- **THEN** bleibt derselbe Eintrag ausgewählt und sein Index steigt um 1
|
||||
|
||||
### Requirement: Timer-Steuerelement
|
||||
Ein Timer SHALL bei `Enabled = -1` und `Interval > 0` sein
|
||||
`Timer`-Ereignis im eingestellten Abstand auslösen, gestützt auf die
|
||||
@@ -101,6 +111,8 @@ Zeitquelle der Ereignissteuerung. `Interval = 0` SHALL ihn abschalten.
|
||||
Sind mehrere Timer gleichzeitig fällig, SHALL die Reihenfolge festgelegt
|
||||
und dokumentiert sein.
|
||||
|
||||
Die erste Frist SHALL ab dem Einschalten beziehungsweise neu gesetzten Intervall zählen. Zeit vor der Aktivierung MUST NOT nachgeholt werden. Deaktivierung oder Interval = 0 SHALL noch anstehende Timerereignisse verwerfen.
|
||||
|
||||
#### Scenario: Timer feuert im Abstand
|
||||
- **WHEN** ein Timer mit `Interval = 100` läuft und die Zeit um 250 ms vorrückt
|
||||
- **THEN** ist sein Ereignis zweimal gelaufen
|
||||
@@ -109,6 +121,14 @@ und dokumentiert sein.
|
||||
- **WHEN** `Timer1.Interval = 0` gesetzt wird
|
||||
- **THEN** läuft kein weiteres Ereignis
|
||||
|
||||
#### Scenario: Späte Aktivierung
|
||||
- **WHEN** bei Hostzeit 1000 ms ein zuvor inaktiver Timer mit Interval 100 eingeschaltet wird
|
||||
- **THEN** läuft bis 1099 ms kein Timerereignis und bei 1100 ms genau eines
|
||||
|
||||
#### Scenario: Abschalten verwirft anstehende Ereignisse
|
||||
- **WHEN** Timerereignisse anstehen und vor ihrer Zustellung Interval auf 0 gesetzt wird
|
||||
- **THEN** werden sie nicht mehr zugestellt
|
||||
|
||||
### Requirement: Menüsystem
|
||||
Ein Formular SHALL eine Menüleiste mit bis zu sechs Ebenen tragen.
|
||||
Menüeinträge SHALL Access-Keys (`&`), Shortcuts, `Checked`, `Enabled`,
|
||||
|
||||
Reference in New Issue
Block a user