Forms-Zustand und Darstellung korrigieren und Change archivieren

This commit is contained in:
2026-09-05 20:48:13 +02:00
parent 644a86212f
commit 58b1f620ea
22 changed files with 1362 additions and 101 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-05

View File

@@ -0,0 +1,32 @@
## Context
Die LISTINDEX-Prüfung konvertiert -1 vor dem oberen Grenzvergleich nach usize. Änderungen an INTERVAL/ENABLED löschen timer_last, dessen nächster Default 0 ist. Timerereignisse werden bereits über die bestehende Queue zugestellt. Siehe proposal.md und die Delta-Specs.
## Goals / Non-Goals
**Goals:** Gültige Übergänge an den bestehenden Property-/Listenmethoden korrigieren und Timerzustand an die Hostzeit koppeln.
**Non-Goals:** Keine neue Control-Bibliothek, keine Änderung der festgelegten Optik, keine zweite Ereignisschleife.
## Decisions
### D1 — Auswahl ist ein eigener gültiger Zustand
-1 vor dem oberen Grenzvergleich behandeln. Einfügungen vor dem ausgewählten Eintrag verschieben dessen Index; Entfernen des gewählten Eintrags hebt die Auswahl auf, Entfernen davor verschiebt sie zurück. ListBox.Text liefert ohne Auswahl leer, eine editierbare ComboBox behält ihren separaten Eingabetext.
### D2 — Aktivierungszeit statt Nullzeit
Beim Übergang auf einen aktiven Timer den aktuellen Hostzeitpunkt als Ausgangsfrist speichern. Ändernde Property-Zugriffe erhalten diesen Zeitpunkt über den bereits vorhandenen VM/Host-Pfad; kein Systemuhrzugriff und kein globaler Zeitcache. Interval=0/Enabled=0 entfernt anstehende Zustellungen dieses Timers. Explizites Umschalten des Intervalls beginnt eine neue Frist.
### D3 — Laufende Timer behalten Nachholregel
Ein kontinuierlich aktiver 100-ms-Timer bekommt bei 250 ms zwei Ereignisse; das gilt nur für Zeit innerhalb seiner aktiven Phase. Reihenfolge gleichzeitig fälliger Timer bleibt dokumentiert nach Namen und bei gleichem Arraynamen nach Index.
## Risks / Trade-offs
- Änderung an Auswahl kann ComboBox-Eingabetext löschen → Stilarten und editierbaren Text getrennt testen.
- Intervalländerung im eigenen Handler kann alte Queueeinträge übriglassen → Reset/Deaktivierung mit bereits wartenden Ereignissen prüfen.
## Migration Plan
Nach ereigniszustellung-und-hostgrenzen. Ändert nur die Requirements zu Listen und Timer, nicht Fokus/Menü desselben Capability-Pfads. Bestehende Spec-/Codeverträge erst nach erfolgreicher Umsetzung synchronisieren und archivieren. Bis dahin bleiben alle Tasks offen. Änderungen als zusammenhängenden Commit je Change integrieren; bei Fehlschlag auf den vorherigen Code zurückgehen und neue Datenformatversionen nicht mit alten Lesern öffnen.

View File

@@ -0,0 +1,30 @@
## 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).
## What Changes
- Explizites Aufheben einer Listenauswahl unterstützen und List/Text/Count bei Einfügen/Entfernen konsistent halten.
- Timerfristen ab Aktivierung bzw. neu gesetztem Intervall starten und deaktivierte Zeiten nicht nachholen.
## Capabilities
### New Capabilities
Keine neuen Capability-Pfade.
### Modified Capabilities
- `forms-steuerelemente`: Listen-Auswahl und Zeitbezug des Timer-Steuerelements durch präzise Zustandsübergänge ergänzen.
## Impact
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.

View File

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

View File

@@ -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 D1D3 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 | D1D3 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 47 ü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
```

View File

@@ -0,0 +1,51 @@
## MODIFIED Requirements
### Requirement: Steuerelemente mit Listeninhalt
ListBox und ComboBox SHALL `ADDITEM` und `REMOVEITEM` unterstützen und
`List`, `ListCount`, `ListIndex` und `Text` konsistent führen; bei
`Sorted = -1` SHALL die Einfügereihenfolge der Sortierung folgen.
`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
#### Scenario: Keine Auswahl
- **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
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
#### Scenario: Interval 0 schaltet ab
- **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

View File

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

View File

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