Kundenansicht neu Chat-Fix: Chat als Vollbild-Ansicht (Verlauf scrollt in sich, Eingabe unten, Spalte zentriert, "Chat" klein im Kopf)
Pilot-Feedback 17.10.: Der Chat wirkte durchsichtig und verrutscht. - Handy: Inhalt steht fest zwischen Kopf und Leiste (wie das Blatt); die Eingabe sitzt höchstens 6 px über der Leiste bzw. dem Widerrufsstreifen und bei offener Tastatur am unteren Rand des sichtbaren Bereichs. "Chat" steht klein im Kopf (die H1 bleibt nur für Screenreader), der Kopf ist deckend mit Haarlinie, der Verlauf beginnt direkt darunter und scrollt in sich. - Desktop: Spalte in Fensterhöhe, Chat höchstens 760 px breit und mittig, Verlauf füllt die Höhe, Eingabe am unteren Spaltenrand. - Verlauf steht unten (neueste Nachricht sichtbar), kurze Verläufe unten bei der Eingabe; Blasen fremd #f8f9fb, eigen #ebeef1. - "Senden" bleibt schwarz, auch bei leerem Feld (gesperrt im Ansichtsmodus weiter gedämpft). - haltFokus: Druck auf Senden, Anhang oder Mikrofon nimmt dem Textfeld den Fokus nicht; sonst kommt die Leiste zurück, schiebt die feste Eingabe hoch und der Tipp auf "Senden" geht bei offener Tastatur verloren (im Browser nachgestellt). public/chat-composer.js und die alte Ansicht bleiben unverändert. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5cd49d6a4a
commit
670ead3091
4 files changed
+128
-8
No files matched your search
@@ -0,0 +1,81 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { haltFokus } from '../public/neu/ansicht-chat.js'
|
||||
import { lies, handyTeil } from './support/knHuelle.js'
|
||||
|
||||
// Chat als Vollbild-Ansicht (Pilot-Feedback 17.10.: „durchsichtig und verrutscht“). Die Lage prüfen die Ansichten kn-p2-chat-* des Werkzeugs im Browser;
|
||||
// diese Pins lesen nur den Quelltext und halten die Regeln fest, an denen die Lage hängt.
|
||||
const ANSICHTEN = lies('ansichten.css')
|
||||
const HUELLE = lies('huelle.css')
|
||||
const regel = (css, selektor) => {
|
||||
const m = css.match(new RegExp(`(?:^|\\n)${selektor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\{([^}]*)\\}`))
|
||||
return m ? m[1] : ''
|
||||
}
|
||||
const chatBlock = () => HUELLE.slice(HUELLE.indexOf('Chat: Vollbild-Ansicht'))
|
||||
|
||||
test('Chat: der Verlauf füllt die Höhe und scrollt in sich, ohne feste Höhengrenze (kein 60vh/55vh mehr)', () => {
|
||||
const verlauf = regel(ANSICHTEN, '.kn .kn-chat .chat-messages')
|
||||
assert.match(verlauf, /flex:\s*1 1 0/)
|
||||
assert.match(verlauf, /min-height:\s*0/)
|
||||
assert.match(verlauf, /overflow-y:\s*auto/)
|
||||
assert.doesNotMatch(verlauf, /max-height/)
|
||||
assert.doesNotMatch(ANSICHTEN, /\.chat-messages\s*\{[^}]*(?:min-height:\s*(?:240|280)px|max-height:\s*\d+vh)/)
|
||||
assert.match(ANSICHTEN, /\.kn \.kn-chat \.chat-messages > :first-child\s*\{\s*margin-top:\s*auto/) // kurze Verläufe stehen unten bei der Eingabe
|
||||
})
|
||||
|
||||
test('Chat: Spalte höchstens 760 px, mittig; Eingabe fest unten', () => {
|
||||
const spalte = ANSICHTEN.match(/\n\.kn-chat \{[^}]*\}/)[0]
|
||||
assert.match(spalte, /max-width:\s*760px/)
|
||||
assert.match(spalte, /margin-inline:\s*auto/)
|
||||
assert.match(regel(ANSICHTEN, '.kn .kn-chat .kn-chat-composer'), /flex:\s*none/)
|
||||
assert.match(regel(HUELLE, '.kn-haupt > #bereich-chat:not([hidden])'), /max-width:\s*760px[^}]*margin:\s*0 auto/)
|
||||
})
|
||||
|
||||
test('Chat: Blasen ruhig (Fläche #f8f9fb für fremde, #ebeef1 für eigene), über Tokens', () => {
|
||||
assert.match(ANSICHTEN, /--chat-bubble-in:\s*var\(--kn-bg-subtle\)/)
|
||||
assert.match(ANSICHTEN, /--chat-bubble-out:\s*var\(--kn-grau-bg\)/)
|
||||
const tokens = lies('tokens.css')
|
||||
assert.match(tokens, /--kn-bg-subtle:\s*#f8f9fb/)
|
||||
assert.match(tokens, /--kn-grau-bg:\s*#ebeef1/)
|
||||
})
|
||||
|
||||
test('Chat: „Senden“ bleibt der schwarze Hauptknopf, auch bei leerem Feld; gesperrt (Ansichtsmodus) und beim Senden bleibt er gedämpft', () => {
|
||||
assert.match(regel(ANSICHTEN, '.kn .kn-chat .chat-composer:not(.is-disabled) .chat-send:disabled:not(.is-busy)'), /opacity:\s*1\b/)
|
||||
assert.match(regel(ANSICHTEN, '.kn .kn-chat .chat-icon-btn:disabled, .kn .kn-chat .chat-send:disabled'), /opacity:\s*\.55/)
|
||||
assert.match(regel(ANSICHTEN, '.kn .kn-chat .chat-send.is-busy:disabled'), /opacity:\s*\.8/)
|
||||
})
|
||||
|
||||
test('Chat in der Hülle: Fensterhöhe am Desktop, am Handy fest zwischen Kopf und Leiste, „Chat“ klein im Kopf statt große Überschrift', () => {
|
||||
const block = chatBlock()
|
||||
assert.match(regel(block, '.kn-spalte:has(#bereich-chat:not([hidden]))'), /display:\s*flex[^}]*height:\s*calc\(100dvh - var\(--kn-banner-h\)\)[^}]*min-height:\s*0/)
|
||||
assert.match(regel(block, '#chat-body'), /flex:\s*1 1 auto[^}]*min-height:\s*0/)
|
||||
const handy = handyTeil(block)
|
||||
assert.match(handy, /\.kn-haupt:has\(#bereich-chat:not\(\[hidden\]\)\)\s*\{[^}]*position:\s*fixed[^}]*inset:\s*calc\(var\(--kn-banner-h\) \+ var\(--kn-kopf-h\) \+ env\(safe-area-inset-top\)\) 0 var\(--kn-unten, var\(--kn-zeile-h\)\)/)
|
||||
assert.match(handy, /\.kn-haupt:has\(#bereich-chat:not\(\[hidden\]\)\)\s*\{[^}]*padding:\s*0 max\(16px, env\(safe-area-inset-right\)\) 6px max\(16px, env\(safe-area-inset-left\)\)/) // höchstens 8 px Luft über der Leiste
|
||||
assert.match(handy, /#bereich-chat > \.kn-titel\s*\{[^}]*position:\s*absolute[^}]*width:\s*1px[^}]*height:\s*1px[^}]*clip:\s*rect\(0 0 0 0\)/) // nur noch für Screenreader
|
||||
assert.match(handy, /\.kn:has\(#bereich-chat:not\(\[hidden\]\)\) \.kn-kopf\s*\{[^}]*height:[^}]*border-bottom-color:\s*var\(--kn-line\)/) // deckender Kopf mit Haarlinie
|
||||
assert.match(handy, /\.kn:has\(#bereich-chat:not\(\[hidden\]\)\) \.kn-kopf-titel\s*\{[^}]*opacity:\s*1[^}]*transform:\s*none/)
|
||||
assert.doesNotMatch(block, /backdrop-filter|gradient|rgba\(/) // deckend, kein Glas
|
||||
})
|
||||
|
||||
test('haltFokus: Druck auf einen Knopf im Composer nimmt dem Eingabefeld den Fokus nicht (sonst springt die Eingabe, wenn die Leiste zurückkommt, und der Tipp auf „Senden“ geht verloren)', () => {
|
||||
const hoerer = new Map()
|
||||
const knopf = { matches: (s) => s === 'button' }
|
||||
const wurzel = {
|
||||
addEventListener: (n, f) => hoerer.set(n, f),
|
||||
removeEventListener: (n) => hoerer.delete(n),
|
||||
contains: (el) => el === knopf || el === symbol,
|
||||
}
|
||||
const symbol = { closest: (s) => (s === 'button' ? knopf : null) } // das Symbol im Knopf ist das Ziel des Drucks
|
||||
const feld = { closest: () => null }
|
||||
const draussen = { closest: () => ({}) } // ein Knopf, der nicht im Composer liegt
|
||||
const h = haltFokus(wurzel)
|
||||
assert.deepEqual([...hoerer.keys()], ['mousedown'])
|
||||
const druck = (target) => { let verhindert = false; hoerer.get('mousedown')({ target, preventDefault: () => { verhindert = true } }); return verhindert }
|
||||
assert.equal(druck(symbol), true, 'Knopf im Composer: Fokus bleibt')
|
||||
assert.equal(druck(feld), false, 'Textfeld: normal')
|
||||
assert.equal(druck(draussen), false, 'Knopf außerhalb: normal')
|
||||
assert.equal(druck(undefined), false)
|
||||
h.abbau()
|
||||
assert.equal(hoerer.size, 0)
|
||||
})
|
||||
Reference in new issue
Block a user