From 1b8bf06973077c85102d05355d1e4d776c184a28 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Sun, 23 Aug 2026 14:20:25 +0200 Subject: [PATCH] change: Textfeld bricht Zeilen nicht mehr um, waagerechter Balken (D49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gewünscht — und damit die ausdrückliche Umkehrung der D33-Abwägung („Verworfen: wrap=off … der Umbruch ist die wichtigere Eigenschaft"). Der dort notierte Preis bleibt richtig; nicht bedacht war die andere Seite: Ein weicher Umbruch zerstört die Einrückung, und die trägt in dieser Notation die Hierarchie. Die Fortsetzung einer umbrochenen Zeile beginnt am linken Rand und sieht aus wie ein Wurzelknoten — betroffen sind genau die langen Zeilen, also die mit ID, Größe und Kommentar, seit D48 praktisch jede Zeile des Plans. Umgesetzt als `wrap="off"` am Element (Eigenschaft des Feldes, nicht seiner Gestaltung), `overflow:auto` daneben im Stylesheet. Der Spiegel musste mit: `syncMirror()` maß mit `white-space:pre-wrap` und fester Breite — genau die Kombination, die im Spiegel Zeilen umbräche, die im Textfeld ungebrochen stehen; alles darunter läge zu tief und die Zeilennummern wanderten weg. Jetzt `pre` und ohne Breitenvorgabe. Gemessen wird trotzdem weiter statt `Zeilenhöhe × n` zu rechnen: Die Schriftgröße unterscheidet sich zwischen Telefon und Schreibtisch, die Messung stimmt in beiden Fällen von selbst. Der Sprung setzt `scrollLeft` zurück. `jumpToLine()` markiert die ganze Zeile, woraufhin der Browser von sich aus an deren Ende scrollt — man landete am rechten Rand, ohne Einrückung, Zeichen und Statusbox zu sehen. Der Sprung zeigt auf eine Zeile, nicht auf ihr Ende. Nachgemessen am Plan (903 Zeilen, längste 122 Zeichen): 903 Bildzeilen zu 903 logischen Zeilen in beiden Ansichten; Schiebeweg 211 px am Schreibtisch, 432 px auf 375 px Breite; der Zeilennummern-Streifen bleibt beim waagerechten Verschieben stehen (linke Kante unverändert bei 180 px Versatz); Abweichung der Zahlen am Dateiende 2 px auf 903 Zeilen (Rundung von `offsetTop`, sammelt sich nicht auf). 236 Tests grün. SPEC §9 (Zeilennummern) und D49 halten es fest; die D33-Stelle bekommt einen Verweis, dass die Abwägung umgekehrt wurde — gelöscht wird dort nichts. Co-Authored-By: Claude Fable 5 --- docs/DECISIONS.md | 67 ++++++++++++++++++++++++++++++++++++++++++ docs/SPEC.md | 12 ++++++-- frontend/index.html | 6 +++- frontend/src/app.js | 35 +++++++++++++--------- frontend/src/style.css | 16 +++++++--- 5 files changed, 114 insertions(+), 22 deletions(-) diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index a6f8236..186afc7 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -1829,6 +1829,10 @@ waagerechtes Scrollen — im schmalen Spiegel neben einem eingebetteten Pad (D31 oder auf dem Telefon (D17) wäre der Text damit unlesbar. Der Umbruch ist die wichtigere Eigenschaft. +*(Diese Abwägung ist später umgekehrt worden: **D49** schaltet den Umbruch ab. +Übersehen war, was ein weicher Umbruch mit der **Einrückung** macht — und die +trägt hier die Hierarchie.)* + **Kein Umschalter.** Die Kopfzeile ist eng (D17), und ein Bedienelement kostet i18n in neun Sprachen für etwas, das keinen Zustand hat, den jemand pflegen will. Der Streifen ist so schmal wie die Ziffern es verlangen @@ -3117,3 +3121,66 @@ Beschreibung, **0 Warnungen**, Pfad weiterhin exakt gerechnet mit 24 Stationen. Der Cursor in einem Beschreibungsblock wählt den beschriebenen Knoten aus (D40-Nachtrag 2) — auf Kopf- wie Textzeile geprüft; der `---`-Trenner selbst wählt nichts. + +## D49 — Das Textfeld bricht nicht mehr um: `wrap="off"` plus waagerechter Balken +Gewünscht vom Nutzer, und damit die ausdrückliche Umkehrung der D33-Abwägung +(„Verworfen: `wrap=off` … der Umbruch ist die wichtigere Eigenschaft"). Der +dort notierte Preis bleibt richtig und wird in Kauf genommen; was dort **nicht** +bedacht war, ist die andere Seite: + +**Ein weicher Umbruch zerstört die Einrückung — und die trägt hier die +Hierarchie.** In dieser Notation ist der linke Rand keine Formsache, sondern +die Ebene (§2). Bricht eine Zeile um, beginnt ihre Fortsetzung am linken Rand +und sieht damit aus wie ein Wurzelknoten; wer die Struktur überfliegt, liest +eine Ebene, die es nicht gibt. Das trifft genau die langen Zeilen, also die mit +ID, Größe und Kommentar — und seit D48 ist das praktisch jede Zeile des +mitgelieferten Plans. Waagerechtes Scrollen kostet Bequemlichkeit; der Umbruch +kostete Lesbarkeit der Struktur. + +**Umgesetzt am Element, nicht in CSS:** `wrap="off"` am ` + + diff --git a/frontend/src/app.js b/frontend/src/app.js index fef3c78..48c52f4 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -701,22 +701,23 @@ function lineRange(line){ return {start, end: start + lines[line - 1].length}; } -/* Vertikale Position eines Zeichenoffsets im Textfeld. Zeilenhöhe × n scheitert - an weichen Umbrüchen (lange Zeilen belegen mehrere Bildzeilen), deshalb ein - unsichtbarer Spiegel mit gleicher Typografie und Breite plus Marker-Span. */ +/* Vertikale Position eines Zeichenoffsets im Textfeld: ein unsichtbarer Spiegel + mit gleicher Typografie plus Marker-Span. + + Seit `wrap="off"` (D49) bricht das Textfeld nicht mehr um — der Spiegel + deshalb ebenfalls `white-space:pre` und **ohne** vorgegebene Breite. Beides + gehört zusammen: Eine feste Breite bei `pre-wrap` bräche im Spiegel Zeilen um, + die im Textfeld ungebrochen stehen, und alles darunter läge zu tief. + Gemessen wird trotzdem weiter, statt `Zeilenhöhe × n` zu rechnen — die + Schriftgröße unterscheidet sich zwischen Telefon und Schreibtisch (D17), und + die Messung stimmt in beiden Fällen von selbst. */ let mirrorEl = null; function syncMirror(){ if(!mirrorEl){ mirrorEl = document.createElement('div'); mirrorEl.setAttribute('aria-hidden', 'true'); - /* `box-sizing:border-box` ist Pflicht: `src.clientWidth` **enthält** die - Innenabstände. Ohne das ist der Spiegel um genau diese 32 px breiter als - das Textfeld und bricht später um — lange Zeilen landeten dadurch zu weit - oben (fiel beim Bau der Zeilennummern auf, D33; betraf auch schon das - Scrollen beim Sprung, D25). */ mirrorEl.style.cssText = 'position:absolute;visibility:hidden;top:0;left:-9999px;' + - 'box-sizing:border-box;' + - 'white-space:pre-wrap;overflow-wrap:break-word;'; + 'box-sizing:border-box;white-space:pre;'; document.body.appendChild(mirrorEl); } const cs = getComputedStyle(src); @@ -724,7 +725,6 @@ function syncMirror(){ 'paddingTop','paddingLeft','paddingRight','borderTopWidth','tabSize']){ mirrorEl.style[p] = cs[p]; } - mirrorEl.style.width = src.clientWidth + 'px'; return mirrorEl; } const ZWSP = '​'; @@ -804,9 +804,11 @@ function markCurrentLineNo(){ if(s) s.classList.add('cur'); } src.addEventListener('scroll', syncLineNoScroll); -/* Der Umbruch hängt an der Breite: Splitter, Fenster, Drehung, Tastatur. Beim - Ziehen am Splitter kämen sonst je Bild mehrere Messungen — einmal je Bild - genügt. */ +/* Seit D49 hängen die Zeilenhöhen nicht mehr an der Breite (es wird nicht mehr + umbrochen), wohl aber an der Schriftgröße — und die wechselt beim Übergang + zwischen Telefon und Schreibtisch (D17), den der Beobachter als + Größenänderung mitbekommt. Beim Ziehen am Splitter kämen sonst je Bild + mehrere Messungen; einmal je Bild genügt. */ let lineNoPending = false; if(window.ResizeObserver) new ResizeObserver(() => { if(lineNoPending) return; @@ -868,6 +870,11 @@ function jumpToLine(line){ src.focus({preventScroll: true}); src.setSelectionRange(r.start, r.end); scrollEditorToOffset(r.start); + /* Ohne Umbruch (D49) scrollt der Browser beim Markieren einer langen Zeile + bis an ihr **Ende** — man landete am rechten Rand und sähe den Anfang der + Zeile nicht, also gerade Einrückung, Zeichen und Statusbox. Der Sprung + zeigt auf eine Zeile, nicht auf ihr Ende. */ + src.scrollLeft = 0; caretLine = line; highlightCurrentNode(true); } diff --git a/frontend/src/style.css b/frontend/src/style.css index f566510..66535f6 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -467,9 +467,13 @@ .docdel:hover{border-color:#D897A8;color:#B4415E} .togglebtn[aria-pressed="true"]{background:var(--or);color:#fff;border-color:var(--or)} .togglebtn[aria-pressed="true"]:hover{color:#fff} + /* Kein Zeilenumbruch (D49, `wrap="off"` am Element): Die Einrückung trägt die + Hierarchie, und ein weicher Umbruch setzt die Fortsetzung an den linken Rand + — die Ebene wäre damit gerade dort nicht mehr abzulesen, wo eine Zeile lang + ist. Lange Zeilen bekommen stattdessen einen waagerechten Balken. */ textarea{ display:block;width:100%;min-height:0; - border:0;outline:none;resize:none; + border:0;outline:none;resize:none;overflow:auto; padding:14px 16px; font-family:'IBM Plex Mono',monospace; font-size:.85rem;line-height:1.7; @@ -482,9 +486,13 @@ ist der Hauptinhalt und kein ausgegrautes Formularfeld. */ textarea.readonly{background:#F1F4F7;cursor:default} /* Zeilennummern (D33). Die Warnungen nennen Zeilennummern — ohne Streifen - muss man sie im Textfeld abzählen. Weich umbrochene Zeilen belegen mehrere - Bildzeilen, deshalb steht jede Zahl auf einer **gemessenen** Höhe - (`renderLineNos()` in app.js), nicht auf „Zeilenhöhe × n". Typografie muss + muss man sie im Textfeld abzählen. Jede Zahl steht auf einer **gemessenen** + Höhe (`renderLineNos()` in app.js), nicht auf „Zeilenhöhe × n"; seit D49 + bricht nichts mehr um, aber die Schriftgröße unterscheidet sich zwischen + Telefon und Schreibtisch, und die Messung stimmt in beiden Fällen von + selbst. Der Streifen scrollt **nur senkrecht** mit: Er verschiebt sich + gegen `src.scrollTop`, den waagerechten Balken des Textfelds macht er + nicht mit — die Zahlen bleiben stehen, wo sie hingehören. Typografie muss mit dem Textfeld übereinstimmen, sonst wandern die Zahlen weg. */ .lineno{ position:relative;overflow:hidden;flex:0 0 auto;