Skip to content

Text, Größe und freie Positionierung dokumentieren und ermöglichen - #62

Merged
flomotlik merged 3 commits into
mainfrom
feature/llms-txt-luecken-schliessen
Sep 25, 2026
Merged

flomotlik merged 3 commits into
mainfrom
feature/llms-txt-luecken-schliessen

Conversation

@flomotlik

Copy link
Copy Markdown
Contributor

Eine fremde Session kam mit der llms.txt nicht weiter: mehrere Texte, andere Farben, Größe, links- und rechtsbündig — alles unklar. Zu Recht. Teils war es undokumentiert, teils gab es die Fähigkeit gar nicht.

Was nur undokumentiert war

Dass addText() überhaupt Optionen hat. Die Tabelle sagte | addText(text, opts) | Schlagzeile hinzufügen | — Farbe, Schriftwahl, Ausrichtung, Zeilenabstand und Schatten standen nirgends. Ebensowenig, dass addText() beliebig oft aufgerufen werden kann.

Was tatsächlich gefehlt hat

Mehrere Texte in render() über texts, jeder mit eigener Farbe, Größe und Position. Ohne das landet jeder weitere Text zentriert auf dem vorigen.

resize(faktor). Die Oberfläche hat einen Größenregler auf dem ausgewählten Objekt (#scale, 0,1 bis 2) — die Fassade legte ihn nur nicht frei. Ein Schriftgrößenfeld gibt es nicht: Text wird auf 80 % der Bildbreite skaliert, und das war bisher die einzige Größe, die ein Aufrufer bekommen konnte.

Freie Koordinaten in place({ x, y }) neben den neun Feldern. Die Schutzzone gilt weiter, und Werte außerhalb 0 bis 1 werden abgelehnt statt stillschweigend begrenzt — sonst wäre die Zusage in der llms.txt unwahr.

Der Abschnitt „Text im Detail"

Erklärt die Verwechslung, die am meisten Zeit kostet:

  • align richtet die Zeilen innerhalb des Blocks aus. Bei einzeiligem Text wirkungslos.
  • place() bewegt den Block auf dem Bild.

Linksbündig am linken Bildrand braucht beides. Das war vermutlich genau die Frage, an der deine Session hing.

Dazu eine ausdrückliche Liste dessen, was nicht geht — keine freie Schriftgröße in Punkt, keine freien Farben, kein Textrahmen, keine Drehung — mit der Anweisung, das zu sagen statt etwas Ähnliches zu liefern.

Ein Test, der die Beispiele ausführt

e2e/llms-examples.spec.js führt jeden Codeblock der llms.txt gegen die echte Seite aus.

Begründung: Ein Beispiel, das nicht läuft, ist schlechter als keines. Das Modell folgt ihm, bekommt einen Fehler, den es nicht deuten kann, und improvisiert — genau der Fehlermodus, gegen den diese Schnittstelle gebaut ist. Beispiele sind der Teil des Dokuments, der am ehesten wörtlich übernommen wird.

Der Test hat sofort etwas gefunden: Das Beispiel zur freien Positionierung war ein Fragment ohne Kontext, kopierbar aber nicht lauffähig. Jetzt vollständig.

Tests

132 Jest grün, 83 e2e grün — davon 27 für die Fassade und 7 für die Beispiele aus der llms.txt.

VERSION auf 4.

https://claude.ai/code/session_01Qtib9MPRZATQU3j9w7ymDk

Eine fremde Session kam mit der llms.txt nicht weiter: mehrere Texte,
andere Farben, Groesse, links- und rechtsbuendig — alles unklar. Zu
Recht. Teils war es undokumentiert, teils gab es die Faehigkeit gar
nicht.

Undokumentiert war, dass addText() ueberhaupt Optionen hat. Die Tabelle
sagte nur "Schlagzeile hinzufuegen"; Farbe, Schriftwahl, Ausrichtung,
Zeilenabstand und Schatten standen nirgends. Ebensowenig, dass addText()
beliebig oft aufgerufen werden kann.

Gefehlt hat:

- Mehrere Texte in render() ueber `texts`, jeder mit eigener Farbe,
  Groesse und Position. Ohne das landet jeder weitere Text zentriert auf
  dem vorigen.
- resize(faktor). Die Oberflaeche hat einen Groessenregler auf dem
  ausgewaehlten Objekt, die Fassade legte ihn nur nicht frei. Ein
  Schriftgroessenfeld gibt es nicht — Text wird auf 80 % der Bildbreite
  skaliert, und das war die einzige Groesse, die ein Aufrufer bekam.
- Freie Koordinaten in place({x, y}) neben den neun Feldern. Die
  Schutzzone gilt weiter; Werte ausserhalb 0 bis 1 werden abgelehnt statt
  stillschweigend begrenzt, sonst waere die Zusage in der llms.txt
  unwahr.

Der neue Abschnitt "Text im Detail" erklaert ausserdem die Verwechslung,
die am meisten Zeit kostet: `align` richtet die Zeilen INNERHALB des
Blocks aus, `place()` bewegt den Block auf dem Bild. Linksbuendig am
linken Rand braucht beides. Dazu eine Liste dessen, was NICHT geht —
keine freie Schriftgroesse in Punkt, keine freien Farben, kein
Textrahmen, keine Drehung — mit der Anweisung, das zu sagen statt etwas
Aehnliches zu liefern.

e2e/llms-examples.spec.js fuehrt jeden Codeblock der llms.txt gegen die
echte Seite aus. Ein Beispiel, das nicht laeuft, ist schlechter als
keines: Das Modell folgt ihm, bekommt einen Fehler, den es nicht deuten
kann, und improvisiert — genau der Fehlermodus, gegen den die
Schnittstelle gebaut ist. Der Test hat sofort ein Fragment ohne Kontext
gefunden, das jetzt vollstaendig ist.

VERSION auf 4.

Claude-Session: https://claude.ai/code/session_01Qtib9MPRZATQU3j9w7ymDk
Die llms.txt fuehrte "Keine Drehung" unter dem auf, was nicht geht. Das
war ungeprueft. relativeScalingControlsOnly() setzt in canvas-utils.js:61
ausdruecklich mtr: true — der Drehgriff ist auf Text, Bildern, QR-Codes
und Formen aktiv, und beim Ziehen rastet er bei 0, 90, 180 und 270 Grad
mit zwei Grad Toleranz ein.

Eine falsche Angabe unter "geht nicht" ist besonders schaedlich: Sie
bringt ein Modell dazu, eine Anforderung abzulehnen, die das Werkzeug
erfuellen kann.

Neu: rotate(grad), dazu textRotate und rotate in den Element-Eintraegen
von render(). Der Winkel ist absolut, nicht relativ; negative Werte
werden normalisiert, -90 wird zu 270. Programmatisch wird exakt gesetzt,
ohne Einrasten — das steht so in der llms.txt, damit sich niemand auf
Verhalten verlaesst, das nur beim Ziehen mit der Maus greift.

Dazu die Einordnung, dass gedrehte Headlines im Erscheinungsbild nicht
vorgesehen sind, beim Stoerer und beim Wahlkreuz eine leichte Schraege
dagegen ueblich ist. Ohne den Satz wuerde die neue Faehigkeit
vermutlich ueberstrapaziert.

Die verbleibende "geht nicht"-Liste: keine Schriftgroesse in Punkt,
keine freien Farben, kein Textrahmen und keine Hintergrundflaeche hinter
dem Text.

VERSION auf 5.

Claude-Session: https://claude.ai/code/session_01Qtib9MPRZATQU3j9w7ymDk
@flomotlik

Copy link
Copy Markdown
Contributor Author

Nachtrag: Drehung geht doch

Die llms.txt führte „Keine Drehung" unter dem auf, was nicht geht. Das war ungeprüft und falsch.

relativeScalingControlsOnly() setzt in canvas-utils.js:61 ausdrücklich mtr: true — der Drehgriff ist auf Text, Bildern, QR-Codes und Formen aktiv. Es gibt sogar ein Einrasten bei 0, 90, 180 und 270 Grad mit zwei Grad Toleranz (ROTATION_SNAP_ANGLES).

Eine falsche Angabe unter „geht nicht" ist besonders schädlich: Sie bringt ein Modell dazu, eine Anforderung abzulehnen, die das Werkzeug erfüllen kann. Ein fehlendes Feature lässt sich nachtragen, eine falsche Absage nicht bemerken.

Neu ist rotate(grad), dazu textRotate und rotate in den Element-Einträgen von render(). Der Winkel ist absolut, nicht relativ; negative Werte werden normalisiert, -90 wird zu 270.

Programmatisch wird der Winkel exakt gesetzt, ohne Einrasten — das steht so in der llms.txt, damit sich niemand auf Verhalten verlässt, das nur beim Ziehen mit der Maus greift.

Dazu die Einordnung, dass gedrehte Headlines im Erscheinungsbild nicht vorgesehen sind, beim Störer und beim Wahlkreuz eine leichte Schräge dagegen üblich ist — so zeigt es auch der CD-Quickguide. Ohne den Satz würde die neue Fähigkeit vermutlich überstrapaziert.

Die beiden anderen Punkte der Liste sind bestätigt und bleiben: Schriftgröße entsteht ausschließlich über Skalierung, Farben nur aus der Auswahl.

Verbleibende „geht nicht"-Liste: keine Schriftgröße in Punkt, keine freien Farben, kein Textrahmen und keine Hintergrundfläche hinter dem Text.

VERSION auf 5. 132 Jest grün, 86 e2e grün.

https://claude.ai/code/session_01Qtib9MPRZATQU3j9w7ymDk

Systematische Bestandsaufnahme aller Bedienelemente statt aus dem
Gedaechtnis. Ergebnis: Die Fassade konnte hinzufuegen, aber nichts mit
dem Hinzugefuegten anfangen. In der Oberflaeche klickt man ein Objekt an
und bedient dann die Regler — ueber die Schnittstelle ging das gar nicht.

Neu:

- objects() listet alles auf dem Canvas mit Index, Typ, Rolle, Inhalt,
  Position, Groesse, Winkel und ob die Schutzzone eingehalten ist.
- select(index) spricht ein Element an.
- update({...}) aendert Text, Farbe, Ausrichtung, Zeilenabstand, Schatten
  und Schrift eines gesetzten Elements — samt initDimensions(), sonst
  behaelt Fabric die alten Textmasse und der neue Text laeuft ueber.
- remove(index) loescht; Logo und Hintergrund sind geschuetzt, genau wie
  der Loeschknopf sie schuetzt.
- bringToFront(index) ordnet um und laesst das Logo oben.
- qrPayload(spec) baut die vier Inhaltstypen des QR-Assistenten. Bisher
  verlangten renderQRCode() und addQRCode() eine fertige Zeichenkette —
  ein Modell haette mailto:-Parameter und vCard-Records selbst
  zusammensetzen muessen, obwohl die Formatierungsregeln im Werkzeug
  stecken (QRFormatHelpers).

e2e/api-coverage.spec.js haelt die Fassade an ihrem Versprechen: Es
laeuft die Bedienelemente des Assistenten ab und prueft fuer jedes eine
Entsprechung, vergleicht jeden <option>-Wert gegen options() und prueft,
dass jeder QR-Inhaltstyp baubar ist. Ohne so einen Test verfaellt das
Versprechen lautlos — ein neuer Knopf waere fuer Aufrufer unerreichbar,
und es fiele erst auf, wenn jemand danach fragt.

Der Beispieltest hat wieder vier Fragmente ohne Kontext gefunden; alle 13
Codebloecke der llms.txt laufen jetzt gegen die echte Seite.

Die fest verdrahtete Versionsnummer in api.spec.js ist raus — die
Uebereinstimmung von Code und Dokument prueft llms-txt.spec.js, eine
zweite Stelle waere nur eine zweite zum Vergessen.

Nebenbefund, nicht angefasst: #scale-direction, #stroke-width und
#opacity haben gebundene Handler, aber die Elemente existieren im HTML
nicht. Der opacity-Wert wird beim Bildhinzufuegen gelesen und ist
undefined.

VERSION auf 6.

Claude-Session: https://claude.ai/code/session_01Qtib9MPRZATQU3j9w7ymDk
@flomotlik

Copy link
Copy Markdown
Contributor Author

Nachtrag: vollständige Abdeckung

Systematische Bestandsaufnahme aller Bedienelemente, statt aus dem Gedächtnis zu arbeiten. Ergebnis: Die Fassade konnte hinzufügen, aber nichts mit dem Hinzugefügten anfangen. In der Oberfläche klickt man ein Objekt an und bedient dann die Regler — über die Schnittstelle ging das gar nicht.

Neu Entspricht in der Oberfläche
objects() hinsehen, was auf dem Bild liegt
select(index) ein Objekt anklicken
update({...}) Farbe, Ausrichtung, Zeilenabstand, Schatten, Schrift, Text ändern
remove(index) „Element entfernen"
bringToFront(index) „Nach vorne holen"
qrPayload(spec) die vier Inhaltstypen des QR-Assistenten

update() ruft initDimensions() nach — sonst behält Fabric die alten Textmaße und der neue Text läuft über seinen Kasten hinaus. Ein eigener Test hält das fest.

remove() schützt Logo und Hintergrund, genau wie der Löschknopf sie schützt.

Beim QR-Generator war es besonders schief: Die Oberfläche bietet Text, URL, E-Mail und vCard als Formulare an, die Fassade verlangte eine fertige Zeichenkette. Ein Modell hätte mailto:-Parameter und vCard-Records selbst zusammensetzen müssen, obwohl die Formatierungsregeln längst im Werkzeug stecken (QRFormatHelpers).

Der Test, der das Versprechen hält

e2e/api-coverage.spec.js läuft die Bedienelemente des Assistenten ab und prüft für jedes, ob es eine Entsprechung auf der Fassade gibt. Dazu: Jeder <option>-Wert der Oberfläche muss in options() auftauchen, und jeder QR-Inhaltstyp muss baubar sein.

Ohne so einen Test verfällt das Versprechen „alles, was die Seite kann" lautlos. Ein neuer Knopf in der Oberfläche wäre für Aufrufer unerreichbar, und es fiele erst auf, wenn jemand danach fragt — genau so ist diese Lücke ja aufgefallen.

Der Beispieltest hat wieder zugeschlagen: vier neue Codeblöcke waren Fragmente ohne Kontext. Alle 13 laufen jetzt gegen die echte Seite.

Aufgeräumt

Die fest verdrahtete Versionsnummer in api.spec.js ist raus. Die Übereinstimmung von Code und Dokument prüft llms-txt.spec.js — eine zweite Stelle wäre nur eine zweite zum Vergessen. Genau das war eben passiert.

Nebenbefund, nicht angefasst

#scale-direction, #stroke-width und #opacity haben gebundene Ereignisbehandler, aber die Elemente existieren im HTML nicht. Der opacity-Wert wird beim Bildhinzufügen gelesen und ist undefined. Altlast, gehört nicht in diesen PR.

VERSION auf 6. 132 Jest grün, 104 e2e grün.

https://claude.ai/code/session_01Qtib9MPRZATQU3j9w7ymDk

@flomotlik
flomotlik merged commit 9acf5d3 into main Sep 25, 2026
7 checks passed
@flomotlik
flomotlik deleted the feature/llms-txt-luecken-schliessen branch September 25, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant