Files
TerminalBasic/openspec/changes/archive/2026-09-04-phase-3-runtime-bildschirm/design.md
Chili Palmer d0461bea34 OpenSpec: phase-3-runtime-bildschirm archiviert, Delta-Specs gesynct
Zwei neue Capabilities in den Hauptspecs:
- datei-eio: Dateiverwaltung, drei Zugriffsarten, UTF-32-Festtextfelder,
  Recordkonvertierung, Sperren, Dateisystem-Anweisungen
- sprachinventar: das Vollstaendigkeits-Inventar als pruefbares Artefakt

Fortgeschrieben: textbildschirm (Doppelzellen, Groessenunabhaengigkeit,
Bildschirmanweisungen), konsolen-basisbibliothek (Host-Neuschnitt, volle
String-/Format-/Datums-/Finanzbibliothek), kompat-testkorpus
(Snapshot-Vergleich, Nachweis der Groessenunabhaengigkeit),
sprach-frontend (Signaturpflicht, ON-ERROR-Scoping, DATA-Rohtext).

Bilanz: +32 Anforderungen, 6 geaendert, 1 ersetzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-04 06:55:02 +02:00

14 KiB
Raw Blame History

Context

Siehe proposal.md — Why. Technisch entscheidend ist der Ist-Zustand dreier Stellen:

  • tb-ui::screen::TextScreen (385 Zeilen) trägt die Bildschirmsemantik bereits vollständig: cls, set_color, locate, csrlin, pos, view_print, cell, print, scroll_up, resize. Ratatui kommt darin an genau zwei Stellen vor: basic_color() (4 Zeilen) und impl Widget for &TextScreen (~35 Zeilen).
  • tb-runtime::host::Host ist ein Stream-Trait (write(&str), read_line()). tb-runtime und tb-ui sind beide Blätter im Abhängigkeitsgraphen und treffen sich erst in tb-cli/tb-ide.
  • Rund 50 dokumentierte Elemente besitzen im Frontend eine Signatur in builtin_fn/builtin_stmt, senken aber auf HStmtKind::Unsupported bzw. HExpr::Unsupported ab → Opcode 0x05 → Laufzeitfehler 73.

Zwei Entscheidungen vom 2026-09-03 rahmen die Arbeit: Das Windowing wird — wie beim Vorbild — vom erzeugten Programm mitgebracht und nicht von der IDE, sodass Forms und PRINT in denselben Zellenpuffer zeichnen. Die Forms-Engine selbst bleibt dabei am von PLAN.md vorgesehenen Ort tb-ui::forms.

Goals / Non-Goals

Goals:

  • Der Zellenpuffer liegt dort, wo sowohl die Laufzeitbibliothek als auch die spätere Forms-Engine in tb-ui ihn erreichen, ohne dass die Ausführungsschicht eine Terminal-Bibliothek zieht.
  • Bildschirmverhalten ist ohne Terminal automatisiert prüfbar.
  • Die Abdeckung misst sich selbst statt gepflegt zu werden.
  • Die Umstellung der acht bestehenden Golden Files geschieht in einem nachvollziehbaren, einmaligen Schritt.

Non-Goals:

  • Optimierung der Bildschirmausgabe (Diff-Minimierung, Teilaktualisierung) — erst wenn Messungen sie verlangen.
  • Ein Rendering-Backend jenseits des Terminals.
  • Die Forms-Engine selbst (Phase 4, tb-ui::forms); dieser Change liefert ihr nur die Zeichenfläche.

Decisions

D1: Zellenpuffer nach tb-runtime::screen, ratatui-frei

Cell und TextScreen ziehen unverändert nach tb-runtime::screen. basic_color() und impl Widget bleiben in tb-ui und arbeiten künftig auf dem Runtime-Typ.

Warum: PRINT, LOCATE, CLS, COLOR, CSRLIN, POS und die Funktionsform von SCREEN sind Elemente der Laufzeitbibliothek. Sie müssen den Puffer erreichen, ohne dass tb-runtime eine Terminal-Bibliothek zieht — sonst braucht jeder VM- und Bibliothekstest ein Terminal. Das trägt die Entscheidung allein.

Verhältnis zur Forms-Engine: Forms bleibt laut PLAN.md in tb-ui::forms (Entscheidung 2026-09-03). Das passt zur Richtung: tb-ui hängt künftig an tb-runtime und zeichnet von oben in denselben Puffer wie PRINT — ein MSGBOX über einer Konsolenausgabe überdeckt deren Zellen und gibt sie beim Schließen zurück. Läge der Puffer weiterhin in tb-ui, wäre genau diese gemeinsame Fläche für die Bibliothek unerreichbar. Umgekehrt entsteht kein Zyklus: die Ausführungsschicht erreicht die Forms-Engine über die Host-Schnittstelle (D2), die sie selbst deklariert und tb-ui implementiert.

Alternativen:

  • tb-runtime hängt an tb-ui: null Doppelung, aber ratatui und crossterm landen in jedem VM-Test. Verworfen.
  • Semantik bleibt in tb-ui, jeder Host führt einen eigenen Puffer: dieselbe Scroll-/Umbruch-/Doppelzellenlogik dreimal. Verworfen.

Aufwand: Der Umzug ist mechanisch — betroffen sind der use-Block, die zwei ratatui-Stellen und die Modulzuordnung. Die Semantik selbst wird nicht angefasst; erweitert wird sie nur um die Doppelzellen (D4).

D2: Host wird Bildschirm- statt Stream-Schnittstelle

write/read_line weichen einer Schnittstelle, die den Zellenpuffer zur Anzeige übergibt und Ereignisse (Tastendruck, Größenänderung, Abbruch) zurückliefert. Die Bibliothek schreibt nicht mehr in den Host, sondern in den Puffer; der Host stellt dar.

Warum: LOCATE 5,10, COLOR 14,1, CSRLIN und besonders SCREEN(r,c) — das ein Zeichen vom Bildschirm zurückliest — sind über einen Zeichenstrom nicht ausdrückbar. Das ist ein Zwang, keine Präferenz.

Wann dargestellt wird: Der Host wird an denselben Tick-Grenzen aufgerufen, an denen die VM heute schon die Abbruchprüfung macht, sowie vor jeder blockierenden Eingabe. Kein zusätzlicher Mechanismus.

Blockieren bleibt erlaubt (Entscheidung D3 aus Phase 2 bleibt gültig): INPUT und INPUT$ blockieren im Host. Erst die Ereignisschleife der Phase 4 bringt kooperative Zustellpunkte.

Alternativen:

  • Stream-Host behalten und Bildschirmwirkungen als ANSI-Escapes hindurchreichen: CSRLIN/POS/SCREEN() bräuchten trotzdem einen Schattenpuffer, und der Capture-Host müsste ANSI parsen. Verworfen.

D3: Korpusvergleich als getrimmter Snapshot

Die .out-Datei hält das Textbild des Puffers bis zur letzten belegten Zeile und Spalte. Eine zweite Ebene mit Farbattributen kommt nur hinzu, wenn das Programm COLOR verwendet.

Warum getrimmt: Ein voller 80×25-Snapshot für hello.bas wären 2000 Zeichen für zwei Zeichen Signal, und jeder Diff zeigte Rauschen. Getrimmt bleiben die bestehenden acht Sollausgaben inhaltlich fast unverändert — PRINT-only-Programme ergeben dasselbe Textbild wie bisher der Stream.

Warum Attribute optional: Programme ohne COLOR erzeugen sonst eine zweite Ebene aus lauter identischen Werten.

Umstellung: Die acht Golden Files werden einmalig neu erzeugt und der Diff wird geprüft — erwartet wird, dass sich nur Zeilen ändern, die vorher auf \r/Umbruchverhalten beruhten. Ein unerwarteter Diff ist ein Befund, kein Anpassungsgrund.

Alternativen:

  • Zwei Harnesses (Stream für Alt-Tests, Snapshot für neue): dauerhaft doppelte Infrastruktur für einen einmaligen Umstellungsschmerz. Verworfen.
  • Voller Snapshot: siehe oben. Verworfen.

D4: Doppelzellen über Darstellungsbreite

Breite Zeichen belegen zwei Zellen: die erste trägt das Zeichen, die zweite ist als Fortsetzung markiert. Neue Abhängigkeit unicode-width in tb-runtime.

Warum eine Abhängigkeit: Die Breitentabelle folgt einem Unicode-Annex und ändert sich mit jeder Unicode-Version; sie nachzubauen hieße, sie dauerhaft nachzupflegen.

Konsequenzen, die die Spec festhält: Cursor rückt um zwei Spalten vor; POS zählt die Anfangsspalte; ein breites Zeichen in der letzten Spalte wird vollständig umgebrochen und die Spalte bleibt leer; LOCATE auf eine Fortsetzungszelle wirkt auf den Zeichenanfang. Beim Überschreiben einer Hälfte muss die andere geleert werden, sonst entstehen Geisterzeichen.

D4a: 80×25 ist nirgends eine Konstante

Anforderung des Projektinhabers (2026-09-03), Priorität für das ganze Programm: 80×25 ist stets Synonym für „voller Bildschirm", nie eine feste Grenze. Moderne Terminals haben keine feste Größe, und sie ändert sich während der Ausführung.

Konsequenz für die Umsetzung: Kein Zweig darf gegen 80 oder 25 vergleichen. Die Abmessungen kommen ausschließlich aus dem Puffer (cols()/rows()), abgeleitet aus der Darstellungsfläche. Betroffen sind CLS (voller aktueller Bildschirm), der voreingestellte Scrollbereich, die Umbruchspalte, die Scrollzeile sowie die Grenzprüfungen von LOCATE und der Funktionsform von SCREEN.

Größenänderung zur Laufzeit ist damit kein Sonderfall, sondern der Normalfall: Das Größenänderungsereignis kommt über den Host (D2), der Puffer wird angepasst, Inhalt bleibt oben links, Cursor und VIEW PRINT-Bereich werden geklemmt. Ein Bereich, der durch die Verkleinerung leer würde, fällt auf den vollen Bildschirm zurück, statt ungültig zu bleiben.

Das Mindestmaß bleibt eine Darstellungsschranke: Ist die Fläche kleiner als 80×25, zeigt die Darstellungsschicht den Hinweis „Terminal zu klein" (bestehende Entscheidung 2026-09-02). Das ist eine untere Schranke der Anzeige, keine Bildschirmgröße für das Programm.

Prüfbarkeit: Der Test-Host bekommt seine Größe je Korpusprogramm explizit gesetzt, ohne stillschweigenden Vorgabewert. Mindestens ein Bildschirmprogramm läuft bei zwei Größen mit je eigener Sollausgabe — eine hartkodierte 80 oder 25 fällt damit sofort auf. Ein weiteres durchläuft eine Größenänderung mitten in der Ausführung.

D4b: Das Inventar geht der Umfangsschätzung vor

Die Erhebung in Aufgabe 1.1 (2026-09-03) hat gezeigt, dass die ursprüngliche Umfangsliste dieses Changes drei ganze Gruppen nicht enthielt: die 13 finanzmathematischen Funktionen, die 14 Record-Konvertierungsfunktionen und einen Rest an Dateisystem- und Systemelementen. Sie hat außerdem zwei Elemente widerlegt, die phase-3-isam fälschlich führte (CHECKPOINT, TEXTCOMP).

Konsequenz für die Arbeitsweise: Maßgeblich ist das Inventar, nicht die Aufzählung im Proposal. Wo beide auseinandergehen, wird das Proposal nachgezogen. Aufzählungen in Proposal und Specs sind Mindestumfang.

Konsequenz für die Quelle: bas7advr.hlp/TOPIC_LIST.html ist nachweislich unvollständig — SGN, SQR, SPACE$, STRING$, CSRLIN, BEEP und DATE$ als Funktion fehlen dort. Das Inventar bildet die Vereinigungsmenge aus dieser Liste, qb45advr.hlp/TOPIC_LIST.html und docs/sprachreferenz.md; jeder Eintrag nennt seine Fundstelle. Die Zählung einer einzelnen Themenliste ist kein Vollständigkeitsnachweis.

D5: Inventar als Markdown-Tabelle mit Abgleichstest

docs/inventar.md trägt eine Pipe-Tabelle mit festen Spalten (Name, Art, Gruppe, Status, Fundstelle). Ein Test parst sie und hält sie gegen builtin_fn/builtin_stmt, die Unsupported-Marker und banned_feature.

Warum Markdown und nicht TOML/YAML: Der Plan nennt „maschinenlesbar", aber keinen Konsumenten außer der Messbarkeit selbst; Zielpfad ist ausdrücklich docs/inventar.md, und das IDE-Hilfesystem (Phase 5) rendert docs/-Markdown. Eine Pipe-Tabelle ist mit einem Split maschinenlesbar. Ein zweites Format bräuchte einen Generator, um wieder bei Markdown zu landen.

Der Test ist der eigentliche Punkt: Ohne ihn verrottet der Status. Mit ihm kann das Inventar nicht falsch werden, ohne dass die Suite rot wird.

D6: Unsupported-Marker bleiben, aber nur noch für Phase 4

Die Marker verschwinden nicht — Ereignissteuerung, SETUEVENT, MSGBOX und INPUTBOX$ behalten sie bis Phase 4. Der Abgleichstest aus D5 stellt sicher, dass genau die im Inventar als offen geführten Elemente noch Marker tragen.

D7: Reihenfolge der Umsetzung

  1. Inventar (Sollseite)         -> misst alles Folgende
  2. Puffer-Umzug + Host + Snapshot -> BREAKING, zusammen in einem Schritt
  3. Bildschirmanweisungen        -> braucht 2
  4. Strings/Format/Datum/Mathematik -> unabhaengig, jederzeit
  5. Datei-E/A                    -> unabhaengig von 2/3
  6. Altlasten Phase 2            -> unabhaengig

Schritt 2 ist der einzige Bruch und wird bewusst als eine Einheit umgesetzt: Puffer-Umzug, Host-Neuschnitt und Snapshot-Umstellung hängen so eng zusammen, dass ein Zwischenzustand die Suite ohnehin rot ließe. Schritte 46 können vorgezogen werden, falls Schritt 2 sich zieht.

Risks / Trade-offs

  • Der Host-Bruch legt kurzzeitig die gesamte Suite lahm → Schritt 2 als eine Einheit umsetzen, nicht in Teilschritten; erst wenn Puffer, Host und Harness zusammen stehen, wird gemessen.
  • Neu erzeugte Golden Files zementieren einen Fehler → Der Diff der acht Dateien wird einzeln geprüft. Erwartung: PRINT-only-Programme ändern sich nicht. Jede Abweichung wird begründet oder ist ein Bug.
  • Doppelzellen brechen bestehende Annahmen (POS = Zeichenzahl, Zelle = Zeichen) → Korpustest mit CJK/Emoji inklusive Umbruch am rechten Rand, bevor die übrige Bildschirmarbeit darauf aufsetzt.
  • RND-Kompatibilität ist Rekonstruktionsarbeit — die Zahlenfolge des Vorbilds ist nur über die Dokumentation und Beobachtung greifbar → Algorithmus dokumentieren; gelingt die exakte Folge nicht, wird die Abweichung in der Sprachreferenz festgehalten statt still abzuweichen.
  • UTF-32-Records machen Dateien binär inkompatibel zum Vorbild → bewusst entschieden (2026-09-02); Preis ist, dass Originaldateien nicht gelesen werden können. Muss in der Sprachreferenz stehen.
  • SHELL, FILES, Satzsperren und LPRINT sind plattformabhängig → jede Abweichung wird in der Sprachreferenz unter „Abweichungen" benannt; stilles Abweichen ist laut Guiding Principle ein Bug.
  • Das Inventar aus der Original-Hilfe zu erheben ist der stillste Aufwandstreiber → als eigener erster Schritt geführt, damit sein Umfang sichtbar wird, statt sich in die Implementierungsarbeit zu mischen.

Migration Plan

  1. Inventar anlegen (rein additiv, kein Bruch).
  2. Bruchschritt in einem Zug: TextScreen nach tb-runtime::screen, tb-ui auf Adapterrolle, Host neu, Harness auf Snapshot, acht Golden Files neu erzeugen und Diff prüfen.
  3. Alles Weitere additiv — jedes neue Element wechselt im Inventar von offen auf implementiert, der Abgleichstest hält beides zusammen.

Rücknahme: Schritt 2 ist ein zusammenhängender Commit und als Ganzes rücknehmbar. Danach gibt es keinen weiteren Bruch.

Open Questions

  • Umfang der KEY n-Funktionstasten-Makros (KEY LIST/ON/OFF): Die Anweisung steht im Scope; welche Makro-Semantik das Vorbild genau zeigt, ist bei der Umsetzung aus der Original-Hilfe zu klären und in docs/sprachreferenz.md festzuhalten. Berührt weder Puffer noch Host noch Aufgabenschnitt.
  • Ersatzwert für FRE auf einer Plattform ohne DOS-Speichermodell: bei der Umsetzung zu wählen und zu dokumentieren.
  • Konkrete Gestalt der Schnittstelle, über die die Forms-Engine in tb-ui später eigene Zeichenbereiche im Puffer belegt und wieder freigibt: erst in Phase 4 zu klären, wenn die Steuerelemente stehen. Ändert weder Puffer noch Host noch Aufgabenschnitt dieses Changes.