Files
dogfather-universe/workspace/chat.html
T
DogFatherGitandClaude Opus 5 7de32a5ec7 Punkt 15: Der Chat bekommt Suche, Antworten mit Zitat und die Ungelesen-Linie
Wunsch Filipe: "ich will dass du diese seite viel krasser und
detaillierter machst, ich will dass du dich informierst und alles
reinsetzt was wir noch gebrauchen koennten."

NICHT ALLES, SONDERN WAS TAEGLICH FEHLT. Der Chat konnte schon Raeume,
Verlauf, Gelesen-Stand, Live-Zustellung, Gruppen und Zuruecknehmen.
Drei Dinge fehlten, und jedes davon kostet ohne es echte Zeit:

1. SUCHE IN DEN NACHRICHTEN. Ein Chat ohne sie ist ab dem zweiten
   Monat ein Archiv, in dem man nichts findet. Ein Suchfeld gab es --
   es durchsuchte aber nur die NAMEN der Gespraeche, also die kleinere
   Haelfte. Jetzt durchsucht dasselbe Feld beides und zeigt die
   Fundstellen UNTER der Gespraechsliste: Wer "Patrick" eingibt, will
   vielleicht das Gespraech und vielleicht die Nachricht -- ein
   Umschalter haette ihn zwingen wollen, das vorher zu wissen.

2. ANTWORTEN MIT ZITAT. Zu zweit weiss man meistens, worauf sich etwas
   bezieht. In einer Gruppe laufen drei Faeden parallel, und "ja, mach
   das" kann alles heissen. Das Zitat steht IN der Blase (es gehoert
   zur Antwort, nicht darueber) und fuehrt per Klick zur Stelle.

3. DIE LINIE "AB HIER NEU". Wer nach zwei Tagen zurueckkommt, sucht
   sonst die Stelle, an der er aufgehoert hat, indem er Uhrzeiten
   liest.

BEWUSST NICHT GEBAUT: Anhaenge (dafuer gibt es den Dateien-Bereich mit
Rechten und Ablauf), Reaktionen (eine vierte Sache, bevor die drei sich
bewaehrt haben) und Tipp-Anzeigen (dauernder Verkehr fuer eine
Auskunft, die man in zwei Sekunden ohnehin sieht).

DIE SICHERHEIT DER SUCHE STEHT IM JOIN, nicht in einer nachtraeglichen
Pruefung: `chat_teilnehmer` wird mit der eigenen Personenkennung
verbunden, und was dort nicht drinsteht, kommt gar nicht erst aus der
Datenbank. Ein Filter, der erst hinterher aussortiert, ist eine Zeile
davon entfernt, vergessen zu werden. Ebenso beim Zitat: Worauf
geantwortet wird, muss im SELBEN Raum liegen -- sonst koennte jemand
die Kennung aus einem fremden Gespraech mitschicken, und beim
Empfaenger stuende ein Zitat aus einem Raum, den er nie gesehen hat.

DREI FEHLER, DIE DER DURCHLAUF GEFUNDEN HAT:

1. `ESCAPE '\'` IN EINEM TEMPLATE-LITERAL. Dort ist `\'` eine
   Fluchtsequenz fuer das Anfuehrungszeichen -- SQLite bekam ein
   LEERES Fluchtzeichen und antwortete "ESCAPE expression must be a
   single character". Die Suche war damit komplett tot. Kein
   Syntaxfehler, kein Warnhinweis: Erst der Aufruf mit echten Daten
   hat es gezeigt.

2. DIE MASKIERUNG KANNTE ZWEI VON DREI ZEICHEN. `%` und `_` waren
   dabei, der Backslash nicht -- ausgerechnet das Fluchtzeichen selbst.
   Geprueft wird das jetzt an der ZEILE AUS DER DATEI, nicht an einem
   Nachbau: Mein erster Test hat die Maskierung nachgebaut und dabei
   die Shell-Maskierung mitgeschleppt -- er meldete einen Fehler, den
   nur er hatte.

3. DIE UNGELESEN-LINIE SCHIEN NICHT ZU FUNKTIONIEREN. Sie tat es --
   mein Testaufbau war falsch: Filipes Seite war noch offen, die neuen
   Nachrichten kamen ueber den Live-Strom an und wurden sofort als
   gelesen gemeldet. Es gab schlicht nichts Ungelesenes. Erst als er
   die Seite verlaesst, bevor Patrick schreibt, steht die Linie da --
   und zwar genau vor "Neu von Patrick, eins", und beim zweiten
   Oeffnen ist sie weg.

Der Gelesen-Stand wird deshalb beim OEFFNEN mitgeschickt, bevor er
gesetzt wird -- eine Zeile spaeter waere er immer die letzte Nachricht,
und die Linie staende nie irgendwo.

Ohne Volltextindex, mit Absicht: `LIKE` liest die Tabelle, und bei
einem Team dieser Groesse sind das einige tausend Zeilen. Ein
FTS5-Index waere eine zweite Tabelle, die synchron gehalten werden
muss -- genau daran gehen solche Sachen kaputt. Wenn der Verlauf
sechsstellig wird, ist das der Zeitpunkt dafuer, nicht heute.

Geprueft: pruef-chat, pruef-chat-optik, pruef-css-klassen,
pruef-workspace-seiten, pruef-handy -- alle in Ordnung. Dazu im
Browser durchgespielt: Zitat gesetzt und gelesen, Suche nach
"Bitrate" (1 Treffer), nach "100 %" (1 Treffer -- die Maskierung
haelt), nach Unsinn (0), einbuchstabige Suche (zu kurz), Antwortleiste
mit dem richtigen Namen, Ungelesen-Linie an der richtigen Stelle und
beim zweiten Oeffnen weg.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-09 02:18:59 +02:00

209 lines
9.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Chat · Spicy & Dogi</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<!-- Die Farbe der Fensterleiste, wenn der Workspace als App laeuft.
Stand auf #0674b9 (kraeftiges Blau) und stach damit als einziger
heller Balken ueber einer durchweg dunklen Seite. Jetzt derselbe
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609090218" />
<link rel="stylesheet" href="assets/css/start.css?v=202609090218" />
<!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog,
.neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem
Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen
zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html.
Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten
Dialog zu sehen bekam. -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609090218" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609090218" />
<link rel="stylesheet" href="assets/css/module.css?v=202609090218" />
</head>
<body class="start">
<div class="schleier" aria-hidden="true"></div>
<header class="kopfleiste">
<button type="button" class="zurueck-knopf" id="zurueck" hidden>
<svg class="zurueck-knopf__pfeil" viewBox="0 0 24 24" aria-hidden="true">
<path d="M14.5 5.5 8 12l6.5 6.5" />
</svg>
<span class="zurueck-knopf__text">Zurück</span>
</button>
<p class="marke"><span class="marke__text"><a class="zurueck" href="start.html">Creator&nbsp;Workspace</a> · Chat</span></p>
<div class="kopfleiste__rechts">
<span class="wer" id="wer">…</span>
<button type="button" class="abmelden" id="abmelden">Abmelden</button>
</div>
</header>
<main class="inhalt inhalt--breit chat-seite">
<!-- DIE GANZE SEITE IST EINE FLÄCHE, kein Dokument zum Scrollen.
Ein Chat ist kein Text, den man von oben nach unten liest --
man springt zwischen Gesprächen, und das Eingabefeld muss
immer erreichbar sein, ohne zu suchen. Deshalb: feste Höhe,
und gescrollt wird nur INNERHALB der beiden Spalten.
data-ansicht steuert am Handy, welche Spalte zu sehen ist:
"liste" oder "verlauf". Am Rechner stehen beide nebeneinander
und das Merkmal bewirkt nichts. -->
<div class="chat" id="chat" data-ansicht="liste">
<!-- ---------- Links: die Gespräche ---------- -->
<section class="chat__spalte chat__spalte--liste" aria-labelledby="liste-titel">
<div class="chat__kopf">
<h1 class="chat__titel" id="liste-titel">Gespräche</h1>
<button type="button" class="knopf knopf--klein" id="neu-oeffnen">Neu</button>
</div>
<!-- Suchen erst, wenn es etwas zu suchen gibt (chat.js blendet
es ab acht Gesprächen ein). Ein Suchfeld über drei Einträgen
ist Zierde. -->
<div class="chat__suche" id="suche-zeile" hidden>
<input type="search" id="suche" placeholder="Name oder Gruppe …"
aria-label="Gespräche durchsuchen" autocomplete="off" />
</div>
<ul class="chat__liste" id="raeume" aria-live="polite"></ul>
<!-- ---------- Fundstellen in Nachrichten (09.09.2026, Punkt 15)
Das Suchfeld darueber durchsuchte bisher nur die NAMEN der
Gespraeche. Das ist die kleinere Haelfte: Was man wirklich
sucht, ist ein Satz, den jemand vor drei Wochen geschrieben
hat.
Die Fundstellen stehen UNTER der Gespraechsliste und nicht
an ihrer Stelle -- beides ist eine Antwort auf dieselbe
Eingabe, und wer nach "Patrick" sucht, will vielleicht das
Gespraech und vielleicht die Nachricht. Ein Umschalter
zwischen zwei Ansichten haette ihn zwingen wollen, das
vorher zu wissen. -->
<div class="chat-funde" id="funde" hidden>
<p class="chat-funde__schild" id="funde-schild">In Nachrichten</p>
<ul class="chat-funde__liste" id="funde-liste"></ul>
</div>
<p class="chat__leer" id="liste-leer" hidden>
Noch keine Gespräche. Mit <strong>Neu</strong> schreibst du der
ersten Person.
</p>
</section>
<!-- ---------- Rechts: der Verlauf ---------- -->
<section class="chat__spalte chat__spalte--verlauf" aria-labelledby="verlauf-titel">
<!-- Der Kopf trägt am Handy den Zurück-Weg zur Liste. -->
<div class="chat__kopf chat__kopf--verlauf">
<button type="button" class="chat__zurueck" id="zur-liste" aria-label="Zurück zu den Gesprächen">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14.5 5.5 8 12l6.5 6.5" /></svg>
</button>
<div class="chat__kopf-text">
<h2 class="chat__titel" id="verlauf-titel">Wähle ein Gespräch</h2>
<p class="chat__unter" id="verlauf-unter"></p>
</div>
</div>
<!-- aria-live="polite": Ein Vorleseprogramm sagt neue Nachrichten
an, ohne den Nutzer mitten im Satz zu unterbrechen. -->
<div class="chat__verlauf" id="verlauf" role="log" aria-live="polite" aria-relevant="additions">
<p class="chat__leer" id="verlauf-leer">
Links ein Gespräch auswählen – oder mit <strong>Neu</strong> eines anfangen.
</p>
</div>
<!-- Das Eingabefeld. Ein <form>, damit Enter abschickt, ohne
dass dafür Code nötig wäre. -->
<!-- DIE ANTWORTLEISTE (09.09.2026, Punkt 15).
Sie steht UEBER dem Eingabefeld und innerhalb desselben
<form>: So sieht man beim Tippen, worauf man antwortet, und
kann es mit einem Griff wieder loswerden. Eine Leiste
ausserhalb waere beim Tippen ausserhalb des Blickfelds.
`hidden` als Vorgabe -- sie erscheint erst, wenn wirklich
auf etwas geantwortet wird. Ein leerer Kasten ueber dem
Eingabefeld waere jeden Tag im Weg fuer den Fall, dass man
ihn vielleicht braucht. -->
<form class="chat__eingabe" id="schreiben" hidden>
<div class="antwort-leiste" id="antwort-leiste" hidden>
<span class="antwort-leiste__strich" aria-hidden="true"></span>
<span class="antwort-leiste__inhalt">
<span class="antwort-leiste__schild">Antwort an</span>
<span class="antwort-leiste__von" id="antwort-von"></span>
<span class="antwort-leiste__text" id="antwort-text"></span>
</span>
<button type="button" class="antwort-leiste__weg" id="antwort-weg"
aria-label="Antwort verwerfen">&#10005;</button>
</div>
<label class="chat__eingabe-schild" for="text">Nachricht</label>
<!-- KURZER PLATZHALTER. Hier stand der Hinweis "Enter schickt,
Umschalt+Enter macht eine neue Zeile" -- zwei Fehler auf
einmal: Am Handy brach er auf zwei Zeilen um und schob das
Feld halb unter den Bildschirmrand, UND er stimmte dort
nicht: Auf Geräten ohne Tastatur schickt Enter absichtlich
nicht, sonst käme man nie zu einer zweiten Zeile.
Die Tastenbelegung steht jetzt im title -- sie ist ein
Hinweis für Wiederkehrer, keine Aufforderung. -->
<textarea id="text" rows="1" maxlength="4000"
placeholder="Nachricht schreiben …"
title="Enter schickt ab, Umschalt+Enter macht eine neue Zeile"></textarea>
<button type="submit" class="chat__senden" id="senden" aria-label="Abschicken">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 12l16-7-7 16-2.5-6.5z" />
</svg>
</button>
</form>
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
</section>
</div>
</main>
<!-- Neues Gespräch. Beides in einem Dialog: Wer eine Person anklickt,
schreibt ihr; wer mehrere anhakt, legt eine Gruppe an. Zwei
getrennte Dialoge hätten die Frage "Einzeln oder Gruppe?" VOR die
eigentliche Absicht gestellt. -->
<dialog class="dialog" id="neu-dialog">
<form method="dialog" id="neu-form" class="neu neu--blank">
<h2 class="dialog__titel">Neues Gespräch</h2>
<p class="dialog__unter">
Eine Person antippen, um ihr zu schreiben. Mehrere auswählen ergibt
eine Gruppe.
</p>
<div class="chat-wahl" id="partner"></div>
<!-- Erscheint erst ab zwei Ausgewählten -- vorher wäre ein
Gruppenname eine Frage ohne Anlass. -->
<div class="neu__zeile" id="gruppenname-zeile" hidden>
<label class="feld-schild" for="gruppenname">Name der Gruppe</label>
<input id="gruppenname" maxlength="80" placeholder="z. B. Luna &amp; Betreuung" />
</div>
<p class="fehler" id="neu-fehler" role="alert" aria-live="polite"></p>
<div class="dialog__knoepfe">
<button type="submit" class="knopf knopf--klein" id="neu-los" disabled>Schreiben</button>
<button type="button" class="abmelden" id="neu-zu">Abbrechen</button>
</div>
</form>
</dialog>
<script src="assets/js/bereiche.js?v=202609090218"></script>
<script src="assets/js/wahl.js?v=202609090218" defer></script>
<script src="assets/js/kopf.js?v=202609090218"></script>
<script src="assets/js/glocke.js?v=202609090218"></script>
<script src="assets/js/chat.js?v=202609090218"></script>
</body>
</html>