change: Textfeld bricht Zeilen nicht mehr um, waagerechter Balken (D49)

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 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 14:20:25 +02:00
co-authored by Claude Fable 5
parent 623b68799f
commit 1b8bf06973
5 changed files with 114 additions and 22 deletions
+5 -1
View File
@@ -98,7 +98,11 @@
Rahmen gegen diese Spalte, nicht gegen das nackte Textfeld. -->
<div class="src-wrap" id="srcWrap">
<div class="lineno" id="lineNos" aria-hidden="true"><div class="lineno-inner"></div></div>
<textarea id="src" spellcheck="false" data-i18n-aria="editorTitle" aria-label="Struktur (Text)"></textarea>
<!-- `wrap="off"`: Zeilen werden nicht umbrochen, lange Zeilen bekommen
einen waagerechten Balken (D49). Die Einrückung trägt hier die
Hierarchie — ein weicher Umbruch setzt die Fortsetzung an den linken
Rand und lässt die Ebene verschwinden. -->
<textarea id="src" wrap="off" spellcheck="false" data-i18n-aria="editorTitle" aria-label="Struktur (Text)"></textarea>
</div>
</div>
<div class="hint-gutter" id="hintGutter" role="separator" data-i18n-title="gutterTooltip" data-i18n-aria="hintGutterAria" title="Ziehen zum Verschieben, Doppelklick setzt zurück" aria-label="Editor und Legende größenverändern" hidden></div>
+21 -14
View File
@@ -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);
}
+12 -4
View File
@@ -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;