Brett und Kalender kennen einander jetzt in beide Richtungen

Der Kalender zeigt seit heute, was auf den Brettern steht und einen
Termin hat. Die Verbindung war aber EINSEITIG, und beide Halften
fehlten:

  Beim SCHREIBEN konnte niemand wissen, dass ein Tag in der Zukunft den
  Zettel in den Kalender bringt. Das Feld heisst "Datum" und ist mit
  HEUTE vorbelegt -- die Funktion war vorhanden und unauffindbar. Eine
  Funktion, die niemand findet, ist keine.

  Beim LESEN sagte die Karte nichts. Am Fuss stand bloss ein Datum, und
  "24.09.2026" sieht aus wie "17.09.2026" -- obwohl das eine ein Termin
  ist und das andere der Tag, an dem jemand getippt hat.

EINE REGEL, ZWEI VERWENDER -> server/workspace-termin-regel.js.
Der Kalender fragt "welche gehoeren hinein?" (SQL, ganze Tabelle), die
Brettkarte fragt "stehe ICH drin?" (JavaScript, je Zeile). Beide Fassungen
stehen jetzt nebeneinander in EINER Datei, importfrei wie
treff-tabellen.js.

Das ist kein Vorsichtsprinzip, sondern Erfahrung: Zwei Fassungen
derselben Regel sind im Haus dreimal auseinandergelaufen -- die
abgeschriebene Spaltenliste (11.09., drei Spalten samt Inhalt weg), die
zweite Artenliste neben ARTNAME (ein BigMatch liess sich anlegen und
war unsichtbar) und `.teilen` heute frueh.

server/pruef-terminregel.mjs, 35 Pruefungen. Der Kern ist Abschnitt 2:
Er prueft die beiden Fassungen GEGENEINANDER, nicht jede fuer sich --
zwei Pruefungen, die je eine Seite bestaetigen, finden genau diesen
Fehler nicht. Verglichen wird beides: WELCHE Zettel und WELCHER Tag.
Neun Faelle, davon vier, die zu Recht wegbleiben.

Vier Gegenproben, jede in ihre eigene Richtung:
  JS laesst mehr durch -> "die Karte behauptet einen Termin, den der
                           Kalender nicht kennt"
  JS nennt anderen Tag -> "verschiedene Tage: Kalender 22., Karte 17."
  SQL laesst mehr durch-> "steht im Kalender, aber die Karte sagt nichts"
  Marke ausgebaut      -> 0 statt 1

EIN ECHTER FEHLER DABEI BEHOBEN, und er war aelter als diese Arbeit:
Das Formularraster gibt jedem Feld GENAU ZWEI Zeilen (Schild dehnbar,
Eingabe fest 44 px) -- deshalb sitzen die Eingaben auf einer Linie. Ein
Hinweisabsatz erzeugt eine dritte Zeile, und weil das Raster alle
Felder auf gleiche Hoehe dehnt, rutscht ausgerechnet diese Eingabe nach
oben. Gemessen: 328..372 gegen 351..395. 23 px -- genau das, was man
als "verzogen" sieht.

Mit `git stash` getrennt: aufgaben.html war dadurch SCHON VORHER rot
(ein anderes Feld hat dort ebenfalls einen Hinweis), bereich.html hatte
ich neu verursacht. Der Hinweis haengt jetzt absolut unter dem Feld --
beide sind gruen, und zwar an der Wurzel, nicht symptomatisch.
`pointer-events: none`, sonst faengt der Satz Klicks ab, die dem
Element darunter galten.

Am Handy gemessen statt geschaetzt: 0 Ueberschneidungen mit anderen
Feldern. Gegenprobe (row-gap auf 2 px): 1 Ueberschneidung.

Und noch zwei eigene Pruefungsfehler behoben, beide von derselben Sorte:
ein falscher Selektor samt `.catch(() => {})`, der den Fehlgriff
lautlos verschluckte (danach war das Hinweisfeld leer und die Zeile
darunter trotzdem gruen, weil "" nun einmal nicht "ja" ist) -- und die
Marke wurde ueber `textContent` gezaehlt statt ueber ihre Sichtbarkeit.
Aufgefallen ist das am Bildschirmfoto, auf dem ich sie fuer fehlend
hielt; deshalb macht die Pruefung jetzt zusaetzlich einen AUSSCHNITT
der Karte -- auf einem Vollbild von 2000 px ist eine Pille von 28 px
nicht zu beurteilen.

Gruen: terminregel, kalender, formulare, ueberlappung, countdown,
namen, css-klassen, struktur.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-17 23:53:05 +02:00
co-authored by Claude Opus 5
parent 528ee1ecc7
commit 5e628e0078
40 changed files with 1087 additions and 425 deletions
+31 -1
View File
@@ -40,6 +40,11 @@
die Beschriftung darueber ein- oder zweizeilig ist. Ohne das sitzt
ein Feld mit langer Beschriftung tiefer als seine Nachbarn. */
.neu__raster { align-items: stretch; }
/* ZEILENABSTAND FUER DEN HINWEIS. Er haengt ausserhalb des Feldes und
braucht Luft nach unten -- sonst liegt er auf dem naechsten Feld,
und zwar genau dann, wenn die Felder untereinander stehen (schmaler
Bildschirm). Der Spaltenabstand bleibt unveraendert. */
.neu__raster { row-gap: 30px; }
/* Zwei Zeilen je Feld: Die Beschriftung oben DEHNT sich (1fr), das
Eingabefeld unten hat feste Hoehe. Dadurch sitzen alle Eingabefelder
auf exakt derselben Linie -- unabhaengig davon, ob die Beschriftung
@@ -55,7 +60,32 @@
hoeher. Gemessen: Container beider Felder 652+71 identisch, Eingabe
aber 676..720 gegen 679..723. Drei Pixel -- genau das, was man als
"verzogen" sieht. */
.neu__raster > * { display: grid; grid-template-rows: 1fr 44px; }
.neu__raster > * { display: grid; grid-template-rows: 1fr 44px; position: relative; }
/* EIN HINWEIS UNTER EINEM FELD HAENGT UNTER DEM RASTER, NICHT DARIN
(17.09.2026).
Die zwei Rasterzeilen darueber sind der ganze Grund, warum die
Eingaben auf einer Linie sitzen. Ein dritter Absatz im Feld erzeugt
eine dritte Zeile -- und weil das Raster alle Felder auf gleiche
Hoehe dehnt, rutscht dann ausgerechnet DIESE Eingabe nach oben.
Gemessen am 17.09.: 328..372 gegen 351..395 bei den Nachbarn. 23 px,
und genau das sieht man als "verzogen".
Der Hinweis wird deshalb aus dem Fluss genommen. Er beschreibt das
Feld darueber und braucht dessen Breite -- deshalb absolut IM Feld
und nicht als eigenes Rasterkind, das in der naechsten Zeile ganz
links landen wuerde.
`pointer-events: none`: Er ist Text, kein Ziel. Ohne das faengt er
Klicks ab, die dem Element darunter galten -- und niemand versteht,
warum ein Knopf manchmal nicht reagiert. */
.neu__raster > * > .feld-hinweis {
position: absolute;
top: 100%; left: 0; right: 0;
margin: 4px 0 0;
pointer-events: none;
}
.neu__raster > * > .feld-schild { align-self: end; margin-bottom: 6px; }
.neu input, .neu select { height: 44px; box-sizing: border-box; }
/* Die Zeile ganz ausfuellen -- sonst zentriert der Browser das Element
+50
View File
@@ -1327,3 +1327,53 @@
@media (prefers-reduced-motion: reduce) {
.tl-brett { transition: none; }
}
/* =====================================================================
„STEHT IM KALENDER" (17.09.2026)
Die Verbindung zwischen Brett und Kalender war einseitig: Vom
Kalender kam man auf das Brett, zurueck nicht. Am Kartenfuss stand
bloss ein Datum -- und „24.09.2026" sieht aus wie „17.09.2026",
obwohl das eine ein Termin ist und das andere der Tag, an dem
jemand getippt hat.
ES IST EIN LINK UND KEINE MARKE. Wer liest „steht im Kalender", will
als Naechstes wissen, was sonst noch an dem Tag ist.
===================================================================== */
.im-kalender {
display: inline-flex; align-items: center; gap: 7px;
margin-top: 10px;
padding: 5px 11px;
border: 1px solid color-mix(in srgb, var(--akzent) 34%, var(--rand));
border-radius: 999px;
/* Undurchsichtig gemischt, nicht mit `transparent`: Sonst scheint das
Wasserzeichen der Seite durch den Text. Am 17.09. genau so an einer
vertraulichen Bewerbung aufgefallen. */
background: color-mix(in srgb, var(--tinte) 90%, #ffffff 10%);
color: var(--text-leise);
font-size: .78rem; font-weight: 600;
text-decoration: none;
transition: border-color .16s ease, color .16s ease;
}
.im-kalender:hover { border-color: var(--akzent); color: var(--text); }
.im-kalender:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.im-kalender__zeichen { font-size: .9rem; line-height: 1; }
/* EINE ZUSAGE SIEHT ANDERS AUS ALS EIN EIGENER TERMIN. „Wird gemacht --
am 24." ist ein Versprechen an jemanden; ein selbstgesetztes Datum
ist eine Notiz an sich selbst. Unterschieden wird ueber das WORT
(„Zugesagt für") und zusaetzlich ueber den Rand -- eine Auskunft,
die nur aus einer Farbe besteht, kommt bei niemandem an, der Farben
schlecht unterscheidet. */
.im-kalender[data-grund="zugesagt"] {
border-color: color-mix(in srgb, var(--gut, #79d1a2) 48%, var(--rand));
color: color-mix(in srgb, var(--gut, #79d1a2) 30%, #ffffff);
}
/* Der Hinweis unter dem Datumsfeld. Wenn er „steht damit im Kalender"
sagt, darf man das sehen -- sonst liest man ihn nicht. */
#hinweis-datum[data-imkalender="ja"] {
color: color-mix(in srgb, var(--akzent) 42%, #ffffff);
}
+90 -2
View File
@@ -281,6 +281,40 @@
/* ---------- Zeichnen --------------------------------------------------- */
/** Sagt unter dem Datumsfeld, ob dieser Tag im Kalender landet.
*
* DIESELBE REGEL WIE AUF DEM SERVER -- aber bewusst nur die eine
* Haelfte, die der Browser ueberhaupt beantworten kann: Liegt der
* Tag in der Zukunft? Ob am Ende `geplant`, `uhrzeit` oder
* `event_ende` gesetzt sind, entscheidet der Server, und die Karte
* zeigt danach sein Ergebnis. Der Hinweis hier ist eine Vorschau,
* keine zweite Wahrheit -- deshalb steht er im Konjunktiv-Ton
* („steht damit im Kalender") und nicht als Zusage. */
function kalenderhinweisSetzen() {
const feld = $('f-datum');
const ziel = $('hinweis-datum');
if (!feld || !ziel) return;
const wert = feld.value;
/* Denselben Rueckfall wie an den zwei Stellen darueber: `heuteLokal`
kommt aus kopf.js, und wenn das noch nicht geladen ist, waere ein
direkter Aufruf ein Absturz mitten im Formularaufbau. */
const heute = window.heuteLokal ? window.heuteLokal()
: new Date().toISOString().slice(0, 10);
if (!wert) { ziel.textContent = ''; return; }
if (wert > heute) {
ziel.textContent = 'Liegt in der Zukunft – steht damit im Kalender.';
ziel.dataset.imkalender = 'ja';
} else if ($('f-uhrzeit') && !$('feld-uhrzeit').hidden && $('f-uhrzeit').value) {
ziel.textContent = 'Mit Uhrzeit – steht damit im Kalender.';
ziel.dataset.imkalender = 'ja';
} else {
ziel.textContent = wert === heute
? 'Heute – das ist der Tag der Notiz, kein Termin.'
: 'Liegt in der Vergangenheit – kein Termin.';
delete ziel.dataset.imkalender;
}
}
function karte(e) {
const k = el('article', 'eintrag-karte');
k.dataset.dringlich = e.dringlichkeit;
@@ -507,10 +541,41 @@
}
}
/* ---- STEHT IM KALENDER (17.09.2026) ------------------------------
Der Kalender zeigt seit heute, was auf den Brettern steht und
einen Termin hat. Die Verbindung war einseitig: vom Kalender
aufs Brett ja, zurueck nicht. Am Kartenfuss stand bloss ein
Datum -- und „24.09.2026" sieht aus wie „17.09.2026", obwohl das
eine ein Termin ist und das andere der Tag, an dem jemand
getippt hat.
OB ER DRINSTEHT, SAGT DER SERVER (`e.termin`). Hier wird es
nicht nachgerechnet: Eine zweite Fassung der Regel behauptet
irgendwann etwas, das der Kalender nicht zeigt -- und eine
Anzeige, die etwas Falsches sagt, ist schlimmer als keine.
ES IST EIN LINK, keine Marke. Wer liest „steht im Kalender",
will als Naechstes nachsehen, was noch an dem Tag ist. */
if (e.termin) {
const zeile = el('a', 'im-kalender');
zeile.dataset.grund = e.termin.grund;
zeile.href = 'kalender.html';
zeile.append(el('span', 'im-kalender__zeichen', '\u{1F5D3}'));
zeile.append(el('span', 'im-kalender__text',
(e.termin.grund === 'zugesagt' ? 'Zugesagt für ' : 'Im Kalender am ')
+ datum(e.termin.tag)
+ (e.uhrzeit ? ' um ' + e.uhrzeit : '')));
k.append(zeile);
}
const fuss = el('div', 'eintrag-fuss');
/* Beim Event steht der Zeitraum schon oben -- das Datum hier waere
dieselbe Zahl ein zweites Mal. */
if (!stand) fuss.append(el('span', null, datum(e.datum)));
dieselbe Zahl ein zweites Mal. Und steht der Zettel im Kalender,
steht der Tag ebenfalls schon da; dann waere das Erfassungsdatum
hier eine ZWEITE Zahl neben einer wichtigeren -- und die
verwirrt mehr, als sie sagt. */
if (!stand && !e.termin) fuss.append(el('span', null, datum(e.datum)));
if (LEITUNG.has(ich.rolle) && e.creator_name) fuss.append(el('span', null, '· ' + e.creator_name));
if (e.erstellt_name) fuss.append(el('span', null, '· von ' + e.erstellt_name));
if (e.status === 'erledigt') fuss.append(el('span', null, '· erledigt'));
@@ -1408,6 +1473,19 @@
if (einstellung?.vertraulich) $('feld-vertraulich').hidden = event;
$('schild-datum').textContent = event ? 'Von' : 'Datum';
/* WAS DAS DATUM BEWIRKT, STEHT DARUNTER (17.09.2026).
Das Feld ist mit HEUTE vorbelegt und heisst schlicht „Datum".
Damit war die Kalenderfunktion zwar vorhanden, aber unauffindbar:
Niemand konnte wissen, dass ein Tag in der Zukunft den Zettel in
den Kalender bringt. Eine Funktion, die niemand findet, ist
keine.
Der Satz aendert sich MIT DER EINGABE, statt allgemein zu
bleiben -- ein fester Hinweis („kann im Kalender erscheinen")
beantwortet die Frage nicht, die man gerade hat: „Steht der
jetzt drin oder nicht?" */
kalenderhinweisSetzen();
$('schild-titel').textContent = event ? 'Titel des Events' : 'Überschrift';
/* textContent wuerde das <span class="leise"> mit erschlagen --
deshalb nur der erste Textknoten, das kleine "(optional)" bleibt
@@ -1439,6 +1517,7 @@
};
$('neu-oeffnen').addEventListener('click', () => {
$('f-datum').value = window.heuteLokal();
kalenderhinweisSetzen();
formularAnpassen();
umschalten(true);
});
@@ -1450,6 +1529,15 @@
am urspruenglichen <select> -- ohne dieses Ereignis merkte das
Formular vom Artwechsel nichts. */
$('f-art').addEventListener('change', formularAnpassen);
/* DER HINWEIS ZIEHT MIT. Ohne diese beiden Zeilen stuende er auf dem
Stand vom Oeffnen des Formulars -- also dauerhaft auf „Heute", auch
nachdem jemand den 24. eingetragen hat. Ein Hinweis, der beim Tippen
stehenbleibt, sagt nach dem ersten Tastendruck etwas Falsches, und
das ist schlechter als gar keiner. */
$('f-datum')?.addEventListener('change', kalenderhinweisSetzen);
$('f-datum')?.addEventListener('input', kalenderhinweisSetzen);
$('f-uhrzeit')?.addEventListener('change', kalenderhinweisSetzen);
$('f-uhrzeit')?.addEventListener('input', kalenderhinweisSetzen);
$('neu').addEventListener('submit', async (e) => {
e.preventDefault();