Files
dogfather-universe/workspace/aufgaben.html
T
DogFatherGitandClaude Opus 5 14e9bbf7a5 Chat: Fotos und PDFs schicken, Gespraeche wegraeumen
Filipe, mit Bildschirmfoto: "man muss die chats auch geloescht
bekommen!!! am besten waere es auch wenn man da auch im chat pdfs
schicken koennte. pdfs und fotos."

Zwei Entscheidungen vorher abgestimmt, weil sie den Bau bestimmen:
Geloescht wird NUR BEI MIR. Schicken duerfen ALLE im Gespraech, auch
Creator -- anders als in der Dateiablage, wo etwas in einem Bereich
landet, den mehrere sehen. Hier bekommt es genau der, mit dem man
ohnehin gerade spricht.

--- ANHAENGE ---

Drei Wege zur selben Sache, weil Leute unterschiedlich arbeiten: die
Bueroklammer, Hineinziehen und Einfuegen mit Strg+V (der Weg fuer einen
Screenshot -- der liegt in der Zwischenablage und nirgends als Datei).
Alle drei laufen durch dieselbe Funktion; drei Fassungen waeren drei
Gelegenheiten, dass eine die Pruefung vergisst.

Ein Foto wird GEZEIGT, ein PDF wird als Karte ANGEBOTEN. Das ist kein
Schoenheitsunterschied: Ein Bild erkennt man in einer Zehntelsekunde,
ein PDF muss man ohnehin oeffnen -- eine Vorschau davon waere ein
grauer Kasten, der so tut, als koennte man etwas lesen.

DIE SICHERHEIT STEHT IN dateiErkennen(). Dateiname und Content-Type
kommen vom Absender und sind frei erfunden; der Typ wird deshalb aus
den ersten Bytes bestimmt (PNG/GIF/WebP/JPEG/PDF, mit Breite und Hoehe
im selben Durchgang). SVG steht absichtlich NICHT in der Liste -- das
ist XML, das Skripte enthalten darf, ein als Bild getarntes Programm.
Ausgeliefert wird spaeter genau der ERKANNTE Typ, nie der eingeschickte,
dazu nosniff und "default-src 'none'; sandbox".

Auf der Platte bekommt jede Datei einen erzeugten Zufallsnamen, in
einem Ordner ausserhalb des Repos. Zuruecknehmen loescht die Datei
wirklich -- sonst waere sie im Verlauf weg und ueber die Adresse noch
da, also nur der Anschein einer Ruecknahme.

--- WEGRAEUMEN ---

Zwei Spalten in chat_teilnehmer statt einer, wegen eines Randfalls: Bei
einem Gespraech ohne jede Nachricht waere die Grenze 0 -- und 0 heisst
sonst "nie geloescht". geloescht_am unterscheidet die beiden.

Die Grenze wirkt in der Liste, im Verlauf, in der Vorschau, in der
SUCHE, bei den Anhaengen und in der Ungelesen-Zahl. Die Suche ist die
Stelle, an der so etwas typischerweise durchrutscht: aus der Liste weg,
ueber die Suche noch da.

--- DER FUND: 323 PIXEL ---

Ein Bild ist spaeter fertig als der Rest der Seite. Beim Oeffnen springt
der Verlauf ans Ende, dann kommt das Foto an und schiebt alles darunter
weg -- man steht ploetzlich mittendrin, ohne etwas angeklickt zu haben.

Das width/height-Attribut am <img>, wie man es ueberall liest, hilft
hier NICHT: Es gibt nur das Seitenverhaeltnis. Damit daraus eine Hoehe
wird, muss die Breite feststehen -- bei "width: auto" und einem nicht
geladenen Bild ist sie 0. Der Platz wird jetzt am KASTEN reserviert
(aspect-ratio + max-width aus den gemessenen Massen).

Und die Pruefung dazu hatte denselben Fehler wie das Auge: Sie mass in
einem Fenster, in dem das Bild laengst im Zwischenspeicher lag, und
meldete tadellose 0 px. Sie misst jetzt in einem FRISCHEN Browser --
der Fall, um den es geht, ist der erste.

--- SICHERUNG ---

chat-anhaenge/ ist im selben Zug in tools/sicherung-holen.sh
eingetragen, nicht spaeter, wenn es auffaellt: Genau so ist die Luecke
bei den Profilbildern entstanden. tools/wiederherstellung-proben.mjs
bestaetigt: "der Server benutzt 4 Datenordner", keiner fehlt.

--- Pruefung ---

server/pruef-chat-anhaenge.mjs, neu, 60 Pruefungen, alle gruen.
Abschnitt 3 versucht ausdruecklich, hereinzukommen: HTML als bild.png,
SVG als foto.png, SVG als svg, eine Textdatei, ein PNG-Kopf ohne Inhalt
-- alle fuenf mit 415 abgewiesen, ein echtes JPEG danach mit 201
angenommen (sonst bewiese der Block nur, dass gar nichts durchkommt).
13 MB geben 413 mit lesbarem Text, nicht 500.

Konsolenfehler werden nach ZEITPUNKT getrennt, nicht nach Statusnummer:
Was waehrend eines absichtlichen Versuchs entsteht, ist gewollt. Nach
Nummer zu filtern waere bequem und wuerde ab morgen echte 404 mit
verschlucken.

Ausserdem gruen: pruef-chat, pruef-chat-optik, pruef-chat-ausbau,
pruef-css-klassen. Angesehen bei 1440 px und bei 390 px.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-09 12:51:54 +02:00

241 lines
10 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>Aufgaben · 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=202609091251" />
<link rel="stylesheet" href="assets/css/start.css?v=202609091251" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609091251" />
<link rel="stylesheet" href="assets/css/module.css?v=202609091251" />
</head>
<body class="start">
<!-- Der Schleier ueber der Buehne (Casper und HasiDog in den unteren
Ecken, siehe start.css). Ein eigenes Element statt eines dritten
Pseudo-Elements -- body hat nur ::before und ::after. -->
<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> · Aufgaben</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">
<section class="kopf-zeile">
<div>
<p class="marke">Aufgaben</p>
<h1 class="titel">Offen. In Arbeit. Review. Erledigt.</h1>
</div>
<button type="button" class="knopf knopf--klein" id="neu-oeffnen">Neue Aufgabe</button>
</section>
<!-- Die Leiste über dem Brett (01.09.2026). Drei Zeilen, in dieser
Reihenfolge – die Reihenfolge ist die Aussage:
1. was brennt (ein Satz, sofort lesbar)
2. Ausschnitte (Nur meine · Heute · Überfällig)
3. die eigenen Creator (nur für Betreuer, siehe aufgaben.js)
Alle drei bleiben verborgen, bis feststeht, wer angemeldet ist –
eine Filterleiste ohne Zahlen ist ein Versprechen ohne Deckung. -->
<p class="lage" id="lage" role="status" hidden></p>
<div class="ausschnitte" id="ausschnitte" role="group"
aria-label="Ausschnitt wählen" hidden></div>
<div class="creator-reihe" id="creator-reihe" role="group"
aria-label="Nach Creator einschränken" hidden></div>
<!-- Formular zum Anlegen. Standardmäßig zu, damit das Brett im Vordergrund
steht und die Seite nicht mit einem Formular begrüßt. -->
<form class="neu" id="neu" hidden>
<div class="neu__zeile">
<label class="feld-schild" for="f-titel">Titel</label>
<input id="f-titel" name="titel" maxlength="160" placeholder="Was ist zu tun?" required />
</div>
<div class="neu__zeile">
<label class="feld-schild" for="f-text">Beschreibung <span class="leise">(optional)</span></label>
<textarea id="f-text" name="beschreibung" rows="3" maxlength="4000"
placeholder="Worum geht es genau?"></textarea>
</div>
<div class="neu__raster">
<div>
<label class="feld-schild" for="f-prio">Priorität</label>
<select id="f-prio" name="prioritaet">
<option value="hoch">hoch</option>
<option value="mittel" selected>mittel</option>
<option value="niedrig">niedrig</option>
</select>
</div>
<div>
<label class="feld-schild" for="f-frist">Frist <span class="leise">(optional)</span></label>
<input id="f-frist" name="frist" type="date" />
</div>
<div id="feld-creator" hidden>
<label class="feld-schild" for="f-creator">Bereich von</label>
<select id="f-creator" name="creator_id"><option value="">—</option></select>
</div>
<div id="feld-verant" hidden>
<label class="feld-schild" for="f-verant">Verantwortlich</label>
<select id="f-verant" name="verantwortlich_id"><option value="">—</option></select>
</div>
</div>
<p class="fehler" id="neu-fehler" role="alert" aria-live="polite"></p>
<div class="neu__knoepfe">
<button type="submit" class="knopf knopf--klein" id="neu-speichern">Anlegen</button>
<button type="button" class="abmelden" id="neu-abbrechen">Abbrechen</button>
</div>
</form>
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
<!-- AUFGABEN AUS DER VORLAGE (09.09.2026).
Filipe: "ich will dass du die vier klassen ueberall mit aufgaben
perfektionnierst ... mit perfekt kategorien und aufgaben."
Die Checkliste sagt, WAS sitzen muss. Sie sagt nicht, was jemand
als Naechstes TUT. Hier stehen 48 fertige Aufgaben -- vier
Bereiche mal vier Stufen mal drei -- die man mit einem Klick zu
einer echten Aufgabe mit Frist und Verantwortlichem macht.
Der Block steht UEBER dem Brett: Wer ein leeres Brett hat, soll
nicht erst daran vorbeiscrollen, um zu sehen, womit man anfaengt. -->
<section class="vorlagenbrett" id="vorlagenbrett" hidden></section>
<div class="brett" id="brett" aria-busy="true">
<p class="leise" id="laedt">Aufgaben werden geladen …</p>
</div>
<!-- ABGEBROCHENE AUFGABEN (05.09.2026).
Ein eigener Bereich UNTER dem Brett, nicht als fünfte Spalte:
Am Handy wären dann alle fünf so schmal, dass keine mehr lesbar
ist. Und Abgebrochenes sieht man nicht täglich.
Verschwinden darf es aber auch nicht -- sonst wäre der Abbruch
ein Löschen mit Zwischenschritt, und genau das soll er nicht
sein. Deshalb: zugeklappt, mit der Anzahl im Titel, einen Klick
entfernt.
<details> und nicht selbstgebaut: bringt Tastaturbedienung,
aria-expanded und das Verhalten von Suchfunktionen im Browser
von sich aus mit. -->
<details class="abgebrochen" id="abgebrochen" hidden>
<summary class="abgebrochen__kopf">
<span class="abgebrochen__name">Abgebrochen</span>
<span class="abgebrochen__zahl" id="abgebrochen-zahl">0</span>
</summary>
<p class="abgebrochen__unter">
Nicht gelöscht – nur beiseitegelegt. Mit Grund, damit später
nachvollziehbar bleibt, warum. Jederzeit wieder aufnehmbar.
</p>
<div class="abgebrochen__liste" id="abgebrochen-liste"></div>
</details>
</main>
<!-- Bearbeiten. Als natives <dialog>: bringt Fokusfang, Schließen mit Esc
und die Abdunklung des Hintergrunds ohne eigenen Code mit. -->
<dialog class="dialog" id="bearbeiten-dialog">
<form method="dialog" id="bearbeiten" class="neu neu--blank">
<h2 class="dialog__titel">Aufgabe bearbeiten</h2>
<div class="neu__zeile">
<label class="feld-schild" for="b-titel">Titel</label>
<input id="b-titel" maxlength="160" required />
</div>
<div class="neu__zeile">
<label class="feld-schild" for="b-text">Beschreibung</label>
<textarea id="b-text" rows="3" maxlength="4000"></textarea>
</div>
<div class="neu__raster">
<div>
<label class="feld-schild" for="b-prio">Priorität</label>
<select id="b-prio">
<option value="hoch">hoch</option>
<option value="mittel">mittel</option>
<option value="niedrig">niedrig</option>
</select>
</div>
<div>
<label class="feld-schild" for="b-frist">Frist</label>
<input id="b-frist" type="date" />
</div>
<div id="b-feld-creator" hidden>
<label class="feld-schild" for="b-creator">Bereich von</label>
<select id="b-creator"><option value="">—</option></select>
</div>
<div id="b-feld-verant" hidden>
<label class="feld-schild" for="b-verant">Verantwortlich</label>
<select id="b-verant"><option value="">—</option></select>
</div>
</div>
<p class="fehler" id="b-fehler" role="alert" aria-live="polite"></p>
<div class="dialog__knoepfe">
<button type="button" class="knopf knopf--klein" id="b-speichern">Speichern</button>
<button type="button" class="abmelden" id="b-abbrechen">Abbrechen</button>
<button type="button" class="abmelden abmelden--warnung" id="b-loeschen" hidden>Löschen</button>
</div>
</form>
</dialog>
<!-- ABBRECHEN MIT GRUND (05.09.2026).
Kein confirm(): Das blockiert die ganze Seite, nimmt kein Textfeld
auf und sieht auf jedem Rechner anders aus.
Der Grund ist PFLICHT. Ohne ihn weiß in vier Wochen niemand mehr,
warum etwas wegfiel -- und dann ist ein Abbruch von einem Löschen
nicht mehr zu unterscheiden. Der Server verlangt ihn ebenfalls;
hier steht er, damit man es merkt, bevor man abschickt. -->
<dialog class="dialog" id="abbruch-dialog">
<form method="dialog" id="abbruch-form" class="neu neu--blank">
<h2 class="dialog__titel" id="abbruch-titel">Aufgabe abbrechen?</h2>
<!-- Sagt, was passiert UND was nicht passiert. Die Sorge bei so
einem Knopf ist immer, ob etwas verloren geht. -->
<p class="dialog__unter" id="abbruch-was"></p>
<div class="neu__zeile">
<label class="feld-schild" for="abbruch-grund">Warum wird sie abgebrochen?</label>
<textarea id="abbruch-grund" rows="3" maxlength="500"
placeholder="z. B. Kampagne wurde verschoben – kommt im Oktober neu."></textarea>
</div>
<p class="fehler" id="abbruch-fehler" role="alert" aria-live="polite"></p>
<div class="dialog__knoepfe">
<button type="submit" class="knopf knopf--klein" id="abbruch-ok">Abbrechen &amp; beiseitelegen</button>
<button type="button" class="abmelden" id="abbruch-zurueck">Doch nicht</button>
</div>
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609091251" defer></script>
<script src="assets/js/bereiche.js?v=202609091251" defer></script>
<script src="assets/js/kopf.js?v=202609091251" defer></script>
<script src="assets/js/glocke.js?v=202609091251" defer></script>
<script src="assets/js/aufgaben.js?v=202609091251" defer></script>
</body>
</html>