Aufgaben-Vorlagen: vier Bereiche mal vier Stufen, 48 fertige Aufgaben

Filipe: "ich will dass du die vier klassen ueberall mit aufgaben
perfektionnierst ... ich will dass die 4 kategorien ueberall perfekt
angepasst sind, mit perfekt kategorien und aufgaben."

WAS GEFEHLT HAT, NACHGEMESSEN

Die Tabelle `aufgaben` kannte weder Bereich noch Stufe, und
Aufgabenvorlagen gab es keine. Die Checkliste sagt, WAS sitzen muss --
sie sagt nicht, was jemand als Naechstes TUT. Zwischen "Ton ist der
Grund Nummer eins" und einem Abend, an dem der Ton wirklich besser
wird, liegt eine Aufgabe mit Frist und Verantwortlichem. Die musste
bisher jemand von Hand tippen.

WAS JETZT DASTEHT

  live       12 Aufgaben ·  3 je Stufe
  content    12 Aufgaben ·  3 je Stufe
  technik    12 Aufgaben ·  3 je Stufe
  community  12 Aufgaben ·  3 je Stufe
  zusammen   48

Drei je Feld und nicht zehn: Wer zehn Aufgaben bekommt, macht keine.
Drei sind ein Abend, eine Woche, ein Monat -- und die Fristen sind
entsprechend gestaffelt. Jede Aufgabe bringt ihre Frist als `tage` mit;
eine Aufgabe ohne Frist ist ein Wunsch.

Die Sachaussagen stammen aus derselben Recherche wie die
Checklistenpunkte: Lautheit minus 20 bis minus 24 LUFS, Tonspur
160 kbit/s, Bitrate hoechstens 75 Prozent des Uploads, Ankuendigung
30 bis 60 Minuten vorher, Verweildauer ueber 15 Minuten, Eskalation in
vier Stufen, Intro-Retention ab 70 Prozent.

DER WEG BIS ZUR ECHTEN AUFGABE

`/workspace/api/vorlagen` liefert sie aus, `/uebernehmen` legt sie an --
als Zeile in `aufgaben`, nicht als weiterer Eintrag in `eintraege`. Eine
Aufgabe hat einen Verantwortlichen, eine Frist und einen Status, der
sich bewegt; das ist etwas anderes als eine Notiz. Zwei Orte fuer
dieselbe Sache waeren der Anfang davon, dass sie auseinanderlaufen.

Ohne Creator wird abgelehnt statt herrenlos angelegt. Ein Creator legt
sie immer fuer SICH an, auch wenn er jemand anderen angibt.

AUF DER SEITE

Ein Block ueber dem Aufgabenbrett: zwei Knopfreihen (Bereich, Stufe),
drei Karten mit Titel, Erklaerung, Frist und einem Knopf. Die
Knopfreihen benutzen `.stufenleiter` aus der Checkliste -- dieselbe
Einteilung, dieselbe Darstellung. Eine zweite Optik fuer dieselben vier
Bereiche waeren zwei Sachen zum Lernen statt einer.

ZWEI EIGENE FEHLER, BEIDE VON PRUEFUNGEN GEFUNDEN

1. Ich hatte verlangt, dass die Frist mit der Stufe waechst. Die
   Pruefung schlug fehl: Profi 6,7 Tage gegen Fortgeschritten 7,4. Beim
   Nachsehen war die ERWARTUNG falsch. Eine Profi-Aufgabe ist meist eine
   Messung -- anspruchsvoll, aber an einem Abend erledigt; eine
   Fortgeschrittenen-Aufgabe wie "Saeulen anlegen und drei Wochen
   halten" braucht zwangslaeufig Wochen. Die Stufe sagt, was man KOENNEN
   muss, nicht wie lange es dauert. Geprueft wird jetzt, was wirklich
   gilt: keine Anfaengeraufgabe ueber fuenf Tage, keine Meisteraufgabe
   unter einer Woche.

2. Der Block war zu durchsichtig. pruef-buehne mass den Untergrund als
   rgb(84, 69, 70) -- das Buehnenfoto -- und die Ueberschrift kam auf
   3,61:1 statt 4,5:1. Ein durchsichtiger Kasten hat keinen Untergrund,
   sondern den, der gerade dahinterliegt. Jetzt eine deckende Flaeche
   mit Weichzeichner: 5,21:1.

GEPRUEFT

Neu: server/pruef-aufgaben-vorlagen.mjs, 30 Pruefungen -- Vollstaendigkeit,
der Weg bis zur Aufgabe auf dem Brett des Creators, und sechs
Gegenproben (unbekannte Stufe, unbekannter Bereich, Nummer ohne
Vorlage, ohne Creator, fremder Creator, und ein Creator, der jemand
anderen angibt). Dazu ein Browserteil, der klickt statt nur zu zaehlen.

Ausserdem gruen: pruef-aufgabenbrett, pruef-buehne, pruef-css-klassen,
pruef-handy.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-09 11:21:54 +02:00
co-authored by Claude Opus 5
parent 5b80d7893b
commit a116b286cb
5 changed files with 775 additions and 18 deletions
+162
View File
@@ -841,5 +841,167 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe
}
await laden();
await vorlagenbrettZeigen();
})();
/* =====================================================================
AUFGABEN AUS DER VORLAGE
Vier Bereiche, vier Stufen, drei Aufgaben je Feld. Der Block ist
bewusst KEINE zweite Aufgabenverwaltung: Er legt eine ganz normale
Aufgabe an, mit Frist und Verantwortlichem, und verschwindet danach
aus dem Weg. Alles Weitere macht das Brett darunter.
WARUM DIE BEIDEN REIHEN DIESELBEN KNOEPFE SIND WIE IN DER
CHECKLISTE: Es sind dieselben vier Bereiche und dieselben vier
Stufen. Zwei verschiedene Darstellungen fuer dieselbe Einteilung
waeren zwei Sachen zum Lernen statt einer.
OHNE CREATOR KEINE AUFGABE. Ein Creator legt sie fuer sich an, ein
Betreuer fuer den, den er oben gewaehlt hat. Ist keiner gewaehlt,
steht das da -- statt eines Knopfes, der beim Druecken einen Fehler
zeigt.
===================================================================== */
const BEREICHE_VORLAGE = [
{ schluessel: 'live', name: 'LIVE-Analyse' },
{ schluessel: 'content', name: 'Content-Ideen' },
{ schluessel: 'technik', name: 'Technik' },
{ schluessel: 'community', name: 'Community' },
];
const STUFEN_VORLAGE = [
{ schluessel: 'anfaenger', name: 'Anfänger',
hilfe: 'Was von Anfang an sitzen muss. Meist an einem Abend zu schaffen.' },
{ schluessel: 'fort', name: 'Fortgeschritten',
hilfe: 'Aus Zufall wird Routine. Braucht Wiederholung, also Wochen.' },
{ schluessel: 'profi', name: 'Profi',
hilfe: 'Gemessen statt geschätzt: ein Wert, ein Versuch, ein Beleg.' },
{ schluessel: 'meister', name: 'Meister',
hilfe: 'Was auch ohne dich weiterläuft — Vorlage, Vertretung, Ablage.' },
];
let vorlagen = null;
let vBereich = 'live';
let vStufe = 'anfaenger';
async function vorlagenbrettZeigen() {
const ziel = $('vorlagenbrett');
if (!ziel || !ich) return;
if (!vorlagen) {
try {
const a = await hole('/workspace/api/vorlagen');
if (!a.ok) return;
vorlagen = (await a.json()).aufgaben || null;
} catch { return; }
}
if (!vorlagen) return;
ziel.hidden = false;
ziel.textContent = '';
const kopf = el('div', 'vorlagenbrett__kopf');
kopf.append(el('h2', 'feldschild', 'Aufgaben aus der Vorlage'));
kopf.append(el('p', 'vorlagenbrett__unter',
'Vier Bereiche, vier Stufen. Übernommen wird als ganz normale Aufgabe '
+ '– mit Frist, veränderbar, auf dem Brett darunter.'));
ziel.append(kopf);
/* Reihe 1: der Bereich. */
const r1 = el('div', 'stufenleiter');
r1.setAttribute('role', 'group');
r1.setAttribute('aria-label', 'Bereich wählen');
for (const b of BEREICHE_VORLAGE) {
const k = el('button', 'stufenleiter__knopf');
k.type = 'button';
k.dataset.stufe = b.schluessel;
k.setAttribute('aria-pressed', String(vBereich === b.schluessel));
k.append(el('span', 'stufenleiter__name', b.name));
k.addEventListener('click', () => { vBereich = b.schluessel; vorlagenbrettZeigen(); });
r1.append(k);
}
ziel.append(r1);
/* Reihe 2: die Stufe, mit der Zahl der Aufgaben darin. */
const r2 = el('div', 'stufenleiter');
r2.setAttribute('role', 'group');
r2.setAttribute('aria-label', 'Könnensstufe wählen');
for (const st of STUFEN_VORLAGE) {
const n = ((vorlagen[vBereich] || {})[st.schluessel] || []).length;
if (!n) continue;
const k = el('button', 'stufenleiter__knopf');
k.type = 'button';
k.dataset.stufe = st.schluessel;
k.setAttribute('aria-pressed', String(vStufe === st.schluessel));
k.title = st.hilfe;
k.append(el('span', 'stufenleiter__name', st.name));
k.append(el('span', 'stufenleiter__zahl', String(n)));
k.addEventListener('click', () => { vStufe = st.schluessel; vorlagenbrettZeigen(); });
r2.append(k);
}
ziel.append(r2);
const hilfe = STUFEN_VORLAGE.find((x) => x.schluessel === vStufe)?.hilfe;
if (hilfe) ziel.append(el('p', 'stufenleiter__hilfe', hilfe));
/* Für wen? */
const fuer = ich.rolle === 'creator' ? ich.id : nurCreator;
if (!fuer) {
ziel.append(el('p', 'vorlagenbrett__hinweis',
'Wähle oben einen Creator, dann lassen sich diese Aufgaben übernehmen.'));
}
const liste = (vorlagen[vBereich] || {})[vStufe] || [];
const box = el('div', 'vorlagenbrett__liste');
for (const [nr, v] of liste.entries()) {
const karte = el('div', 'vorlagenbrett__karte');
karte.append(el('h3', 'vorlagenbrett__titel', v.titel));
karte.append(el('p', 'vorlagenbrett__text', v.text));
const fuss = el('div', 'vorlagenbrett__fuss');
fuss.append(el('span', 'vorlagenbrett__frist',
v.tage === 1 ? 'Frist: morgen' : `Frist: in ${v.tage} Tagen`));
if (fuer) {
const k = el('button', 'knopf knopf--klein', 'Übernehmen');
k.type = 'button';
k.addEventListener('click', () => uebernehmen(k, { art: 'aufgabe', nr }, fuer));
fuss.append(k);
}
karte.append(fuss);
box.append(karte);
}
ziel.append(box);
if (fuer && liste.length > 1) {
const alle = el('button', 'abmelden', `Alle ${liste.length} übernehmen`);
alle.type = 'button';
alle.style.marginTop = '12px';
alle.addEventListener('click', () => uebernehmen(alle, { art: 'aufgaben_alle' }, fuer));
ziel.append(alle);
}
}
/* Ein Weg fuer beide Knoepfe -- einzeln und alle. Zwei fast gleiche
Funktionen waeren der sichere Weg dazu, dass die eine irgendwann
anders reagiert als die andere. */
async function uebernehmen(knopf, was, fuer) {
knopf.disabled = true;
const vorher = knopf.textContent;
knopf.textContent = 'Wird übernommen …';
try {
const a = await hole('/workspace/api/vorlagen/uebernehmen', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...was, bereich: vBereich, stufe: vStufe, creator_id: fuer }),
});
if (!a.ok) {
const f = await a.json().catch(() => ({}));
melde(f.fehler || 'Das hat nicht geklappt.');
knopf.disabled = false; knopf.textContent = vorher;
return;
}
melde('');
/* Erst das Brett neu laden, dann den Block -- so sieht man die
neue Aufgabe sofort dort, wo sie ab jetzt lebt. */
await laden();
await vorlagenbrettZeigen();
} catch {
melde('Das hat nicht geklappt.');
knopf.disabled = false; knopf.textContent = vorher;
}
}
})();