diff --git a/00_Konzept/README_konzept.md b/00_Konzept/README_konzept.md
index d07369d..f5642c6 100644
--- a/00_Konzept/README_konzept.md
+++ b/00_Konzept/README_konzept.md
@@ -130,6 +130,8 @@ formcodiert. Figuren werden **gestellt, nicht gesteckt**; es gibt **zwei** Orte:
**R | A** (oben) und **C | I** (unten). Die beteiligten Rollen werden zusätzlich in
die passende RACI-Zone gestellt — sichtbar wird nicht nur *wer*, sondern *in welcher
Verantwortung*. **A** hat genau einen Platz (genau eine Rolle accountable).
+ Didaktische Gewichtung (Frank): **R und A** sind die Pflicht und unbedingt zu
+ durchdenken (obere Zeile R | A), **C und I** sind ergänzend / nice-to-have.
Alle Standfelder sind Ø 22 (gleich wie die Puck-Mulden — dieselben Ø-20-Figuren).
Details & Designvarianten: [`../02_Spielfiguren/`](../02_Spielfiguren/).
diff --git a/04_Tablet-Quiz/app/DEPLOY.md b/04_Tablet-Quiz/app/DEPLOY.md
index 8fe2f1b..0783e14 100644
--- a/04_Tablet-Quiz/app/DEPLOY.md
+++ b/04_Tablet-Quiz/app/DEPLOY.md
@@ -1,7 +1,14 @@
# Deployment — SLC-Workshop Companion (App)
**Auftrag für die Server-Claude / Ops:** Diese App **statisch** ausliefern. Kein
-Build-Schritt, kein Backend, keine Secrets, keine Datenbank.
+Build-Schritt, keine Secrets, keine Datenbank.
+
+> **Neu (v0.10): kleiner Feedback-Endpoint.** Die App sammelt jetzt Workshop-Feedback
+> (Phasen + Prüfschritte) und **speichert es auf dem Server** zur späteren Auswertung.
+> Dafür braucht es **einen** schlanken POST-Sammelpunkt (Flat-File, **keine Datenbank**).
+> Referenz liegt bei: [`feedback.php`](feedback.php). Ist der Endpoint (noch) nicht
+> aktiv, geht **nichts verloren**: die App hält das Feedback lokal vor (Retry-Queue)
+> und der Header-Button **„⇩ Feedback"** exportiert es als Backup-Datei.
## Was das ist
- Eine **statische Single-Page-PWA**. Alle Inhalte (Stationen, Quiz, Use-Cases)
@@ -11,6 +18,7 @@ Build-Schritt, kein Backend, keine Secrets, keine Datenbank.
- `manifest.webmanifest` — PWA-Manifest
- `sw.js` — Service Worker (Offline-Cache der App-Shell)
- `icon.svg` — App-Icon
+ - `feedback.php` — **optionaler** Feedback-Sammelpunkt (Flat-File, JSON Lines)
## Ziel
Den Ordner `04_Tablet-Quiz/app/` als **statisches Web-Root** über den vorhandenen
@@ -47,11 +55,44 @@ slc.example.intern {
}
```
+## Feedback-Endpoint (empfohlen — für die Auswertung)
+
+Die App schickt jedes gespeicherte Feedback per `POST` als JSON an den im
+`` hinterlegten Pfad (Default: `feedback.php`,
+gleiche Domain). Der Service Worker fasst POSTs nicht an — der Offline-Cache stört
+also nicht.
+
+**Variante A — PHP (Referenz, einfachste Option).** `feedback.php` liegt im App-Ordner.
+Voraussetzung: `php-fpm` aktiv und das App-Verzeichnis für PHP freigegeben; das
+Datenverzeichnis `feedback-data/` muss vom Webserver-User beschreibbar sein.
+
+```nginx
+# innerhalb des server{}-Blocks von oben:
+location ~ \.php$ {
+ include fastcgi_params;
+ fastcgi_pass unix:/run/php/php-fpm.sock; # Pfad anpassen
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+}
+```
+Daten landen in `feedback-data/feedback.jsonl` (eine JSON-Zeile pro Feedback) →
+später für die Auswertung einlesen/zu CSV konvertieren.
+
+**Variante B — kein PHP verfügbar.** Endpoint auf einen beliebigen JSON-POST-Empfänger
+zeigen lassen (z. B. ein kleines Node-/Worker-Skript, das den Body an eine Datei
+anhängt) und im `` dessen URL eintragen.
+
+**Variante C — gar kein Server-Save.** Auch ohne Endpoint funktioniert alles: das
+Feedback bleibt in der Retry-Queue und kann über den Header-Button **„⇩ Feedback"**
+als JSON-Datei exportiert werden.
+
## Verifikation nach dem Deploy
-1. URL öffnen → Startbildschirm „SLC Companion · Schritt 1 · Action Card".
+1. URL öffnen → Startbildschirm „SLC Companion".
2. DevTools → Application → **Service Workers**: `sw.js` ist *activated*.
3. Flugmodus/Offline → Seite neu laden → App lädt weiterhin (Offline-Cache).
-4. Einen Durchlauf spielen → „Debrief" → **↓ Markdown / ↓ JSON** lädt eine Datei.
+4. Einen Major-Durchlauf spielen, an einem Gate Feedback eintragen → **„💾 Feedback
+ speichern"**. Bei aktivem Endpoint erscheint kein Fehler in der Konsole; in
+ `feedback-data/feedback.jsonl` taucht eine neue Zeile auf. Ohne Endpoint: Header
+ **„⇩ Feedback"** lädt die Backup-Datei.
## Updates
- Bei neuem Stand: `git pull`. Wenn sich App-Assets geändert haben, in `sw.js`
diff --git a/04_Tablet-Quiz/app/feedback.php b/04_Tablet-Quiz/app/feedback.php
new file mode 100644
index 0000000..9c1981a
--- /dev/null
+++ b/04_Tablet-Quiz/app/feedback.php
@@ -0,0 +1,58 @@
+ true, 'service' => 'slc-feedback', 'hint' => 'POST JSON to store feedback']);
+ exit;
+}
+
+$raw = file_get_contents('php://input');
+if ($raw === false || strlen($raw) === 0 || strlen($raw) > 65536) {
+ http_response_code(400);
+ echo json_encode(['ok' => false, 'error' => 'empty or oversized body']);
+ exit;
+}
+
+$data = json_decode($raw, true);
+if (!is_array($data)) {
+ http_response_code(400);
+ echo json_encode(['ok' => false, 'error' => 'invalid json']);
+ exit;
+}
+
+// Serverseitigen Eingangszeitstempel + Roh-Metadaten ergänzen (nicht überschreibend).
+$data['_received'] = gmdate('c');
+$data['_ip'] = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
+
+$dir = __DIR__ . '/feedback-data';
+$file = $dir . '/feedback.jsonl';
+if (!is_dir($dir)) { @mkdir($dir, 0775, true); }
+
+$line = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
+$fp = @fopen($file, 'ab');
+if ($fp === false) {
+ http_response_code(500);
+ echo json_encode(['ok' => false, 'error' => 'cannot open data file (Schreibrechte prüfen)']);
+ exit;
+}
+flock($fp, LOCK_EX);
+fwrite($fp, $line);
+flock($fp, LOCK_UN);
+fclose($fp);
+
+echo json_encode(['ok' => true]);
diff --git a/04_Tablet-Quiz/app/index.html b/04_Tablet-Quiz/app/index.html
index ba26e3d..a6745a7 100644
--- a/04_Tablet-Quiz/app/index.html
+++ b/04_Tablet-Quiz/app/index.html
@@ -6,6 +6,8 @@
@@ -752,13 +782,21 @@ const STATIONEN = [
beschreibung:"Entry-Gate der Transition: Die SOR entscheidet zuerst, ob das Vorhaben überhaupt in die Transition darf — und wenn ja, ob die Service-Komponenten neu entwickelt oder nur konfiguriert werden. Erfordert SOR-Approval (Budget- und Ressourcenimplikationen).",
umfasst:["Aufwandsschätzung (Build vs. Configure)","Technische Risiken","Budget-Abgleich","ggf. Lieferanten-Einbindung","SOR-Vorlage für Freigabe"],
artefakt:"Gate-Entscheidung + SOR-Vorlage",
+ ziel:"Sicherstellen, dass nur Vorhaben in die weitere Entwicklung gehen, die ausreichend geplant sind und für die ein grundsätzliches Commitment bezüglich Ressourcen besteht (Budget, Betrieb & Support, Projektressourcen).",
+ // Schritt A: Freigabe-Entscheidung (Frank-Template). Schritt B: Routing (pfade).
+ freigabe:[
+ ["Freigabe","Ausreichend geplant, Ressourcen-Commitment liegt vor → weiter zum Build-/Konfigurations-Routing"],
+ ["Freigabe mit Auflagen","Weiter zum Routing; definierte Auflagen müssen nachgearbeitet werden"],
+ ["Zurück an Design","Planung oder Commitment unzureichend → zurück in die Design-Phase"],
+ ["Ablehnung","Vorhaben wird nicht weiterverfolgt — erfordert SOR-Eskalation"]
+ ],
pfade:[
["Entwicklung","Neuentwicklung/wesentliche Anpassung → weiter zu tr_02"],
["Konfiguration","Bestehende Komponenten konfigurieren → springt zu tr_05"]
],
pruef:[
["Design-Vollständigkeit","Ist das Service Design Document vollständig?"],
- ["Budget-Verfügbarkeit","Ist Budget für die Strategie verfügbar?"],
+ ["Budget-Verfügbarkeit","Ist Budget für die Implementierung verfügbar?"],
["Projektressourcen","Können Ressourcen mobilisiert werden?"],
["Betriebs-/Support-Bereitschaft","Grundsätzliches Commitment vorhanden?"]
],
@@ -851,6 +889,7 @@ const STATIONEN = [
{ id:"tr_09", phase:"transition", typ:"gate", gateNr:2,
name:"Gate 2: Entry-Prüfung nach Build",
beschreibung:"Validierung der Übergabefähigkeit nach Abschluss des Builds. Dies ist eine SO-Einzelentscheidung (keine Gremienentscheidung).",
+ ziel:"Sicherstellen, dass die grundlegenden Voraussetzungen für das Ausrollen eines Service gegeben sind.",
umfasst:["Prüfung der Übergabe-Vollständigkeit","Prüfung des Abnahme-Status","Ressourcen-Outlook (Betrieb/Support vorbereitet)","Pilot-Entscheidung (falls erforderlich)","Dokumentation im Transition-Steckbrief"],
artefakt:"Gate-2-Entscheidung (Transition-Steckbrief)",
pfade:[
@@ -899,6 +938,7 @@ const STATIONEN = [
{ id:"tr_12", phase:"transition", typ:"gate", gateNr:3,
name:"Gate 3: Go-Live-Freigabe",
beschreibung:"Portfolio-Freigabe und formale Aktivierung des Services. Exit-Gate der Transition — SOR-Entscheidung nach dem Konsent-Prinzip.",
+ ziel:"Sicherstellen, dass nur Services für den produktiven Betrieb aktiviert werden, die ausreichend geprüft und qualitätsgesichert sind und für die sowohl im Betrieb als auch im Support genügend Ressourcen zur Verfügung stehen.",
umfasst:["Prüfung der Portfolio-Konsistenz","Prüfung der Betriebsreife (SO-Bestätigung)","Prüfung der Support-Bereitschaft","Prüfung der SLA/SLO-Vereinbarung","Prüfung der Auflagen-Erfüllung aus Gate 2","Bewertung der Geschäftskritikalität","Formale Aufnahme ins Service-Portfolio"],
artefakt:"Gate-3-Entscheidung + Portfolio-Aufnahme",
pfade:[
@@ -1119,8 +1159,9 @@ const STATIONEN = [
RACI + Quiz hier abgeleitet (Franks Entwurf nennt nur die Aktivitaeten). */
{ id:"rv_01", phase:"review", typ:"aktivitaet",
name:"Durchführen von Service-Reviews",
- beschreibung:"Den Service systematisch auswerten und die Ergebnisse im Service-Review-Dokument festhalten.",
- umfasst:["KPIs & Monitoring auswerten","Problems & Incidents auswerten","Kundenfeedback sammeln/einholen","zugrunde liegende Infrastruktur bewerten","Service-Review-Dokument ausfüllen"],
+ beschreibung:"Den Service systematisch auswerten und die Ergebnisse im Service-Review-Dokument festhalten (4 Dimensionen → Handlungsempfehlung).",
+ ziel:"Eine fundierte Entscheidung ermöglichen, ob der Service unverändert weiterbetrieben werden kann oder ob Änderungen erforderlich sind.",
+ umfasst:["KPIs & Monitoring auswerten","Problems & Incidents auswerten","Kundenfeedback sammeln/einholen","zugrunde liegende Infrastruktur bewerten","Bewertung über 4 Dimensionen: Leistungserbringung · Betriebsstabilität · Nutzerzufriedenheit · Zukunftsfähigkeit","Handlungsempfehlung ableiten: Weiterbetrieb · Normal-Change · Major-Change"],
artefakt:"Service-Review-Dokument",
raci:[["service_owner","A"],["betriebsteam","R"],["service_support_team","C"],["problem_manager","C"]],
quiz:[
@@ -1221,9 +1262,9 @@ const ARTEFAKTE = {
A13:{name:"Wissensdatenbank-Eintrag", phase:"support", live:true,
was:"Standardlösungen, Known Errors, Workarounds, FAQ und Anleitungen — das zentrale Arbeitsmittel für 1st und 2nd Level Support.",
warum:"Es beschleunigt den Support, sichert konsistente Antworten und entlastet die höheren Support-Level."},
- A14:{name:"Service-Review-Bericht", phase:"review",
- was:"Das „Service Performance & Improvement Review“: Bewertung über 4 Dimensionen (Leistung, Stabilität, Nutzerzufriedenheit, Zukunftsfähigkeit) per Ampel und eine Handlungsempfehlung (CONTINUE / IMPROVEMENT / REDESIGN / RETIRE).",
- warum:"Es liefert die strukturierte Grundlage für die SOR-Entscheidung über die Zukunft des Service."},
+ A14:{name:"Service-Review-Dokument", phase:"review",
+ was:"Verantwortlich: Service-Owner. Bewertung über 4 Dimensionen mit Leitfrage — Leistungserbringung (liefert der Service den erwarteten Nutzen?), Betriebsstabilität (läuft er störungsarm und beherrschbar?), Nutzerzufriedenheit (wie bewerten die Nutzer den Service?) und Zukunftsfähigkeit (ist er mittelfristig tragfähig?). Daraus eine Handlungsempfehlung: Weiterbetrieb (ggf. mit Monitoring-Fokus) · Änderung als Normal-Change · Änderung als Major-Change.",
+ warum:"Es ermöglicht eine fundierte Entscheidung, ob der Service unverändert weiterbetrieben werden kann oder ob Änderungen erforderlich sind — die Grundlage für die SOR-Bewertung."},
A15:{name:"DPM-Rücklauf", phase:"review",
was:"Die Übergabe an den Demand-Lifecycle — Variante A: Neuer Demand (Redesign/Erweiterung) oder Variante B: Retirement-Plan / Decommissioning-Auftrag (Stilllegung).",
warum:"Es schließt den Lebenszyklus: größere Änderungen oder das Ende eines Service laufen kontrolliert über den Demand-Lifecycle, nicht „nebenbei“."}
@@ -1246,6 +1287,20 @@ const FOLDS_INTO = {
op_02:"A6", op_05:"A9", op_07:"A11",
sp_03:"A10", sp_04:"A10", sp_05:"A10", sp_06:"A10", sp_08:"A10", sp_10:"A11"
};
+/* Bearbeitungstiefe (Frank): "bearbeitet" = eigene Arbeitsergebnisse/Templates,
+ alles andere "entwurf" (konzeptioneller Best-Practice-Entwurf). Fuer Spieler sichtbar. */
+const BEARBEITET = new Set(["ds_01","tr_01","tr_09","tr_12","rv_01"]);
+function stationTiefe(id){ return BEARBEITET.has(id) ? "bearbeitet" : "entwurf"; }
+function tiefeBadge(id){
+ const t = stationTiefe(id);
+ return t==="bearbeitet"
+ ? `● Bearbeitet`
+ : `○ Entwurf`;
+}
+/* Dokumente mit eigenem Detail-Feedback (Frank): Gate 1/2/3 + Service Review. */
+const FEEDBACK_DOCS = {
+ tr_01:"Gate 1", tr_09:"Gate 2", tr_12:"Gate 3", rv_01:"Service Review"
+};
function addArtefakt(a){ if(a){ S.akte = S.akte || {}; S.akte[a] = true; } }
// Beim Start nach Einstiegspunkt vorbefuellen: alles, was VOR der Einstiegs-Station
@@ -1270,7 +1325,8 @@ function defaultState(){
freigabeDone:false, freigabeWrong:null,
entryDone:false, entryWrong:null,
bonusReveal:false, bonusDone:{}, servicesDone:{}, akteFlash:null, svcModalSel:null,
- index:0, stage:"discuss", quizIndex:0, gateDeciderDone:false, gateCrit:0,
+ index:0, stage:"discuss", quizIndex:0, gateDeciderDone:false, gateCrit:0, gate1Approval:null,
+ feedback:{}, feedbackSaved:{}, feedbackQueue:[],
actStep:0, actReveal:false, actDone:false, arteWrong:null,
picks:{}, done:{}, akte:{},
loopback:null, revisit:{}, endReason:null, endGate:null };
@@ -1462,6 +1518,7 @@ function renderMainIntro(){
${card.text}
Als Major Change wird der Service neu eingeführt und durchläuft den kompletten Lifecycle ab Design. Die Freigaben fallen unterwegs an den Gates (SOR bzw. Service Owner) an — hier müsst ihr nichts einordnen. Das Einordnen (Change-Art · Freigabe · Einstieg) kommt danach bei den Bonus-Varianten.
Service-Beschreibung
${serviceDetailHtml(S.service)}
+ ${slcOverview("design","Start — ihr durchlauft den vollen Lifecycle ab Design")}
`;
}
-/* RACI-Legende (deutsch) — wird bei der RACI-Frage immer mit angezeigt. */
+/* RACI-Legende (deutsch) — wird bei der RACI-Frage immer mit angezeigt.
+ Frank: R + A sind die Pflicht (durchdenken!), C + I ergänzend (nice-to-have). */
function raciLegendHtml(){
- const items = [
+ const kern = [
["R","Responsible","verantwortlich für die Durchführung — erledigt die Aufgabe operativ"],
- ["A","Accountable","rechenschaftspflichtig — trägt die Ergebnisverantwortung (genau eine Rolle)"],
+ ["A","Accountable","rechenschaftspflichtig — trägt die Ergebnisverantwortung (genau eine Rolle)"]
+ ];
+ const erg = [
["C","Consulted","konsultiert — wird vorab um Rat/Beitrag gefragt"],
["I","Informed","informiert — wird über das Ergebnis in Kenntnis gesetzt"]
];
+ const row = ([l,en,de])=>`
${l}${en} — ${de}
`;
return `
Wofür RACI steht
` +
- items.map(([l,en,de])=>`
${l}${en} — ${de}
`).join("") +
+ `
★ Pflicht — unbedingt durchdenken
${kern.map(row).join("")}
` +
+ `
ergänzend — nice-to-have
${erg.map(row).join("")}
` +
`
`;
}
@@ -1821,7 +1894,7 @@ function renderRun(){
${cardBig}
-
${chip}${st.id}
+
${chip}${st.id}${tiefeBadge(st.id)}
${st.name}
${stepBody}
@@ -1841,7 +1914,7 @@ function activitySteps(st){
frage:`Welche Rollen setzen diese Aktivität operativ um — wer sorgt für die Umsetzung (Responsible)? Stellt deren Figuren auf die Mulden des Station-Pucks.`,
auf:`
Operativ verantwortlich (R)
${(st.raci.filter(([r,c])=>c.includes("R")).map(([r,c])=>`${roleLabel(r)}${c==="A/R"?" (zugleich A)":""}`).join("")) || '— (keine eigene R-Rolle)'}
Das sind die „Macher" der Aktivität. Wer dafür geradesteht (A) sowie beratend (C) bzw. informiert (I) ist, klärt Schritt 3.
` },
{ label:"RACI",
- frage:`Ergänzt nun die vollständige RACI: Wer ist Accountable (trägt die Verantwortung), wer Consulted, wer Informed — zusätzlich zu den Responsible aus Schritt 2? Sortiert die Figuren ins Aktiv-Feld (R·A·C·I).`,
+ frage:`Vervollständigt die RACI. Wichtig zuerst: Wer ist Accountable (trägt die Ergebnisverantwortung — genau eine Rolle)? Zusammen mit den Responsible aus Schritt 2 sind R + A die Pflicht. Consulted und Informed danach ergänzen (nice-to-have). Sortiert die Figuren ins Aktiv-Feld (R·A·C·I).`,
legend: raciLegendHtml(),
auf:`
Ihr habt den Change von der Einordnung bis zur Umsetzung begleitet. Im Workshop schließt hier das gemeinsame Debriefing am Tisch an (Reflexion der Stationen, offene Fragen).