fix: Bildschirmtastatur quetscht den Editor nicht mehr zusammen

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 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-23 08:19:46 +02:00
co-authored by Claude Fable 5
parent 1bda54ae59
commit 6463a76e1e
3 changed files with 93 additions and 5 deletions
+50
View File
@@ -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 auslösen (Maus-Klick, emulierter Touch und synthetische Touch-Folge, jeweils
ohne Bereichswechsel). 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 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 **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 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 ohne Knoten (Kommentar, Leerzeile), wird nur umgeschaltet — dieselbe stille
Regel wie überall sonst bei dieser Geste. 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 **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 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 wieder aufbaute; auf dem Telefon verdeckte es damit dauerhaft die untere rechte
+12
View File
@@ -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 **einzige** Touch-Navigation Text → Diagramm (Alt gibt es dort nicht). Keinen
langen Druck ins Textfeld legen — der gehört dem OS (Wortauswahl, langen Druck ins Textfeld legen — der gehört dem OS (Wortauswahl,
Auswahlgriffe), anders als im Diagramm, wo D25 ihn sich nehmen konnte. 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** **Der verborgene Bereich ist `display:none` und misst sich damit zu null**
`setMobilePane()` MUSS neu zeichnen: zum Diagramm hin dieselben vier Schritte `setMobilePane()` MUSS neu zeichnen: zum Diagramm hin dieselben vier Schritte
wie `applyLayout` (`applyOptStairs`/`alignStems`/`drawCheapPath`/ wie `applyLayout` (`applyOptStairs`/`alignStems`/`drawCheapPath`/
+31 -5
View File
@@ -2862,17 +2862,43 @@ function isMobile(){ return mqMobile.matches; }
zeichnen: 100dvh meldet dann weiterhin die volle Höhe, und Footer/Editor- zeichnen: 100dvh meldet dann weiterhin die volle Höhe, und Footer/Editor-
Titelzeile verschwinden dahinter. visualViewport.height liefert die wirklich Titelzeile verschwinden dahinter. visualViewport.height liefert die wirklich
sichtbare Fläche. Fällt es weg, greift die CSS-Kaskade (dvh/vh). */ 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 vv = window.visualViewport;
const h = vv ? vv.height : window.innerHeight; const h = vv ? vv.height : window.innerHeight;
document.documentElement.style.setProperty('--app-height', Math.round(h) + 'px'); document.documentElement.style.setProperty('--app-height', Math.round(h) + 'px');
} }
setAppHeight(); setAppHeight(true);
window.addEventListener('resize', setAppHeight); window.addEventListener('resize', () => setAppHeight());
window.addEventListener('orientationchange', 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){ 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) ---------- /* ---------- Legende: auf/zu + Splitter zum Editor (D26) ----------
Die Legende ist ein gewöhnlicher Container (kein <details> mehr, siehe Die Legende ist ein gewöhnlicher Container (kein <details> mehr, siehe
style.css/D26); der Auf-/Zu-Zustand hängt an der Klasse `open`. */ style.css/D26); der Auf-/Zu-Zustand hängt an der Klasse `open`. */