Files
dogfather-universe/workspace/assets/js/kalender-abo.js
T
DogFatherGitandClaude Opus 5 233cd76d78 Der Handy-Durchgang: drei echte Bedienfehler, alle vom selben Ursprung
Filipe: "ich will dass du die komplette seite auf dem handy abcheckst.
ich will dass alles perfekt aussieht und bedienbar ist."

DER SCHWERSTE FUND: Auf ZWOELF Seiten war der "Meine Sicht"-Umschalter
nicht bedienbar -- wer darauf tippte, landete im Chat.

Bei 412 px (der haeufigsten Android-Breite ueberhaupt) brach die
Kopfleiste nicht um, der Umschalter wurde auf 50 px zusammengedrueckt,
sein Knopf behielt aber seine 104 px Mindestbreite und lag damit quer
ueber dem Chat-Knopf. Sichtbar war davon nichts.

Und die Ursache steht seit dem 05.09. woertlich im Kommentar daneben:
"Die Rechnung ging genau auf, solange rechts VIER Dinge standen. Mit der
Glocke sind es FUENF, und bei 320 px passte es nicht mehr." Am 06.09.
habe ich den Chat-Knopf dazugesetzt -- SECHS -- und die Schwelle bei 380
gelassen. Derselbe Fehler, eine Position weiter.

Die Lehre ist nicht "380 auf 430 erhoehen"; das waere er ein drittes
Mal, nur mit einer anderen Zahl. Eine feste Schwelle ist eine Rechnung,
die jemand einmal aufgestellt hat und die beim naechsten Knopf still
falsch wird. `flex-wrap: wrap` OHNE Schwelle rechnet nicht, sondern
misst -- es bricht genau dann um, wenn der Platz nicht reicht.

DASSELBE NOCH EINMAL, am anderen Ende: Bei 1280 px brauchte die Leiste
1235 px (Marke 375 + Bedienelemente 844 + Abstand 16) und hatte 1200.
Auch hier war der Chat-Knopf der Tropfen. Sie brach um, sobald das
Skript die Bedienelemente eingehaengt hatte, und schob die ganze Seite
52 px nach unten -- CLS 0,94. Jetzt gibt die MARKE nach (sie darf
gekuerzt werden, ein Knopf nicht), und umgebrochen wird nur noch auf
sehr schmalen Geraeten.

WEITERE ECHTE FUNDE:
  * /workspace/api/chat/ungelesen wurde auf JEDER Seite ZWEIMAL geholt:
    einmal beim Laden, einmal Millisekunden spaeter beim Aufgehen des
    Ereignisstroms. Der 'open'-Zuhoerer war fuer Wiederverbindungen
    gedacht und feuerte auch beim ersten Mal.
  * Drei Kacheln teilten sich einen Farbton mit einer anderen (18 Farben
    auf 21 Kacheln). Filipe wollte ausdruecklich, dass jede ihre eigene
    hat. Nicht eine Farbe dazuerfunden -- der ganze Farbkreis ist mit
    tools/kachel-farben.mjs neu in 21 geteilt; kleinster Abstand zweier
    Nachbarn jetzt 120 Grad (vorher 106). Dabei fiel eine feste 16 in
    der Mischschleife auf: Bei 18 Kacheln wurden die letzten beiden nie
    mitgemischt.
  * Der Agentur-Untertitel wurde auf dem Handy abgeschnitten.
  * Ein zugeklappter <details>-Kasten (Kalender-Abo) verdeckte einen
    Filter-Chip: Chromium versteckt dessen Inhalt ueber
    `content-visibility`, nicht ueber `display` -- die Kaesten behalten
    eine Groesse. Dieselbe Falle wie bei den Zeitbloecken, dieselbe
    Loesung.

UND DREI PRUEFUNGEN, DIE SELBST FALSCH LAGEN:
  * "achtzehn Kacheln" stand als feste Zahl im Test. Eine Pruefung, die
    bei jeder neuen Kachel rot wird, erzieht dazu, ihr Rotwerden zu
    ignorieren. Sie zaehlt jetzt aus bereiche.js.
  * "das Licht der Kalender-Kachel (tuerkis) muss mehr Blau als Rot
    haben" -- Wissen von aussen, und nach dem Neurechnen war der
    Kalender rosa. Gemessen wird jetzt gegen den Ton der Kachel selbst.
  * pruef-workspace-umzug meldete sein Ergebnis in eigenen Worten. Der
    Gesamtlauf las daraus NULL Pruefungen und schrieb bei JEDEM Lauf ein
    FEHL, obwohl alle 31 Punkte bestanden. Ein Fehlalarm, der immer
    kommt, macht den einen echten unsichtbar.

Und weil "BUTTON 'Meine Sicht' verdeckt" mich eine Stunde gekostet hat,
sagen pruef-handy und pruef-tempo jetzt DAZU, was verdeckt und was
springt -- mit Elternkette und Koordinaten. Ein Befund, den man nicht
verorten kann, ist ein halber.

NEU: der Kalender zum Abonnieren (Stufe 3.2). Persoenlicher, jederzeit
widerrufbarer Link fuer Google, Apple und Outlook. 36 Pruefungen, die
das ICS ZURUECKLESEN statt es anzusehen -- entfaltet, entschluesselt,
verglichen. Wichtigster Punkt: Der Weg ohne Anmeldung darf nichts
zeigen, was der Weg mit Anmeldung nicht zeigt.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-06 23:15:37 +02:00

127 lines
4.8 KiB
JavaScript
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.
/* ===================================================================
kalender-abo.js — den eigenen Kalender abonnierbar machen.
Eine eigene Datei und nicht ein Anhängsel in kalender.js: Der
Kalender selbst ist die größte Datei der Anwendung, und diese Sache
hier hat mit dem Zeichnen von Monaten nichts zu tun. Fällt sie aus,
steht der Kalender trotzdem.
Der Link wird NICHT beim Öffnen der Seite angelegt. Ein Geheimnis,
das entsteht, ohne dass jemand es wollte, liegt danach herum -- und
niemand weiß, dass es existiert. Angelegt wird es beim Klick.
=================================================================== */
(() => {
'use strict';
const $ = (id) => document.getElementById(id);
if (!$('abo-block')) return; // Seite ohne diesen Block
const melde = (t) => { $('abo-fehler').textContent = t || ''; };
async function hole(weg, einst) {
const a = await fetch(weg, { credentials: 'same-origin', ...einst });
if (a.status === 401) { location.assign('/workspace/'); throw new Error('weg'); }
return a;
}
function zeige(daten) {
const hat = !!daten?.link;
$('abo-hat').hidden = !hat;
$('abo-hilfe').hidden = !hat;
$('abo-erzeugen').hidden = hat;
$('abo-neu').hidden = !hat;
$('abo-aus').hidden = !hat;
if (hat) $('abo-link').value = daten.link;
/* WANN ZULETZT ABGEHOLT -- die einzige Auskunft, die wirklich sagt,
ob die Sache funktioniert. "Link erzeugt" beweist nur, dass ein
Link da ist; erst der Abruf beweist, dass ein Kalender ihn auch
benutzt. Ohne diese Zeile sucht man bei "kommt nicht an" an der
falschen Stelle. */
const stand = $('abo-stand');
if (!hat) { stand.textContent = ''; return; }
if (!daten.zuletzt) {
stand.textContent = 'Noch nie abgerufen – sobald dein Kalender ihn holt, steht es hier.';
stand.dataset.warten = 'ja';
return;
}
delete stand.dataset.warten;
const d = new Date(daten.zuletzt);
const her = Math.round((Date.now() - d.getTime()) / 60000);
const wann = her < 2 ? 'gerade eben'
: her < 60 ? `vor ${her} Minuten`
: her < 60 * 48 ? `vor ${Math.round(her / 60)} Stunden`
: `am ${d.toLocaleDateString('de-DE')}`;
stand.textContent = `Dein Kalender hat die Termine zuletzt ${wann} abgeholt.`;
}
async function laden() {
try {
const a = await hole('/workspace/api/ics');
if (!a.ok) return;
zeige(await a.json());
} catch { /* umgeleitet */ }
}
async function erzeugen(erneuern) {
if (erneuern && !window.confirm(
'Neuen Link erzeugen?\n\nDer alte hört im selben Moment auf zu funktionieren. '
+ 'Ein Kalender, der ihn abonniert hat, bekommt danach keine Termine mehr, '
+ 'bis du ihm den neuen gibst.')) return;
melde('');
try {
const a = await hole('/workspace/api/ics', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
});
if (!a.ok) { melde('Ging gerade nicht. Bitte später noch einmal.'); return; }
const d = await a.json();
zeige({ link: d.link, zuletzt: null });
/* Nach dem Erzeugen die Anleitung aufklappen -- wer gerade einen
Link bekommen hat, will als Nächstes wissen, wohin damit. */
$('abo-hilfe').hidden = false;
} catch { /* umgeleitet */ }
}
async function abschalten() {
if (!window.confirm(
'Abschalten?\n\nDeine Termine verschwinden dann aus jedem Kalender, '
+ 'der sie abonniert hat.')) return;
melde('');
try {
const a = await hole('/workspace/api/ics', { method: 'DELETE' });
if (!a.ok) { melde('Ging gerade nicht.'); return; }
zeige(null);
} catch { /* umgeleitet */ }
}
async function kopieren() {
const feld = $('abo-link');
const knopf = $('abo-kopieren');
try {
await navigator.clipboard.writeText(feld.value);
} catch {
/* Ohne Zwischenablage-Recht (unsicherer Kontext, alter Browser)
wenigstens markieren -- dann reicht Strg+C. Wortlos scheitern
wäre das Schlechteste: Man klickt dreimal und weiß nicht, ob
etwas passiert ist. */
feld.focus();
feld.select();
knopf.textContent = 'Jetzt Strg+C';
setTimeout(() => { knopf.textContent = 'Kopieren'; }, 3000);
return;
}
knopf.textContent = 'Kopiert';
setTimeout(() => { knopf.textContent = 'Kopieren'; }, 2000);
}
$('abo-erzeugen').addEventListener('click', () => erzeugen(false));
$('abo-neu').addEventListener('click', () => erzeugen(true));
$('abo-aus').addEventListener('click', abschalten);
$('abo-kopieren').addEventListener('click', kopieren);
/* Erst laden, wenn jemand aufklappt -- die Seite hat beim Öffnen
Wichtigeres zu tun, und die meisten klappen nie auf. */
$('abo-block').addEventListener('toggle', () => {
if ($('abo-block').open) laden();
}, { once: true });
})();