Kundenansicht neu: Leiste im Chat am iPhone wieder ganz unten

Die Messung am iPhone (iOS 26.2, App vom Home-Bildschirm) zeigt die
Ursache: Auf der Startseite ist das Fenster 1002 px hoch. Im Chat
schrumpft die Spalte auf den Kopf (alles andere steht fest), die Seite
wird kürzer als der Bildschirm, und iOS hält unten rund 60 pt für eine
Safari-Leiste frei, die es in der App nicht gibt: 932 px, Leiste und
Eingabe stehen zu hoch, darunter ein weißer Streifen. Mit der Tastatur
hat das nichts zu tun.

Am Handy hat die Spalte im Chat jetzt wie in den anderen Bereichen
mindestens volle Bildschirmhöhe (100vh, dann 100lvh). Ohne
Browserleisten scrollt die Seite dadurch nicht; in Chromium sind alle
Chat-Bilder am Handy und Desktop pixelgleich zu main.

Der Höhen-Wächter aus 0fa8fdb (Hülle aus- und einblenden) hat nicht
geholfen und ist wieder raus. Die Messung bleibt für die Kontrolle am
iPhone und wird danach entfernt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-29 11:21:13 +00:00
1 parent cca29a4aef
commit 17bce304eb
6 files changed
+28 -224

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { lies, handyTeil } from './support/knHuelle.js'
// iPhone-App vom Home-Bildschirm (Messung 2026-10-29): ist die Seite im Chat kürzer als der Bildschirm, macht iOS 26 das Fenster rund 60 pt kleiner
// und die Leiste steht zu hoch. Am Handy muss die Spalte im Chat daher mindestens volle Bildschirmhöhe haben, wie in den anderen Bereichen.
test('huelle.css: am Handy hat die Spalte im Chat mindestens volle Höhe (100vh, dann 100lvh), am Desktop bleibt min-height 0', () => {
const css = lies('huelle.css')
const handy = handyTeil(css)
assert.match(handy, /\.kn-spalte:has\(#bereich-chat:not\(\[hidden\]\)\) \{ display: block; height: auto; min-height: 100vh; min-height: 100lvh; \}/)
const desktop = css.slice(0, css.indexOf('.kn-spalte:has(#bereich-chat:not([hidden])) { display: block'))
assert.match(desktop, /\.kn-spalte:has\(#bereich-chat:not\(\[hidden\]\)\) \{ display: flex;[^}]*min-height: 0; \}/)
assert.match(css, /\.kn-spalte \{ position: relative; min-height: 100vh;/)
})
test('huelle.js: der alte Höhen-Wächter (Hülle aus- und einblenden) ist weg, die Messung läuft nur am iPhone und nicht in der Mitarbeiter-Ansicht', () => {
const js = lies('huelle.js')
assert.doesNotMatch(js, /beobachteHoehe|'\.\/hoehe\.js'/)
assert.match(js, /if \(istIos\(\) && istHandy\(\) && !viewAs\) starteDiagHoehe\(\{ win, doc \}\)/)
})
-122
View File
@@ -1,122 +0,0 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { LUFT, MAX_VERSUCHE, PRUEF_MS, beobachteHoehe, haengt, hoeheZustand } from '../public/neu/hoehe.js'
import { lies } from './support/knHuelle.js'
test('hoeheZustand: größte Höhe je Breite, Drehen fängt neu an', () => {
let z = hoeheZustand(null, { hoehe: 932, breite: 430 })
z = hoeheZustand(z, { hoehe: 873, breite: 430 }); assert.deepEqual(z, { voll: 932, breite: 430 })
z = hoeheZustand(z, { hoehe: 430, breite: 932 }); assert.deepEqual(z, { voll: 430, breite: 932 })
})
test('haengt: nur bei gleicher Breite, ohne Textfeld und mehr als LUFT kleiner (iOS-Fall 932 → 873)', () => {
const z = { voll: 932, breite: 430 }
assert.equal(haengt(z, { hoehe: 873, breite: 430, textfeldAktiv: false }), true)
assert.equal(haengt(z, { hoehe: 873, breite: 430, textfeldAktiv: true }), false) // Tastatur offen
assert.equal(haengt(z, { hoehe: 932 - LUFT, breite: 430, textfeldAktiv: false }), false)
assert.equal(haengt(z, { hoehe: 400, breite: 932, textfeldAktiv: false }), false) // gedreht
assert.equal(haengt(null, { hoehe: 1, breite: 1 }), false)
})
/** Kleine Attrappe: Fenster, Dokument und eine Hülle, die mitschreibt, wann sie aus- und eingeblendet wird. */
function aufbau({ aktiv = () => true } = {}) {
const sender = (felder = {}) => {
const hoerer = {}
return Object.assign({
addEventListener(typ, fn) { (hoerer[typ] ??= []).push(fn) },
removeEventListener(typ, fn) { hoerer[typ] = (hoerer[typ] ?? []).filter((x) => x !== fn) },
senden(typ) { for (const fn of hoerer[typ] ?? []) fn({ type: typ }) },
anzahl: (typ) => (hoerer[typ] ?? []).length,
}, felder)
}
const spaeter = []
const protokoll = []
const win = sender({
innerHeight: 932, innerWidth: 430, scrollX: 0, scrollY: 0,
setTimeout: (fn, ms) => { spaeter.push({ fn, ms }); return spaeter.length },
clearTimeout: (id) => { if (spaeter[id - 1]) spaeter[id - 1].fn = () => {} },
scrollTo: (x, y) => { win.scrollX = x; win.scrollY = y; protokoll.push(['scrollTo', x, y]) },
})
const verlauf = { scrollTop: 1200, scrollLeft: 0 }
const haken = {}
const style = {}
let display = ''
Object.defineProperty(style, 'display', { get: () => display, set: (v) => { display = v; protokoll.push(['display', v]); if (v === 'none') { verlauf.scrollTop = 0; win.scrollY = 0; haken.beimAusblenden?.() } } })
const knopf = { focus: () => protokoll.push(['focus']) }
const element = {
style,
get offsetHeight() { protokoll.push(['umbruch']); return 100 },
querySelectorAll: () => [verlauf, { scrollTop: 0, scrollLeft: 0 }],
contains: (el) => el === knopf || el === verlauf,
}
const doc = sender({ activeElement: null, body: {}, visibilityState: 'visible' })
const istFeld = (el) => el?.feld === true
const h = beobachteHoehe({ win, doc, element, aktiv, istTextfeld: istFeld })
const laufe = () => { while (spaeter.length) spaeter.shift().fn() }
return { win, doc, element, verlauf, knopf, protokoll, spaeter, h, laufe, haken }
}
test('beobachteHoehe: Tastatur zu und Höhe hängt → Hülle einmal aus und ein, Scrollstände zurück; danach Ruhe', () => {
const t = aufbau()
t.win.scrollY = 40
t.doc.activeElement = { feld: true }
t.win.innerHeight = 873; t.win.senden('resize') // iOS: die Höhe fällt mit der Tastatur und bleibt unten
assert.equal(t.spaeter.length, 0) // solange das Feld den Fokus hat, wird nicht geprüft
t.doc.activeElement = t.doc.body; t.doc.senden('focusout')
assert.deepEqual(t.spaeter.map((s) => s.ms), PRUEF_MS)
t.spaeter.shift().fn()
assert.deepEqual(t.protokoll, [['display', 'none'], ['umbruch'], ['display', ''], ['umbruch'], ['scrollTo', 0, 40]])
assert.equal(t.verlauf.scrollTop, 1200)
t.win.innerHeight = 932 // WebKit hat neu gemessen
t.protokoll.length = 0; t.laufe()
assert.deepEqual(t.protokoll, [])
assert.deepEqual(t.h.stand(), { voll: 932, breite: 430, versuche: 1 })
})
test('beobachteHoehe: onHeile meldet vorher, nachher, voll und Versuch; ein Fehler im Beobachter stört nicht', () => {
const t = aufbau()
const meldungen = []
t.h.beende()
const h = beobachteHoehe({ win: t.win, doc: t.doc, element: t.element, istTextfeld: () => false, onHeile: (w) => { meldungen.push(w); throw new Error('kaputt') } })
t.win.innerHeight = 873
assert.equal(h.pruefe(), true)
assert.deepEqual(meldungen, [{ vorher: 873, nachher: 873, voll: 932, versuche: 1 }])
})
test('beobachteHoehe: hilft es nicht, höchstens MAX_VERSUCHE je Anlass; App wieder vorne ist ein neuer Anlass', () => {
const t = aufbau()
t.win.innerHeight = 873
t.doc.senden('focusout'); t.laufe()
t.win.senden('resize'); t.laufe()
assert.equal(t.protokoll.filter((p) => p[0] === 'display' && p[1] === 'none').length, MAX_VERSUCHE)
t.doc.visibilityState = 'hidden'; t.doc.senden('visibilitychange'); assert.equal(t.spaeter.length, 0)
t.doc.visibilityState = 'visible'; t.doc.senden('visibilitychange'); t.laufe()
assert.equal(t.protokoll.filter((p) => p[0] === 'display' && p[1] === 'none').length, 2 * MAX_VERSUCHE)
})
test('beobachteHoehe: Fokus auf einem Knopf in der Hülle bleibt, ohne Scrollen', () => {
const t = aufbau()
t.win.innerHeight = 873
t.doc.activeElement = t.knopf
t.haken.beimAusblenden = () => { t.doc.activeElement = t.doc.body } // beim Ausblenden verliert der Knopf den Fokus (wie im Browser)
assert.equal(t.h.pruefe(), true)
assert.deepEqual(t.protokoll.at(-1), ['focus'])
})
test('beobachteHoehe: nicht aktiv (kein iOS-App-Modus), keine Hülle oder Höhe passt → nichts passiert; beende räumt auf', () => {
const aus = aufbau({ aktiv: () => false })
aus.win.innerHeight = 873; assert.equal(aus.h.pruefe(), false); assert.deepEqual(aus.protokoll, [])
const ok = aufbau()
assert.equal(ok.h.pruefe(), false)
ok.win.innerHeight = 932 - LUFT; assert.equal(ok.h.pruefe(), false); assert.deepEqual(ok.protokoll, [])
ok.doc.senden('focusout'); ok.h.beende(); ok.win.innerHeight = 873; ok.laufe()
assert.deepEqual(ok.protokoll, [])
assert.deepEqual([ok.win.anzahl('resize'), ok.doc.anzahl('focusout'), ok.doc.anzahl('visibilitychange')], [0, 0, 0])
})
test('huelle.js: Höhen-Wächter nur in der iOS-App vom Home-Bildschirm am Handy, auf der Hülle', () => {
const js = lies('huelle.js')
assert.match(js, /import \{ beobachteHoehe \} from '\.\/hoehe\.js'/)
assert.match(js, /beobachteHoehe\(\{ win, doc, element: doc\.querySelector\('\.kn-huelle'\), aktiv: \(\) => istIos\(\) && standalone\(\) && istHandy\(\), istTextfeld, onHeile:/)
assert.match(js, /if \(istIos\(\) && istHandy\(\) && !viewAs\) diag = starteDiagHoehe\(/)
})