From 6463a76e1eb29f997989f1cedb6812c37491d0d2 Mon Sep 17 00:00:00 2001 From: mhoennig Date: Sun, 23 Aug 2026 08:19:46 +0200 Subject: [PATCH] fix: Bildschirmtastatur quetscht den Editor nicht mehr zusammen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die eigentliche Ursache der Meldung — meine erste Diagnose (Titelzeilen-Tipp im alten Splitter-Modell) war für 1.1.39 richtig, aber nicht das, was der Nutzer auf dem Pages-Build sah. setAppHeight() schreibt visualViewport.height nach --app-height, und body{height:var(--app-height)} macht daraus die Höhe der ganzen Seite. Genau diesen Wert verkleinert die Bildschirmtastatur — dafür ist visualViewport gemacht. Nachgemessen linear: Textfeldhöhe = --app-height minus ~206 px feste Aufbauten. 812 px -> 606 px Text, 440 -> 234, 260 -> 54 px, also drei Zeilen. Das ist das gemeldete „komplett zusammengeklappt". Sichtbar ausschließlich auf echten Geräten: In der Emulation gibt es keine Tastatur, deshalb liefen alle drei Reproduktionsversuche vorher ins Leere. Unterschieden wird am Fokus, nicht an der Größe: Tastatur und überlagernde Browserleiste (Brave, der ursprüngliche Anlass des Mechanismus) erzeugen dieselbe Signatur — vv.height fällt, innerHeight bleibt. Die Tastatur steht aber nur, wenn ein editierbares Feld den Fokus hat. Solange das so ist, bleibt die letzte tastaturfreie Höhe stehen; die Seite behält ihre Größe und der Browser schiebt den Ausschnitt zur Schreibmarke. Mit nachgebildetem visualViewport geprüft: mit Fokus im Textfeld 812/606 px (unverändert), ohne Fokus 440/234 px — die Brave-Leiste wirkt also weiter. orientationchange durchbricht die Sperre (sonst behielte die Seite beim Drehen während des Tippens das alte Hochformat); der focusout-Timer ruft () => setAppHeight(), sonst reichte er ein wahres `force` durch. D17 (Nachtrag 4) und frontend/CLAUDE.md nachgezogen, samt der Lehre: Ein „nicht reproduzierbar" aus dem Emulator ist bei geräteabhängigen Meldungen kein Befund (vgl. D25 zu synthetischen TouchEvents). Co-Authored-By: Claude Fable 5 --- docs/DECISIONS.md | 50 +++++++++++++++++++++++++++++++++++++++++++++ frontend/CLAUDE.md | 12 +++++++++++ frontend/src/app.js | 36 +++++++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 6459434..ac4013f 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -292,6 +292,12 @@ Es gibt keinen Splitter und kein Titelzeilen-Tippen mehr, den Bereich wechselt auslösen (Maus-Klick, emulierter Touch und synthetische Touch-Folge, jeweils ohne Bereichswechsel). +**Das war aber nur die halbe Wahrheit — siehe Nachtrag 4.** Der Nutzer meldete +denselben Fehler auf dem Pages-Build, der das Ein-Bereich-Modell bereits +enthielt. Die obige Diagnose war für die alte Fassung richtig und trotzdem +nicht die Ursache seiner Beobachtung; die eigentliche stand woanders und war +mit Emulation grundsätzlich nicht zu finden. + Der zweite Teil der Meldung deckte aber eine echte Lücke auf: Für die Richtung **Text → Diagramm** gibt es nur Alt+Klick bzw. Alt+Enter (D25) — und Alt gibt es auf dem Telefon nicht. Die Gegenrichtung hat dort ihren langen Druck, diese @@ -315,6 +321,50 @@ Geste und kann nicht versehentlich auslösen. Steht der Cursor auf einer Zeile ohne Knoten (Kommentar, Leerzeile), wird nur umgeschaltet — dieselbe stille Regel wie überall sonst bei dieser Geste. +**Nachtrag 4 — die eigentliche Ursache: `--app-height` folgte der +Bildschirmtastatur.** Nach Nachtrag 3 blieb der Fehler auf dem Pages-Build +bestehen („dort klappt der Text komplett zusammen, sobald ich irgendwo den +Cursor reinsetze"). Gefunden über die einzige Frage, die nach zwei +fehlgeschlagenen Emulations-Versuchen noch trägt: **Was ist auf einem echten +Telefon anders?** Antwort: die Tastatur. + +`setAppHeight()` schreibt `window.visualViewport.height` nach `--app-height`, +und `body{height:var(--app-height)}` macht daraus die Höhe der ganzen Seite. +Genau diesen Wert verkleinert die Bildschirmtastatur — dafür ist +`visualViewport` gemacht. Nachgemessen ist der Zusammenhang linear: +Textfeldhöhe = `--app-height` − rund 206 px feste Aufbauten (Kopfzeile 57, +Titelzeile 44, Fußzeile 36, Innenabstände). Bei 812 px bleiben 606 px Text, +bei 440 px noch 234, bei 260 px — realistisch für ein kleines Gerät mit +Tastatur und Browserleiste — nur **54 px**, also drei Zeilen. Das ist das +gemeldete „komplett zusammengeklappt", und es tritt **ausschließlich auf +echten Geräten** auf: In der Emulation gibt es keine Tastatur, deshalb liefen +alle drei Reproduktionsversuche aus Nachtrag 3 ins Leere. + +**Unterschieden wird am Fokus, nicht an der Größe.** Die Tastatur und eine +überlagernde Browserleiste (Brave — der ursprüngliche Anlass des Mechanismus) +erzeugen dieselbe Signatur: `visualViewport.height` fällt, `innerHeight` +bleibt. An den Zahlen sind sie nicht zu trennen. Am Zustand schon: Die +Tastatur steht nur, wenn ein **editierbares** Feld den Fokus hat. Solange das +so ist, bleibt die zuletzt tastaturfreie Höhe stehen. Die Seite behält damit +ihre Größe, und der Browser schiebt den sichtbaren Ausschnitt zur +Schreibmarke — das Verhalten jeder anderen App. Nachgemessen mit +nachgebildetem `visualViewport`: mit Fokus im Textfeld 812/606 px (unverändert), +ohne Fokus 440/234 px (die Brave-Leiste wirkt also weiter). + +**Drehen muss die Sperre durchbrechen** (`setAppHeight(true)` bei +`orientationchange`) — sonst behielte die Seite beim Drehen während des +Tippens die Höhe des alten Hochformats. Dazu ein `focusout`-Nachzug für +Browser, die das Schließen der Tastatur nicht als `resize` melden. Und der +Timer dort ruft `() => setAppHeight()`, nicht `setAppHeight` direkt: Ein +durchgereichtes Argument wäre wahr und hebelte die Fokus-Sperre aus. + +**Lehre, schon zweimal bezahlt:** D25 hielt fest, dass synthetische +`TouchEvent`s nur die eigene Ereignis-Logik beweisen. Dieselbe Grenze gilt für +alles, was die **Geräteumgebung** stellt — Bildschirmtastatur, Browserleisten, +Nutzergesten-Regeln. Ein „lässt sich nicht reproduzieren" aus dem Emulator ist +bei solchen Meldungen kein Befund, sondern nur die Feststellung, dass das +Werkzeug die Ursache nicht enthält. + **Das Debug-Panel minimiert sich jetzt, statt sich zu schließen.** Ein Klick entfernte es bisher ganz — was nichts half, weil der 15-Sekunden-Takt es sofort wieder aufbaute; auf dem Telefon verdeckte es damit dauerhaft die untere rechte diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 5fd4a91..4e45ff4 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -398,6 +398,18 @@ verworfene Elemente. Quelle sind ES-Module unter `src/`; `index.html` ist der **einzige** Touch-Navigation Text → Diagramm (Alt gibt es dort nicht). Keinen langen Druck ins Textfeld legen — der gehört dem OS (Wortauswahl, Auswahlgriffe), anders als im Diagramm, wo D25 ihn sich nehmen konnte. +- `--app-height` (`setAppHeight()`, D17-Nachtrag 4): `body{height:var(--app-height)}` + kommt aus `visualViewport.height` — nötig gegen überlagernde Browserleisten + (Brave). **Die Bildschirmtastatur verkleinert denselben Wert** und quetschte + damit den Editor zusammen, sobald der Cursor ins Textfeld kam (Textfeldhöhe = + `--app-height` − ~206 px feste Aufbauten; bei 260 px Viewport bleiben 54 px). + Deshalb die Fokus-Sperre `editingNow()`: Solange ein editierbares Feld den + Fokus hat, bleibt die letzte tastaturfreie Höhe stehen. Tastatur und + Browserleiste sind an den **Zahlen** nicht zu unterscheiden (beide: `vv.height` + fällt, `innerHeight` bleibt) — nur am Fokus. `orientationchange` ruft + `setAppHeight(true)` und durchbricht die Sperre; der `focusout`-Timer muss + `() => setAppHeight()` sein, sonst reicht er ein wahres `force` durch. + **Nur auf echten Geräten sichtbar** — in der Emulation gibt es keine Tastatur. **Der verborgene Bereich ist `display:none` und misst sich damit zu null** — `setMobilePane()` MUSS neu zeichnen: zum Diagramm hin dieselben vier Schritte wie `applyLayout` (`applyOptStairs`/`alignStems`/`drawCheapPath`/ diff --git a/frontend/src/app.js b/frontend/src/app.js index a3eadf4..36d0e7b 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -2862,17 +2862,43 @@ function isMobile(){ return mqMobile.matches; } zeichnen: 100dvh meldet dann weiterhin die volle Höhe, und Footer/Editor- Titelzeile verschwinden dahinter. visualViewport.height liefert die wirklich sichtbare Fläche. Fällt es weg, greift die CSS-Kaskade (dvh/vh). */ -function setAppHeight(){ +/* ABER: Die **Bildschirmtastatur** verkleinert genau diesen Wert — dafür ist + `visualViewport` gemacht. Ungefiltert übernommen quetscht sie den Editor + zusammen, sobald man den Cursor ins Textfeld setzt: Die Textfeldhöhe ist + `--app-height` minus rund 206 px feste Aufbauten (Kopfzeile, Titelzeile, + Fußzeile), aus 812 px werden also mit offener Tastatur schnell 260 px und + damit ein Textfeld von 54 px. Genau das war die Fehlermeldung — und sie + zeigt sich **nur auf echten Geräten**, weil es in der Emulation keine + Tastatur gibt. + Unterschieden wird am **Fokus**: Die Tastatur steht nur, wenn ein + editierbares Feld den Fokus hat. Solange das so ist, bleibt die zuletzt + tastaturfreie Höhe stehen — die Seite behält ihre Größe und der Browser + schiebt den sichtbaren Ausschnitt zur Schreibmarke, wie in jeder anderen + App. Die Brave-Leiste (der eigentliche Anlass) wird davon nicht berührt, + sie erscheint ohne Fokus im Textfeld. */ +function editingNow(){ + const el = document.activeElement; + if(!el) return false; + return el.tagName === 'TEXTAREA' || el.tagName === 'INPUT' || el.isContentEditable; +} +function setAppHeight(force){ + if(!force && editingNow()) return; const vv = window.visualViewport; const h = vv ? vv.height : window.innerHeight; document.documentElement.style.setProperty('--app-height', Math.round(h) + 'px'); } -setAppHeight(); -window.addEventListener('resize', setAppHeight); -window.addEventListener('orientationchange', setAppHeight); +setAppHeight(true); +window.addEventListener('resize', () => setAppHeight()); +/* Drehen MUSS auch beim Tippen greifen — sonst behielte die Seite die Höhe des + alten Hochformats. */ +window.addEventListener('orientationchange', () => setAppHeight(true)); if(window.visualViewport){ - window.visualViewport.addEventListener('resize', setAppHeight); + window.visualViewport.addEventListener('resize', () => setAppHeight()); } +/* Nach dem Verlassen des Textfelds nachziehen: Die meisten Browser melden das + Schließen der Tastatur ohnehin als `resize`, aber dann steht der Fokus schon + woanders — und falls die Meldung ausbleibt, holt es dieser Weg nach. */ +window.addEventListener('focusout', () => setTimeout(() => setAppHeight(), 250)); /* ---------- Legende: auf/zu + Splitter zum Editor (D26) ---------- Die Legende ist ein gewöhnlicher Container (kein
mehr, siehe style.css/D26); der Auf-/Zu-Zustand hängt an der Klasse `open`. */