update
451
LEARNINGS.md
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
# Learnings
|
||||||
|
|
||||||
|
Was beim Bau von planer2 gemessen wurde. Nicht was plausibel klang — was sich
|
||||||
|
nachweisen ließ. Geschrieben, bevor Prüfung 3 entfernt wurde, damit ihr Wissen
|
||||||
|
bleibt.
|
||||||
|
|
||||||
|
Stand: 29.07.2026
|
||||||
|
|
||||||
|
|
||||||
|
## Der Aufbau in einem Absatz
|
||||||
|
|
||||||
|
Markdown-Dateien sind die Wahrheit. Eine Karte ist ein Auftrag, eine Funktion
|
||||||
|
etwas, das man für sich testen kann, eine Regel eine Zusicherung. Der Code eines
|
||||||
|
fremden Projekts ist Beleg, nie Quelle der Wahrheit. Drei Prüfungen glichen ab:
|
||||||
|
Stil, „hat jede Funktion Code" und „hat jede Codestelle eine Funktion".
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Was funktioniert hat
|
||||||
|
|
||||||
|
### Beweislast statt Meinung
|
||||||
|
|
||||||
|
Jede Aussage über den Code muss eine wörtlich zitierte Zeile mitbringen.
|
||||||
|
`quellcode.belegt()` schlägt sie gegen die Datei. Ein misslungenes Zitat zählt
|
||||||
|
nicht als Fund.
|
||||||
|
|
||||||
|
Das ist der wichtigste Baustein des ganzen Systems. Ohne ihn schlug ein früher
|
||||||
|
Versuch vor, gültige Inhalte zu löschen — das Modell urteilte direkt und lag
|
||||||
|
falsch. Mit ihm ist ein Fund nachprüfbar und ein Nichtfund die einzige Aussage,
|
||||||
|
die überhaupt Stimmen braucht.
|
||||||
|
|
||||||
|
**Die Asymmetrie ist der Hebel.** Vorhandensein lässt sich zitieren,
|
||||||
|
Nichtvorhandensein nicht. Also: ein geprüfter Fund genügt zum Freispruch, sechs
|
||||||
|
Fehlversuche sind die Anklage.
|
||||||
|
|
||||||
|
### Das Urteilsgedächtnis
|
||||||
|
|
||||||
|
Ein Urteil kostet sechs Anfragen und fällt bei Grenzfällen mal so, mal so. Genau
|
||||||
|
daran scheiterte die Wiederholbarkeit: derselbe Stand lieferte in Runde 1 einen
|
||||||
|
Befund, in Runde 2 keinen, in Runde 3 wieder einen.
|
||||||
|
|
||||||
|
Gespeichert wird deshalb das Urteil, nicht nur das Ergebnis (`ledger.py`,
|
||||||
|
`.pruefung.json` im Projektordner). Ein Eintrag gilt weiter, solange seine
|
||||||
|
Voraussetzung nachweisbar dasteht:
|
||||||
|
|
||||||
|
- ein Beleg, solange die zitierte Zeile noch in der Datei steht
|
||||||
|
- eine Fehlmeldung, solange sich am Quelltext nichts geändert hat
|
||||||
|
|
||||||
|
Das machte gleichen Stand zu gleichem Ergebnis. Ohne dieses Gedächtnis gibt es
|
||||||
|
keine Konvergenz — der Loop würfelt jede Runde neu.
|
||||||
|
|
||||||
|
**Der Zuschnitt des Fingerabdrucks entscheidet über die Kosten.** Anfangs ging
|
||||||
|
der Abdruck über den ganzen Funktionstext. Eine Stilkorrektur an einer Regel
|
||||||
|
tötete damit auch das Inhaltsurteil. Gemessen: R1 code 384 Anfragen, R2 code 384
|
||||||
|
Anfragen — null Treffer im Gedächtnis. Nach der Kürzung auf Karten- und
|
||||||
|
Funktionstitel: R3 feature 30 statt 259, R3 code 21 statt 201. Rund 88 %
|
||||||
|
Trefferquote, sobald die Stiländerungen abgeklungen sind.
|
||||||
|
|
||||||
|
Regel daraus: **ein Urteil verfällt nur, wenn sich ändert, worauf es tatsächlich
|
||||||
|
beruht.** Nicht wenn sich der Text drumherum ändert.
|
||||||
|
|
||||||
|
### Frühabbruch statt fester Stimmenzahl
|
||||||
|
|
||||||
|
Sechs Stimmen je Einheit waren gemessen richtig für die *Schwelle*, aber
|
||||||
|
verschwenderisch für den *Normalfall*. Ein nachgeschlagener Beleg beendet die
|
||||||
|
Frage — die anderen fünf Antworten wurden bezahlt und weggeworfen.
|
||||||
|
|
||||||
|
Stufen (1, dann 2, dann 3) mit Abbruchprüfung nach jeder Stufe:
|
||||||
|
|
||||||
|
- Prüfung 2: von 6 auf ~1 Anfrage im Normalfall
|
||||||
|
- Prüfung 3: von 6 auf ~3
|
||||||
|
|
||||||
|
Gesamter Loop auf planer2: **2719 → 1354 Anfragen** bei gleicher Rundenzahl.
|
||||||
|
|
||||||
|
**Eine Falle dabei:** Eine ausgefallene Stimme ist keine Ja-Stimme. Der erste
|
||||||
|
Wurf brach ab, sobald die verbleibenden Stimmen die Schwelle rechnerisch nicht
|
||||||
|
mehr reißen konnten — und hielt damit einen HTTP-429 für „ist abgedeckt". Fix:
|
||||||
|
nur abbrechen, wenn alle gestellten Fragen auch beantwortet wurden.
|
||||||
|
|
||||||
|
### Schwellen misst man, man rät sie nicht
|
||||||
|
|
||||||
|
Alle Zahlen in `inhalt.py` stammen aus Messungen an gepflanzten Lücken:
|
||||||
|
|
||||||
|
- `FEHLT_AB = 4` von 6 — eine echte Lücke war immer einstimmig; strittige Stellen
|
||||||
|
lagen darunter und kippten zwischen Läufen
|
||||||
|
- `TITEL_KANAL = 0.85`, `INHALT_KANAL = 0.7` — darunter explodierten die
|
||||||
|
Kandidatenpaare
|
||||||
|
- `KERN_MIN = 3` — unter drei Wörtern kennt die Überlappung nur 0, 0,5 und 1
|
||||||
|
- `BUENDEL = 6` Stellen je Anfrage — größere Bündel verloren Stellen aus der Mitte
|
||||||
|
|
||||||
|
Der Versuch, Grenzfälle *lexikalisch* zu filtern, ist gescheitert: „Produkt
|
||||||
|
merken" und „Konfiguration merken" sehen identisch aus, einer ist eine echte
|
||||||
|
Lücke, der andere nicht. Die Lösung war eine Entscheidung, keine Formel:
|
||||||
|
empfindlich sein (4 von 6) und Ablehnungen dauerhaft merken.
|
||||||
|
|
||||||
|
### Der Musterkatalog als Daten
|
||||||
|
|
||||||
|
Was eine auslösbare Stelle ist, steht in `muster/*.toml`, nicht im Code. Ein neues
|
||||||
|
Rahmenwerk ist eine Datei, keine Codeänderung.
|
||||||
|
|
||||||
|
Auslöser dafür: Bei einem Shopware-Plugin fand das Inventar genau *eine* Stelle,
|
||||||
|
weil Twig, Symfony-Attribute und `element.addEventListener` als Regex im Programm
|
||||||
|
nicht vorkamen. Nach dem Katalog: 19 Stellen.
|
||||||
|
|
||||||
|
Zwei Nachbesserungen, die viel ausmachten:
|
||||||
|
|
||||||
|
- `store.reroll()` ist die Fähigkeit `reroll`, nicht `store`. Ohne diese Regel
|
||||||
|
fielen zwölf Knöpfe eines Stores auf einen einzigen Eintrag zusammen.
|
||||||
|
- Symfony schreibt Routen benannt (`path: '/x'`) **oder** positionell
|
||||||
|
(`Route('/x', ...)`). Nur die benannte Form zu kennen fand 5 von 127 Routen.
|
||||||
|
|
||||||
|
### Erzeugen aus dem Inventar, nicht aus dem Quelltext
|
||||||
|
|
||||||
|
Der Erzeuger geht von den Stellen aus, nicht vom Code als Ganzem. Damit ist
|
||||||
|
Vollständigkeit Bauart, nicht Hoffnung: jede Stelle bekommt eine Funktion, jede
|
||||||
|
Funktion stammt von mindestens einer Stelle.
|
||||||
|
|
||||||
|
Was das Modell frei aufzählt, war jede Runde anders, und niemand konnte sagen, ob
|
||||||
|
etwas fehlt.
|
||||||
|
|
||||||
|
Vier Schritte: benennen (je Stelle) → falten (gleiche Titel zusammenlegen) →
|
||||||
|
verdichten (alle Funktionen nebeneinander neu ordnen) → füllen (je Funktion Ziel,
|
||||||
|
Regeln, Testschritte aus dem Code).
|
||||||
|
|
||||||
|
**Verdichten ist der Schritt, an dem Minimalität entsteht.** Ohne ihn kamen aus
|
||||||
|
21 Stellen 17 Karten mit je einer Funktion. Mit ihm: 5 Karten, 19 Funktionen.
|
||||||
|
|
||||||
|
### Nachfassen statt Verlust
|
||||||
|
|
||||||
|
Fällt eine Anfrage aus, fehlen sechs Stellen auf einmal. Bei 19 Stellen ist das
|
||||||
|
fast die Hälfte — und die Karten waren still unvollständig. Gemessen: 8 von 19
|
||||||
|
Stellen blieben unbenannt.
|
||||||
|
|
||||||
|
Fix: kleinere Bündel nachfragen (6 → 2 → 1), und was auch einzeln keinen Namen
|
||||||
|
bekommt, aus dem Schlüssel benennen. Ein grober Titel fällt auf; eine fehlende
|
||||||
|
Stelle fällt niemandem auf.
|
||||||
|
|
||||||
|
Dasselbe Muster beim Verdichten: fiel eine Zuordnung aus, blieben ihre zwölf
|
||||||
|
Funktionen auf Einzelkarten stehen — aus 19 Fähigkeiten wurden wieder 16 Karten.
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Was nicht funktioniert hat
|
||||||
|
|
||||||
|
### Prüfung 3 — drei Konstruktionsfehler
|
||||||
|
|
||||||
|
Die Prüfung „hat jede Codestelle eine Funktion" hat nie zuverlässig funktioniert.
|
||||||
|
Nicht wegen des Modells, sondern wegen der Grundmenge, gegen die sie zählt.
|
||||||
|
Gemessen an planer2 (64 Stellen, 45 Funktionen):
|
||||||
|
|
||||||
|
**1. Eingabefelder fehlen ganz.** `v-model="suche"` ist kein Muster. Vier solcher
|
||||||
|
Felder im Projekt, keins im Inventar. Die Feature-Suche existiert, wird aber nie
|
||||||
|
gezählt und nie gemeldet.
|
||||||
|
|
||||||
|
**2. Argumente fallen weg.** Der Schlüssel ist der Funktionsname. Alle vier
|
||||||
|
Prüfknöpfe rufen `startePruefung(projekt, art)` → eine Stelle statt vier. Vier
|
||||||
|
Fähigkeiten, ein Punkt.
|
||||||
|
|
||||||
|
**3. Gleichnamige Funktionen fallen zusammen.** `alleAnnehmen` gibt es in
|
||||||
|
`FeatureKarte.vue` und in `FeatureListe.vue` — zwei verschiedene Dinge, ein
|
||||||
|
Punkt. Der neue Sammelknopf „Alle N Karten anlegen" ist dadurch unsichtbar.
|
||||||
|
|
||||||
|
Ergebnis: Gegen das eigene Inventar meldete Prüfung 3 **62 von 64 = 97 %**. Gegen
|
||||||
|
das, was das Programm wirklich kann, waren es **rund 44 von 52 = 85 %**.
|
||||||
|
|
||||||
|
**Dazu zwei falsche Abgedeckt-Urteile.** `DELETE /pruefung` und `GET /chat`
|
||||||
|
tragen keine Funktion. Prüfung 3 meldete null Lücken — das Modell hielt sie für
|
||||||
|
gedeckt, weil eine ähnlich klingende Funktion danebenstand.
|
||||||
|
|
||||||
|
### Zusicherungen ohne Auslöser sind unsichtbar
|
||||||
|
|
||||||
|
Der größte Fund, und er gilt weit über Prüfung 3 hinaus.
|
||||||
|
|
||||||
|
Vieles, was ein Programm zusichert, hängt an keinem Auslöser: eine Beschriftung
|
||||||
|
im Markup, eine Vorbelegung beim ersten Aufruf, die Reihenfolge von Dingen, ein
|
||||||
|
Standardfall, eine Grenze.
|
||||||
|
|
||||||
|
Gemessen an MtoVariantConfigurator: Von acht Anforderungen fehlten **fünf** auf
|
||||||
|
den erzeugten Karten, obwohl der Code sie umsetzte. Transitivität der Dropdowns,
|
||||||
|
Reihenfolge aus dem Backend, Vorbelegung aus dem Elternartikel, Schritt-Badge,
|
||||||
|
Hinweistexte. Alle vorhanden, alle unsichtbar.
|
||||||
|
|
||||||
|
Die Vollständigkeitsprüfung meldete dabei **null Lücken** — sie zählt nur
|
||||||
|
Auslöser.
|
||||||
|
|
||||||
|
**Der Fix, der wirkte:** Karten bekamen eigenes Ziel und eigene Regeln, gefüllt
|
||||||
|
aus den *ganzen Dateien* statt aus fünf Zeilen um die Stelle. Dazu ein
|
||||||
|
Nachtrag-Schritt je Karte: „was steht im Code und fehlt hier?". Danach 8 von 8.
|
||||||
|
|
||||||
|
**Was nicht wirkte:** Der Prompt allein. „Nenne die Sache, nicht die Umsetzung"
|
||||||
|
stand von Anfang an drin und half nicht.
|
||||||
|
|
||||||
|
### Dubletten: Zeichenähnlichkeit fängt Typ 4 nicht
|
||||||
|
|
||||||
|
„Galerie vorwärts blättern" und „Galerie nächste anzeigen" sind dieselbe Sache.
|
||||||
|
Gemessene Titelähnlichkeit: **0,53**. Schwelle: **0,85**. Das Paar wurde nie
|
||||||
|
Kandidat — kein Modell hat es je gesehen.
|
||||||
|
|
||||||
|
Die Klon-Typologie erklärt es: Typ 1 (exakt) und Typ 2 (umbenannt) fängt
|
||||||
|
String-Matching. Typ 4 (gleiche Bedeutung, andere Worte) nicht.
|
||||||
|
|
||||||
|
**Der Fix:** ein vierter Kanal — alles auf derselben Karte wird Kandidat.
|
||||||
|
Bezahlbar, weil eine Dublette fast immer neben ihrer Zwillingsschwester steht.
|
||||||
|
Zehn Funktionen ergeben 45 Paare, nicht Tausende. Fand sofort zwei echte
|
||||||
|
Dubletten, die vier Runden lang durchgerutscht waren.
|
||||||
|
|
||||||
|
### Streuung: gemessen, nicht behoben
|
||||||
|
|
||||||
|
Gleicher Code, gleiche Einstellungen, andere Karten. Lange nur eine Beobachtung,
|
||||||
|
dann eine Zahl.
|
||||||
|
|
||||||
|
Adjusted Rand Index über die Zerlegung der *Codestellen* (Titel ändern sich
|
||||||
|
zwischen Läufen, Stellen nicht):
|
||||||
|
|
||||||
|
| | Mittel | Minimum |
|
||||||
|
|---|---|---|
|
||||||
|
| Karten | **0,34** | 0,28 |
|
||||||
|
| Funktionen | **0,53** | 0,51 |
|
||||||
|
|
||||||
|
Kartenzahl über drei Läufe: 9, 9, 13.
|
||||||
|
|
||||||
|
0,34 heißt: die Kartenzuordnung ist näher am Zufall als an einer Entscheidung.
|
||||||
|
Das ist der ungelöste Kern des Erzeugers.
|
||||||
|
|
||||||
|
Was die Streuung *senkte*, aber nicht beseitigte: die Codezeile als Anker beim
|
||||||
|
Umbenennen. Ohne sie wurde aus „Konfigurator öffnen" ein „Erweiterung
|
||||||
|
aktivieren" — das Modell sah nur den Titel.
|
||||||
|
|
||||||
|
### Der volle Stilkatalog verdirbt Struktur-Antworten
|
||||||
|
|
||||||
|
Nach Kartentiteln gefragt, antwortete das Modell mit ganzen Karten samt
|
||||||
|
Testschritten. Die Beispiele im Katalog sind stärker als die Formatangabe.
|
||||||
|
|
||||||
|
Fix: nur die Titelregeln mitgeben. Allgemeine Lehre — **gib einem Schritt nur
|
||||||
|
den Teil des Katalogs, den er braucht.**
|
||||||
|
|
||||||
|
### Zu viel Parallelität
|
||||||
|
|
||||||
|
Vier gleichzeitige Läufe → HTTP 429. Ausgefallene Stimmen sahen aus wie „nichts
|
||||||
|
gefunden". Nacheinander laufen lassen war die Lösung, nicht mehr Wiederholungen.
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Was aus der Recherche belegt ist
|
||||||
|
|
||||||
|
Eine Recherche zu Verfahren für vollständige und minimale Extraktion lieferte
|
||||||
|
Belege für einiges, was hier gebaut wurde, und ein paar Warnungen.
|
||||||
|
|
||||||
|
**Bestätigt:**
|
||||||
|
|
||||||
|
- *Adaptive Consistency* (Aggarwal et al., EMNLP 2023): Frühabbruch senkt das
|
||||||
|
Stichprobenbudget „by up to 7.9 times with an average accuracy drop of less
|
||||||
|
than 0.1%". Deckt sich mit der hier gemessenen Halbierung.
|
||||||
|
- *Early cutoff* aus der Build-System-Forschung (Mokhov et al., ICFP 2018): ein
|
||||||
|
Urteil verfällt nur, wenn sich seine echten Abhängigkeiten ändern. Genau das
|
||||||
|
Problem des zu groben Fingerabdrucks.
|
||||||
|
- *Mutanten als Ersatz für echte Fehler* (Just et al., FSE 2014): 73 % Kopplung
|
||||||
|
über 357 echte Fehler. Rechtfertigt gepflanzte Lücken als Vollständigkeitsmaß —
|
||||||
|
mit der Einschränkung, dass rund ein Viertel ungekoppelt bleibt.
|
||||||
|
- *Extraktive statt abstraktive Erzeugung* senkt Halluzination messbar. Hier: das
|
||||||
|
Zitat, das gegen die Datei geschlagen wird.
|
||||||
|
|
||||||
|
**Gewarnt:**
|
||||||
|
|
||||||
|
- Chao1 und Capture-Recapture schätzen Vollständigkeit systematisch zu niedrig,
|
||||||
|
besonders bei kleinen Stichproben. Für 19 Einheiten wertlos.
|
||||||
|
- Testsuite-Minimierung erkauft Größenreduktion mit Erkennungsverlust: „over 70 %"
|
||||||
|
Reduktion, „around 12.5 %" weniger gefundene Fehler. Aggressive Minimalität hat
|
||||||
|
einen Preis.
|
||||||
|
- Self-Consistency hilft bei konvergenter richtiger Antwort und *schadet* bei
|
||||||
|
systematischem Bias. Mehr Stimmen ersetzen keine bessere Verankerung.
|
||||||
|
|
||||||
|
**Nicht anwendbar hier:** Dynamische Invariantenerkennung (Daikon) und
|
||||||
|
Coverage-Kriterien brauchen einen *laufenden* Zielprozess mit Tests. Das System
|
||||||
|
liest nur Dateien fremder Projekte.
|
||||||
|
|
||||||
|
|
||||||
|
## 4. Synchronisation zwischen Planung und Code
|
||||||
|
|
||||||
|
Zweite Recherche, gezielt auf die Frage: wie hält man eine fachliche Beschreibung
|
||||||
|
und den Code dauerhaft synchron, in beide Richtungen? 25 Behauptungen gegengeprüft,
|
||||||
|
13 überlebt, 12 verworfen.
|
||||||
|
|
||||||
|
### Beide Richtungen enden bei etwa der Hälfte
|
||||||
|
|
||||||
|
**Planung → Code: 53,5 %.** E2EDevBench (50 reale PyPI-Projekte, Anforderungsdokument
|
||||||
|
als einziger Eingang) misst 53,50 % umgesetzte Anforderungen in der besten
|
||||||
|
Konfiguration. Ein unabhängig gebauter Benchmark mit anderen Autoren und anderen
|
||||||
|
Agenten-Rahmenwerken kommt auf 53,75 %. „The other half of the requirements
|
||||||
|
remained unfulfilled."
|
||||||
|
|
||||||
|
Die Abnahme durch einen Menschen ist damit kein Sicherheitsgurt, sondern die
|
||||||
|
gemessene Notwendigkeit.
|
||||||
|
|
||||||
|
**Code → Planung: 59 %.** Polikarpova, Ciupa, Meyer (ISSTA 2009, 25 Klassen, 831
|
||||||
|
menschliche Klauseln, über 19.500 abgeleitete): Automatische Inferenz findet 59 %
|
||||||
|
aller von Programmierern geschriebenen Zusicherungen wieder — mit kleiner
|
||||||
|
Testsuite nur 50 %. Autorenfazit: „contract inference cannot completely replace
|
||||||
|
the manual work of writing assertions."
|
||||||
|
|
||||||
|
Und die Ausbeute ist verrauscht: 90 % der abgeleiteten Klauseln sind korrekt, aber
|
||||||
|
nur **64 % relevant**. Rund ein Drittel muss aussortiert werden.
|
||||||
|
|
||||||
|
### Der brauchbarste Einzelbefund
|
||||||
|
|
||||||
|
Der Engpass ist **nicht das Denkvermögen, sondern der Ausdrucksvorrat**.
|
||||||
|
|
||||||
|
Von der Abdeckungslücke gehen 31 Prozentpunkte auf „nicht ausdrückbar" und nur
|
||||||
|
etwa 10 auf „ausdrückbar, aber nicht gefunden" — Verhältnis rund 3:1. Und von den
|
||||||
|
nicht ausdrückbaren Klauseln scheiterten **54 % allein daran, dass sie eine
|
||||||
|
Funktion mit einem Argument aufrufen**, weitere 19 % an fehlenden
|
||||||
|
Implikations-Schablonen.
|
||||||
|
|
||||||
|
Die Autoren widersprechen ausdrücklich der naheliegenden Deutung: „It is tempting
|
||||||
|
to draw the conclusion that at present the tool cannot compete with humans on
|
||||||
|
higher levels of abstraction. However … 'abstract' assertions are mostly not
|
||||||
|
expressible by the tool."
|
||||||
|
|
||||||
|
Übertragen auf uns, als Hypothese ohne Messung: dem Extraktor das Fachvokabular
|
||||||
|
mitzugeben — Funktionsnamen, Domänenbegriffe — schlägt vermutlich ein besseres
|
||||||
|
Modell.
|
||||||
|
|
||||||
|
### Die Beleglücke ist der eigentliche Befund
|
||||||
|
|
||||||
|
Für drei Dinge liefert der ganze Korpus **keinen einzigen belegten Mechanismus**:
|
||||||
|
|
||||||
|
- **Inkrementelle Synchronisation** aus Commits, Diffs, Pull Requests. Alles
|
||||||
|
Gemessene ist Greenfield-Vollerzeugung.
|
||||||
|
- **Drift-Erkennung ohne Vollvergleich.** Die einzige geprüfte Behauptung dazu —
|
||||||
|
Invarianten als Verhaltens-Fingerabdruck — wurde 1:2 verworfen.
|
||||||
|
- **Zwei-Wege-Konflikte.** In dem untersuchten Spec-Driven-Rahmenwerk kommen die
|
||||||
|
Begriffe *traceability*, *drift*, *bidirectional* und *round-trip* **null mal**
|
||||||
|
vor. Diese Verfahren sind einbahnig.
|
||||||
|
|
||||||
|
Das heißt nicht, dass es nicht geht. Es heißt, dass niemand es gemessen hat.
|
||||||
|
|
||||||
|
### Was gemessen wurde und fast nichts brachte
|
||||||
|
|
||||||
|
Prozess- und Strukturarbeit auf der Spezifikationsseite:
|
||||||
|
|
||||||
|
- Kontext-Hooks in einem Vier-Phasen-Ablauf (Specify → Plan → Tasks → Implement):
|
||||||
|
**+3,0 %** der Skala
|
||||||
|
- Spezifikation nach Epics gliedern: **+1,4 Prozentpunkte** Abdeckung, gleichzeitig
|
||||||
|
**−4,0 Prozentpunkte** auf der lexikalischen Messgröße
|
||||||
|
|
||||||
|
Der einzige stabile Gewinn kommt aus der Erdung im echten Code und aus
|
||||||
|
ausführbaren Testsuiten. Das deckt sich mit unserem eigenen Fund: der Prompt allein
|
||||||
|
half nie, das Zeigen ganzer Dateien half sofort.
|
||||||
|
|
||||||
|
### Verworfen — und nicht durch die Hintertür zurückholen
|
||||||
|
|
||||||
|
Sechs Mechanismen, die für unsere Architektur naheliegend gewesen wären, haben die
|
||||||
|
Gegenprüfung nicht überstanden:
|
||||||
|
|
||||||
|
| Behauptung | Abstimmung |
|
||||||
|
|---|---|
|
||||||
|
| Invarianten-Vergleich als Drift-Fingerabdruck | 1:2 |
|
||||||
|
| LLM-Richter je Anforderung als referenzfreie Abdeckungsmessung | 0:3 |
|
||||||
|
| Spurious Scenario Rate als Überspezifikations-Melder | 0:3 |
|
||||||
|
| Einbettungs- statt Wortabdeckung als Messgröße | 0:3 |
|
||||||
|
| Daikon-Präzision über 95 % gegen ESC/Java | 0:3 |
|
||||||
|
| Ein Zwischenartefakt Plan/Design schade der Treue | 0:3 |
|
||||||
|
|
||||||
|
Besonders bitter: der Fingerabdruck-Gedanke. Er hätte genau unsere Drift-Frage
|
||||||
|
gelöst.
|
||||||
|
|
||||||
|
### Ein Widerspruch, der offen bleibt
|
||||||
|
|
||||||
|
Die Behauptung, Inferenz decke systematisch Zusicherungen auf, wo kein Mensch
|
||||||
|
etwas notiert hatte — im Mittel 4,9-fach so viele relevante Klauseln, 13,3-fach bei
|
||||||
|
Schleifeninvarianten — wurde als eigenständige Behauptung **0:3 verworfen**.
|
||||||
|
Dieselben Zahlen tauchen aber in der bestätigten Belegkette einer anderen
|
||||||
|
Behauptung wieder auf. Der Widerspruch ist ungelöst.
|
||||||
|
|
||||||
|
Die Zahlen gelten damit nicht als belegt. Die Richtung — Extraktion ergänzt den
|
||||||
|
Menschen, ersetzt ihn nicht — ist mehrfach gestützt.
|
||||||
|
|
||||||
|
### Grenzen dieser Recherche
|
||||||
|
|
||||||
|
- **Alter.** Die härtesten Zahlen (59 %, 64 %, 54 %) sind von 2009 und hängen am
|
||||||
|
damaligen Schablonensatz. Sie sind **keine Obergrenze für LLM-Extraktion** — dazu
|
||||||
|
existiert keine Messung.
|
||||||
|
- **Quellenlage.** Vier der sechs tragenden Quellen sind unbegutachtete Preprints.
|
||||||
|
Begutachtet sind zwei plus eine Industriefallstudie.
|
||||||
|
- **Transfergrenze.** Alle harten Zahlen messen maschinenprüfbare Artefakte —
|
||||||
|
ausführbare Vor- und Nachbedingungen, Eiffel-Kontrakte, Gherkin. Unsere Planung
|
||||||
|
ist Prosa für Projektmanager. Der Schluss von „59 % der formalen Zusicherungen"
|
||||||
|
auf „x % der fachlichen Beschreibung" ist Plausibilität, keine Messung.
|
||||||
|
- **Zirkularität.** Die 53,5 % beruhen auf einem Modell als Richter, das teilweise
|
||||||
|
eigene Ausgaben bewertet.
|
||||||
|
|
||||||
|
### Was das für planer2 heißt
|
||||||
|
|
||||||
|
- Prüfung 2 sitzt auf dem belegten Teil. Beleg gegen die Datei geschlagen, ein Fund
|
||||||
|
genügt, ein Nichtfund braucht Mehrheit.
|
||||||
|
- Prüfung 3 hätte auf dem unbelegten gesessen. Das Streichen war richtig — und
|
||||||
|
jetzt auch von außen gestützt.
|
||||||
|
- Der inkrementelle Normalfall ist Neuland. Kein Beleg dagegen, aber auch keiner
|
||||||
|
dafür. Wer ihn baut, misst selbst oder weiß nichts.
|
||||||
|
- Die Kostenlage stützt ihn trotzdem: Vollsynchronisation kostete im Benchmark
|
||||||
|
~7 $ und 5,24 Mio. gesendete Token je Projekt — für 53,5 %.
|
||||||
|
|
||||||
|
|
||||||
|
## 5. Handkarten gegen erzeugte Karten
|
||||||
|
|
||||||
|
Ein direkter Vergleich auf MtoVariantConfigurator.
|
||||||
|
|
||||||
|
| | Handkarten | Erzeuger |
|
||||||
|
|---|---|---|
|
||||||
|
| Runden bis still | 2 | 3 |
|
||||||
|
| Anfragen | 361 | 1044 |
|
||||||
|
| Karten | 7 | 4 |
|
||||||
|
| Funktionen | 26 | 17 |
|
||||||
|
| Anforderungen erfüllt | 8 von 8 | 5 von 8 |
|
||||||
|
|
||||||
|
Die Handkarten entstanden aus Issue, Wiki **und allen 47 Kommentaren** plus dem
|
||||||
|
Code. Die Prüfungen änderten daran nur drei Stilkleinigkeiten.
|
||||||
|
|
||||||
|
Der Unterschied liegt nicht bei den Funktionen — die findet der Erzeuger. Er
|
||||||
|
liegt bei den Kartenregeln, also bei den Zusicherungen ohne Auslöser.
|
||||||
|
|
||||||
|
**Ein Fehler dabei, der eine eigene Lehre ist:** Beim ersten Anlauf habe ich nur
|
||||||
|
Beschreibung und Wiki gelesen und „8 von 8 erfüllt" gemeldet. Die 47 Kommentare
|
||||||
|
standen in derselben Datei. Die Zahl war falsch, weil die Grundmenge falsch war.
|
||||||
|
Wer Vollständigkeit misst, muss zuerst prüfen, ob er die Quelle ganz gelesen hat.
|
||||||
|
|
||||||
|
|
||||||
|
## 6. Zahlen zum Nachschlagen
|
||||||
|
|
||||||
|
Vier Projekte, Loop aus Erzeugen und drei Prüfungen bis zur Ruhe.
|
||||||
|
|
||||||
|
| Projekt | Dateien | Stellen | Karten | Funktionen | Runden | Anfragen |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| MtoVariantConfigurator | 19 | 19 | 4–7 | 17–26 | 2–3 | 361–1044 |
|
||||||
|
| tft | 44 | 21 | 5 | 20 | 3 | 616 |
|
||||||
|
| planer2 | 34 | 64 | 8–13 | 46–51 | 4–5 | 1354–2719 |
|
||||||
|
| MtoRagSystem | 475 | 183 | 23–33 | 150–168 | 5+ | ~4000 |
|
||||||
|
|
||||||
|
MtoRagSystem: 6,6 Mio. Zeichen Quelltext. Prüfung 2 schickte anfangs alles je
|
||||||
|
Funktion und fiel für das ganze Projekt aus. Mit Vorauswahl (erst fragen, welche
|
||||||
|
Dateien, dann nur die zeigen): 149 von 150 Funktionen belegt, 0 Fehler.
|
||||||
|
|
||||||
|
|
||||||
|
## 7. Offene Fragen
|
||||||
|
|
||||||
|
- **Streuung.** ARI 0,34 bei den Karten. Verankerung half, löste es nicht.
|
||||||
|
Deterministische Gruppierung wäre reproduzierbar, aber nicht zwingend richtig.
|
||||||
|
- **Grundmenge für Zusicherungen.** Karten-Regeln plus Nachtrag-Schritt haben den
|
||||||
|
konkreten Fall gelöst. Ein *zählbares* Maß dafür gibt es nicht.
|
||||||
|
- **Belege je Funktion.** Prüfung 2 merkt sich nur einen Beleg — den ersten, der
|
||||||
|
sich nachschlagen ließ. Für eine spätere, deterministische Abdeckungsrechnung
|
||||||
|
bräuchte sie alle.
|
||||||
|
- **Körnung.** Wann sind zwei Dinge eine Funktion, wann zwei? Es gibt kein
|
||||||
|
belegtes absolutes Maß dafür ohne Referenzmodell.
|
||||||
@@ -14,8 +14,10 @@ API_VERSION = "2023-06-01"
|
|||||||
|
|
||||||
# Der Provider drosselt bei vielen gleichzeitigen Anfragen
|
# Der Provider drosselt bei vielen gleichzeitigen Anfragen
|
||||||
WIEDERHOLBAR = (429, 500, 502, 503, 504)
|
WIEDERHOLBAR = (429, 500, 502, 503, 504)
|
||||||
WIEDERHOLUNGEN = 4
|
WIEDERHOLUNGEN = 6
|
||||||
PAUSE = 2.0
|
PAUSE = 2.0
|
||||||
|
# Länger als das zu warten hilft nicht mehr; dann ist die Drossel zu weit offen
|
||||||
|
HOECHSTPAUSE = 60.0
|
||||||
|
|
||||||
|
|
||||||
class NichtKonfiguriert(ValueError):
|
class NichtKonfiguriert(ValueError):
|
||||||
@@ -37,6 +39,15 @@ def pruefe_konfiguration():
|
|||||||
raise NichtKonfiguriert(f"Nicht konfiguriert: {', '.join(fehlend)} in .env fehlt")
|
raise NichtKonfiguriert(f"Nicht konfiguriert: {', '.join(fehlend)} in .env fehlt")
|
||||||
|
|
||||||
|
|
||||||
|
def _pause(res, versuch: int) -> float:
|
||||||
|
"""Wie lange bis zum nächsten Versuch — der Provider darf es selbst sagen."""
|
||||||
|
gesagt = res.headers.get("retry-after")
|
||||||
|
try:
|
||||||
|
return min(float(gesagt), HOECHSTPAUSE)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return min(PAUSE * (2 ** versuch), HOECHSTPAUSE)
|
||||||
|
|
||||||
|
|
||||||
async def frage(system: str, inhalt: str, temperatur: float = 0.0) -> str:
|
async def frage(system: str, inhalt: str, temperatur: float = 0.0) -> str:
|
||||||
"""Eine Anfrage, eine Antwort — der Text der Modellantwort.
|
"""Eine Anfrage, eine Antwort — der Text der Modellantwort.
|
||||||
Kalt geroutet: Temperatur 0, kein Thinking; die Prüfung soll nicht raten.
|
Kalt geroutet: Temperatur 0, kein Thinking; die Prüfung soll nicht raten.
|
||||||
@@ -63,7 +74,7 @@ async def frage(system: str, inhalt: str, temperatur: float = 0.0) -> str:
|
|||||||
# Zu viele Anfragen oder Serverfehler: warten und noch einmal.
|
# Zu viele Anfragen oder Serverfehler: warten und noch einmal.
|
||||||
# Ohne das fielen bei großen Läufen ganze Stimmen aus — und ein
|
# Ohne das fielen bei großen Läufen ganze Stimmen aus — und ein
|
||||||
# fehlendes Urteil sah aus wie „nichts gefunden".
|
# fehlendes Urteil sah aus wie „nichts gefunden".
|
||||||
await asyncio.sleep(PAUSE * (2 ** versuch))
|
await asyncio.sleep(_pause(res, versuch))
|
||||||
if res.status_code != 200:
|
if res.status_code != 200:
|
||||||
raise RuntimeError(f"Provider antwortet mit HTTP {res.status_code}")
|
raise RuntimeError(f"Provider antwortet mit HTTP {res.status_code}")
|
||||||
antwort = res.json()
|
antwort = res.json()
|
||||||
|
|||||||
292
backend/chat.py
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
"""Karten im Gespräch entwickeln.
|
||||||
|
|
||||||
|
Man schreibt, was man vorhat. Die KI antwortet und legt daneben, wie die Karten
|
||||||
|
danach aussähen. Übernommen wird einzeln — dasselbe ✓ und ✕ wie bei der Prüfung.
|
||||||
|
|
||||||
|
Warum kein eigener Anzeigeweg: Ein Vorschlag ist ein Vorschlag, egal ob ihn die
|
||||||
|
Prüfung oder das Gespräch gemacht hat. Der Chat liefert deshalb genau die Form, die
|
||||||
|
`pruefung._stellen` erzeugt — Karten zeigen ihn an derselben Stelle an, der Vergleich
|
||||||
|
funktioniert, Annehmen schreibt die Datei.
|
||||||
|
|
||||||
|
Das Modell schreibt nie selbst in eine Datei. Es beschreibt den Sollzustand einer
|
||||||
|
Karte; was sich dadurch ändert, rechnet der Code aus. Das ist dieselbe Beweislast wie
|
||||||
|
bei der Prüfung und der Grund, warum ein Missverständnis hier nichts kaputt macht.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import agents
|
||||||
|
import features
|
||||||
|
import parser
|
||||||
|
import projekte
|
||||||
|
import pruefung
|
||||||
|
|
||||||
|
DATEI = ".chat.json"
|
||||||
|
# So viele Wortmeldungen bleiben stehen. Mehr wäre teuer je Anfrage und hilft wenig:
|
||||||
|
# was vor zwanzig Schritten besprochen wurde, steht längst auf den Karten.
|
||||||
|
TIEFE = 30
|
||||||
|
# Antworten in einem Rutsch. Ein Gespräch verträgt keine sechs Stimmen — es soll
|
||||||
|
# schnell sein, und was falsch ist, sieht man am Vorschlag.
|
||||||
|
TEMPERATUR = 0.3
|
||||||
|
|
||||||
|
SYSTEM = """Du entwickelst mit einer Person zusammen die Feature-Karten eines Projekts.
|
||||||
|
|
||||||
|
Eine Karte ist ein Auftrag: die Person will ein Problem gelöst haben. Die Lösung
|
||||||
|
besteht aus Regeln und Funktionen. Eine Funktion ist etwas, das man für sich testen
|
||||||
|
kann. Was nur die Umsetzung betrifft, gehört auf keine Karte.
|
||||||
|
|
||||||
|
{stil}
|
||||||
|
|
||||||
|
Der aktuelle Stand:
|
||||||
|
|
||||||
|
{karten}
|
||||||
|
|
||||||
|
Antworte ausschließlich mit JSON:
|
||||||
|
{{"antwort": "...", "karten": [
|
||||||
|
{{"titel": "...", "art": "kern", "ziel": "...", "regeln": ["..."],
|
||||||
|
"funktionen": [{{"titel": "...", "ziel": "...", "regeln": ["..."],
|
||||||
|
"testen": ["..."]}}]}}
|
||||||
|
]}}
|
||||||
|
|
||||||
|
- antwort: was du der Person sagst, auf Deutsch, höchstens vier Sätze
|
||||||
|
- karten: nur die Karten, die sich ändern oder dazukommen sollen — jeweils
|
||||||
|
VOLLSTÄNDIG, so wie sie danach aussehen sollen. Der Stand oben hat dieselbe Form:
|
||||||
|
kopiere die Karte von dort und ändere nur, was sich ändern soll. Jede Zeile, die
|
||||||
|
du nicht wörtlich mit abschreibst, gilt als gestrichen.
|
||||||
|
- art: „kern", wenn die Anwendung ohne die Karte keinen Sinn hätte, sonst „optional"
|
||||||
|
- ziel, regeln und testen dürfen leer sein oder fehlen
|
||||||
|
|
||||||
|
Ist nichts zu ändern, ist karten leer. Ändere nur, wonach gefragt wurde; Karten,
|
||||||
|
die niemand erwähnt hat, lässt du weg.
|
||||||
|
|
||||||
|
Frag nach, was du nicht weißt — aber leg trotzdem einen Entwurf daneben. Eine Frage
|
||||||
|
ohne Vorschlag hilft niemandem: an einem Entwurf sieht die Person sofort, was du
|
||||||
|
falsch verstanden hast. Erfinde dabei nichts, was nicht gesagt wurde; lass eine
|
||||||
|
Funktion lieber knapp als ausgedacht."""
|
||||||
|
|
||||||
|
# Die Formatregel steht auch hier, nicht nur im System-Prompt. Bei langen Antworten
|
||||||
|
# vergaß das Modell sie sonst und schrieb reinen Fließtext — die ganze Wortmeldung
|
||||||
|
# war verloren.
|
||||||
|
NACHRICHT = """{verlauf}
|
||||||
|
|
||||||
|
Neue Nachricht der Person:
|
||||||
|
{text}
|
||||||
|
|
||||||
|
Antworte ausschließlich mit dem JSON-Objekt, ohne Text davor oder danach."""
|
||||||
|
|
||||||
|
NACHBESSERN = """Deine Antwort war kein JSON.
|
||||||
|
|
||||||
|
Schreibe sie noch einmal, ausschließlich als das JSON-Objekt aus der Vorgabe —
|
||||||
|
keine Einleitung, kein Nachsatz, keine Code-Zäune."""
|
||||||
|
|
||||||
|
|
||||||
|
def _pfad(projekt: str):
|
||||||
|
return projekte.wurzel(projekt) / DATEI
|
||||||
|
|
||||||
|
|
||||||
|
def verlauf(projekt: str) -> list:
|
||||||
|
try:
|
||||||
|
eintraege = json.loads(_pfad(projekt).read_text())
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return []
|
||||||
|
return eintraege if isinstance(eintraege, list) else []
|
||||||
|
|
||||||
|
|
||||||
|
def _sichere(projekt: str, eintraege: list):
|
||||||
|
ziel = _pfad(projekt)
|
||||||
|
neben = ziel.with_suffix(".json.neu")
|
||||||
|
neben.write_text(json.dumps(eintraege[-TIEFE:], indent=1, ensure_ascii=False) + "\n")
|
||||||
|
neben.replace(ziel)
|
||||||
|
|
||||||
|
|
||||||
|
def leeren(projekt: str):
|
||||||
|
_pfad(projekt).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _punkte(text: str) -> list:
|
||||||
|
return [z.strip("- ").strip() for z in (text or "").splitlines() if z.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def _als_spec(f: dict) -> dict:
|
||||||
|
"""Eine Karte in genau der Form, in der das Modell antworten soll."""
|
||||||
|
s = f["sektionen"]
|
||||||
|
return {"titel": s["titel"], "art": s["art"], "ziel": s["ziel"],
|
||||||
|
"regeln": _punkte(s["regeln"]),
|
||||||
|
"funktionen": [{"titel": x["text"], "ziel": x["ziel"],
|
||||||
|
"regeln": _punkte(x["regeln"]),
|
||||||
|
"testen": _punkte(x["testen"])}
|
||||||
|
for x in s["funktionen"]]}
|
||||||
|
|
||||||
|
|
||||||
|
def _kartenstand(projekt: str) -> str:
|
||||||
|
"""Der Stand als JSON — dieselbe Form wie die Antwort.
|
||||||
|
|
||||||
|
Vorher stand er als Fließtext da. Das Modell musste ihn beim Zurückschreiben
|
||||||
|
übersetzen und schob dabei Regeln zwischen Karten- und Funktionsebene hin und
|
||||||
|
her: aus „eine Funktion ergänzen" wurden vier Änderungen. Gleiche Form heißt,
|
||||||
|
dass Abschreiben nichts kostet.
|
||||||
|
"""
|
||||||
|
vision, karten = "", []
|
||||||
|
for f in features.liste(projekt):
|
||||||
|
if f["sektionen"]["art"] == parser.VISION:
|
||||||
|
vision = f["sektionen"]["ziel"]
|
||||||
|
else:
|
||||||
|
karten.append(_als_spec(f))
|
||||||
|
text = json.dumps(karten, indent=1, ensure_ascii=False) if karten \
|
||||||
|
else "Noch keine Karten."
|
||||||
|
if vision:
|
||||||
|
text = f"So ist das Projekt gedacht:\n\n{vision}\n\nDie Karten:\n\n{text}"
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def _zeilen(wert) -> str:
|
||||||
|
"""regeln und testen kommen als Liste oder als Block — hier wird eins daraus."""
|
||||||
|
if isinstance(wert, list):
|
||||||
|
eintraege = [str(z).strip("- ").strip() for z in wert]
|
||||||
|
else:
|
||||||
|
eintraege = [z.strip("- ").strip() for z in str(wert or "").splitlines()]
|
||||||
|
return "\n".join(f"- {z}" for z in eintraege if z)
|
||||||
|
|
||||||
|
|
||||||
|
TRENNER = "-" * 40
|
||||||
|
|
||||||
|
|
||||||
|
def roh(karte: dict) -> str:
|
||||||
|
"""Aus dem Sollzustand die Markdown-Datei — dieselbe Form wie im Frontend."""
|
||||||
|
art = str(karte.get("art") or "kern").strip().lower()
|
||||||
|
teile = [f'<!-- titel -->\n{str(karte.get("titel") or "").strip()}',
|
||||||
|
f'<!-- art -->\n{art if art in parser.ARTEN else "kern"}']
|
||||||
|
if str(karte.get("ziel") or "").strip():
|
||||||
|
teile.append(f'<!-- ziel -->\n{str(karte["ziel"]).strip()}')
|
||||||
|
if _zeilen(karte.get("regeln")):
|
||||||
|
teile.append(f'<!-- regeln -->\n{_zeilen(karte.get("regeln"))}')
|
||||||
|
for f in karte.get("funktionen") or []:
|
||||||
|
block = [f'<!-- funktion -->\n{str(f.get("titel") or "").strip()}']
|
||||||
|
if f.get("stand") == parser.OFFEN:
|
||||||
|
block.append(f'<!-- stand -->\n{parser.OFFEN}')
|
||||||
|
if str(f.get("ziel") or "").strip():
|
||||||
|
block.append(f'<!-- ziel -->\n{str(f["ziel"]).strip()}')
|
||||||
|
for feld in ("regeln", "testen"):
|
||||||
|
if _zeilen(f.get(feld)):
|
||||||
|
block.append(f'<!-- {feld} -->\n{_zeilen(f.get(feld))}')
|
||||||
|
teile.append("\n".join(block))
|
||||||
|
return f"\n{TRENNER}\n".join(teile) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def _felder(sektionen: dict) -> dict:
|
||||||
|
return {"titel": sektionen["titel"], "badge": sektionen["art"],
|
||||||
|
"ziel": sektionen["ziel"], "regeln": sektionen["regeln"],
|
||||||
|
"funktionen": "\n".join(f["text"] for f in sektionen["funktionen"])}
|
||||||
|
|
||||||
|
|
||||||
|
def _vergleiche(alt: dict, neu: dict, feature_id: str, grund: str) -> list:
|
||||||
|
"""Was sich an einer bestehenden Karte ändert, Feld für Feld.
|
||||||
|
|
||||||
|
Auf Funktionsebene wird nur die Liste der Titel verglichen. Wer den Inhalt einer
|
||||||
|
Funktion ändern will, bekommt dafür eigene Stellen — sonst wäre ein Vorschlag ein
|
||||||
|
Klotz, den man nur ganz oder gar nicht nehmen kann.
|
||||||
|
"""
|
||||||
|
raus = []
|
||||||
|
for feld in ("titel", "badge", "ziel", "regeln", "funktionen"):
|
||||||
|
for p in pruefung._stellen(feld, alt.get(feld, ""), neu.get(feld, ""),
|
||||||
|
grund, "Aus dem Gespräch"):
|
||||||
|
p["feature"] = feature_id
|
||||||
|
p["funktion"] = ""
|
||||||
|
# Was das Gespräch an Funktionen erfindet, ist bestellt, nicht gebaut.
|
||||||
|
# Dieselbe Stelle aus Prüfung 3 hätte belegten Code — dort wäre „offen" falsch.
|
||||||
|
if feld == "funktionen":
|
||||||
|
p["neu_offen"] = True
|
||||||
|
raus.append(p)
|
||||||
|
return raus
|
||||||
|
|
||||||
|
|
||||||
|
def _funktionsstellen(alt: dict, neu: dict, feature_id: str, grund: str) -> list:
|
||||||
|
"""Änderungen innerhalb einer Funktion, je Feld eine eigene Stelle."""
|
||||||
|
vorher = {f["text"]: f for f in alt["funktionen"]}
|
||||||
|
raus = []
|
||||||
|
for f in neu["funktionen"]:
|
||||||
|
bestand = vorher.get(f["text"])
|
||||||
|
if not bestand:
|
||||||
|
continue # neue Funktion: steckt schon im Listenvergleich
|
||||||
|
for feld in ("ziel", "regeln", "testen"):
|
||||||
|
for p in pruefung._stellen(feld, bestand[feld], f[feld], grund,
|
||||||
|
"Aus dem Gespräch"):
|
||||||
|
p["feature"] = feature_id
|
||||||
|
p["funktion"] = f["text"]
|
||||||
|
raus.append(p)
|
||||||
|
return raus
|
||||||
|
|
||||||
|
|
||||||
|
def vorschlaege(projekt: str, karten: list) -> list:
|
||||||
|
"""Aus den Sollzuständen die einzelnen Stellen — bestehende Karten wie neue."""
|
||||||
|
nach_titel = {f["sektionen"]["titel"].lower(): f for f in features.liste(projekt)}
|
||||||
|
raus = []
|
||||||
|
for karte in karten:
|
||||||
|
if not isinstance(karte, dict) or not str(karte.get("titel") or "").strip():
|
||||||
|
continue
|
||||||
|
titel = str(karte["titel"]).strip()
|
||||||
|
# Den Stand entscheidet der Code, nicht das Modell: was schon gebaut war,
|
||||||
|
# bleibt gebaut; was das Gespräch neu erfindet, ist bestellt und noch offen.
|
||||||
|
# Das Modell sieht das Feld gar nicht — ein Feld weniger, das es vergessen kann.
|
||||||
|
bestand = nach_titel.get(titel.lower())
|
||||||
|
gebaut = {x["text"] for x in bestand["sektionen"]["funktionen"]
|
||||||
|
if x["stand"] == parser.GEBAUT} if bestand else set()
|
||||||
|
for f in karte.get("funktionen") or []:
|
||||||
|
f["stand"] = parser.GEBAUT if str(f.get("titel") or "").strip() in gebaut \
|
||||||
|
else parser.OFFEN
|
||||||
|
text = roh(karte)
|
||||||
|
neu = parser.zerlege(text)
|
||||||
|
if bestand is None:
|
||||||
|
# Neue Karte: es gibt keine Stelle, an der man ansetzen könnte —
|
||||||
|
# deshalb ein eigener Vorschlag, der die ganze Datei mitbringt.
|
||||||
|
raus.append({"feld": "neue_karte", "original": "", "vorschlag": titel,
|
||||||
|
"roh": text, "ganzes_feld": True, "feature": "",
|
||||||
|
"funktion": "", "regel": "Aus dem Gespräch",
|
||||||
|
"problem": "Neue Karte"})
|
||||||
|
continue
|
||||||
|
grund = f"Aus dem Gespräch: {titel}"
|
||||||
|
raus += _vergleiche(_felder(bestand["sektionen"]), _felder(neu),
|
||||||
|
bestand["id"], grund)
|
||||||
|
raus += _funktionsstellen(bestand["sektionen"], neu, bestand["id"], grund)
|
||||||
|
return raus
|
||||||
|
|
||||||
|
|
||||||
|
async def frage(projekt: str, text: str) -> dict:
|
||||||
|
"""Eine Runde: Frage rein, Antwort und Vorschläge raus."""
|
||||||
|
text = (text or "").strip()
|
||||||
|
if not text:
|
||||||
|
raise ValueError("Nachricht ist leer")
|
||||||
|
agents.pruefe_konfiguration()
|
||||||
|
|
||||||
|
bisher = verlauf(projekt)
|
||||||
|
gespraech = "\n\n".join(f'{"Person" if e["rolle"] == "person" else "Du"}: {e["text"]}'
|
||||||
|
for e in bisher) or "Noch nichts besprochen."
|
||||||
|
# Beide Kataloge: der Chat schreibt Karten UND Funktionen. Vorher kannte er nur
|
||||||
|
# die Kartenregeln und erfand Funktionstitel wie „Galerie im Modal" — ein
|
||||||
|
# Bereich statt einer Handlung, und die Prüfung merkte es erst hinterher.
|
||||||
|
system = SYSTEM.format(
|
||||||
|
stil="\n\n".join(pruefung._stil_lesen(b) for b in pruefung.BEREICHE),
|
||||||
|
karten=_kartenstand(projekt))
|
||||||
|
nachricht = NACHRICHT.format(verlauf=gespraech, text=text)
|
||||||
|
|
||||||
|
# Ein Ausrutscher beim JSON kostet sonst die ganze Wortmeldung. Zweiter Versuch
|
||||||
|
# mit etwas mehr Streuung; danach fliegt der Fehler nach oben und ist sichtbar.
|
||||||
|
daten = None
|
||||||
|
for versuch in range(3):
|
||||||
|
antwort = await agents.frage(system, nachricht, TEMPERATUR + 0.1 * versuch)
|
||||||
|
try:
|
||||||
|
daten = pruefung._json_lesen(antwort)
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
if versuch == 2:
|
||||||
|
raise ValueError("Antwort war nicht lesbar — bitte noch einmal senden")
|
||||||
|
# Nachbessern statt blind wiederholen: das Modell sieht seinen eigenen
|
||||||
|
# Fehlversuch und weiß, was daran fehlte.
|
||||||
|
nachricht = (f"{nachricht}\n\nDein letzter Versuch war:\n"
|
||||||
|
f"{antwort[:2000]}\n\n{NACHBESSERN}")
|
||||||
|
satz = str(daten.get("antwort") or "").strip() or "Verstanden."
|
||||||
|
probleme = vorschlaege(projekt, daten.get("karten") or [])
|
||||||
|
|
||||||
|
bisher += [{"rolle": "person", "text": text}, {"rolle": "ki", "text": satz}]
|
||||||
|
_sichere(projekt, bisher)
|
||||||
|
return {"antwort": satz, "probleme": probleme, "verlauf": verlauf(projekt)}
|
||||||
@@ -27,6 +27,13 @@ MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY", "")
|
|||||||
MINIMAX_MODELL = os.environ.get("MINIMAX_MODELL", "")
|
MINIMAX_MODELL = os.environ.get("MINIMAX_MODELL", "")
|
||||||
MINIMAX_URL = os.environ.get("MINIMAX_URL", "")
|
MINIMAX_URL = os.environ.get("MINIMAX_URL", "")
|
||||||
|
|
||||||
|
# Das Gespräch darf woanders laufen als die Prüfungen. Es ist ein einzelner Aufruf
|
||||||
|
# und lebt von Sprachgefühl; die Prüfungen sind hundert Stimmen und leben von der
|
||||||
|
# Temperaturstreuung. Nicht gesetzt heißt: alles über MiniMax.
|
||||||
|
CHAT_API_KEY = os.environ.get("CHAT_API_KEY", "")
|
||||||
|
CHAT_MODELL = os.environ.get("CHAT_MODELL", "")
|
||||||
|
CHAT_URL = os.environ.get("CHAT_URL", "https://api.anthropic.com/v1/messages")
|
||||||
|
|
||||||
|
|
||||||
def _zahl(name: str, standard: int) -> int:
|
def _zahl(name: str, standard: int) -> int:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import parser
|
|||||||
import projekte
|
import projekte
|
||||||
|
|
||||||
_UMLAUTE = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"})
|
_UMLAUTE = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"})
|
||||||
|
# Dateisysteme lassen 255 Zeichen je Name zu. Der Titel darf länger sein — er steht
|
||||||
|
# in der Datei, nicht im Namen. Ohne Deckel warf ein langer Titel einen Serverfehler.
|
||||||
|
NAMENSLAENGE = 80
|
||||||
|
|
||||||
|
|
||||||
def _slug(titel: str) -> str:
|
def _slug(titel: str) -> str:
|
||||||
s = titel.lower().translate(_UMLAUTE)
|
s = titel.lower().translate(_UMLAUTE)
|
||||||
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
|
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
|
||||||
return s or "feature"
|
return s[:NAMENSLAENGE].strip("-") or "feature"
|
||||||
|
|
||||||
|
|
||||||
def _eintrag(datei) -> dict:
|
def _eintrag(datei) -> dict:
|
||||||
@@ -26,8 +29,14 @@ def _datei(projekt: str, id: str):
|
|||||||
return datei
|
return datei
|
||||||
|
|
||||||
|
|
||||||
def liste(projekt: str) -> list[dict]:
|
def liste(projekt: str, nur_stand: bool = False) -> list[dict]:
|
||||||
dateien = projekte.wurzel(projekt).glob("*.md")
|
"""Die Karten. Standardmäßig die, an denen gerade gearbeitet wird.
|
||||||
|
|
||||||
|
`nur_stand` liest stattdessen den Projektordner — die Übersicht soll sagen,
|
||||||
|
was das Projekt heute kann, nicht was jemand gerade plant.
|
||||||
|
"""
|
||||||
|
ordner = projekte.stand(projekt) if nur_stand else projekte.wurzel(projekt)
|
||||||
|
dateien = ordner.glob("*.md")
|
||||||
eintraege = [_eintrag(d) for d in sorted(dateien)]
|
eintraege = [_eintrag(d) for d in sorted(dateien)]
|
||||||
return sorted(eintraege, key=lambda e: e["sektionen"]["titel"].lower())
|
return sorted(eintraege, key=lambda e: e["sektionen"]["titel"].lower())
|
||||||
|
|
||||||
@@ -38,17 +47,21 @@ def pruefbar(projekt: str) -> list[dict]:
|
|||||||
return [f for f in liste(projekt) if f["sektionen"]["art"] != parser.VISION]
|
return [f for f in liste(projekt) if f["sektionen"]["art"] != parser.VISION]
|
||||||
|
|
||||||
|
|
||||||
def anlegen(projekt: str, roh: str) -> dict:
|
def _frei(ordner, slug: str):
|
||||||
ordner = projekte.wurzel(projekt)
|
"""Der erste freie Dateiname zum Slug; gleiche Namen bekommen eine Nummer."""
|
||||||
titel = parser.zerlege(roh)["titel"]
|
|
||||||
if not titel:
|
|
||||||
raise ValueError("Titel fehlt")
|
|
||||||
slug = _slug(titel)
|
|
||||||
datei = ordner / f"{slug}.md"
|
datei = ordner / f"{slug}.md"
|
||||||
n = 2
|
n = 2
|
||||||
while datei.exists():
|
while datei.exists():
|
||||||
datei = ordner / f"{slug}-{n}.md"
|
datei = ordner / f"{slug}-{n}.md"
|
||||||
n += 1
|
n += 1
|
||||||
|
return datei
|
||||||
|
|
||||||
|
|
||||||
|
def anlegen(projekt: str, roh: str) -> dict:
|
||||||
|
titel = parser.zerlege(roh)["titel"]
|
||||||
|
if not titel:
|
||||||
|
raise ValueError("Titel fehlt")
|
||||||
|
datei = _frei(projekte.wurzel(projekt), _slug(titel))
|
||||||
datei.write_text(roh)
|
datei.write_text(roh)
|
||||||
return _eintrag(datei)
|
return _eintrag(datei)
|
||||||
|
|
||||||
@@ -58,11 +71,7 @@ def _folge_titel(datei, titel: str):
|
|||||||
slug = _slug(titel)
|
slug = _slug(titel)
|
||||||
if re.fullmatch(re.escape(slug) + r"(-\d+)?", datei.stem):
|
if re.fullmatch(re.escape(slug) + r"(-\d+)?", datei.stem):
|
||||||
return datei # passt schon (inkl. Kollisions-Suffix)
|
return datei # passt schon (inkl. Kollisions-Suffix)
|
||||||
ziel = datei.parent / f"{slug}.md"
|
ziel = _frei(datei.parent, slug)
|
||||||
n = 2
|
|
||||||
while ziel.exists():
|
|
||||||
ziel = datei.parent / f"{slug}-{n}.md"
|
|
||||||
n += 1
|
|
||||||
datei.rename(ziel)
|
datei.rename(ziel)
|
||||||
return ziel
|
return ziel
|
||||||
|
|
||||||
@@ -72,9 +81,35 @@ def speichern(projekt: str, id: str, roh: str) -> dict:
|
|||||||
titel = parser.zerlege(roh)["titel"]
|
titel = parser.zerlege(roh)["titel"]
|
||||||
if not titel:
|
if not titel:
|
||||||
raise ValueError("Titel fehlt")
|
raise ValueError("Titel fehlt")
|
||||||
|
vorher = datei.read_text()
|
||||||
datei.write_text(roh)
|
datei.write_text(roh)
|
||||||
return _eintrag(_folge_titel(datei, titel))
|
neu = _folge_titel(datei, titel)
|
||||||
|
# Ein neuer Titel benennt die Datei um. Der Rückschritt muss beides kennen:
|
||||||
|
# die neue Datei fällt weg, die alte kommt mit ihrem alten Inhalt zurück.
|
||||||
|
schritt = {datei.stem: vorher}
|
||||||
|
if neu.stem != datei.stem:
|
||||||
|
schritt[neu.stem] = None
|
||||||
|
return _eintrag(neu)
|
||||||
|
|
||||||
|
|
||||||
def loeschen(projekt: str, id: str):
|
def loeschen(projekt: str, id: str):
|
||||||
_datei(projekt, id).unlink()
|
datei = _datei(projekt, id)
|
||||||
|
titel = parser.zerlege(datei.read_text())["titel"] or id
|
||||||
|
datei.unlink()
|
||||||
|
|
||||||
|
|
||||||
|
def verschieben(projekt: str, id: str, ziel: str) -> dict:
|
||||||
|
"""Ein Feature in ein anderes Projekt umhängen.
|
||||||
|
|
||||||
|
Kein Kopieren: die Datei wandert, damit es sie nur einmal gibt. Der Verlauf
|
||||||
|
hält beide Seiten fest — sonst ließe sich der Schritt nicht zurücknehmen.
|
||||||
|
"""
|
||||||
|
if ziel == projekt:
|
||||||
|
raise ValueError("Feature liegt schon in diesem Projekt")
|
||||||
|
datei = _datei(projekt, id)
|
||||||
|
roh = datei.read_text()
|
||||||
|
titel = parser.zerlege(roh)["titel"] or id
|
||||||
|
# Erst das Ziel auflösen: gibt es das Projekt nicht, ist noch nichts passiert
|
||||||
|
neu = _frei(projekte.wurzel(ziel), _slug(titel))
|
||||||
|
datei.rename(neu)
|
||||||
|
return _eintrag(neu)
|
||||||
|
|||||||
@@ -1,748 +0,0 @@
|
|||||||
"""Zwei Abgleiche zwischen Karten und Quelltext, in beide Richtungen.
|
|
||||||
|
|
||||||
**Feature im Code** — steht hinter jeder Funktion einer Karte auch Code? Dazu:
|
|
||||||
beschreiben zwei Funktionen dieselbe Fähigkeit?
|
|
||||||
**Code im Feature** — hat jede Stelle im Code auch eine Funktion auf einer Karte?
|
|
||||||
|
|
||||||
Beide arbeiten mit derselben Beweislast, nur andersherum.
|
|
||||||
|
|
||||||
Ein Vorhandensein lässt sich zitieren, ein Nichtvorhandensein nicht. Darum fragt
|
|
||||||
„Feature im Code" sechsmal unabhängig nach dem Beleg und meldet erst, wenn keine
|
|
||||||
einzige Stimme eine Codezeile findet, die auch wirklich dasteht. Ein geprüfter Fund
|
|
||||||
genügt zum Freispruch — sechs Fehlversuche sind die Anklage. Ein früherer Versuch
|
|
||||||
ließ das Modell direkt urteilen und schlug dabei vor, gültige Inhalte zu löschen.
|
|
||||||
|
|
||||||
„Code im Feature" fragt umgekehrt je Stelle, ob eine bestehende Funktion sie
|
|
||||||
abdeckt, und meldet ab fünf von sechs Nein-Stimmen. Einstimmigkeit war zu scharf:
|
|
||||||
dieselbe Frage kippt auch bei Temperatur 0 gelegentlich.
|
|
||||||
|
|
||||||
Was der Code selbst weiß, entscheidet der Code: die Liste der Bedienstellen kommt
|
|
||||||
aus `quellcode.interaktionen()`, jeder Beleg wird gegen die Datei geschlagen.
|
|
||||||
|
|
||||||
Jedes Urteil wandert in `ledger`. Der nächste Lauf fragt nur noch, was er nicht
|
|
||||||
schon weiß — dadurch liefert derselbe Stand dasselbe Ergebnis. Auch das unentschiedene
|
|
||||||
Urteil wird gemerkt: es galt als abgedeckt, und ein Münzwurf, der jede Runde neu
|
|
||||||
fällt, ist schlimmer als einer, der einmal fällt. Ändert sich die Stelle im Code,
|
|
||||||
wird sie ohnehin neu beurteilt.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import difflib
|
|
||||||
import re
|
|
||||||
from collections import Counter
|
|
||||||
|
|
||||||
import agents
|
|
||||||
import config
|
|
||||||
import ledger
|
|
||||||
import quellcode
|
|
||||||
|
|
||||||
# Ohne Drossel liefen alle Anfragen gleichzeitig los und der Provider antwortete
|
|
||||||
# mit 429. Die ausgefallenen Stimmen sahen dann aus wie „nichts gefunden".
|
|
||||||
_schranke = None
|
|
||||||
|
|
||||||
|
|
||||||
def _drossel():
|
|
||||||
global _schranke
|
|
||||||
if _schranke is None:
|
|
||||||
_schranke = asyncio.Semaphore(config.PRUEFUNG_PARALLEL)
|
|
||||||
return _schranke
|
|
||||||
|
|
||||||
|
|
||||||
async def _frage(system: str, nachricht: str, temperatur: float) -> str:
|
|
||||||
async with _drossel():
|
|
||||||
return await agents.frage(system, nachricht, temperatur)
|
|
||||||
|
|
||||||
|
|
||||||
async def _urteil(system: str, nachricht: str, temperatur: float):
|
|
||||||
"""Eine Stimme als JSON. Unlesbares wird einmal neu gefragt, dann aufgegeben —
|
|
||||||
bei hunderten Anfragen je Lauf ist ein Ausrutscher normal, ein Abbruch nicht."""
|
|
||||||
for versuch in range(3):
|
|
||||||
try:
|
|
||||||
return _json(await _frage(system, nachricht, temperatur + 0.1 * versuch))
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Achtung: diese Texte gehen unformatiert als System-Prompt raus. Doppelte Klammern
|
|
||||||
# wären hier keine Escapes, sondern landeten wörtlich in der Antwort.
|
|
||||||
SUCHE_SYSTEM = """Du suchst im Quelltext den Code zu einer beschriebenen Funktion.
|
|
||||||
|
|
||||||
Antworte ausschließlich mit JSON:
|
|
||||||
{"gefunden": true, "datei": "...", "beleg": "..."}
|
|
||||||
oder
|
|
||||||
{"gefunden": false, "grund": "..."}
|
|
||||||
|
|
||||||
- datei: der Pfad genau so, wie er über der Datei steht
|
|
||||||
- beleg: eine Zeile aus dieser Datei, Zeichen für Zeichen abgeschrieben
|
|
||||||
- grund: höchstens 8 Wörter, wenn es den Code nicht gibt
|
|
||||||
|
|
||||||
Suche gründlich und im ganzen Quelltext. Es zählt nicht nur, was einen Knopf hat:
|
|
||||||
Hintergrundlogik, Parser, Dateiverwaltung, Prüfregeln und Anzeigeverhalten sind
|
|
||||||
genauso Code. Ein erfundener Beleg ist wertlos — er wird nachgeschlagen."""
|
|
||||||
|
|
||||||
SUCHE_NACHRICHT = """{quelle}
|
|
||||||
|
|
||||||
Ende des Quelltextes.
|
|
||||||
|
|
||||||
Eine Karte beschreibt diese Funktion:
|
|
||||||
|
|
||||||
{funktion}
|
|
||||||
|
|
||||||
Finde die Stelle im Quelltext, die das umsetzt. Antworte ausschließlich mit dem
|
|
||||||
JSON-Objekt."""
|
|
||||||
|
|
||||||
ABDECKUNG_SYSTEM = """Du entscheidest, ob eine Stelle im Programm schon von einer
|
|
||||||
beschriebenen Funktion abgedeckt ist. Antworte ausschließlich mit JSON."""
|
|
||||||
|
|
||||||
ABDECKUNG = """Diese Stelle im Programm lässt sich auslösen:
|
|
||||||
|
|
||||||
{art} in {datei}, Zeile {zeile}
|
|
||||||
|
|
||||||
```
|
|
||||||
{umfeld}
|
|
||||||
```
|
|
||||||
{rumpf}
|
|
||||||
Die Karten des Projekts beschreiben bisher das hier:
|
|
||||||
|
|
||||||
{funktionen}
|
|
||||||
|
|
||||||
Beschreibt eine dieser Funktionen, was an der Stelle oben passiert?
|
|
||||||
Wortwahl darf abweichen; entscheidend ist die Sache. Ziel, Regeln und Testschritte
|
|
||||||
einer Funktion gehören mit zu dem, was sie abdeckt, und die Regeln der Karte
|
|
||||||
ebenso: eine Zusicherung wie „alphabetisch sortiert" ist beschrieben, auch wenn
|
|
||||||
kein Knopf dazu gehört.
|
|
||||||
|
|
||||||
Antworte ausschließlich mit JSON:
|
|
||||||
{{"abgedeckt": true, "funktion": "..."}}
|
|
||||||
oder
|
|
||||||
{{"abgedeckt": false, "karte": "...", "titel": "..."}}
|
|
||||||
|
|
||||||
- funktion: der bestehende Funktionstitel, wörtlich, wenn abgedeckt
|
|
||||||
- karte: der Titel der Karte, in die die Fähigkeit thematisch gehört
|
|
||||||
- titel: wie die neue Funktion heißen soll — Objekt und Verb im Infinitiv,
|
|
||||||
höchstens 5 Wörter
|
|
||||||
|
|
||||||
Rein technische Aufhänger gelten als abgedeckt: Speichern beim Verlassen eines
|
|
||||||
Feldes, Abbrechen mit Escape, Gesundheitsprüfungen, interne Weiterleitung.
|
|
||||||
Fehlerbehandlung ebenso — eine Fehlermeldung ist kein eigener Auftrag, sondern
|
|
||||||
gehört zu der Fähigkeit, bei der etwas schiefging.
|
|
||||||
Im Zweifel abgedeckt. Nur was einer Person wirklich fehlt, soll dazukommen."""
|
|
||||||
|
|
||||||
DUBLETTE_SYSTEM = """Du entscheidest, ob zwei Funktionsbeschreibungen dieselbe
|
|
||||||
Fähigkeit meinen. Antworte ausschließlich mit JSON."""
|
|
||||||
|
|
||||||
DUBLETTE = """Zwei Funktionen aus den Karten eines Projekts:
|
|
||||||
|
|
||||||
A
|
|
||||||
{text_a}
|
|
||||||
|
|
||||||
B
|
|
||||||
{text_b}
|
|
||||||
|
|
||||||
Aufgefallen sind sie so: {anlass}
|
|
||||||
|
|
||||||
Beantworte der Reihe nach, allein aus Titel und Testschritten:
|
|
||||||
|
|
||||||
1. Woran arbeitet A? Der Gegenstand, an dem sich etwas ändert.
|
|
||||||
2. Woran arbeitet B?
|
|
||||||
3. Meinen 1 und 2 denselben Gegenstand?
|
|
||||||
4. Was ist anders, nachdem A ausgeführt wurde?
|
|
||||||
5. Was ist anders, nachdem B ausgeführt wurde?
|
|
||||||
6. Beschreiben 4 und 5 denselben Zustand? Es zählt das Ergebnis, nicht das Wort:
|
|
||||||
verschiedene Wörter für dasselbe Ergebnis sind gleich, ein Gegenstück nicht.
|
|
||||||
|
|
||||||
Antworte ausschließlich mit JSON:
|
|
||||||
{{"gegenstand_a": "...", "gegenstand_b": "...", "gegenstand_gleich": true,
|
|
||||||
"ergebnis_a": "...", "ergebnis_b": "...", "ergebnis_gleich": true}}
|
|
||||||
|
|
||||||
Urteile nicht selbst über die Dublette. Ob es eine ist, wird aus deinen Antworten
|
|
||||||
auf 3 und 6 errechnet."""
|
|
||||||
|
|
||||||
# Sechs unabhängige Versuche, den Beleg zu finden. Eine einzige Stimme, die etwas
|
|
||||||
# findet, genügt zum Freispruch — die Anklage lautet auf Löschen, da wiegt ein
|
|
||||||
# Fehlurteil schwerer als ein übersehener Rest.
|
|
||||||
SUCHE_TEMPERATUREN = (0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
|
|
||||||
# So viele Stimmen müssen überhaupt geantwortet haben, bevor angeklagt wird
|
|
||||||
ANKLAGE_AB = 4
|
|
||||||
# Sechs Stimmen, fünf müssen „fehlt" sagen. Eindeutige Stellen antworten 6:0 oder
|
|
||||||
# 0:6, Grenzfälle etwa 3:3 — die Schwelle bei fünf trennt beides.
|
|
||||||
URTEIL_TEMPERATUREN = (0.0, 0.2, 0.4, 0.6, 0.8, 1.0)
|
|
||||||
FEHLT_AB = 5
|
|
||||||
# So viele Stimmen müssen geantwortet haben, bevor überhaupt geurteilt wird. Das
|
|
||||||
# Urteil wird gemerkt und gilt dann dauerhaft — dafür reichen zwei Überlebende nicht.
|
|
||||||
URTEIL_AB = 4
|
|
||||||
# Ab dieser Ähnlichkeit gelten zwei Titel als dieselbe Sache
|
|
||||||
GLEICH = 0.75
|
|
||||||
# Drei Stimmen über ein Paar, einstimmig für die Dublette. Ein Löschvorschlag darf
|
|
||||||
# nicht an einer Einzelmeinung hängen.
|
|
||||||
DUBLETTE_TEMPERATUREN = (0.0, 0.3, 0.6)
|
|
||||||
# Kandidatenkanal „ähnlicher Titel". Höher als GLEICH: hier zahlt jedes Paar drei
|
|
||||||
# Anfragen, und „Feature anlegen"/„Feature entfernen" liegen schon bei 0,75.
|
|
||||||
TITEL_KANAL = 0.85
|
|
||||||
# Kandidatenkanal „ähnlicher Inhalt". Anteil der Wörter, die die kürzere der beiden
|
|
||||||
# Beschreibungen mit der längeren teilt. Überlappung statt Jaccard, weil eine knappe
|
|
||||||
# Dublette sonst an der Länge der ausführlicheren scheitert.
|
|
||||||
INHALT_KANAL = 0.7
|
|
||||||
# Wörter, die in so vielen Funktionen vorkommen, tragen nichts zur Unterscheidung
|
|
||||||
# bei. „Vollmodus öffnen", „Text ändern" stehen fast überall — ohne diesen Filter
|
|
||||||
# ähnelt jede Bearbeiten-Funktion jeder anderen zu 90 %.
|
|
||||||
FLOSKEL_ANTEIL = 0.2
|
|
||||||
# Deckel gegen Explosion bei vielen Funktionen; was darüber liegt, meldet der Lauf
|
|
||||||
DUBLETTEN_CAP = 40
|
|
||||||
|
|
||||||
# Ein gemerktes Urteil gehört zu der Frage, auf die es geantwortet hat. Wird die
|
|
||||||
# Frage umgeschrieben, ist das Urteil von gestern — deshalb hängt der Fingerabdruck
|
|
||||||
# am Wortlaut des Prompts. Sonst müsste man daran denken, eine Zahl hochzuzählen.
|
|
||||||
_SUCHE_STAND = ledger.abdruck(SUCHE_SYSTEM, SUCHE_NACHRICHT)
|
|
||||||
_ABDECKUNG_STAND = ledger.abdruck(ABDECKUNG_SYSTEM, ABDECKUNG)
|
|
||||||
_DUBLETTE_STAND = ledger.abdruck(DUBLETTE_SYSTEM, DUBLETTE)
|
|
||||||
|
|
||||||
|
|
||||||
def _normal(text: str) -> str:
|
|
||||||
return re.sub(r"[^a-zä-ü0-9 ]", "", (text or "").lower()).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _aehnlich(a: str, b: str) -> float:
|
|
||||||
return difflib.SequenceMatcher(None, _normal(a), _normal(b)).ratio()
|
|
||||||
|
|
||||||
|
|
||||||
def _json(antwort: str) -> dict:
|
|
||||||
import pruefung # zur Laufzeit, sonst Ringschluss
|
|
||||||
|
|
||||||
return pruefung._json_lesen(antwort)
|
|
||||||
|
|
||||||
|
|
||||||
async def _sammle(aufgaben: list, was: str, melde=None) -> list:
|
|
||||||
"""Alle Stellen beurteilen, auch wenn einzelne platzen.
|
|
||||||
|
|
||||||
Vorher riss die erste kaputte Stelle den ganzen Lauf ab. Zwei Folgen, beide
|
|
||||||
schlecht: die schon bezahlten Urteile der anderen waren weg, und das Gedächtnis
|
|
||||||
wurde gespeichert, während die übrigen Anfragen noch liefen — es blieb leer.
|
|
||||||
|
|
||||||
Jetzt wird gewartet, bis alle durch sind. Was ging, zählt; was nicht ging, steht
|
|
||||||
in der Meldung. Der nächste Lauf holt nur den Rest nach.
|
|
||||||
"""
|
|
||||||
ergebnisse = await asyncio.gather(*aufgaben, return_exceptions=True)
|
|
||||||
kaputt = [e for e in ergebnisse if isinstance(e, BaseException)]
|
|
||||||
if kaputt and melde:
|
|
||||||
melde(f"{len(kaputt)} von {len(ergebnisse)} {was} nicht beurteilt: "
|
|
||||||
f"{kaputt[0]}", len(kaputt))
|
|
||||||
return [x for x in ergebnisse if x and not isinstance(x, BaseException)]
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Feature im Code
|
|
||||||
|
|
||||||
def _funktionstext(karte: str, f: dict) -> str:
|
|
||||||
"""Alles, was die Funktion behauptet — danach wird im Code gesucht.
|
|
||||||
|
|
||||||
Ohne Karte, wenn die anderswo schon dasteht: doppelt genannt zog sie beim
|
|
||||||
Dublettenurteil mehr Gewicht auf sich als Titel und Testschritte zusammen.
|
|
||||||
"""
|
|
||||||
zeilen = [f"Karte: {karte}"] if karte else []
|
|
||||||
zeilen.append(f'Funktion: {f["text"]}')
|
|
||||||
if f["ziel"]:
|
|
||||||
zeilen.append(f'Ziel: {f["ziel"]}')
|
|
||||||
for feld in ("regeln", "testen"):
|
|
||||||
if f[feld]:
|
|
||||||
eintraege = [z.strip("- ").strip() for z in f[feld].splitlines() if z.strip()]
|
|
||||||
zeilen.append(f'{feld.capitalize()}: {"; ".join(eintraege)}')
|
|
||||||
return "\n".join(zeilen)
|
|
||||||
|
|
||||||
|
|
||||||
def _quellabdruck(quelle: dict) -> str:
|
|
||||||
return ledger.abdruck(_SUCHE_STAND,
|
|
||||||
*(f"{p}\n{t}" for p, t in sorted(quelle.items())))
|
|
||||||
|
|
||||||
|
|
||||||
async def feature_im_code(features: list, quelle: dict, quelltext: str, stand: dict,
|
|
||||||
zaehl=None, melde=None) -> list:
|
|
||||||
"""Funktionen, für die keine Stimme einen nachprüfbaren Beleg findet.
|
|
||||||
|
|
||||||
Ein gemerkter Beleg zählt weiter, solange die zitierte Zeile noch dasteht —
|
|
||||||
dafür braucht es kein Modell, das prüft `quellcode.belegt()` nach. Eine gemerkte
|
|
||||||
Fehlanzeige gilt nur, solange sich am Quelltext nichts geändert hat: sie fordert
|
|
||||||
zum Löschen auf, und dafür muss der Befund frisch sein.
|
|
||||||
"""
|
|
||||||
quellstand = _quellabdruck(quelle)
|
|
||||||
auftraege = [(f, x) for f in features for x in f["sektionen"]["funktionen"]]
|
|
||||||
bekannt = {ledger.schluessel(f["id"], x["text"]) for f, x in auftraege}
|
|
||||||
stand["belege"] = ledger.behalte(stand["belege"], bekannt)
|
|
||||||
stand["ohne_code"] = ledger.behalte(stand["ohne_code"], bekannt)
|
|
||||||
|
|
||||||
async def eine(paar):
|
|
||||||
feature, x = paar
|
|
||||||
schluessel = ledger.schluessel(feature["id"], x["text"])
|
|
||||||
text = _funktionstext(feature["sektionen"]["titel"], x)
|
|
||||||
wie_gehabt = ledger.abdruck(text)
|
|
||||||
|
|
||||||
gemerkt = stand["belege"].get(schluessel)
|
|
||||||
if gemerkt and gemerkt["abdruck"] == wie_gehabt and quellcode.belegt(
|
|
||||||
quelle, gemerkt["datei"], gemerkt["beleg"]):
|
|
||||||
if zaehl:
|
|
||||||
zaehl()
|
|
||||||
return None
|
|
||||||
gemerkt = stand["ohne_code"].get(schluessel)
|
|
||||||
if gemerkt and gemerkt["abdruck"] == wie_gehabt \
|
|
||||||
and gemerkt["quelle"] == quellstand:
|
|
||||||
if zaehl:
|
|
||||||
zaehl()
|
|
||||||
return {"feature": feature["id"], "titel": x["text"],
|
|
||||||
"art": "ohne_code", "grund": gemerkt["grund"]}
|
|
||||||
|
|
||||||
nachricht = SUCHE_NACHRICHT.format(quelle=quelltext, funktion=text)
|
|
||||||
proben = await asyncio.gather(*(
|
|
||||||
_urteil(SUCHE_SYSTEM, nachricht, t) for t in SUCHE_TEMPERATUREN
|
|
||||||
), return_exceptions=True)
|
|
||||||
|
|
||||||
if zaehl:
|
|
||||||
zaehl()
|
|
||||||
gruende = []
|
|
||||||
brauchbar = behauptet = 0
|
|
||||||
beleg = None
|
|
||||||
for u in proben:
|
|
||||||
if not isinstance(u, dict):
|
|
||||||
continue
|
|
||||||
brauchbar += 1
|
|
||||||
if u.get("gefunden"):
|
|
||||||
# Auch ein misslungenes Zitat ist ein Widerspruch. Ein Modell
|
|
||||||
# verschreibt sich beim Abschreiben von Backticks — daraus eine
|
|
||||||
# Löschempfehlung zu machen, wäre grob falsch.
|
|
||||||
behauptet += 1
|
|
||||||
datei = str(u.get("datei") or "").strip()
|
|
||||||
zitat = str(u.get("beleg") or "").strip()
|
|
||||||
if beleg is None and quellcode.belegt(quelle, datei, zitat):
|
|
||||||
beleg = {"abdruck": wie_gehabt, "datei": datei, "beleg": zitat}
|
|
||||||
elif u.get("grund"):
|
|
||||||
gruende.append(str(u["grund"]).strip())
|
|
||||||
|
|
||||||
stand["belege"].pop(schluessel, None)
|
|
||||||
stand["ohne_code"].pop(schluessel, None)
|
|
||||||
if beleg:
|
|
||||||
stand["belege"][schluessel] = beleg
|
|
||||||
return None # geprüfter Beleg: die Funktion steht im Code
|
|
||||||
if behauptet:
|
|
||||||
return None # widersprüchliche Stimmen: nichts behaupten und nichts merken
|
|
||||||
if brauchbar < ANKLAGE_AB:
|
|
||||||
# Zu wenige Stimmen für eine Löschempfehlung. Lieber laut scheitern
|
|
||||||
# als eine gültige Funktion wegen eines Ausfalls anzuklagen.
|
|
||||||
raise RuntimeError(
|
|
||||||
f'{x["text"]}: nur {brauchbar} von '
|
|
||||||
f"{len(SUCHE_TEMPERATUREN)} Stimmen brauchbar")
|
|
||||||
grund = gruende[0] if gruende else "Kein Beleg im Code gefunden"
|
|
||||||
stand["ohne_code"][schluessel] = {"abdruck": wie_gehabt, "quelle": quellstand,
|
|
||||||
"grund": grund}
|
|
||||||
return {"feature": feature["id"], "titel": x["text"],
|
|
||||||
"art": "ohne_code", "grund": grund}
|
|
||||||
|
|
||||||
return await _sammle([eine(a) for a in auftraege], "Funktionen", melde)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Dubletten
|
|
||||||
|
|
||||||
def _funktionen(features: list) -> dict:
|
|
||||||
"""schluessel -> alles, was zur Funktion gehört."""
|
|
||||||
raus = {}
|
|
||||||
for f in features:
|
|
||||||
for x in f["sektionen"]["funktionen"]:
|
|
||||||
raus[ledger.schluessel(f["id"], x["text"])] = {
|
|
||||||
"feature": f["id"], "karte": f["sektionen"]["titel"],
|
|
||||||
"titel": x["text"], "eintrag": x,
|
|
||||||
"text": _funktionstext("", x),
|
|
||||||
# Ohne die Feldnamen: „Regeln:" steht über jedem Block, und eine
|
|
||||||
# Funktion namens „Regeln anpassen" verlöre sonst ihr eigenes Wort
|
|
||||||
"worte": " ".join((f["sektionen"]["titel"], x["text"], x["ziel"],
|
|
||||||
x["regeln"], x["testen"]))}
|
|
||||||
return raus
|
|
||||||
|
|
||||||
|
|
||||||
def _gewicht(f: dict) -> tuple:
|
|
||||||
"""Wer mehr sagt, bleibt stehen. Bei Gleichstand entscheidet der Name — nicht
|
|
||||||
schön, aber vorhersagbar; ein Zufallssieger machte den Vorschlag unstet."""
|
|
||||||
x = f["eintrag"]
|
|
||||||
zeilen = len([z for z in (x["regeln"] + "\n" + x["testen"]).splitlines() if z.strip()])
|
|
||||||
return (zeilen, len(x["ziel"]), f["titel"])
|
|
||||||
|
|
||||||
|
|
||||||
_WORT = re.compile(r"[a-zä-üß0-9]{4,}")
|
|
||||||
# Beugung abschneiden statt richtig stemmen: „entfernen" und „entfernt" sind
|
|
||||||
# dasselbe Wort, und ein Stemmer legte die Sprache im Code fest.
|
|
||||||
STAMM = 5
|
|
||||||
|
|
||||||
|
|
||||||
def _kerne(alle: dict) -> dict:
|
|
||||||
"""Je Funktion die Wörter, die sie von den anderen unterscheiden.
|
|
||||||
|
|
||||||
Was in jeder fünften Funktion steht, fliegt raus. Sonst misst der Vergleich nur
|
|
||||||
noch die gemeinsame Schrittfolge, und „Titel anpassen" und „Ziel anpassen"
|
|
||||||
stimmen zu hundert Prozent überein.
|
|
||||||
"""
|
|
||||||
mengen = {k: set(_WORT.findall(v["worte"].lower())) for k, v in alle.items()}
|
|
||||||
haeufig = Counter(wort for m in mengen.values() for wort in m)
|
|
||||||
grenze = max(3, int(len(mengen) * FLOSKEL_ANTEIL))
|
|
||||||
return {k: {w[:STAMM] for w in m if haeufig[w] < grenze} for k, m in mengen.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def _ueberlappung(a: set, b: set) -> float:
|
|
||||||
return len(a & b) / min(len(a), len(b)) if a and b else 0.0
|
|
||||||
|
|
||||||
|
|
||||||
def _kandidaten(alle: dict, belege: dict) -> list[tuple]:
|
|
||||||
"""Paare, die überhaupt eine Dublette sein könnten, mit dem Anlass.
|
|
||||||
|
|
||||||
Drei Kanäle, weil eine Dublette auf drei Arten auffällt: dieselbe Codezeile als
|
|
||||||
Beleg, fast derselbe Titel, fast dieselben Wörter. Der letzte fängt, was die
|
|
||||||
ersten beiden durchlassen — „Funktion löschen" und „Funktion entfernen" haben
|
|
||||||
weder denselben Titel noch zwingend denselben Beleg.
|
|
||||||
|
|
||||||
Jedes Paar zu fragen wäre quadratisch und träfe fast nie. Erst billig
|
|
||||||
eingrenzen, dann einmal teuer urteilen.
|
|
||||||
"""
|
|
||||||
paare = {}
|
|
||||||
|
|
||||||
def merke(a, b, anlass):
|
|
||||||
if a != b:
|
|
||||||
paare.setdefault(ledger.paarschluessel(a, b), (a, b, anlass))
|
|
||||||
|
|
||||||
nach_stelle = {}
|
|
||||||
for name, eintrag in belege.items():
|
|
||||||
if name in alle:
|
|
||||||
stelle = (eintrag["datei"], re.sub(r"\s+", " ", eintrag["beleg"]).strip())
|
|
||||||
nach_stelle.setdefault(stelle, []).append(name)
|
|
||||||
for (datei, zeile), gruppe in nach_stelle.items():
|
|
||||||
gruppe = sorted(gruppe)
|
|
||||||
for i, a in enumerate(gruppe):
|
|
||||||
for b in gruppe[i + 1:]:
|
|
||||||
merke(a, b, f"Beide belegen dieselbe Zeile in {datei}: {zeile}")
|
|
||||||
|
|
||||||
kern = _kerne(alle)
|
|
||||||
namen = sorted(alle)
|
|
||||||
for i, a in enumerate(namen):
|
|
||||||
for b in namen[i + 1:]:
|
|
||||||
if _aehnlich(alle[a]["titel"], alle[b]["titel"]) >= TITEL_KANAL:
|
|
||||||
merke(a, b, "Die Titel sind fast gleich.")
|
|
||||||
elif _ueberlappung(kern[a], kern[b]) >= INHALT_KANAL:
|
|
||||||
merke(a, b, "Beide Beschreibungen bestehen fast aus denselben Wörtern.")
|
|
||||||
return [(k, *paare[k]) for k in sorted(paare)]
|
|
||||||
|
|
||||||
|
|
||||||
async def _ist_dublette(a: dict, b: dict, anlass: str) -> bool:
|
|
||||||
"""Einstimmig gleicher Gegenstand und gleiches Ergebnis — sonst keine Dublette.
|
|
||||||
|
|
||||||
Das Gesamturteil rechnet der Code aus den Teilantworten. Gefragt nach der
|
|
||||||
Dublette an sich, widersprach eine Stimme ihrer eigenen Analyse: derselbe
|
|
||||||
Gegenstand, dasselbe Tun, und trotzdem „nein". Zwei kleine Fragen beantwortet
|
|
||||||
sie verlässlich, die große nicht.
|
|
||||||
|
|
||||||
Gefragt wird nach dem Ergebnis, nicht nach dem Verb. „Löschen" und „entfernen"
|
|
||||||
hielten zwei von drei Stimmen für verschiedenes Tun — was danach anders ist,
|
|
||||||
beschrieben sie gleich.
|
|
||||||
"""
|
|
||||||
nachricht = DUBLETTE.format(text_a=a["text"], text_b=b["text"], anlass=anlass)
|
|
||||||
proben = await asyncio.gather(*(
|
|
||||||
_urteil(DUBLETTE_SYSTEM, nachricht, t) for t in DUBLETTE_TEMPERATUREN
|
|
||||||
), return_exceptions=True)
|
|
||||||
stimmen = [p for p in proben if isinstance(p, dict)]
|
|
||||||
if len(stimmen) < len(DUBLETTE_TEMPERATUREN):
|
|
||||||
raise RuntimeError(f'Dublette {a["titel"]}/{b["titel"]}: '
|
|
||||||
f"nur {len(stimmen)} von "
|
|
||||||
f"{len(DUBLETTE_TEMPERATUREN)} Stimmen brauchbar")
|
|
||||||
return all(u.get("gegenstand_gleich") is True and u.get("ergebnis_gleich") is True
|
|
||||||
for u in stimmen)
|
|
||||||
|
|
||||||
|
|
||||||
async def dubletten(features: list, stand: dict, melde=None) -> list:
|
|
||||||
"""Funktionen, die eine andere Funktion doppelt beschreiben.
|
|
||||||
|
|
||||||
Gemeldet wird immer die schwächere von beiden — die stärkere bleibt stehen.
|
|
||||||
Ein einmal gefälltes Urteil bleibt gemerkt, auch das ablehnende: sonst zahlt
|
|
||||||
jeder Lauf erneut für dieselbe Frage und antwortet womöglich anders.
|
|
||||||
"""
|
|
||||||
alle = _funktionen(features)
|
|
||||||
paare = _kandidaten(alle, stand["belege"])
|
|
||||||
stand["paare"] = ledger.behalte(stand["paare"], {p[0] for p in paare})
|
|
||||||
if len(paare) > DUBLETTEN_CAP:
|
|
||||||
if melde:
|
|
||||||
melde(f"{len(paare)} Kandidatenpaare, geprüft wurden {DUBLETTEN_CAP}")
|
|
||||||
paare = paare[:DUBLETTEN_CAP]
|
|
||||||
|
|
||||||
async def urteile(paar):
|
|
||||||
schluessel, a, b, anlass = paar
|
|
||||||
links, rechts = alle[a], alle[b]
|
|
||||||
wie_gehabt = ledger.abdruck(_DUBLETTE_STAND, links["text"], rechts["text"])
|
|
||||||
gemerkt = stand["paare"].get(schluessel)
|
|
||||||
if gemerkt and gemerkt["abdruck"] == wie_gehabt:
|
|
||||||
gleich = gemerkt["gleich"]
|
|
||||||
else:
|
|
||||||
gleich = await _ist_dublette(links, rechts, anlass)
|
|
||||||
stand["paare"][schluessel] = {"abdruck": wie_gehabt, "gleich": gleich}
|
|
||||||
if not gleich:
|
|
||||||
return None
|
|
||||||
schwach, stark = sorted((links, rechts), key=_gewicht)
|
|
||||||
return {"feature": schwach["feature"], "titel": schwach["titel"],
|
|
||||||
"art": "dublette",
|
|
||||||
"grund": f'Beschreibt „{stark["titel"]}“ '
|
|
||||||
f'(Karte {stark["karte"]}) ein zweites Mal'}
|
|
||||||
|
|
||||||
return await _sammle([urteile(p) for p in paare], "Funktionspaare", melde)
|
|
||||||
|
|
||||||
|
|
||||||
def _wegfall_grund(betroffen: list) -> str:
|
|
||||||
"""Was die Karte verliert, in einem Halbsatz. Ohne Trennung nach Anlass stand
|
|
||||||
über einer Dublette „ohne eigenen Code" — beides heißt streichen, aber aus
|
|
||||||
verschiedenen Gründen, und der Grund entscheidet, ob man zustimmt."""
|
|
||||||
teile = []
|
|
||||||
for art, eins, viele in (("dublette", "Funktion doppelt", "Funktionen doppelt"),
|
|
||||||
("ohne_code", "Funktion ohne eigenen Code",
|
|
||||||
"Funktionen ohne eigenen Code")):
|
|
||||||
n = sum(x["art"] == art for x in betroffen)
|
|
||||||
if n:
|
|
||||||
teile.append(f"{n} {eins if n == 1 else viele}")
|
|
||||||
return ", ".join(teile)
|
|
||||||
|
|
||||||
|
|
||||||
def probleme_feature(features: list, ohne_code: list) -> list:
|
|
||||||
"""Ein Vorschlag je Karte: die betroffenen Funktionen aus der Liste nehmen."""
|
|
||||||
import pruefung
|
|
||||||
|
|
||||||
raus = []
|
|
||||||
for f in features:
|
|
||||||
ist = [x["text"] for x in f["sektionen"]["funktionen"]]
|
|
||||||
betroffen, gesehen = [], set()
|
|
||||||
for x in ohne_code:
|
|
||||||
if x["feature"] == f["id"] and x["titel"] not in gesehen:
|
|
||||||
gesehen.add(x["titel"])
|
|
||||||
betroffen.append(x)
|
|
||||||
if not betroffen:
|
|
||||||
continue
|
|
||||||
weg = [x["titel"] for x in betroffen]
|
|
||||||
neu = [t for t in ist if t not in weg]
|
|
||||||
grund = _wegfall_grund(betroffen)
|
|
||||||
beleg = " · ".join(f'{x["titel"]}: {x["grund"]}' for x in betroffen)
|
|
||||||
for p in pruefung._stellen("funktionen", "\n".join(ist), "\n".join(neu),
|
|
||||||
grund, beleg):
|
|
||||||
p["feature"] = f["id"]
|
|
||||||
p["funktion"] = ""
|
|
||||||
raus.append(p)
|
|
||||||
return raus
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Code im Feature
|
|
||||||
|
|
||||||
def _funktionsliste(features: list) -> str:
|
|
||||||
"""Die Karten mit allem, was sie sagen.
|
|
||||||
|
|
||||||
Nur die Titel reichten nicht: der Richter musste raten, ob eine Funktion eine
|
|
||||||
Stelle abdeckt, und urteilte von Runde zu Runde anders.
|
|
||||||
|
|
||||||
Die Regeln der Karte gehören dazu. Vieles, was ein Kunde bestellt, ist keine
|
|
||||||
Bedienung, sondern eine Zusicherung — „eine Datei je Feature", „alphabetisch
|
|
||||||
sortiert". Ohne sie sah der Richter den Code dahinter als unbeschrieben an.
|
|
||||||
"""
|
|
||||||
teile = []
|
|
||||||
for f in features:
|
|
||||||
s = f["sektionen"]
|
|
||||||
block = [f'## Karte: {s["titel"]}']
|
|
||||||
if s["ziel"]:
|
|
||||||
block.append(f'Ziel: {s["ziel"]}')
|
|
||||||
if s["regeln"]:
|
|
||||||
zeilen = ", ".join(z.strip("- ").strip()
|
|
||||||
for z in s["regeln"].splitlines() if z.strip())
|
|
||||||
block.append(f"Regeln der Karte: {zeilen}")
|
|
||||||
for x in s["funktionen"]:
|
|
||||||
block.append(f'\n### {x["text"]} (Karte: {s["titel"]})')
|
|
||||||
if x["ziel"]:
|
|
||||||
block.append(f'Ziel: {x["ziel"]}')
|
|
||||||
for feld in ("regeln", "testen"):
|
|
||||||
if x[feld]:
|
|
||||||
zeilen = ", ".join(z.strip("- ").strip()
|
|
||||||
for z in x[feld].splitlines() if z.strip())
|
|
||||||
block.append(f'{feld.capitalize()}: {zeilen}')
|
|
||||||
teile.append("\n".join(block))
|
|
||||||
return "\n\n".join(teile) or "—"
|
|
||||||
|
|
||||||
|
|
||||||
async def _abdeckung(punkt: dict, liste: str) -> list:
|
|
||||||
rumpf = f'\nDahinter steht:\n\n```\n{punkt["rumpf"]}\n```\n' if punkt["rumpf"] else ""
|
|
||||||
nachricht = ABDECKUNG.format(funktionen=liste, art=punkt["art"],
|
|
||||||
datei=punkt["datei"], zeile=punkt["zeile"],
|
|
||||||
umfeld=punkt["umfeld"], rumpf=rumpf)
|
|
||||||
proben = await asyncio.gather(*(
|
|
||||||
_urteil(ABDECKUNG_SYSTEM, nachricht, t) for t in URTEIL_TEMPERATUREN
|
|
||||||
), return_exceptions=True)
|
|
||||||
return [p for p in proben if isinstance(p, dict)]
|
|
||||||
|
|
||||||
|
|
||||||
def _naechste_karte(name: str, karten: dict, schwelle: float = 0.6):
|
|
||||||
if not name or not karten:
|
|
||||||
return None
|
|
||||||
treffer = max(karten, key=lambda k: _aehnlich(name, k))
|
|
||||||
return karten[treffer] if _aehnlich(name, treffer) >= schwelle else None
|
|
||||||
|
|
||||||
|
|
||||||
def _zielkarte(urteile: list, karten: dict):
|
|
||||||
"""Auf welche Karte gehört die fehlende Funktion?
|
|
||||||
|
|
||||||
Die Stimmen schlagen oft Karten vor, die es nicht gibt („Export",
|
|
||||||
„Datenverwaltung"). Vorher fiel der Befund dann weg — eine Stelle, die
|
|
||||||
einstimmig fehlte, wurde verschwiegen, weil man sich über die Ablage nicht
|
|
||||||
einig war. Jetzt gewinnt der erste Vorschlag, der sich auflösen lässt, und
|
|
||||||
zur Not der ähnlichste. Falsch abgelegt kann man verschieben; verschwiegen nicht.
|
|
||||||
"""
|
|
||||||
namen = Counter(str(u.get("karte") or "").strip() for u in urteile
|
|
||||||
if u.get("abgedeckt") is False and u.get("karte"))
|
|
||||||
for name, _ in namen.most_common():
|
|
||||||
if treffer := karten.get(name) or _naechste_karte(name, karten):
|
|
||||||
return treffer
|
|
||||||
haeufigste = namen.most_common(1)
|
|
||||||
return _naechste_karte(haeufigste[0][0], karten, schwelle=0.0) if haeufigste else None
|
|
||||||
|
|
||||||
|
|
||||||
def _stellenabdruck(punkt: dict) -> str:
|
|
||||||
return ledger.abdruck(_ABDECKUNG_STAND, punkt["beleg"], punkt["umfeld"],
|
|
||||||
punkt["rumpf"])
|
|
||||||
|
|
||||||
|
|
||||||
async def code_im_feature(features: list, punkte: list, stand: dict,
|
|
||||||
zaehl=None, melde=None) -> list:
|
|
||||||
"""Bedienstellen, die von keiner Funktion abgedeckt sind.
|
|
||||||
|
|
||||||
Ein gemerktes Urteil gilt, solange es tragen kann: „abgedeckt durch X", solange
|
|
||||||
X noch auf einer Karte steht; „fehlt als Y", solange es kein Y gibt. Beides
|
|
||||||
fällt weg, sobald sich die Codestelle selbst ändert.
|
|
||||||
"""
|
|
||||||
liste = _funktionsliste(features)
|
|
||||||
karten = {f["sektionen"]["titel"]: f for f in features}
|
|
||||||
kennungen = {f["id"] for f in features}
|
|
||||||
titel_da = [x["text"] for f in features for x in f["sektionen"]["funktionen"]]
|
|
||||||
normal_da = {_normal(t) for t in titel_da}
|
|
||||||
stand["abdeckung"] = ledger.behalte(stand["abdeckung"],
|
|
||||||
{p["schluessel"] for p in punkte})
|
|
||||||
stand["ohne_karte"] = ledger.behalte(stand["ohne_karte"],
|
|
||||||
{p["schluessel"] for p in punkte})
|
|
||||||
|
|
||||||
async def eine(punkt):
|
|
||||||
schluessel = punkt["schluessel"]
|
|
||||||
wie_gehabt = _stellenabdruck(punkt)
|
|
||||||
|
|
||||||
gemerkt = stand["abdeckung"].get(schluessel)
|
|
||||||
if gemerkt and gemerkt["abdruck"] == wie_gehabt \
|
|
||||||
and (not gemerkt["funktion"] or _normal(gemerkt["funktion"]) in normal_da):
|
|
||||||
if zaehl:
|
|
||||||
zaehl()
|
|
||||||
return None
|
|
||||||
gemerkt = stand["ohne_karte"].get(schluessel)
|
|
||||||
if gemerkt and gemerkt["abdruck"] == wie_gehabt \
|
|
||||||
and gemerkt["feature"] in kennungen \
|
|
||||||
and not any(_aehnlich(gemerkt["titel"], t) > GLEICH for t in titel_da):
|
|
||||||
if zaehl:
|
|
||||||
zaehl()
|
|
||||||
return {"feature": gemerkt["feature"], "titel": gemerkt["titel"],
|
|
||||||
"beleg": gemerkt["beleg"]}
|
|
||||||
|
|
||||||
urteile = await _abdeckung(punkt, liste)
|
|
||||||
if zaehl:
|
|
||||||
zaehl()
|
|
||||||
nein = sum(u.get("abgedeckt") is False for u in urteile)
|
|
||||||
ausgefallen = len(URTEIL_TEMPERATUREN) - len(urteile)
|
|
||||||
if len(urteile) < URTEIL_AB or nein < FEHLT_AB <= nein + ausgefallen:
|
|
||||||
# Zu dünn oder offen. Dünn: zwei überlebende Stimmen genügen rechnerisch
|
|
||||||
# für „abgedeckt", weil die Schwelle unerreichbar wird — als Dauerurteil
|
|
||||||
# wäre das zu wenig. Offen: die ausgefallenen Stimmen hätten es kippen
|
|
||||||
# können. Beides nicht verschweigen; sonst sähe der Ausfall aus wie „ist
|
|
||||||
# abgedeckt" und der Lauf meldete stillschweigend zu wenig.
|
|
||||||
raise RuntimeError(
|
|
||||||
f'{punkt["schluessel"]}: nur {len(urteile)} von '
|
|
||||||
f"{len(URTEIL_TEMPERATUREN)} Stimmen brauchbar")
|
|
||||||
stand["abdeckung"].pop(schluessel, None)
|
|
||||||
stand["ohne_karte"].pop(schluessel, None)
|
|
||||||
|
|
||||||
# Steht das Ergebnis schon fest, zählen die fehlenden Stimmen nicht mehr.
|
|
||||||
# Vier Stimmen „abgedeckt" bei sechs möglichen können die Schwelle nicht
|
|
||||||
# mehr reißen — daraus einen Fehlschlag zu machen, wäre Verschwendung.
|
|
||||||
if nein < FEHLT_AB:
|
|
||||||
# Abgedeckt — oder unentschieden, was dasselbe Ergebnis hat. Gemerkt
|
|
||||||
# wird beides; der Name nur, wenn die Stimmen sich auf einen einigen.
|
|
||||||
namen = Counter(str(u.get("funktion") or "").strip()
|
|
||||||
for u in urteile if u.get("abgedeckt"))
|
|
||||||
name, treffer = namen.most_common(1)[0] if namen else ("", 0)
|
|
||||||
# Nur ein Name, den es wirklich gibt, taugt als Verfallsdatum. Deckt
|
|
||||||
# eine Regel die Stelle ab, nennt die Stimme deren Wortlaut — der
|
|
||||||
# stünde auf ewig ungültig in der Datei und würde jeden Lauf neu gefragt.
|
|
||||||
gilt = treffer >= len(urteile) / 2 and _normal(name) in normal_da
|
|
||||||
stand["abdeckung"][schluessel] = {"abdruck": wie_gehabt,
|
|
||||||
"funktion": name if gilt else ""}
|
|
||||||
return None
|
|
||||||
|
|
||||||
treffer = _zielkarte(urteile, karten)
|
|
||||||
titel = [str(u.get("titel") or "").strip() for u in urteile if u.get("titel")]
|
|
||||||
if not treffer or not titel:
|
|
||||||
return None
|
|
||||||
gefunden = {"feature": treffer["id"],
|
|
||||||
"titel": max(titel, key=lambda t: sum(_aehnlich(t, x) for x in titel)),
|
|
||||||
"beleg": f'{punkt["datei"]}:{punkt["zeile"]}'}
|
|
||||||
stand["ohne_karte"][schluessel] = {"abdruck": wie_gehabt, **gefunden}
|
|
||||||
return gefunden
|
|
||||||
|
|
||||||
gefunden = await _sammle([eine(p) for p in punkte], "Codestellen", melde)
|
|
||||||
# Zwei Stellen können auf dieselbe fehlende Funktion zeigen
|
|
||||||
raus = []
|
|
||||||
for x in gefunden:
|
|
||||||
gleich = next((y for y in raus if y["feature"] == x["feature"]
|
|
||||||
and _aehnlich(y["titel"], x["titel"]) > GLEICH), None)
|
|
||||||
if gleich:
|
|
||||||
gleich["beleg"] += f', {x["beleg"]}'
|
|
||||||
else:
|
|
||||||
raus.append(dict(x))
|
|
||||||
return raus
|
|
||||||
|
|
||||||
|
|
||||||
def probleme_code(features: list, fehlend: list) -> list:
|
|
||||||
"""Ein Vorschlag je Karte: die fehlenden Funktionen anhängen."""
|
|
||||||
import pruefung
|
|
||||||
|
|
||||||
raus = []
|
|
||||||
for f in features:
|
|
||||||
ist = [x["text"] for x in f["sektionen"]["funktionen"]]
|
|
||||||
dazu = [x for x in fehlend if x["feature"] == f["id"]]
|
|
||||||
if not dazu:
|
|
||||||
continue
|
|
||||||
# Bestehende Reihenfolge behalten, Neues hinten anhängen — sortieren würde
|
|
||||||
# aus einer Ergänzung einen Umbau machen. Angezeigt wird ohnehin alphabetisch.
|
|
||||||
neu = list(dict.fromkeys(ist + [x["titel"] for x in dazu]))
|
|
||||||
grund = (f"{len(dazu)} Stelle ohne Funktion" if len(dazu) == 1
|
|
||||||
else f"{len(dazu)} Stellen ohne Funktion")
|
|
||||||
beleg = " · ".join(x["beleg"] for x in dazu)
|
|
||||||
for p in pruefung._stellen("funktionen", "\n".join(ist), "\n".join(neu),
|
|
||||||
grund, beleg):
|
|
||||||
p["feature"] = f["id"]
|
|
||||||
p["funktion"] = ""
|
|
||||||
raus.append(p)
|
|
||||||
return raus
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Einstiege
|
|
||||||
|
|
||||||
async def pruefe_feature_im_code(projekt: str, features: list, quelle: dict,
|
|
||||||
quelltext: str, zaehl=None, melde=None) -> list:
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
try:
|
|
||||||
ohne = await feature_im_code(features, quelle, quelltext, stand, zaehl, melde)
|
|
||||||
doppelt = await dubletten(features, stand, melde)
|
|
||||||
finally:
|
|
||||||
# Auch ein abgebrochener Lauf hat schon Urteile gefällt; die sind bezahlt
|
|
||||||
ledger.sichere(projekt, stand)
|
|
||||||
return probleme_feature(features, ohne + doppelt)
|
|
||||||
|
|
||||||
|
|
||||||
async def pruefe_code_im_feature(projekt: str, features: list, quelle: dict,
|
|
||||||
zaehl=None, melde=None) -> list:
|
|
||||||
punkte = quellcode.interaktionen(quelle)
|
|
||||||
if not punkte:
|
|
||||||
raise RuntimeError("Keine Bedienstellen im Quelltext gefunden")
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
try:
|
|
||||||
fehlend = await code_im_feature(features, punkte, stand, zaehl, melde)
|
|
||||||
finally:
|
|
||||||
ledger.sichere(projekt, stand)
|
|
||||||
return probleme_code(features, fehlend)
|
|
||||||
BIN
backend/k1-1024.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
backend/k1-1280.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
backend/k1-1600.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
backend/k1-390.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
backend/k1-640.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
backend/k1-820.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
backend/komfort.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
@@ -9,8 +9,7 @@ nur noch, was er nicht schon weiß. Ein Eintrag gilt weiter, solange seine
|
|||||||
Voraussetzung nachweisbar dasteht:
|
Voraussetzung nachweisbar dasteht:
|
||||||
|
|
||||||
- ein Beleg, solange die zitierte Zeile noch in der Datei steht
|
- ein Beleg, solange die zitierte Zeile noch in der Datei steht
|
||||||
- eine Abdeckung, solange die nennende Funktion noch auf einer Karte steht
|
- eine Fehlanzeige, solange sich am Quelltext nichts geändert hat
|
||||||
- eine Fehlmeldung, solange keine passende Funktion dazugekommen ist
|
|
||||||
|
|
||||||
Fällt die Voraussetzung weg, fällt der Eintrag weg und wird neu gefragt. Damit
|
Fällt die Voraussetzung weg, fällt der Eintrag weg und wird neu gefragt. Damit
|
||||||
bleibt gleicher Stand gleiches Ergebnis, und geänderter Stand wird wirklich neu
|
bleibt gleicher Stand gleiches Ergebnis, und geänderter Stand wird wirklich neu
|
||||||
@@ -30,11 +29,10 @@ DATEI = ".pruefung.json"
|
|||||||
|
|
||||||
# Ein Fach je Frage. Getrennt, weil sie unterschiedlich ungültig werden.
|
# Ein Fach je Frage. Getrennt, weil sie unterschiedlich ungültig werden.
|
||||||
FAECHER = (
|
FAECHER = (
|
||||||
|
"stil", # Prüfstelle -> ihr Stilurteil (Prüfung 1)
|
||||||
"belege", # Funktion -> Codestelle, die sie umsetzt (Prüfung 2)
|
"belege", # Funktion -> Codestelle, die sie umsetzt (Prüfung 2)
|
||||||
"ohne_code", # Funktion -> kein Code gefunden (Prüfung 2)
|
"ohne_code", # Funktion -> kein Code gefunden (Prüfung 2)
|
||||||
"paare", # Funktionspaar -> Dublette oder verschieden (Prüfung 2)
|
"paare", # Funktionspaar -> Dublette oder verschieden (Prüfung 2)
|
||||||
"abdeckung", # Codestelle -> Funktion, die sie beschreibt (Prüfung 3)
|
|
||||||
"ohne_karte", # Codestelle -> keine Funktion beschreibt sie (Prüfung 3)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
backend/offen.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
@@ -19,6 +19,14 @@ TRENNER = re.compile(r"^-{3,}\s*$")
|
|||||||
VISION = "vision"
|
VISION = "vision"
|
||||||
ARTEN = ("kern", "optional", VISION)
|
ARTEN = ("kern", "optional", VISION)
|
||||||
|
|
||||||
|
# Eine Funktion ist entweder geliefert oder noch offen. „offen" heißt: die Karte
|
||||||
|
# beschreibt sie schon, den Code gibt es noch nicht — bestellt, nicht gebaut.
|
||||||
|
# Ohne diese Unterscheidung meldet die Vorwärtsprüfung jede geplante Funktion als
|
||||||
|
# Fehler und will sie streichen.
|
||||||
|
OFFEN = "offen"
|
||||||
|
GEBAUT = "gebaut"
|
||||||
|
STAENDE = (GEBAUT, OFFEN)
|
||||||
|
|
||||||
|
|
||||||
def _art(bloecke: list) -> str:
|
def _art(bloecke: list) -> str:
|
||||||
wert = bloecke[0].strip().lower() if bloecke else ""
|
wert = bloecke[0].strip().lower() if bloecke else ""
|
||||||
@@ -61,7 +69,11 @@ def zerlege(roh: str) -> dict:
|
|||||||
# vor der ersten Funktion: Karten-Ziel, danach: Ziel der Funktion
|
# vor der ersten Funktion: Karten-Ziel, danach: Ziel der Funktion
|
||||||
(funktionen[-1]["ziel"] if funktionen else ziel).append(text)
|
(funktionen[-1]["ziel"] if funktionen else ziel).append(text)
|
||||||
elif schluessel == "funktion":
|
elif schluessel == "funktion":
|
||||||
funktionen.append({"text": text, "ziel": [], "regeln": [], "testen": []})
|
funktionen.append({"text": text, "ziel": [], "regeln": [], "testen": [],
|
||||||
|
"stand": GEBAUT})
|
||||||
|
elif schluessel == "stand" and funktionen:
|
||||||
|
wert = text.strip().lower()
|
||||||
|
funktionen[-1]["stand"] = wert if wert in STAENDE else GEBAUT
|
||||||
elif schluessel in ("regel", "regeln"):
|
elif schluessel in ("regel", "regeln"):
|
||||||
# vor der ersten Funktion: Karten-Regeln, danach: Regeln der Funktion
|
# vor der ersten Funktion: Karten-Regeln, danach: Regeln der Funktion
|
||||||
(funktionen[-1]["regeln"] if funktionen else regeln).append(text)
|
(funktionen[-1]["regeln"] if funktionen else regeln).append(text)
|
||||||
|
|||||||
163
backend/planung.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
"""Eine Planung: der Kartensatz, wie er nach der nächsten Umsetzung aussehen soll.
|
||||||
|
|
||||||
|
Die Übersicht beantwortet „was kann mein Projekt". Eine geplante Funktion gehört
|
||||||
|
dort nicht hin — sie wäre gelogen. Deshalb liegt die Planung in einer eigenen
|
||||||
|
Kopie und der Projektordner bleibt unberührt, bis sie umgesetzt wird.
|
||||||
|
|
||||||
|
Kopiert wird der ganze Ordner, nicht nur die Karten. Gespräch und
|
||||||
|
Urteilsgedächtnis gehören zur Planung: was während des Planens besprochen und
|
||||||
|
beurteilt wurde, gilt für den geplanten Stand, nicht für den heutigen.
|
||||||
|
|
||||||
|
Solange eine Planung läuft, arbeitet `projekte.wurzel()` in der Kopie. Keine
|
||||||
|
Schreibfunktion muss davon wissen.
|
||||||
|
|
||||||
|
Es gibt höchstens eine Planung je Projekt. Sie endet auf zwei Arten: umgesetzt —
|
||||||
|
dann ersetzt die Kopie den Projektstand — oder abgebrochen, dann verschwindet sie
|
||||||
|
spurlos.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import parser
|
||||||
|
import projekte
|
||||||
|
|
||||||
|
# Was beim Vergleich zählt: der sichtbare Inhalt einer Funktion, nicht ihre Datei.
|
||||||
|
# Wird eine Karte umbenannt, wandert ihre Datei — der Vergleich hinge sonst an
|
||||||
|
# einem Dateinamen statt an dem, was dasteht.
|
||||||
|
FELDER = ("ziel", "regeln", "testen", "stand")
|
||||||
|
|
||||||
|
|
||||||
|
def laeuft(projekt: str) -> bool:
|
||||||
|
return (projekte.stand(projekt) / projekte.PLANUNG).is_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def _kopie(projekt: str):
|
||||||
|
return projekte.stand(projekt) / projekte.PLANUNG
|
||||||
|
|
||||||
|
|
||||||
|
def starte(projekt: str):
|
||||||
|
"""Arbeitskopie anlegen. Ab hier zeigt `wurzel()` dorthin."""
|
||||||
|
ziel = _kopie(projekt)
|
||||||
|
if ziel.is_dir():
|
||||||
|
raise ValueError("Für dieses Projekt läuft schon eine Planung")
|
||||||
|
shutil.copytree(projekte.stand(projekt), ziel,
|
||||||
|
ignore=shutil.ignore_patterns(projekte.PLANUNG))
|
||||||
|
|
||||||
|
|
||||||
|
def brich_ab(projekt: str):
|
||||||
|
"""Planung verwerfen. Der Projektstand hat sie nie gesehen."""
|
||||||
|
shutil.rmtree(_kopie(projekt), ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
def setze_um(projekt: str):
|
||||||
|
"""Die Kopie wird der neue Projektstand.
|
||||||
|
|
||||||
|
Erst die Kopie herausheben, dann den alten Stand räumen, dann die Kopie an
|
||||||
|
seine Stelle. Ein Absturz mitten im Räumen ließe sonst ein Projekt ohne Karten
|
||||||
|
zurück — und die Planung gleich mit.
|
||||||
|
"""
|
||||||
|
kopie = _kopie(projekt)
|
||||||
|
if not kopie.is_dir():
|
||||||
|
raise ValueError("Für dieses Projekt läuft keine Planung")
|
||||||
|
ordner = projekte.stand(projekt)
|
||||||
|
daneben = ordner.parent / f"{ordner.name}.neu"
|
||||||
|
shutil.rmtree(daneben, ignore_errors=True)
|
||||||
|
kopie.rename(daneben)
|
||||||
|
for eintrag in ordner.iterdir():
|
||||||
|
shutil.rmtree(eintrag) if eintrag.is_dir() else eintrag.unlink()
|
||||||
|
for eintrag in daneben.iterdir():
|
||||||
|
eintrag.rename(ordner / eintrag.name)
|
||||||
|
daneben.rmdir()
|
||||||
|
|
||||||
|
|
||||||
|
def _karten(ordner) -> dict:
|
||||||
|
"""titel -> Sektionen. Verglichen wird über den Titel, nicht über die Datei."""
|
||||||
|
raus = {}
|
||||||
|
for datei in sorted(ordner.glob("*.md")):
|
||||||
|
s = parser.zerlege(datei.read_text())
|
||||||
|
raus[s["titel"]] = s
|
||||||
|
return raus
|
||||||
|
|
||||||
|
|
||||||
|
def _funktionstext(f: dict) -> str:
|
||||||
|
return "\n".join(str(f.get(feld) or "") for feld in FELDER)
|
||||||
|
|
||||||
|
|
||||||
|
def _funktionen(karte: dict) -> dict:
|
||||||
|
return {f["text"]: _funktionstext(f) for f in karte["funktionen"]}
|
||||||
|
|
||||||
|
|
||||||
|
def _kartentext(karte: dict) -> str:
|
||||||
|
return "\n".join((karte.get("art") or "", karte.get("ziel") or "",
|
||||||
|
karte.get("regeln") or ""))
|
||||||
|
|
||||||
|
|
||||||
|
def aenderungen(projekt: str) -> dict:
|
||||||
|
"""Was die Planung am Projektstand ändert — je Karte und je Funktion.
|
||||||
|
|
||||||
|
Ohne laufende Planung ist das leer. Die Übersicht zeigt dann nichts an, und
|
||||||
|
die Karten sehen aus wie immer.
|
||||||
|
"""
|
||||||
|
if not laeuft(projekt):
|
||||||
|
return {}
|
||||||
|
alt = _karten(projekte.stand(projekt))
|
||||||
|
neu = _karten(_kopie(projekt))
|
||||||
|
|
||||||
|
raus = {}
|
||||||
|
for titel in set(alt) | set(neu):
|
||||||
|
a, b = alt.get(titel), neu.get(titel)
|
||||||
|
if a is None:
|
||||||
|
raus[titel] = {"art": "neu", "funktionen": {
|
||||||
|
f["text"]: "neu" for f in b["funktionen"]}}
|
||||||
|
continue
|
||||||
|
if b is None:
|
||||||
|
raus[titel] = {"art": "weg", "funktionen": {
|
||||||
|
f["text"]: "weg" for f in a["funktionen"]}}
|
||||||
|
continue
|
||||||
|
|
||||||
|
fa, fb = _funktionen(a), _funktionen(b)
|
||||||
|
funktionen = {}
|
||||||
|
for name in set(fa) | set(fb):
|
||||||
|
if name not in fa:
|
||||||
|
funktionen[name] = "neu"
|
||||||
|
elif name not in fb:
|
||||||
|
funktionen[name] = "weg"
|
||||||
|
elif fa[name] != fb[name]:
|
||||||
|
funktionen[name] = "geaendert"
|
||||||
|
kopf = _kartentext(a) != _kartentext(b)
|
||||||
|
if funktionen or kopf:
|
||||||
|
raus[titel] = {"art": "geaendert", "funktionen": funktionen,
|
||||||
|
"kopf": kopf}
|
||||||
|
return raus
|
||||||
|
|
||||||
|
|
||||||
|
def _datei_zu(ordner, titel: str):
|
||||||
|
for datei in ordner.glob("*.md"):
|
||||||
|
if parser.zerlege(datei.read_text())["titel"] == titel:
|
||||||
|
return datei
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def zuruecksetzen(projekt: str, titel: str):
|
||||||
|
"""Eine Karte auf den Projektstand zurückholen.
|
||||||
|
|
||||||
|
Drei Fälle: neu dazugekommen (löschen), weggefallen (zurückholen), geändert
|
||||||
|
(überschreiben). Der Rest der Planung bleibt stehen.
|
||||||
|
"""
|
||||||
|
if not laeuft(projekt):
|
||||||
|
raise ValueError("Für dieses Projekt läuft keine Planung")
|
||||||
|
ordner, kopie = projekte.stand(projekt), _kopie(projekt)
|
||||||
|
urspruenglich = _datei_zu(ordner, titel)
|
||||||
|
geplant = _datei_zu(kopie, titel)
|
||||||
|
|
||||||
|
if urspruenglich is None and geplant is None:
|
||||||
|
raise FileNotFoundError(f"Karte „{titel}“ nicht gefunden")
|
||||||
|
if urspruenglich is None:
|
||||||
|
geplant.unlink()
|
||||||
|
return
|
||||||
|
ziel = geplant or (kopie / urspruenglich.name)
|
||||||
|
ziel.write_text(urspruenglich.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def stand(projekt: str) -> dict:
|
||||||
|
return {"laeuft": laeuft(projekt), "aenderungen": aenderungen(projekt)}
|
||||||
@@ -4,6 +4,10 @@ import shutil
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
# Die Arbeitskopie einer laufenden Planung. Liegt im Projektordner und beginnt mit
|
||||||
|
# einem Punkt — damit taucht sie im Listing nicht als eigenes Projekt auf.
|
||||||
|
PLANUNG = ".planung"
|
||||||
|
|
||||||
|
|
||||||
def _pruefe_name(name: str):
|
def _pruefe_name(name: str):
|
||||||
if not name or not name.strip():
|
if not name or not name.strip():
|
||||||
@@ -51,9 +55,24 @@ def loeschen(name: str):
|
|||||||
shutil.rmtree(ordner)
|
shutil.rmtree(ordner)
|
||||||
|
|
||||||
|
|
||||||
def wurzel(name: str):
|
def stand(name: str):
|
||||||
"""Projektordner für Feature-Zugriffe; FileNotFoundError bei unbekanntem Projekt."""
|
"""Der Projektordner selbst — was das Projekt heute kann.
|
||||||
|
|
||||||
|
Nur wer den umgesetzten Stand meint, ruft das. Alles andere nimmt `wurzel()`.
|
||||||
|
"""
|
||||||
ordner = _ordner(name)
|
ordner = _ordner(name)
|
||||||
if not ordner.is_dir():
|
if not ordner.is_dir():
|
||||||
raise FileNotFoundError(f"Projekt „{name}“ nicht gefunden")
|
raise FileNotFoundError(f"Projekt „{name}“ nicht gefunden")
|
||||||
return ordner
|
return ordner
|
||||||
|
|
||||||
|
|
||||||
|
def wurzel(name: str):
|
||||||
|
"""Der Ordner, in dem gerade gearbeitet wird.
|
||||||
|
|
||||||
|
Ohne offene Planung ist das der Projektordner. Läuft eine Planung, ist es ihre
|
||||||
|
Arbeitskopie — und zwar für alles: Karten, Gespräch, Gedächtnis. Ein
|
||||||
|
Schalter statt einer Fallunterscheidung in jeder Schreibfunktion.
|
||||||
|
"""
|
||||||
|
ordner = stand(name)
|
||||||
|
kopie = ordner / PLANUNG
|
||||||
|
return kopie if kopie.is_dir() else ordner
|
||||||
|
|||||||
@@ -18,10 +18,18 @@ from collections import Counter
|
|||||||
import agents
|
import agents
|
||||||
import config
|
import config
|
||||||
import features
|
import features
|
||||||
import inhalt as inhaltspruefung
|
import ledger
|
||||||
import quellcode
|
|
||||||
import struktur
|
import struktur
|
||||||
|
|
||||||
|
class LaeuftBereits(ValueError):
|
||||||
|
"""Eigener Typ, weil 409 genau eine Bedeutung haben muss.
|
||||||
|
|
||||||
|
Vorher war jeder Fehlstart ein 409 — auch ein fehlendes Quellverzeichnis. Das
|
||||||
|
Frontend liest 409 als „läuft schon" und hängt sich an einen Lauf, den es nicht
|
||||||
|
gibt. Ergebnis: „Keine Probleme gefunden" statt der Fehlermeldung.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
# projektname -> {"laeuft": bool, "fertig": int, "gesamt": int, "fehler": int,
|
# projektname -> {"laeuft": bool, "fertig": int, "gesamt": int, "fehler": int,
|
||||||
# "probleme": [...], "meldung": str}
|
# "probleme": [...], "meldung": str}
|
||||||
_laeufe = {}
|
_laeufe = {}
|
||||||
@@ -445,7 +453,17 @@ async def _neue_felder(system: str, aufgabe: dict, offen: list, erlaubt: tuple,
|
|||||||
return vorschlaege
|
return vorschlaege
|
||||||
|
|
||||||
|
|
||||||
async def _pruefe(stil: dict, aufgabe: dict) -> list[dict]:
|
def _stilschluessel(aufgabe: dict) -> str:
|
||||||
|
return ledger.schluessel(aufgabe["feature"], aufgabe["funktion"])
|
||||||
|
|
||||||
|
|
||||||
|
def _stilabdruck(aufgabe: dict, system: str) -> str:
|
||||||
|
"""Woran das Urteil hängt: der geprüfte Ausschnitt, der Katalog, die Frage.
|
||||||
|
Ändert sich eines davon, ist das gemerkte Urteil von gestern."""
|
||||||
|
return ledger.abdruck(system, inhalt(aufgabe))
|
||||||
|
|
||||||
|
|
||||||
|
async def _pruefe(stil: dict, aufgabe: dict, stand: dict = None) -> list[dict]:
|
||||||
"""Erst messen, dann umschreiben lassen, dann den Unterschied ausrechnen."""
|
"""Erst messen, dann umschreiben lassen, dann den Unterschied ausrechnen."""
|
||||||
bereich = BEREICHE[aufgabe["bereich"]]
|
bereich = BEREICHE[aufgabe["bereich"]]
|
||||||
system = SYSTEM.format(
|
system = SYSTEM.format(
|
||||||
@@ -455,6 +473,15 @@ async def _pruefe(stil: dict, aufgabe: dict) -> list[dict]:
|
|||||||
hinweis=bereich["hinweis"],
|
hinweis=bereich["hinweis"],
|
||||||
)
|
)
|
||||||
offen = struktur.befunde(aufgabe["bereich"], aufgabe["felder"])
|
offen = struktur.befunde(aufgabe["bereich"], aufgabe["felder"])
|
||||||
|
|
||||||
|
# Gemessene Verstöße stehen fest — die dürfen nie aus dem Gedächtnis kommen.
|
||||||
|
# Sonst bliebe eine neu hinzugekommene Messregel an alten Karten wirkungslos.
|
||||||
|
schluessel, wie_gehabt = _stilschluessel(aufgabe), _stilabdruck(aufgabe, system)
|
||||||
|
if stand is not None and not offen:
|
||||||
|
gemerkt = stand["stil"].get(schluessel)
|
||||||
|
if gemerkt and gemerkt["abdruck"] == wie_gehabt:
|
||||||
|
return [dict(p) for p in gemerkt["probleme"]]
|
||||||
|
|
||||||
vorschlaege = await _neue_felder(system, aufgabe, offen, bereich["felder"],
|
vorschlaege = await _neue_felder(system, aufgabe, offen, bereich["felder"],
|
||||||
katalogzeilen(stil[aufgabe["bereich"]]))
|
katalogzeilen(stil[aufgabe["bereich"]]))
|
||||||
|
|
||||||
@@ -466,83 +493,33 @@ async def _pruefe(stil: dict, aufgabe: dict) -> list[dict]:
|
|||||||
for p in probleme:
|
for p in probleme:
|
||||||
p["feature"] = aufgabe["feature"]
|
p["feature"] = aufgabe["feature"]
|
||||||
p["funktion"] = aufgabe["funktion"]
|
p["funktion"] = aufgabe["funktion"]
|
||||||
|
if stand is not None:
|
||||||
|
stand["stil"][schluessel] = {"abdruck": wie_gehabt, "probleme": probleme}
|
||||||
return probleme
|
return probleme
|
||||||
|
|
||||||
|
|
||||||
async def _stil_aufgaben(projekt: str):
|
async def _stil_aufgaben(projekt: str):
|
||||||
"""Prüfstellen der Stilprüfung samt der Kataloge, die sie brauchen."""
|
"""Prüfstellen der Stilprüfung samt der Kataloge, die sie brauchen.
|
||||||
|
|
||||||
|
Auch hier ein Gedächtnis: 176 Anfragen je Lauf brachten den Anbieter zum
|
||||||
|
Drosseln, und die ausgefallenen Stellen meldeten dann nichts. Unveränderter
|
||||||
|
Ausschnitt bei unverändertem Katalog heißt: das Urteil von vorhin gilt noch.
|
||||||
|
"""
|
||||||
stil = {b: _stil_lesen(b) for b in BEREICHE}
|
stil = {b: _stil_lesen(b) for b in BEREICHE}
|
||||||
liste = [a for f in features.pruefbar(projekt) for a in aufgaben(f)]
|
liste = [a for f in features.pruefbar(projekt) for a in aufgaben(f)]
|
||||||
return liste, lambda a, _zaehl: _pruefe(stil, a)
|
stand = ledger.lade(projekt)
|
||||||
|
stand["stil"] = ledger.behalte(stand["stil"], {_stilschluessel(a) for a in liste})
|
||||||
|
return (liste,
|
||||||
def _quelle(projekt: str):
|
lambda a, _zaehl: _pruefe(stil, a, stand),
|
||||||
alle = features.pruefbar(projekt)
|
lambda: ledger.sichere(projekt, stand))
|
||||||
quelle = quellcode.dateien()
|
|
||||||
if not quelle:
|
|
||||||
raise ValueError("Kein Quelltext gefunden")
|
|
||||||
return alle, quelle
|
|
||||||
|
|
||||||
|
|
||||||
async def _feature_aufgaben(projekt: str):
|
|
||||||
"""Ein Lauf über alle Funktionen: steht hinter jeder auch Code? Und beschreibt
|
|
||||||
keine zwei Funktionen dieselbe Sache?
|
|
||||||
|
|
||||||
Ein Aufruf statt vieler, weil das Ergebnis je Karte gebündelt wird — zwei
|
|
||||||
Vorschläge für dieselbe Funktionsliste wären nicht unabhängig anwendbar.
|
|
||||||
Der Zähler läuft trotzdem je Funktion mit.
|
|
||||||
"""
|
|
||||||
alle, quelle = _quelle(projekt)
|
|
||||||
text = quellcode.als_text(quelle)
|
|
||||||
anzahl = sum(len(f["sektionen"]["funktionen"]) for f in alle)
|
|
||||||
|
|
||||||
async def abgleich(_, zaehl):
|
|
||||||
return await inhaltspruefung.pruefe_feature_im_code(
|
|
||||||
projekt, alle, quelle, text, zaehl, _melder(projekt))
|
|
||||||
|
|
||||||
return _platzhalter(anzahl), abgleich
|
|
||||||
|
|
||||||
|
|
||||||
async def _code_aufgaben(projekt: str):
|
|
||||||
"""Ein Lauf über alle Bedienstellen — Vollständigkeit ist eine Eigenschaft der
|
|
||||||
Menge, nicht der einzelnen Karte."""
|
|
||||||
alle, quelle = _quelle(projekt)
|
|
||||||
punkte = quellcode.interaktionen(quelle)
|
|
||||||
|
|
||||||
async def abgleich(_, zaehl):
|
|
||||||
return await inhaltspruefung.pruefe_code_im_feature(
|
|
||||||
projekt, alle, quelle, zaehl, _melder(projekt))
|
|
||||||
|
|
||||||
return _platzhalter(len(punkte)), abgleich
|
|
||||||
|
|
||||||
|
|
||||||
def _melder(projekt: str):
|
|
||||||
"""Sagt dem Lauf, was schiefging, ohne ihn abzubrechen.
|
|
||||||
|
|
||||||
Ein Sammellauf ist eine einzige Aufgabe. Flöge er wegen einer Stelle, wären
|
|
||||||
alle anderen Befunde weg — und der Balken stünde trotzdem auf 100 %. So bleibt
|
|
||||||
das Ergebnis erhalten und der Fehlstand steht daneben.
|
|
||||||
"""
|
|
||||||
def melde(text: str, fehler: int = 0):
|
|
||||||
if lauf := _laeufe.get(projekt):
|
|
||||||
lauf["meldung"] = text
|
|
||||||
lauf["fehler"] += fehler
|
|
||||||
return melde
|
|
||||||
|
|
||||||
|
|
||||||
def _platzhalter(anzahl: int) -> list:
|
|
||||||
"""Eine echte Aufgabe, der Rest nur für den Fortschrittsbalken."""
|
|
||||||
return [True] + [None] * max(0, anzahl - 1)
|
|
||||||
|
|
||||||
|
|
||||||
ARTEN = {
|
ARTEN = {
|
||||||
"stil": _stil_aufgaben,
|
"stil": _stil_aufgaben,
|
||||||
"feature": _feature_aufgaben, # Feature im Code: was steht ohne Code da?
|
|
||||||
"code": _code_aufgaben, # Code im Feature: was fehlt auf den Karten?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def _lauf(projekt: str, liste: list[dict], arbeite):
|
async def _lauf(projekt: str, liste: list[dict], arbeite, abschluss=None):
|
||||||
lauf = _laeufe[projekt]
|
lauf = _laeufe[projekt]
|
||||||
schranke = asyncio.Semaphore(config.PRUEFUNG_PARALLEL)
|
schranke = asyncio.Semaphore(config.PRUEFUNG_PARALLEL)
|
||||||
|
|
||||||
@@ -570,6 +547,9 @@ async def _lauf(projekt: str, liste: list[dict], arbeite):
|
|||||||
try:
|
try:
|
||||||
await asyncio.gather(*(eine(a) for a in liste))
|
await asyncio.gather(*(eine(a) for a in liste))
|
||||||
finally:
|
finally:
|
||||||
|
# Auch ein abgebrochener Lauf hat schon Urteile gefällt; die sind bezahlt
|
||||||
|
if abschluss:
|
||||||
|
abschluss()
|
||||||
lauf["laeuft"] = False
|
lauf["laeuft"] = False
|
||||||
|
|
||||||
|
|
||||||
@@ -578,10 +558,10 @@ async def starte(projekt: str, art: str = "stil") -> dict:
|
|||||||
if art not in ARTEN:
|
if art not in ARTEN:
|
||||||
raise ValueError(f"Unbekannte Prüfart „{art}“")
|
raise ValueError(f"Unbekannte Prüfart „{art}“")
|
||||||
if status(projekt)["laeuft"]:
|
if status(projekt)["laeuft"]:
|
||||||
raise ValueError("Prüfung läuft bereits")
|
raise LaeuftBereits("Prüfung läuft bereits")
|
||||||
agents.pruefe_konfiguration() # früher Stopp statt halbem Lauf
|
agents.pruefe_konfiguration() # früher Stopp statt halbem Lauf
|
||||||
# Vorbereiten vor dem Task: ein Fehler darin wäre sonst unsichtbar
|
# Vorbereiten vor dem Task: ein Fehler darin wäre sonst unsichtbar
|
||||||
liste, arbeite = await ARTEN[art](projekt)
|
liste, arbeite, *rest = await ARTEN[art](projekt)
|
||||||
_laeufe[projekt] = {
|
_laeufe[projekt] = {
|
||||||
"laeuft": True,
|
"laeuft": True,
|
||||||
"art": art,
|
"art": art,
|
||||||
@@ -591,7 +571,7 @@ async def starte(projekt: str, art: str = "stil") -> dict:
|
|||||||
"probleme": [],
|
"probleme": [],
|
||||||
"meldung": "",
|
"meldung": "",
|
||||||
}
|
}
|
||||||
aufgabe = asyncio.create_task(_lauf(projekt, liste, arbeite))
|
aufgabe = asyncio.create_task(_lauf(projekt, liste, arbeite, *rest))
|
||||||
_aufgaben.add(aufgabe)
|
_aufgaben.add(aufgabe)
|
||||||
aufgabe.add_done_callback(_aufgaben.discard)
|
aufgabe.add_done_callback(_aufgaben.discard)
|
||||||
return _laeufe[projekt]
|
return _laeufe[projekt]
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
"""Der Quelltext als Prüfgrundlage.
|
|
||||||
|
|
||||||
Die Inhaltsprüfung vergleicht Karten mit dem, was das Programm wirklich kann.
|
|
||||||
Damit das nicht auf Behauptungen hinausläuft, muss jede Aussage über den Code
|
|
||||||
eine wörtlich zitierte Zeile mitbringen — `belegt()` prüft sie nach.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
import config
|
|
||||||
|
|
||||||
# Was zum lauffähigen Programm gehört. Tests beschreiben keine Nutzerfunktionen.
|
|
||||||
MUSTER = (
|
|
||||||
"backend/*.py",
|
|
||||||
"frontend/index.html",
|
|
||||||
"frontend/src/*.js",
|
|
||||||
"frontend/src/**/*.js",
|
|
||||||
"frontend/src/**/*.vue",
|
|
||||||
)
|
|
||||||
AUSGENOMMEN = ("tests/", "/tests/", "node_modules/", "dist/")
|
|
||||||
|
|
||||||
|
|
||||||
def _passt(pfad: str) -> bool:
|
|
||||||
return not any(a in pfad for a in AUSGENOMMEN)
|
|
||||||
|
|
||||||
|
|
||||||
def dateien() -> dict:
|
|
||||||
"""Pfad relativ zur Projektwurzel -> Inhalt, in fester Reihenfolge."""
|
|
||||||
raus = {}
|
|
||||||
for muster in MUSTER:
|
|
||||||
for datei in sorted(config.PROJECT_ROOT.glob(muster)):
|
|
||||||
pfad = str(datei.relative_to(config.PROJECT_ROOT))
|
|
||||||
if datei.is_file() and _passt(pfad):
|
|
||||||
raus[pfad] = datei.read_text()
|
|
||||||
return dict(sorted(raus.items()))
|
|
||||||
|
|
||||||
|
|
||||||
def als_text(quelle: dict = None) -> str:
|
|
||||||
"""Alle Dateien am Stück, jede mit Überschrift — so kann das Modell zitieren."""
|
|
||||||
quelle = dateien() if quelle is None else quelle
|
|
||||||
return "\n\n".join(f"### {pfad}\n{text.rstrip()}" for pfad, text in quelle.items())
|
|
||||||
|
|
||||||
|
|
||||||
def _normal(zeile: str) -> str:
|
|
||||||
return re.sub(r"\s+", " ", zeile).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def belegt(quelle: dict, pfad: str, schnipsel: str) -> bool:
|
|
||||||
"""Steht der Schnipsel wörtlich in der Datei?
|
|
||||||
|
|
||||||
Verglichen wird ohne Rücksicht auf Einrückung — Modelle geben sie ungenau
|
|
||||||
wieder. Alles andere muss stimmen, sonst ist der Beleg wertlos.
|
|
||||||
"""
|
|
||||||
text = quelle.get(pfad)
|
|
||||||
if text is None or not schnipsel.strip():
|
|
||||||
return False
|
|
||||||
gesucht = [_normal(z) for z in schnipsel.splitlines() if z.strip()]
|
|
||||||
if not gesucht:
|
|
||||||
return False
|
|
||||||
vorhanden = [_normal(z) for z in text.splitlines()]
|
|
||||||
# Zusammenhängend: eine mehrzeilige Angabe muss auch mehrzeilig dastehen
|
|
||||||
for i in range(len(vorhanden) - len(gesucht) + 1):
|
|
||||||
if vorhanden[i:i + len(gesucht)] == gesucht:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# Die Stellen, an denen ein Mensch etwas auslöst. Vorher hat ein Modell die
|
|
||||||
# Fähigkeiten frei aufgezählt — jede Runde andere, jede Runde anders benannt.
|
|
||||||
# Hier steht die Liste fest, weil sie aus dem Code fällt.
|
|
||||||
_EREIGNIS = re.compile(r'@(click|auxclick|contextmenu|keyup|keydown|blur|submit|change)'
|
|
||||||
r'[\w.]*\s*=\s*"([^"]*)"')
|
|
||||||
_MENUEPUNKT = re.compile(r"""\{\s*text:\s*['"](.+?)['"]\s*,\s*aktion:""")
|
|
||||||
_ROUTE = re.compile(r'@router\.(get|post|put|delete)\("([^"]+)"\)')
|
|
||||||
_LAUSCHER = re.compile(r'(window|document)\.addEventListener\(\s*[\'"](\w+)[\'"]')
|
|
||||||
_NAME = re.compile(r"[A-Za-zÄÖÜäöü_$][\w$]*")
|
|
||||||
|
|
||||||
# Rein technische Aufhänger: Speichern beim Verlassen, Abbrechen mit Escape.
|
|
||||||
# Sie tragen keine eigene Fähigkeit, sondern gehören zur Bedienung eines Feldes.
|
|
||||||
_UNINTERESSANT = {"fertig", "abbrechen", "blur", "waehle"}
|
|
||||||
# emit('auswaehlen') ist eine Fähigkeit, emit('abbrechen') nicht — es zählt das
|
|
||||||
# Ereignis, nicht der Aufruf. Vorher fiel mit „emit" auch das Auswählen weg.
|
|
||||||
_EMIT = re.compile(r"""emit\(\s*['"](\w+)['"]""")
|
|
||||||
_ZUWEISUNG = re.compile(r"^\s*[\w.]+\s*=[^=]")
|
|
||||||
|
|
||||||
|
|
||||||
def _umfeld(zeilen: list, nr: int, rand: int = 5) -> str:
|
|
||||||
von, bis = max(0, nr - rand - 1), min(len(zeilen), nr + rand)
|
|
||||||
return "\n".join(zeilen[von:bis]).strip()
|
|
||||||
|
|
||||||
|
|
||||||
def _pyrumpf(zeilen: list, nr: int, hoechstens: int = 14) -> str:
|
|
||||||
"""Der eingerückte Block unter einer Zeile — für Python statt geschweifter Klammern."""
|
|
||||||
if nr > len(zeilen):
|
|
||||||
return "" # Dekorator ohne Rumpf, etwa am Dateiende
|
|
||||||
block = [zeilen[nr - 1]]
|
|
||||||
for weiter in zeilen[nr:nr + hoechstens]:
|
|
||||||
if weiter.strip() and not weiter[:1].isspace() and not weiter.startswith("@"):
|
|
||||||
break
|
|
||||||
block.append(weiter)
|
|
||||||
return "\n".join(block).rstrip()
|
|
||||||
|
|
||||||
|
|
||||||
def _rumpf(zeilen: list, name: str, hoechstens: int = 14) -> str:
|
|
||||||
"""Die Umsetzung hinter einem Namen.
|
|
||||||
|
|
||||||
Das Markup allein sagt nur, dass ein Knopf etwas aufruft. Was er tut, steht im
|
|
||||||
Rumpf — ohne den musste der Prüfer raten und urteilte jede Runde anders.
|
|
||||||
"""
|
|
||||||
anfang = re.compile(rf"^\s*(async\s+)?function\s+{re.escape(name)}\s*\(|"
|
|
||||||
rf"^\s*const\s+{re.escape(name)}\s*=")
|
|
||||||
for i, zeile in enumerate(zeilen):
|
|
||||||
if not anfang.match(zeile):
|
|
||||||
continue
|
|
||||||
block = [zeile]
|
|
||||||
for weiter in zeilen[i + 1:i + hoechstens]:
|
|
||||||
block.append(weiter)
|
|
||||||
if weiter.startswith("}"):
|
|
||||||
break
|
|
||||||
return "\n".join(block)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def interaktionen(quelle: dict = None) -> list[dict]:
|
|
||||||
"""Auslösbare Stellen im Code, eine je Bedienhandlung.
|
|
||||||
|
|
||||||
Zusammengefasst nach dem aufgerufenen Namen: `@click="starte('ziel')"` steht
|
|
||||||
zwölfmal im Markup, ist aber eine Fähigkeit.
|
|
||||||
|
|
||||||
Dazu Fenster-Lauscher: ein Tastaturgriff ist eine Fähigkeit, auch ohne Knopf.
|
|
||||||
|
|
||||||
Dazu die Backend-Routen. Sie standen zwischenzeitlich nicht drin, weil sie
|
|
||||||
doppelt gemeldet und dabei der falschen Karte zugeordnet wurden. Beides ist
|
|
||||||
jetzt aufgefangen: das Gedächtnis hält die Zuordnung fest, und der
|
|
||||||
Dublettenabgleich fängt zwei Funktionen für dieselbe Sache. Was hinter einem
|
|
||||||
Knopf liegt, ist der einzige Weg an Parser, Dateiverwaltung und Prüflogik —
|
|
||||||
ohne die Routen bliebe der ganze Rücken des Programms ungeprüft.
|
|
||||||
|
|
||||||
Reine Hilfsfunktionen bleiben draußen. Ein Kunde bestellt keine Funktion namens
|
|
||||||
`_slug`; sie wird über die Route erreicht, die ihn interessiert. Jedes
|
|
||||||
öffentliche Symbol aufzunehmen ergäbe wieder ein Code-Inventar statt einer
|
|
||||||
Auftragsliste.
|
|
||||||
"""
|
|
||||||
quelle = dateien() if quelle is None else quelle
|
|
||||||
raus, gesehen = [], set()
|
|
||||||
|
|
||||||
def merke(schluessel, art, pfad, nr, zeilen, beleg):
|
|
||||||
if schluessel in gesehen:
|
|
||||||
return None
|
|
||||||
gesehen.add(schluessel)
|
|
||||||
punkt = {"schluessel": schluessel, "art": art, "datei": pfad, "zeile": nr,
|
|
||||||
"beleg": beleg.strip(), "umfeld": _umfeld(zeilen, nr), "rumpf": ""}
|
|
||||||
raus.append(punkt)
|
|
||||||
return punkt
|
|
||||||
|
|
||||||
for pfad, text in quelle.items():
|
|
||||||
zeilen = text.splitlines()
|
|
||||||
for nr, zeile in enumerate(zeilen, 1):
|
|
||||||
for m in _MENUEPUNKT.finditer(zeile):
|
|
||||||
merke(f"menue:{m.group(1)}", "Menüpunkt", pfad, nr, zeilen, zeile)
|
|
||||||
for m in _LAUSCHER.finditer(zeile):
|
|
||||||
merke(f"lauscher:{m.group(1)}.{m.group(2)}", "Tastatur oder Maus",
|
|
||||||
pfad, nr, zeilen, zeile)
|
|
||||||
for m in _ROUTE.finditer(zeile):
|
|
||||||
punkt = merke(f"route:{m.group(1)} {m.group(2)}", "Schnittstelle",
|
|
||||||
pfad, nr, zeilen, zeile)
|
|
||||||
if punkt is not None:
|
|
||||||
punkt["rumpf"] = _pyrumpf(zeilen, nr + 1)
|
|
||||||
if not pfad.endswith(".vue"):
|
|
||||||
continue
|
|
||||||
for m in _EREIGNIS.finditer(zeile):
|
|
||||||
ausdruck = m.group(2)
|
|
||||||
if _ZUWEISUNG.match(ausdruck) or ausdruck.startswith("$"):
|
|
||||||
continue # Zuweisung oder Ereignis-Objekt, keine Fähigkeit
|
|
||||||
treffer = _EMIT.search(ausdruck) or _NAME.search(ausdruck)
|
|
||||||
if not treffer or treffer.group(1 if _EMIT.search(ausdruck) else 0) \
|
|
||||||
in _UNINTERESSANT:
|
|
||||||
continue
|
|
||||||
name = treffer.group(1) if _EMIT.search(ausdruck) else treffer.group(0)
|
|
||||||
punkt = merke(f"bedienen:{name}", "Bedienung", pfad, nr, zeilen, zeile)
|
|
||||||
if punkt is not None:
|
|
||||||
punkt["rumpf"] = _rumpf(zeilen, name)
|
|
||||||
return raus
|
|
||||||
|
|
||||||
|
|
||||||
def uebersicht(quelle: dict = None) -> str:
|
|
||||||
"""Kurze Dateiliste mit Zeilenzahl — für Prompts, die den Code nicht brauchen."""
|
|
||||||
quelle = dateien() if quelle is None else quelle
|
|
||||||
return "\n".join(f"- {p} ({len(t.splitlines())} Zeilen)" for p, t in quelle.items())
|
|
||||||
@@ -4,8 +4,10 @@ hier wird auf 400/404/409 gemappt."""
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
import agents
|
import agents
|
||||||
|
import chat
|
||||||
import features
|
import features
|
||||||
import projekte
|
import projekte
|
||||||
|
import planung
|
||||||
import pruefung
|
import pruefung
|
||||||
|
|
||||||
router = APIRouter(prefix="/api")
|
router = APIRouter(prefix="/api")
|
||||||
@@ -55,9 +57,13 @@ async def projekt_loeschen(name: str):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/projekte/{name}/features")
|
@router.get("/projekte/{name}/features")
|
||||||
async def features_liste(name: str):
|
async def features_liste(name: str, sicht: str = "planung"):
|
||||||
|
"""`sicht=uebersicht` liest den Projektstand, sonst die Arbeitskopie.
|
||||||
|
|
||||||
|
Ohne laufende Planung sind beide dasselbe Verzeichnis.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return features.liste(name)
|
return features.liste(name, nur_stand=sicht == "uebersicht")
|
||||||
except (FileNotFoundError, ValueError) as e:
|
except (FileNotFoundError, ValueError) as e:
|
||||||
raise HTTPException(404, str(e))
|
raise HTTPException(404, str(e))
|
||||||
|
|
||||||
@@ -89,10 +95,10 @@ async def pruefung_starten(name: str, body: dict | None = None):
|
|||||||
return await pruefung.starte(name, art)
|
return await pruefung.starte(name, art)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
raise HTTPException(404, str(e))
|
raise HTTPException(404, str(e))
|
||||||
except agents.NichtKonfiguriert as e:
|
except pruefung.LaeuftBereits as e:
|
||||||
raise HTTPException(400, str(e)) # 409 heißt „läuft bereits" — hier fehlt die .env
|
raise HTTPException(409, str(e)) # 409 heißt genau das und sonst nichts
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise HTTPException(409, str(e))
|
raise HTTPException(400, str(e))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/projekte/{name}/pruefung")
|
@router.get("/projekte/{name}/pruefung")
|
||||||
@@ -100,6 +106,56 @@ async def pruefung_status(name: str):
|
|||||||
return pruefung.status(name)
|
return pruefung.status(name)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/projekte/{name}/planung")
|
||||||
|
async def planung_stand(name: str):
|
||||||
|
try:
|
||||||
|
return planung.stand(name)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/projekte/{name}/planung")
|
||||||
|
async def planung_starten(name: str):
|
||||||
|
try:
|
||||||
|
planung.starte(name)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(409, str(e))
|
||||||
|
return planung.stand(name)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/projekte/{name}/planung/umsetzen")
|
||||||
|
async def planung_umsetzen(name: str):
|
||||||
|
try:
|
||||||
|
planung.setze_um(name)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(409, str(e))
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/projekte/{name}/planung")
|
||||||
|
async def planung_abbrechen(name: str):
|
||||||
|
try:
|
||||||
|
planung.brich_ab(name)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/projekte/{name}/planung/zuruecksetzen")
|
||||||
|
async def planung_zuruecksetzen(name: str, body: dict):
|
||||||
|
try:
|
||||||
|
planung.zuruecksetzen(name, (body or {}).get("titel") or "")
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(409, str(e))
|
||||||
|
return planung.stand(name)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/projekte/{name}/pruefung")
|
@router.delete("/projekte/{name}/pruefung")
|
||||||
async def pruefung_beenden(name: str):
|
async def pruefung_beenden(name: str):
|
||||||
pruefung.beende(name)
|
pruefung.beende(name)
|
||||||
@@ -113,3 +169,42 @@ async def feature_loeschen(name: str, id: str):
|
|||||||
except (FileNotFoundError, ValueError) as e:
|
except (FileNotFoundError, ValueError) as e:
|
||||||
raise HTTPException(404, str(e))
|
raise HTTPException(404, str(e))
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/projekte/{name}/features/{id}/projekt")
|
||||||
|
async def feature_verschieben(name: str, id: str, body: dict):
|
||||||
|
try:
|
||||||
|
return features.verschieben(name, id, (body.get("ziel") or "").strip())
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(400, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/projekte/{name}/chat")
|
||||||
|
async def chat_verlauf(name: str):
|
||||||
|
try:
|
||||||
|
return {"verlauf": chat.verlauf(name)}
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/projekte/{name}/chat")
|
||||||
|
async def chat_frage(name: str, body: dict):
|
||||||
|
try:
|
||||||
|
return await chat.frage(name, body.get("text") or "")
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
except agents.NichtKonfiguriert as e:
|
||||||
|
raise HTTPException(400, str(e))
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(400, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/projekte/{name}/chat")
|
||||||
|
async def chat_leeren(name: str):
|
||||||
|
try:
|
||||||
|
chat.leeren(name)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(404, str(e))
|
||||||
|
return {"ok": True}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ Gilt für jedes Feld.
|
|||||||
Diese Vorgaben prüft der Code selbst, exakt und für jede Zeile. Halte dich beim
|
Diese Vorgaben prüft der Code selbst, exakt und für jede Zeile. Halte dich beim
|
||||||
Schreiben daran — aber berufe dich nie darauf. Als `regel` sind sie nicht zulässig.
|
Schreiben daran — aber berufe dich nie darauf. Als `regel` sind sie nicht zulässig.
|
||||||
|
|
||||||
|
- Jede Funktion hat Ziel, Regeln oder Testschritte — nie nur einen Titel
|
||||||
|
- Titel einer Funktion endet auf ein Verb im Infinitiv
|
||||||
|
- Testschritte beschreiben genau einen Ablauf
|
||||||
- Kein Satzzeichen am Zeilenende, kein Punkt und kein Semikolon
|
- Kein Satzzeichen am Zeilenende, kein Punkt und kein Semikolon
|
||||||
- Keine Füllwörter. Die Liste ist abschließend: auch, noch, einfach, jeweils, ggf.
|
- Keine Füllwörter. Die Liste ist abschließend: auch, noch, einfach, jeweils, ggf.
|
||||||
- Keine Klammern und keine Schrägstriche
|
- Keine Klammern und keine Schrägstriche
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
Feld `testen`. Gibt es nur bei Funktionen, nie auf Kartenebene.
|
Feld `testen`. Gibt es nur bei Funktionen, nie auf Kartenebene.
|
||||||
|
|
||||||
- Eine Zeile je Schritt
|
- Eine Zeile je Schritt
|
||||||
|
- Genau ein Ablauf: handeln, handeln, Ergebnis
|
||||||
|
- Nach dem Ergebnis folgt keine Handlung mehr
|
||||||
- Schritte in Reihenfolge der Bedienung
|
- Schritte in Reihenfolge der Bedienung
|
||||||
- Kein Schritt der Bedienung fehlt
|
- Kein Schritt der Bedienung fehlt
|
||||||
- Letzte Zeile beschreibt den Endzustand, keine Handlung
|
- Letzte Zeile beschreibt den Endzustand, keine Handlung
|
||||||
@@ -16,3 +18,12 @@ Richtig:
|
|||||||
- Funktion entfernt
|
- Funktion entfernt
|
||||||
```
|
```
|
||||||
Falsch: letzte Zeile `Klick zum entfernen` (Handlung statt Endzustand)
|
Falsch: letzte Zeile `Klick zum entfernen` (Handlung statt Endzustand)
|
||||||
|
|
||||||
|
Falsch, weil zwei Abläufe in einer Funktion stehen:
|
||||||
|
```
|
||||||
|
- Klick auf Miniaturbild
|
||||||
|
- Großansicht öffnet und schließt
|
||||||
|
- Option aus Großansicht auswählbar
|
||||||
|
```
|
||||||
|
Jede Zeile ist hier ein eigenes Ergebnis, kein Schritt eines Durchlaufs. Solche
|
||||||
|
Funktionen sind mehrere und gehören getrennt.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ Feld `titel`, wenn `funktion` gesetzt ist.
|
|||||||
|
|
||||||
- Kurzsatz aus Objekt und Verb im Infinitiv
|
- Kurzsatz aus Objekt und Verb im Infinitiv
|
||||||
- Genau eine Tätigkeit
|
- Genau eine Tätigkeit
|
||||||
|
- Endet auf ein Verb im Infinitiv
|
||||||
|
- Nennt eine Handlung, keinen Bereich, keinen Zustand
|
||||||
|
|
||||||
Richtig: `Feature erstellen` · `Anzeigemodus umschalten` · `Feature als Datei speichern`
|
Richtig: `Feature erstellen` · `Anzeigemodus umschalten` · `Feature als Datei speichern`
|
||||||
Falsch: `Erstellung von Features` (Substantivierung) · `Feature erstellen und öffnen` (zwei Tätigkeiten)
|
Falsch: `Erstellung von Features` (Substantivierung) · `Feature erstellen und öffnen` (zwei Tätigkeiten) · `Galerie im Modal` (ein Bereich) · `Preis aktualisiert` (ein Zustand) · `In den Warenkorb` (kein Verb)
|
||||||
|
|||||||
@@ -29,8 +29,21 @@ BADGE = ("kern", "optional")
|
|||||||
# „funktionen" gehört der Inhaltsprüfung, hat aber dieselbe Gestalt.
|
# „funktionen" gehört der Inhaltsprüfung, hat aber dieselbe Gestalt.
|
||||||
ZEILENFELDER = ("regeln", "testen", "funktionen")
|
ZEILENFELDER = ("regeln", "testen", "funktionen")
|
||||||
|
|
||||||
|
# Ein Funktionstitel nennt eine Handlung. Ohne Verb am Ende steht dort ein Bereich
|
||||||
|
# („Galerie im Modal") oder ein Zustand („Preis aktualisiert") — beides beschreibt
|
||||||
|
# nichts, was man für sich testen könnte. Deutsche Infinitive enden auf -en, -ln,
|
||||||
|
# -rn; wie Füllwörter und Wortgrenzen ist auch das an die Sprache gebunden.
|
||||||
|
_INFINITIV = re.compile(r"(?:e[lr]?n|ln|rn)$", re.I)
|
||||||
|
# Eine Handlung steht als Infinitiv am Anfang oder am Ende der Zeile („Bestätigen im
|
||||||
|
# Modal", „Text ändern") oder beginnt mit einer Taste. Alles andere ist ein Ergebnis.
|
||||||
|
# Nur Anfang und Ende, nicht irgendwo: deutsche Mehrzahl endet auch auf -en.
|
||||||
|
_TASTE = re.compile(r"^(?:LMB|RMB|MMB|Klick|Doppelklick|Enter|Escape|Strg|Tab)\b", re.I)
|
||||||
|
|
||||||
SATZENDE = ".;"
|
SATZENDE = ".;"
|
||||||
_KLAMMER = re.compile(r"[()/]")
|
_KLAMMER = re.compile(r"[()/]")
|
||||||
|
# Eine Vorlage ist kein Inhalt. Das Modell schlug `<testschritt>` als Testschritt
|
||||||
|
# vor — formal richtig, gemeint war „hier fehlt noch etwas".
|
||||||
|
_LUECKE = re.compile(r"<[^<>]*>|\.\.\.|\bTODO\b|\bXXX\b", re.I)
|
||||||
_STRICH = re.compile(r"^[-*]\s*")
|
_STRICH = re.compile(r"^[-*]\s*")
|
||||||
|
|
||||||
|
|
||||||
@@ -68,9 +81,15 @@ def _umschreiben(bereich: str, feld: str, zeile: str) -> list[str]:
|
|||||||
gruende = []
|
gruende = []
|
||||||
if _KLAMMER.search(zeile):
|
if _KLAMMER.search(zeile):
|
||||||
gruende.append("Klammer oder Schrägstrich")
|
gruende.append("Klammer oder Schrägstrich")
|
||||||
|
if _LUECKE.search(zeile):
|
||||||
|
gruende.append("Platzhalter statt Inhalt")
|
||||||
grenze = WORTGRENZEN.get((bereich, feld))
|
grenze = WORTGRENZEN.get((bereich, feld))
|
||||||
if grenze and len(zeile.split()) > grenze:
|
if grenze and len(zeile.split()) > grenze:
|
||||||
gruende.append(f"Mehr als {grenze} Wörter")
|
gruende.append(f"Mehr als {grenze} Wörter")
|
||||||
|
if bereich == "funktion" and feld == "titel":
|
||||||
|
woerter = zeile.split()
|
||||||
|
if woerter and not _INFINITIV.search(woerter[-1]):
|
||||||
|
gruende.append("Kein Verb im Infinitiv am Ende")
|
||||||
if feld == "ziel":
|
if feld == "ziel":
|
||||||
saetze = [s.strip() for s in re.split(r"[.!?]+", zeile) if s.strip()]
|
saetze = [s.strip() for s in re.split(r"[.!?]+", zeile) if s.strip()]
|
||||||
if len(saetze) > ZIEL_SAETZE:
|
if len(saetze) > ZIEL_SAETZE:
|
||||||
@@ -80,6 +99,30 @@ def _umschreiben(bereich: str, feld: str, zeile: str) -> list[str]:
|
|||||||
return gruende
|
return gruende
|
||||||
|
|
||||||
|
|
||||||
|
def _handlung(zeile: str) -> bool:
|
||||||
|
woerter = zeile.split()
|
||||||
|
if not woerter:
|
||||||
|
return True
|
||||||
|
return bool(_TASTE.match(zeile) or _INFINITIV.search(woerter[-1])
|
||||||
|
or _INFINITIV.search(woerter[0]))
|
||||||
|
|
||||||
|
|
||||||
|
def _zweiter_ablauf(zeilen: list[str]) -> str:
|
||||||
|
"""Die Zeile, ab der ein zweiter Durchlauf beginnt — sonst leer.
|
||||||
|
|
||||||
|
Ein Ablauf ist: handeln, handeln, Ergebnis. Steht nach dem Ergebnis wieder eine
|
||||||
|
Handlung, sind es zwei Abläufe — und damit zwei Funktionen. Genau so entstand
|
||||||
|
„Bildergalerie bedienen" mit blättern, Miniatur wählen und Großansicht in einem.
|
||||||
|
"""
|
||||||
|
ergebnis = False
|
||||||
|
for zeile in zeilen:
|
||||||
|
if ergebnis and _handlung(zeile):
|
||||||
|
return zeile
|
||||||
|
if not _handlung(zeile):
|
||||||
|
ergebnis = True
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _zeile(bereich: str, feld: str, zeile: str) -> list[dict]:
|
def _zeile(bereich: str, feld: str, zeile: str) -> list[dict]:
|
||||||
"""Höchstens ein Befund je Zeile. Zwei Vorschläge für dieselbe Zeile wären nicht
|
"""Höchstens ein Befund je Zeile. Zwei Vorschläge für dieselbe Zeile wären nicht
|
||||||
unabhängig — der zweite fände seine Stelle nach dem ersten nicht mehr."""
|
unabhängig — der zweite fände seine Stelle nach dem ersten nicht mehr."""
|
||||||
@@ -94,13 +137,28 @@ def _zeile(bereich: str, feld: str, zeile: str) -> list[dict]:
|
|||||||
def befunde(bereich: str, felder: dict) -> list[dict]:
|
def befunde(bereich: str, felder: dict) -> list[dict]:
|
||||||
"""Alle messbaren Verstöße einer Prüfstelle, in Reihenfolge der Felder."""
|
"""Alle messbaren Verstöße einer Prüfstelle, in Reihenfolge der Felder."""
|
||||||
raus = []
|
raus = []
|
||||||
|
# Ein bloßer Titel beschreibt nichts — und lässt sich über alles stülpen. Der
|
||||||
|
# Prüfer hielt damit einen Druckknopf für „Modal mit Tastatur steuern".
|
||||||
|
# Nur wenn die ganze Funktion vorliegt: bei einem einzelnen Feld sagt das Fehlen
|
||||||
|
# der anderen nichts.
|
||||||
|
inhaltsfelder = ("ziel", "regeln", "testen")
|
||||||
|
if bereich == "funktion" and all(f in felder for f in inhaltsfelder) \
|
||||||
|
and not any((felder.get(f) or "").strip() for f in inhaltsfelder):
|
||||||
|
raus.append(_problem("testen", "", "Funktion ohne Ziel, Regeln und Testen"))
|
||||||
|
return raus
|
||||||
for feld, text in felder.items():
|
for feld, text in felder.items():
|
||||||
if feld == "badge":
|
if feld == "badge":
|
||||||
if text and text not in BADGE:
|
if text and text not in BADGE:
|
||||||
raus.append(_problem(feld, text, "Weder kern noch optional", "kern"))
|
raus.append(_problem(feld, text, "Weder kern noch optional", "kern"))
|
||||||
continue
|
continue
|
||||||
for zeile in _zeilen(feld, text or ""):
|
zeilen = _zeilen(feld, text or "")
|
||||||
|
for zeile in zeilen:
|
||||||
raus += _zeile(bereich, feld, zeile)
|
raus += _zeile(bereich, feld, zeile)
|
||||||
|
if feld == "testen" and not raus:
|
||||||
|
# Erst prüfen, wenn die Zeilen für sich in Ordnung sind: sonst hängen
|
||||||
|
# zwei Vorschläge am selben Feld und der zweite findet seine Stelle nicht.
|
||||||
|
if bruch := _zweiter_ablauf(zeilen):
|
||||||
|
raus.append(_problem(feld, bruch, "Zweiter Ablauf in einer Funktion"))
|
||||||
return raus
|
return raus
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import inhalt
|
|
||||||
import quellcode
|
|
||||||
|
|
||||||
|
|
||||||
def test_unformatierte_prompts_ohne_doppelklammern():
|
|
||||||
"""Was nicht durch .format() geht, darf keine Escape-Klammern tragen —
|
|
||||||
das Modell schrieb sie sonst wörtlich in die Antwort und das JSON war kaputt."""
|
|
||||||
for name in ("ABDECKUNG_SYSTEM",):
|
|
||||||
text = getattr(inhalt, name)
|
|
||||||
assert "{{" not in text and "}}" not in text, name
|
|
||||||
|
|
||||||
|
|
||||||
def test_formatierte_prompts_ueberstehen_format():
|
|
||||||
punkt = {"art": "Bedienung", "datei": "a.vue", "zeile": 1,
|
|
||||||
"umfeld": "x", "rumpf": ""}
|
|
||||||
fertig = inhalt.ABDECKUNG.format(funktionen="—", **punkt)
|
|
||||||
assert '{"abgedeckt": true' in fertig
|
|
||||||
assert "{{" not in fertig
|
|
||||||
|
|
||||||
|
|
||||||
def test_beleg_muss_woertlich_dastehen():
|
|
||||||
quelle = {"a.py": "def eins():\n return 1\n"}
|
|
||||||
assert quellcode.belegt(quelle, "a.py", "def eins():")
|
|
||||||
assert quellcode.belegt(quelle, "a.py", " def eins(): ") # Einrückung egal
|
|
||||||
assert quellcode.belegt(quelle, "a.py", "def eins():\nreturn 1")
|
|
||||||
assert not quellcode.belegt(quelle, "a.py", "def zwei():")
|
|
||||||
assert not quellcode.belegt(quelle, "b.py", "def eins():")
|
|
||||||
assert not quellcode.belegt(quelle, "a.py", "")
|
|
||||||
|
|
||||||
|
|
||||||
def test_interaktionen_sind_deterministisch():
|
|
||||||
quelle = {
|
|
||||||
"frontend/src/X.vue": (
|
|
||||||
'<button @click="starte(\'ziel\')">a</button>\n'
|
|
||||||
'<button @click="starte(\'regeln\')">b</button>\n'
|
|
||||||
'<div @contextmenu="menue">c</div>\n'
|
|
||||||
'<input @blur="fertig" @keydown.esc="abbrechen">\n'
|
|
||||||
'<span @click="offen = false">zu</span>\n'
|
|
||||||
),
|
|
||||||
"backend/routes.py": '@router.get("/projekte")\n@router.post("/projekte")\n',
|
|
||||||
"frontend/src/m.js": " { text: '+ Projekt erstellen', aktion: () => x() },\n",
|
|
||||||
}
|
|
||||||
schluessel = [p["schluessel"] for p in quellcode.interaktionen(quelle)]
|
|
||||||
# Zwölf Aufrufe von starte() sind eine Fähigkeit, nicht zwölf
|
|
||||||
assert schluessel.count("bedienen:starte") == 1
|
|
||||||
assert "bedienen:menue" in schluessel
|
|
||||||
# Routen: der einzige Weg an Parser, Dateiverwaltung und Prüflogik
|
|
||||||
assert "route:get /projekte" in schluessel
|
|
||||||
assert "route:post /projekte" in schluessel
|
|
||||||
assert "menue:+ Projekt erstellen" in schluessel
|
|
||||||
# Technische Aufhänger und Zuweisungen sind keine Fähigkeiten
|
|
||||||
assert not any(s.startswith("bedienen:fertig") for s in schluessel)
|
|
||||||
assert not any(s.startswith("bedienen:abbrechen") for s in schluessel)
|
|
||||||
assert not any(s.startswith("bedienen:offen") for s in schluessel)
|
|
||||||
# Zweiter Aufruf liefert exakt dasselbe
|
|
||||||
assert schluessel == [p["schluessel"] for p in quellcode.interaktionen(quelle)]
|
|
||||||
|
|
||||||
|
|
||||||
def test_tests_sind_kein_quelltext():
|
|
||||||
assert not any("tests/" in p for p in quellcode.dateien())
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Das Gedächtnis der Inhaltsprüfungen: was gilt weiter, was wird neu gefragt.
|
"""Das Gedächtnis der Stilprüfung: was gilt weiter, was wird neu gefragt.
|
||||||
|
|
||||||
Die Prüfungen 2 und 3 fragen ein Modell. Dieselbe Frage kippt bei Grenzfällen auch
|
Die Prüfung fragt ein Modell. Dieselbe Frage kippt bei Grenzfällen auch bei
|
||||||
bei Temperatur 0 — vorher meldete Runde 1 einen Befund, Runde 2 keinen, Runde 3
|
Temperatur 0 — vorher meldete Runde 1 einen Befund, Runde 2 keinen, Runde 3 wieder
|
||||||
wieder einen. Hier steht, unter welchen Bedingungen ein Urteil stehen bleibt.
|
einen. Hier steht, unter welchen Bedingungen ein Urteil stehen bleibt.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -11,7 +11,6 @@ import pytest
|
|||||||
|
|
||||||
import agents
|
import agents
|
||||||
import features
|
import features
|
||||||
import inhalt
|
|
||||||
import ledger
|
import ledger
|
||||||
import projekte
|
import projekte
|
||||||
|
|
||||||
@@ -33,9 +32,6 @@ def karte(projekt, titel, *funktionen):
|
|||||||
return features.anlegen(projekt, text)
|
return features.anlegen(projekt, text)
|
||||||
|
|
||||||
|
|
||||||
QUELLE = {"frontend/src/App.vue": '<button @click="starte">los</button>\n'}
|
|
||||||
|
|
||||||
|
|
||||||
class Zaehler:
|
class Zaehler:
|
||||||
"""Ersetzt das Modell und zählt mit, wie oft überhaupt gefragt wurde."""
|
"""Ersetzt das Modell und zählt mit, wie oft überhaupt gefragt wurde."""
|
||||||
|
|
||||||
@@ -82,431 +78,6 @@ def test_gedaechtnis_ist_kein_feature(projekt):
|
|||||||
assert [f["sektionen"]["titel"] for f in features.liste(projekt)] == ["Eins"]
|
assert [f["sektionen"]["titel"] for f in features.liste(projekt)] == ["Eins"]
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Prüfung 2
|
|
||||||
|
|
||||||
GEFUNDEN = json.dumps({"gefunden": True, "datei": "frontend/src/App.vue",
|
|
||||||
"beleg": '<button @click="starte">los</button>'})
|
|
||||||
|
|
||||||
|
|
||||||
async def test_beleg_wird_gemerkt_und_beim_zweiten_lauf_nicht_gefragt(
|
|
||||||
projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(GEFUNDEN)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
|
||||||
assert await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text) == []
|
|
||||||
erste_runde = modell.rufe
|
|
||||||
assert erste_runde >= len(inhalt.SUCHE_TEMPERATUREN)
|
|
||||||
|
|
||||||
assert await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text) == []
|
|
||||||
assert modell.rufe == erste_runde # keine einzige neue Anfrage
|
|
||||||
|
|
||||||
|
|
||||||
async def test_beleg_verfaellt_wenn_die_zeile_verschwindet(projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(GEFUNDEN)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
|
||||||
await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text)
|
|
||||||
vorher = modell.rufe
|
|
||||||
|
|
||||||
anders = {"frontend/src/App.vue": "<div>nichts mehr</div>\n"}
|
|
||||||
await inhalt.pruefe_feature_im_code(projekt, alle, anders, "### x\n<div>")
|
|
||||||
assert modell.rufe > vorher # der Beleg trägt nicht mehr, also neu fragen
|
|
||||||
|
|
||||||
|
|
||||||
async def test_geaenderte_funktion_wird_neu_gefragt(projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(GEFUNDEN)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
|
||||||
await inhalt.pruefe_feature_im_code(projekt, features.liste(projekt), QUELLE, text)
|
|
||||||
vorher = modell.rufe
|
|
||||||
|
|
||||||
datei = projekte.wurzel(projekt) / f"{features.liste(projekt)[0]['id']}.md"
|
|
||||||
datei.write_text(datei.read_text().replace("- Knopf", "- Ganz anderer Inhalt"))
|
|
||||||
await inhalt.pruefe_feature_im_code(projekt, features.liste(projekt), QUELLE, text)
|
|
||||||
assert modell.rufe > vorher
|
|
||||||
|
|
||||||
|
|
||||||
async def test_fehlanzeige_gilt_nur_bei_unveraendertem_quelltext(projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(json.dumps({"gefunden": False, "grund": "gibt es nicht"}))
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
|
||||||
erste = await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text)
|
|
||||||
assert len(erste) == 1
|
|
||||||
vorher = modell.rufe
|
|
||||||
|
|
||||||
zweite = await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text)
|
|
||||||
assert [p["vorschlag"] for p in zweite] == [p["vorschlag"] for p in erste]
|
|
||||||
assert modell.rufe == vorher
|
|
||||||
|
|
||||||
# Neuer Quelltext: eine Löschempfehlung muss frisch belegt werden
|
|
||||||
mehr = {**QUELLE, "backend/neu.py": "def starte():\n pass\n"}
|
|
||||||
await inhalt.pruefe_feature_im_code(projekt, alle, mehr, text)
|
|
||||||
assert modell.rufe > vorher
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Dubletten
|
|
||||||
|
|
||||||
def punkt(schluessel="bedienen:starte", beleg='<button @click="starte">los</button>'):
|
|
||||||
return {"schluessel": schluessel, "art": "Bedienung",
|
|
||||||
"datei": "frontend/src/App.vue", "zeile": 1, "beleg": beleg,
|
|
||||||
"umfeld": beleg, "rumpf": ""}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_gleicher_beleg_macht_zwei_funktionen_zu_kandidaten(projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
karte(projekt, "Zwei", ("Lauf beginnen", "Knopf"))
|
|
||||||
monkeypatch.setattr(agents, "frage", Zaehler(json.dumps({"gegenstand_gleich": True, "ergebnis_gleich": True})))
|
|
||||||
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
for f in alle:
|
|
||||||
stand["belege"][ledger.schluessel(f["id"], f["sektionen"]["funktionen"][0]["text"])] = {
|
|
||||||
"abdruck": "egal", "datei": "frontend/src/App.vue",
|
|
||||||
"beleg": '<button @click="starte">los</button>'}
|
|
||||||
gefunden = await inhalt.dubletten(alle, stand)
|
|
||||||
assert len(gefunden) == 1
|
|
||||||
# Gleich stark: der Titel entscheidet, damit der Vorschlag nicht springt
|
|
||||||
assert gefunden[0]["titel"] == "Etwas starten"
|
|
||||||
assert "Lauf beginnen" in gefunden[0]["grund"]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_uneiniges_urteil_ist_keine_dublette_und_wird_gemerkt(
|
|
||||||
projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
karte(projekt, "Zwei", ("Lauf beginnen", "Knopf"))
|
|
||||||
|
|
||||||
def wechselhaft(nachricht, ruf):
|
|
||||||
return json.dumps({"gegenstand_gleich": True, "ergebnis_gleich": ruf == 1})
|
|
||||||
|
|
||||||
modell = Zaehler(wechselhaft)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
for f in alle:
|
|
||||||
stand["belege"][ledger.schluessel(f["id"], f["sektionen"]["funktionen"][0]["text"])] = {
|
|
||||||
"abdruck": "egal", "datei": "a.vue", "beleg": "x"}
|
|
||||||
assert await inhalt.dubletten(alle, stand) == []
|
|
||||||
vorher = modell.rufe
|
|
||||||
assert await inhalt.dubletten(alle, stand) == []
|
|
||||||
assert modell.rufe == vorher # Ablehnung wird gemerkt, nicht neu bezahlt
|
|
||||||
|
|
||||||
|
|
||||||
def test_gegenstueck_faellt_nicht_ueber_den_titel():
|
|
||||||
"""anlegen/entfernen und öffnen/schließen dürfen den Titelkanal nicht auslösen —
|
|
||||||
sonst kostet jedes Gegensatzpaar drei Anfragen für nichts."""
|
|
||||||
for a, b in (("Feature anlegen", "Feature entfernen"),
|
|
||||||
("Panel öffnen", "Panel schließen")):
|
|
||||||
assert inhalt._aehnlich(a, b) < inhalt.TITEL_KANAL
|
|
||||||
|
|
||||||
|
|
||||||
ENTFERNEN = ["Vollmodus öffnen", "RMB auf die Funktion",
|
|
||||||
'Klick auf "Funktion entfernen"', "Funktion entfernt"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_umschriebene_dublette_wird_kandidat(projekt):
|
|
||||||
"""Anderer Titel, andere Karte, dieselbe Sache. Weder Titel noch Beleg fangen
|
|
||||||
das — nur der Inhaltskanal."""
|
|
||||||
karte(projekt, "Funktionen",
|
|
||||||
("Funktion entfernen", "x-Icon", ENTFERNEN),
|
|
||||||
("Funktion anlegen", "+-Icon", ["Vollmodus öffnen", "RMB auf die Karte",
|
|
||||||
'Klick auf "Funktion erstellen"',
|
|
||||||
"Funktion angelegt"]),
|
|
||||||
("Testschritte anpassen", "Ein Schritt je Zeile",
|
|
||||||
["Vollmodus öffnen", "LMB auf Testen", "Text ändern", "Schritte angepasst"]))
|
|
||||||
karte(projekt, "Features", ("Funktion löschen", "x-Icon", ENTFERNEN))
|
|
||||||
|
|
||||||
alle = inhalt._funktionen(features.liste(projekt))
|
|
||||||
paare = {frozenset((alle[a]["titel"], alle[b]["titel"])) for _, a, b, _ in
|
|
||||||
inhalt._kandidaten(alle, {})}
|
|
||||||
assert frozenset(("Funktion entfernen", "Funktion löschen")) in paare
|
|
||||||
assert frozenset(("Funktion anlegen", "Funktion entfernen")) not in paare
|
|
||||||
|
|
||||||
|
|
||||||
def test_floskeln_zaehlen_nicht_mit(projekt):
|
|
||||||
"""Was fast überall steht, unterscheidet nichts und fliegt aus dem Vergleich."""
|
|
||||||
schritte = ["Vollmodus öffnen", "Text ändern", "Angepasst"]
|
|
||||||
karte(projekt, "Eins",
|
|
||||||
*[(f"{feld} anpassen", "eine Regel", schritte)
|
|
||||||
for feld in ("Titel", "Ziel", "Regeln", "Testen", "Badge")])
|
|
||||||
kerne = inhalt._kerne(inhalt._funktionen(features.liste(projekt)))
|
|
||||||
gemeinsam = set.intersection(*kerne.values())
|
|
||||||
assert gemeinsam == set() # „vollmodus", „ändern", „angepasst" sind raus
|
|
||||||
assert all(k for k in kerne.values()) # jede Funktion behält ihr eigenes Wort
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Prüfung 3
|
|
||||||
|
|
||||||
ABGEDECKT = json.dumps({"abgedeckt": True, "funktion": "Etwas starten"})
|
|
||||||
FEHLT = json.dumps({"abgedeckt": False, "karte": "Eins", "titel": "Neu bedienen"})
|
|
||||||
|
|
||||||
|
|
||||||
async def test_abdeckung_wird_gemerkt_solange_die_funktion_dasteht(
|
|
||||||
projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(ABGEDECKT)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
|
||||||
vorher = modell.rufe
|
|
||||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
|
||||||
assert modell.rufe == vorher
|
|
||||||
|
|
||||||
# Funktion weg: die Abdeckung trägt nicht mehr
|
|
||||||
ohne = features.liste(projekt)
|
|
||||||
ohne[0]["sektionen"]["funktionen"] = []
|
|
||||||
await inhalt.code_im_feature(ohne, [punkt()], stand)
|
|
||||||
assert modell.rufe > vorher
|
|
||||||
|
|
||||||
|
|
||||||
async def test_geaenderte_codestelle_wird_neu_beurteilt(projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(ABGEDECKT)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
await inhalt.code_im_feature(alle, [punkt()], stand)
|
|
||||||
vorher = modell.rufe
|
|
||||||
await inhalt.code_im_feature(alle, [punkt(beleg="ganz anderer Code")], stand)
|
|
||||||
assert modell.rufe > vorher
|
|
||||||
|
|
||||||
|
|
||||||
async def test_fehlmeldung_bleibt_wortgleich_bis_die_funktion_da_ist(
|
|
||||||
projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(FEHLT)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
erste = await inhalt.code_im_feature(alle, [punkt()], stand)
|
|
||||||
assert [x["titel"] for x in erste] == ["Neu bedienen"]
|
|
||||||
vorher = modell.rufe
|
|
||||||
|
|
||||||
zweite = await inhalt.code_im_feature(alle, [punkt()], stand)
|
|
||||||
assert zweite == erste
|
|
||||||
assert modell.rufe == vorher # kein Flackern, keine neue Anfrage
|
|
||||||
|
|
||||||
|
|
||||||
async def test_angenommener_vorschlag_loest_die_fehlmeldung_ab(projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
modell = Zaehler(FEHLT)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand)
|
|
||||||
vorher = modell.rufe
|
|
||||||
|
|
||||||
# Der Nutzer nimmt den Vorschlag an — jetzt muss neu beurteilt werden
|
|
||||||
modell.antwort = ABGEDECKT
|
|
||||||
karte(projekt, "Zwei", ("Neu bedienen", "Knopf"))
|
|
||||||
assert await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand) == []
|
|
||||||
assert modell.rufe > vorher
|
|
||||||
|
|
||||||
|
|
||||||
async def test_unentschiedenes_urteil_wird_festgehalten(projekt, monkeypatch):
|
|
||||||
"""Drei zu drei ist ein Münzwurf. Der darf einmal fallen, nicht jede Runde."""
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
|
|
||||||
def haelfte(nachricht, ruf):
|
|
||||||
return ABGEDECKT if ruf % 2 else FEHLT
|
|
||||||
|
|
||||||
modell = Zaehler(haelfte)
|
|
||||||
monkeypatch.setattr(agents, "frage", modell)
|
|
||||||
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
|
||||||
vorher = modell.rufe
|
|
||||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
|
||||||
assert modell.rufe == vorher
|
|
||||||
|
|
||||||
|
|
||||||
async def test_verschwundene_stelle_raeumt_ihren_eintrag_weg(projekt, monkeypatch):
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
monkeypatch.setattr(agents, "frage", Zaehler(ABGEDECKT))
|
|
||||||
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
alle = features.liste(projekt)
|
|
||||||
await inhalt.code_im_feature(alle, [punkt()], stand)
|
|
||||||
assert stand["abdeckung"]
|
|
||||||
await inhalt.code_im_feature(alle, [punkt(schluessel="bedienen:anderes")], stand)
|
|
||||||
assert "bedienen:starte" not in stand["abdeckung"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_fehlende_stelle_landet_auch_auf_erfundener_karte(projekt):
|
|
||||||
"""Sechs Stimmen sagen „fehlt", nennen aber Karten, die es nicht gibt. Vorher
|
|
||||||
fiel der Befund lautlos weg."""
|
|
||||||
karte(projekt, "Projekte", ("Projekt anlegen", "+-Icon"))
|
|
||||||
karten = {f["sektionen"]["titel"]: f for f in features.liste(projekt)}
|
|
||||||
urteile = [{"abgedeckt": False, "karte": "Datenverwaltung", "titel": "x"}] * 4
|
|
||||||
assert inhalt._zielkarte(urteile, karten) is karten["Projekte"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_erster_aufloesbarer_kartenname_gewinnt(projekt):
|
|
||||||
karte(projekt, "Projekte", ("Projekt anlegen", "+-Icon"))
|
|
||||||
karte(projekt, "Darkmode", ("Darkmode umschalten", "Icon"))
|
|
||||||
karten = {f["sektionen"]["titel"]: f for f in features.liste(projekt)}
|
|
||||||
urteile = ([{"abgedeckt": False, "karte": "Export", "titel": "x"}] * 3
|
|
||||||
+ [{"abgedeckt": False, "karte": "Darkmode", "titel": "x"}] * 2)
|
|
||||||
assert inhalt._zielkarte(urteile, karten) is karten["Darkmode"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_ohne_karten_kein_ziel(projekt):
|
|
||||||
assert inhalt._zielkarte([{"abgedeckt": False, "karte": "X"}], {}) is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_grund_trennt_dublette_von_fehlendem_code():
|
|
||||||
"""Beides heißt streichen — aber aus verschiedenen Gründen, und der Grund
|
|
||||||
entscheidet, ob man zustimmt."""
|
|
||||||
assert inhalt._wegfall_grund([{"art": "dublette"}]) == "1 Funktion doppelt"
|
|
||||||
assert inhalt._wegfall_grund([{"art": "ohne_code"}] * 2) \
|
|
||||||
== "2 Funktionen ohne eigenen Code"
|
|
||||||
assert inhalt._wegfall_grund([{"art": "dublette"}, {"art": "ohne_code"}]) \
|
|
||||||
== "1 Funktion doppelt, 1 Funktion ohne eigenen Code"
|
|
||||||
|
|
||||||
|
|
||||||
def test_halbe_datei_ueberschreibt_nicht_die_ganze(projekt, monkeypatch):
|
|
||||||
"""Absturz beim Schreiben: die alte Datei muss stehen bleiben."""
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
stand["belege"]["a"] = {"abdruck": "x", "datei": "a.py", "beleg": "z"}
|
|
||||||
ledger.sichere(projekt, stand)
|
|
||||||
|
|
||||||
echt = ledger.json.dumps
|
|
||||||
|
|
||||||
def platzt(*a, **k):
|
|
||||||
raise MemoryError("mittendrin")
|
|
||||||
|
|
||||||
monkeypatch.setattr(ledger.json, "dumps", platzt)
|
|
||||||
with pytest.raises(MemoryError):
|
|
||||||
ledger.sichere(projekt, {"belege": {"b": {}}})
|
|
||||||
monkeypatch.setattr(ledger.json, "dumps", echt)
|
|
||||||
assert ledger.lade(projekt)["belege"] == stand["belege"]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_ausgefallene_stimmen_merken_nichts(projekt, monkeypatch):
|
|
||||||
"""Fällt der Anbieter aus, darf keine Löschempfehlung im Gedächtnis landen."""
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
|
|
||||||
async def tot(system, nachricht, temperatur=0.0):
|
|
||||||
raise RuntimeError("429")
|
|
||||||
|
|
||||||
monkeypatch.setattr(agents, "frage", tot)
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
gemeldet = []
|
|
||||||
assert await inhalt.feature_im_code(features.liste(projekt), QUELLE, "x", stand,
|
|
||||||
melde=lambda t, n=0: gemeldet.append(n)) == []
|
|
||||||
assert gemeldet == [1] # laut gescheitert, nicht still
|
|
||||||
assert stand["ohne_code"] == {}
|
|
||||||
assert stand["belege"] == {}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_ein_ausfall_rettet_die_anderen_urteile(projekt, monkeypatch):
|
|
||||||
"""Eine kaputte Stelle darf die bezahlten Urteile der anderen nicht wegwerfen."""
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
|
|
||||||
async def launisch(system, nachricht, temperatur=0.0):
|
|
||||||
if "bedienen:kaputt" in nachricht:
|
|
||||||
raise RuntimeError("429")
|
|
||||||
return ABGEDECKT
|
|
||||||
|
|
||||||
monkeypatch.setattr(agents, "frage", launisch)
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
gemeldet = []
|
|
||||||
await inhalt.code_im_feature(
|
|
||||||
features.liste(projekt),
|
|
||||||
[punkt(), punkt(schluessel="bedienen:kaputt", beleg="bedienen:kaputt")],
|
|
||||||
stand, melde=lambda t, n=0: gemeldet.append(n))
|
|
||||||
assert gemeldet == [1]
|
|
||||||
assert "bedienen:starte" in stand["abdeckung"]
|
|
||||||
assert "bedienen:kaputt" not in stand["abdeckung"]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_projekt_ohne_karten_stuerzt_nicht_ab(projekt, monkeypatch):
|
|
||||||
monkeypatch.setattr(agents, "frage", Zaehler(FEHLT))
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
assert await inhalt.code_im_feature([], [punkt()], stand) == []
|
|
||||||
assert await inhalt.dubletten([], stand) == []
|
|
||||||
assert await inhalt.feature_im_code([], QUELLE, "x", stand) == []
|
|
||||||
|
|
||||||
|
|
||||||
async def test_feststehendes_urteil_braucht_keine_vollen_stimmen(projekt, monkeypatch):
|
|
||||||
"""Vier von sechs sagen „abgedeckt": die zwei fehlenden könnten die Schwelle
|
|
||||||
nicht mehr reißen. Daraus einen Fehlschlag zu machen wäre Verschwendung."""
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
ausfaelle = {"n": 0}
|
|
||||||
|
|
||||||
async def zwei_platzen(system, nachricht, temperatur=0.0):
|
|
||||||
ausfaelle["n"] += 1
|
|
||||||
if ausfaelle["n"] <= 2:
|
|
||||||
raise RuntimeError("429")
|
|
||||||
return ABGEDECKT
|
|
||||||
|
|
||||||
monkeypatch.setattr(agents, "frage", zwei_platzen)
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
gemeldet = []
|
|
||||||
assert await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand,
|
|
||||||
melde=lambda t, n=0: gemeldet.append(n)) == []
|
|
||||||
assert gemeldet == [] # kein Fehler: das Ergebnis stand fest
|
|
||||||
assert "bedienen:starte" in stand["abdeckung"]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_offenes_urteil_bei_ausfall_wird_gemeldet(projekt, monkeypatch):
|
|
||||||
"""Vier Stimmen sagen „fehlt", zwei fehlen — mit ihnen wäre die Schwelle
|
|
||||||
erreicht. Das ist offen und muss laut scheitern, nicht still durchgehen."""
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
ausfaelle = {"n": 0}
|
|
||||||
|
|
||||||
async def zwei_platzen(system, nachricht, temperatur=0.0):
|
|
||||||
ausfaelle["n"] += 1
|
|
||||||
if ausfaelle["n"] <= 2:
|
|
||||||
raise RuntimeError("429")
|
|
||||||
return FEHLT
|
|
||||||
|
|
||||||
monkeypatch.setattr(agents, "frage", zwei_platzen)
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
gemeldet = []
|
|
||||||
assert await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand,
|
|
||||||
melde=lambda t, n=0: gemeldet.append(n)) == []
|
|
||||||
assert gemeldet == [1]
|
|
||||||
assert stand["abdeckung"] == {} and stand["ohne_karte"] == {}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_zwei_ueberlebende_stimmen_urteilen_nicht(projekt, monkeypatch):
|
|
||||||
"""Rechnerisch könnten zwei Stimmen die Schwelle unerreichbar machen. Als
|
|
||||||
dauerhaft gemerktes Urteil ist das zu wenig."""
|
|
||||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
|
||||||
ausfaelle = {"n": 0}
|
|
||||||
|
|
||||||
async def vier_platzen(system, nachricht, temperatur=0.0):
|
|
||||||
ausfaelle["n"] += 1
|
|
||||||
if ausfaelle["n"] <= 4:
|
|
||||||
raise RuntimeError("429")
|
|
||||||
return ABGEDECKT
|
|
||||||
|
|
||||||
monkeypatch.setattr(agents, "frage", vier_platzen)
|
|
||||||
stand = ledger.lade(projekt)
|
|
||||||
gemeldet = []
|
|
||||||
await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand,
|
|
||||||
melde=lambda t, n=0: gemeldet.append(n))
|
|
||||||
assert gemeldet == [1]
|
|
||||||
assert stand["abdeckung"] == {}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------- Vision
|
# ---------------------------------------------------------------- Vision
|
||||||
|
|
||||||
VISION = "<!-- titel -->\nVision\n<!-- art -->\nvision\n<!-- ziel -->\nSo soll es werden\n"
|
VISION = "<!-- titel -->\nVision\n<!-- art -->\nvision\n<!-- ziel -->\nSo soll es werden\n"
|
||||||
@@ -530,3 +101,65 @@ def test_keine_pruefung_fasst_die_vision_an(projekt):
|
|||||||
assert len(features.liste(projekt)) == 2
|
assert len(features.liste(projekt)) == 2
|
||||||
titel = [f["sektionen"]["titel"] for f in features.pruefbar(projekt)]
|
titel = [f["sektionen"]["titel"] for f in features.pruefbar(projekt)]
|
||||||
assert titel == ["Eins"]
|
assert titel == ["Eins"]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Prüfung 1
|
||||||
|
|
||||||
|
async def test_stilurteil_wird_gemerkt_und_nicht_neu_gefragt(projekt, monkeypatch):
|
||||||
|
"""176 Anfragen je Lauf brachten den Anbieter zum Drosseln. Unveränderter
|
||||||
|
Ausschnitt bei unverändertem Katalog heißt: das Urteil von vorhin gilt."""
|
||||||
|
import pruefung
|
||||||
|
|
||||||
|
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||||
|
modell = Zaehler('{"felder": {}}')
|
||||||
|
monkeypatch.setattr(agents, "frage", modell)
|
||||||
|
|
||||||
|
liste, arbeite, abschluss = await pruefung._stil_aufgaben(projekt)
|
||||||
|
for a in liste:
|
||||||
|
assert await arbeite(a, None) == []
|
||||||
|
abschluss()
|
||||||
|
erste = modell.rufe
|
||||||
|
assert erste >= len(pruefung.TEMPERATUREN)
|
||||||
|
|
||||||
|
liste, arbeite, abschluss = await pruefung._stil_aufgaben(projekt)
|
||||||
|
for a in liste:
|
||||||
|
assert await arbeite(a, None) == []
|
||||||
|
assert modell.rufe == erste # keine einzige neue Anfrage
|
||||||
|
|
||||||
|
|
||||||
|
async def test_geaenderter_text_wird_neu_gefragt(projekt, monkeypatch):
|
||||||
|
import pruefung
|
||||||
|
|
||||||
|
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||||
|
modell = Zaehler('{"felder": {}}')
|
||||||
|
monkeypatch.setattr(agents, "frage", modell)
|
||||||
|
|
||||||
|
liste, arbeite, abschluss = await pruefung._stil_aufgaben(projekt)
|
||||||
|
for a in liste:
|
||||||
|
await arbeite(a, None)
|
||||||
|
abschluss()
|
||||||
|
vorher = modell.rufe
|
||||||
|
|
||||||
|
datei = projekte.wurzel(projekt) / f"{features.liste(projekt)[0]['id']}.md"
|
||||||
|
datei.write_text(datei.read_text().replace("- Knopf", "- Ganz anderer Text"))
|
||||||
|
liste, arbeite, _ = await pruefung._stil_aufgaben(projekt)
|
||||||
|
for a in liste:
|
||||||
|
await arbeite(a, None)
|
||||||
|
assert modell.rufe > vorher
|
||||||
|
|
||||||
|
|
||||||
|
async def test_gemessener_verstoss_kommt_nie_aus_dem_gedaechtnis(projekt, monkeypatch):
|
||||||
|
"""Sonst bliebe eine neu hinzugekommene Messregel an alten Karten wirkungslos."""
|
||||||
|
import pruefung
|
||||||
|
|
||||||
|
karte(projekt, "Eins", ("Etwas starten", "Auch Passwort prüfen."))
|
||||||
|
modell = Zaehler('{"felder": {"regeln": {"neu": "- Passwort prüfen",'
|
||||||
|
' "grund": "Füllwort", "regel": "x"}}}')
|
||||||
|
monkeypatch.setattr(agents, "frage", modell)
|
||||||
|
|
||||||
|
for lauf in range(2):
|
||||||
|
liste, arbeite, abschluss = await pruefung._stil_aufgaben(projekt)
|
||||||
|
gefunden = [p for a in liste for p in await arbeite(a, None)]
|
||||||
|
abschluss()
|
||||||
|
assert [p["vorschlag"] for p in gefunden] == ["Passwort prüfen"], lauf
|
||||||
|
assert modell.rufe > len(pruefung.TEMPERATUREN) # zweimal wirklich gefragt
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ def test_alle_sektionen():
|
|||||||
"ziel": "",
|
"ziel": "",
|
||||||
"regeln": "- Passwort nie loggen\n- Sperre nach 5 Fehlversuchen",
|
"regeln": "- Passwort nie loggen\n- Sperre nach 5 Fehlversuchen",
|
||||||
"testen": "- Formular öffnen\n- Daten eingeben",
|
"testen": "- Formular öffnen\n- Daten eingeben",
|
||||||
|
"stand": "gebaut",
|
||||||
},
|
},
|
||||||
{"text": "Abmeldung", "ziel": "", "regeln": "", "testen": ""},
|
{"text": "Abmeldung", "ziel": "", "regeln": "", "testen": "",
|
||||||
|
"stand": "gebaut"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -114,3 +116,22 @@ def test_marker_mit_leerraum():
|
|||||||
def test_titel_erste_nicht_leere_zeile():
|
def test_titel_erste_nicht_leere_zeile():
|
||||||
s = zerlege("<!-- titel -->\n\nErste\nZweite\n")
|
s = zerlege("<!-- titel -->\n\nErste\nZweite\n")
|
||||||
assert s["titel"] == "Erste"
|
assert s["titel"] == "Erste"
|
||||||
|
|
||||||
|
|
||||||
|
def test_stand_offen_wird_gelesen():
|
||||||
|
"""Bestellt, aber noch nicht gebaut — sonst klagt die Vorwärtsprüfung sie an."""
|
||||||
|
s = zerlege("<!-- titel -->\nX\n<!-- funktion -->\nA\n<!-- stand -->\noffen\n"
|
||||||
|
"<!-- funktion -->\nB\n")
|
||||||
|
assert [(f["text"], f["stand"]) for f in s["funktionen"]] == [("A", "offen"),
|
||||||
|
("B", "gebaut")]
|
||||||
|
assert s["sonstiges"] == "" # der Marker landet nicht im Rest
|
||||||
|
|
||||||
|
|
||||||
|
def test_unbekannter_stand_gilt_als_gebaut():
|
||||||
|
s = zerlege("<!-- titel -->\nX\n<!-- funktion -->\nA\n<!-- stand -->\nirgendwas\n")
|
||||||
|
assert s["funktionen"][0]["stand"] == "gebaut"
|
||||||
|
|
||||||
|
|
||||||
|
def test_stand_ohne_funktion_faellt_in_sonstiges():
|
||||||
|
s = zerlege("<!-- titel -->\nX\n<!-- stand -->\noffen\n")
|
||||||
|
assert s["sonstiges"] == "offen"
|
||||||
|
|||||||
201
backend/tests/test_planung.py
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
"""Die Planung: eine Arbeitskopie, die den Projektstand nicht anfasst.
|
||||||
|
|
||||||
|
Geprüft wird die eine Zusage — solange die Planung läuft, bleibt die Übersicht,
|
||||||
|
was sie war. Und dass sie einen Neustart übersteht, weil sie auf der Platte liegt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import features
|
||||||
|
import parser
|
||||||
|
import planung
|
||||||
|
import projekte
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def karte(projekt: str, titel: str, *funktionen: str) -> str:
|
||||||
|
roh = f"<!-- titel -->\n{titel}\n" + "".join(
|
||||||
|
f"{'-' * 40}\n<!-- funktion -->\n{f}\n<!-- regeln -->\n- Etwas gilt\n"
|
||||||
|
for f in funktionen)
|
||||||
|
return features.anlegen(projekt, roh)["id"]
|
||||||
|
|
||||||
|
|
||||||
|
def titel(projekt: str) -> list:
|
||||||
|
return sorted(f["sektionen"]["titel"] for f in features.liste(projekt))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def projekt(datenordner):
|
||||||
|
projekte.anlegen("P")
|
||||||
|
karte("P", "Galerie", "Bild wählen")
|
||||||
|
karte("P", "Shop", "Karte kaufen")
|
||||||
|
return "P"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ohne_planung_arbeitet_alles_im_projektordner(projekt):
|
||||||
|
assert not planung.laeuft(projekt)
|
||||||
|
assert projekte.wurzel(projekt) == projekte.stand(projekt)
|
||||||
|
assert planung.aenderungen(projekt) == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_planung_leitet_das_schreiben_um(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
assert projekte.wurzel(projekt) != projekte.stand(projekt)
|
||||||
|
|
||||||
|
karte(projekt, "Neu", "Etwas tun")
|
||||||
|
# Die Karte steht in der Kopie …
|
||||||
|
assert "Neu" in titel(projekt)
|
||||||
|
# … und nicht im Projektstand
|
||||||
|
assert not (projekte.stand(projekt) / "neu.md").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_zweite_planung_wird_abgelehnt(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
planung.starte(projekt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_planung_ueberlebt_einen_neustart(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
karte(projekt, "Neu", "Etwas tun")
|
||||||
|
# Nichts im Speicher — der nächste Prozess sieht nur die Platte
|
||||||
|
assert planung.laeuft(projekt)
|
||||||
|
assert "Neu" in planung.aenderungen(projekt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_abbruch_laesst_den_projektstand_unberuehrt(projekt):
|
||||||
|
vorher = titel(projekt)
|
||||||
|
planung.starte(projekt)
|
||||||
|
karte(projekt, "Neu", "Etwas tun")
|
||||||
|
planung.brich_ab(projekt)
|
||||||
|
assert not planung.laeuft(projekt)
|
||||||
|
assert titel(projekt) == vorher
|
||||||
|
|
||||||
|
|
||||||
|
def test_umsetzen_macht_die_kopie_zum_stand(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
karte(projekt, "Neu", "Etwas tun")
|
||||||
|
planung.setze_um(projekt)
|
||||||
|
assert not planung.laeuft(projekt)
|
||||||
|
assert "Neu" in titel(projekt)
|
||||||
|
assert not (projekte.stand(projekt) / projekte.PLANUNG).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_umsetzen_ohne_planung_ist_ein_fehler(projekt):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
planung.setze_um(projekt)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Änderungen
|
||||||
|
|
||||||
|
def test_neue_karte_ist_neu(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
karte(projekt, "Neu", "Etwas tun")
|
||||||
|
a = planung.aenderungen(projekt)
|
||||||
|
assert a["Neu"]["art"] == "neu"
|
||||||
|
assert a["Neu"]["funktionen"] == {"Etwas tun": "neu"}
|
||||||
|
assert "Galerie" not in a
|
||||||
|
|
||||||
|
|
||||||
|
def test_entfernte_karte_ist_weg(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
features.loeschen(projekt, "shop")
|
||||||
|
a = planung.aenderungen(projekt)
|
||||||
|
assert a["Shop"]["art"] == "weg"
|
||||||
|
assert a["Shop"]["funktionen"] == {"Karte kaufen": "weg"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_neue_funktion_wird_markiert(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
f = next(x for x in features.liste(projekt) if x["sektionen"]["titel"] == "Galerie")
|
||||||
|
features.speichern(projekt, f["id"],
|
||||||
|
f["roh"] + f"{'-' * 40}\n<!-- funktion -->\nBild löschen\n")
|
||||||
|
a = planung.aenderungen(projekt)
|
||||||
|
assert a["Galerie"]["art"] == "geaendert"
|
||||||
|
assert a["Galerie"]["funktionen"] == {"Bild löschen": "neu"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_geaenderte_regel_wird_markiert(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
f = next(x for x in features.liste(projekt) if x["sektionen"]["titel"] == "Galerie")
|
||||||
|
features.speichern(projekt, f["id"], f["roh"].replace("Etwas gilt", "Anderes gilt"))
|
||||||
|
assert planung.aenderungen(projekt)["Galerie"]["funktionen"] == {
|
||||||
|
"Bild wählen": "geaendert"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_umbenannte_karte_ist_neu_und_weg(projekt):
|
||||||
|
"""Verglichen wird über den Titel. Eine Umbenennung ist zwei Änderungen."""
|
||||||
|
planung.starte(projekt)
|
||||||
|
f = next(x for x in features.liste(projekt) if x["sektionen"]["titel"] == "Shop")
|
||||||
|
features.speichern(projekt, f["id"], f["roh"].replace("Shop", "Laden", 1))
|
||||||
|
a = planung.aenderungen(projekt)
|
||||||
|
assert a["Shop"]["art"] == "weg" and a["Laden"]["art"] == "neu"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gleicher_inhalt_ist_keine_aenderung(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
f = features.liste(projekt)[0]
|
||||||
|
features.speichern(projekt, f["id"], f["roh"])
|
||||||
|
assert planung.aenderungen(projekt) == {}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Zurücksetzen
|
||||||
|
|
||||||
|
def test_zuruecksetzen_loescht_eine_neue_karte(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
karte(projekt, "Neu", "Etwas tun")
|
||||||
|
planung.zuruecksetzen(projekt, "Neu")
|
||||||
|
assert planung.aenderungen(projekt) == {}
|
||||||
|
assert "Neu" not in titel(projekt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_zuruecksetzen_holt_eine_geloeschte_karte_zurueck(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
features.loeschen(projekt, "shop")
|
||||||
|
planung.zuruecksetzen(projekt, "Shop")
|
||||||
|
assert planung.aenderungen(projekt) == {}
|
||||||
|
assert "Shop" in titel(projekt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_zuruecksetzen_stellt_den_alten_inhalt_her(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
f = next(x for x in features.liste(projekt) if x["sektionen"]["titel"] == "Galerie")
|
||||||
|
features.speichern(projekt, f["id"], f["roh"].replace("Etwas gilt", "Anderes gilt"))
|
||||||
|
planung.zuruecksetzen(projekt, "Galerie")
|
||||||
|
assert planung.aenderungen(projekt) == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_zuruecksetzen_laesst_die_uebrigen_stehen(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
karte(projekt, "Eins", "A tun")
|
||||||
|
karte(projekt, "Zwei", "B tun")
|
||||||
|
planung.zuruecksetzen(projekt, "Eins")
|
||||||
|
assert set(planung.aenderungen(projekt)) == {"Zwei"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_zuruecksetzen_ohne_planung_ist_ein_fehler(projekt):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
planung.zuruecksetzen(projekt, "Galerie")
|
||||||
|
|
||||||
|
|
||||||
|
def test_unbekannte_karte_zuruecksetzen_meldet_es(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
planung.zuruecksetzen(projekt, "Gibt es nicht")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- Nachbarschaft
|
||||||
|
|
||||||
|
def test_die_kopie_ist_kein_eigenes_projekt(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
assert [p["name"] for p in projekte.liste()] == ["P"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_gespraech_und_gedaechtnis_wandern_mit(projekt):
|
||||||
|
"""Was während des Planens beurteilt wurde, gilt für den geplanten Stand."""
|
||||||
|
(projekte.stand(projekt) / ".pruefung.json").write_text('{"stil": {}}')
|
||||||
|
planung.starte(projekt)
|
||||||
|
assert (projekte.wurzel(projekt) / ".pruefung.json").is_file()
|
||||||
|
|
||||||
|
|
||||||
|
def test_die_kopie_kopiert_sich_nicht_selbst(projekt):
|
||||||
|
planung.starte(projekt)
|
||||||
|
assert not (projekte.wurzel(projekt) / projekte.PLANUNG).exists()
|
||||||
@@ -197,8 +197,9 @@ async def test_herkunft_kommt_vom_code_nicht_vom_modell(datenordner, monkeypatch
|
|||||||
|
|
||||||
async def luegt(system, inhalt, temperatur=0.0):
|
async def luegt(system, inhalt, temperatur=0.0):
|
||||||
# Das Modell behauptet eine fremde Funktion — das muss ignoriert werden
|
# Das Modell behauptet eine fremde Funktion — das muss ignoriert werden
|
||||||
|
# „Anmelden" ist selbst ein gültiger Titel — sonst weist die Messung ihn ab
|
||||||
return ('{"felder": {"titel": {"regel": "Sprache: Deutsch, Präsens, keine Anrede",'
|
return ('{"felder": {"titel": {"regel": "Sprache: Deutsch, Präsens, keine Anrede",'
|
||||||
' "neu": "Anders", "grund": "x"},'
|
' "neu": "Einloggen", "grund": "x"},'
|
||||||
' "funktion": {"neu": "Erfunden", "grund": "y"}}}')
|
' "funktion": {"neu": "Erfunden", "grund": "y"}}}')
|
||||||
|
|
||||||
monkeypatch.setattr(agents, "frage", luegt)
|
monkeypatch.setattr(agents, "frage", luegt)
|
||||||
|
|||||||
@@ -166,3 +166,96 @@ def test_gemessene_befunde_haengen_am_ausschnitt(datenordner):
|
|||||||
text = pruefung.inhalt(karte, befunde)
|
text = pruefung.inhalt(karte, befunde)
|
||||||
assert "Gemessen, bitte neu schreiben:" in text
|
assert "Gemessen, bitte neu schreiben:" in text
|
||||||
assert "Mehr als 3 Wörter" in text
|
assert "Mehr als 3 Wörter" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_funktionstitel_braucht_ein_verb():
|
||||||
|
"""Ohne Verb steht dort ein Bereich oder ein Zustand — nichts Testbares."""
|
||||||
|
for titel in ("Galerie im Modal", "Preis aktualisiert", "In den Warenkorb",
|
||||||
|
"Modal tastaturgesteuert"):
|
||||||
|
assert _probleme(_pruefe("funktion", "titel", titel)) \
|
||||||
|
== ["Kein Verb im Infinitiv am Ende"], titel
|
||||||
|
|
||||||
|
|
||||||
|
def test_gute_funktionstitel_bleiben_unberuehrt():
|
||||||
|
for titel in ("Feature anlegen", "Anzeigemodus umschalten", "Option wählen",
|
||||||
|
"Nächsten Rückschritt anzeigen", "Karten prüfen", "Stand umschalten",
|
||||||
|
"Projekt aufrufen", "Bildergalerie bedienen", "PDF hochladen"):
|
||||||
|
assert _pruefe("funktion", "titel", titel) == [], titel
|
||||||
|
|
||||||
|
|
||||||
|
def test_kartentitel_darf_ohne_verb_stehen():
|
||||||
|
"""Eine Karte ist ein Gegenstand, keine Handlung."""
|
||||||
|
assert _pruefe("feature", "titel", "Karten prüfen") == []
|
||||||
|
assert _pruefe("feature", "titel", "Darkmode") == []
|
||||||
|
|
||||||
|
|
||||||
|
ABLAUF = "- Vollmodus öffnen\n- LMB auf Regeln\n- Text ändern\n- Regeln angepasst"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ein_ablauf_geht_durch():
|
||||||
|
assert _pruefe("funktion", "testen", ABLAUF) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_zweiter_ablauf_wird_gemeldet():
|
||||||
|
"""So entstand „Bildergalerie bedienen": blättern, Miniatur, Großansicht in einem."""
|
||||||
|
zwei = ("- Zurück-Button klicken\n- Vorheriges Bild erscheint\n"
|
||||||
|
"- Miniaturbild klicken\n- Zugehöriges Bild erscheint")
|
||||||
|
b = _pruefe("funktion", "testen", zwei)
|
||||||
|
assert _probleme(b) == ["Zweiter Ablauf in einer Funktion"]
|
||||||
|
assert b[0]["original"] == "Miniaturbild klicken"
|
||||||
|
|
||||||
|
|
||||||
|
def test_nominalstil_ist_eine_handlung():
|
||||||
|
"""„Bestätigen im Modal" hat das Verb vorn — sonst gälte es als Ergebnis."""
|
||||||
|
text = ("- RMB auf Featurecard\n- Klick auf \"Feature entfernen\"\n"
|
||||||
|
"- Bestätigen im Modal\n- Feature entfernt")
|
||||||
|
assert _pruefe("funktion", "testen", text) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_mehrzahl_auf_en_ist_kein_verb():
|
||||||
|
"""Deutsche Mehrzahl endet auch auf -en — nur Anfang und Ende zählen."""
|
||||||
|
text = "- LMB auf Icon\n- Fehlerhafte Karten rot markiert"
|
||||||
|
assert _pruefe("funktion", "testen", text) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_zeilenfehler_haben_vorrang():
|
||||||
|
"""Erst die Zeilen in Ordnung bringen; zwei Vorschläge je Feld wären nicht
|
||||||
|
unabhängig anwendbar."""
|
||||||
|
text = "- Auch klicken.\n- Ergebnis da\n- Weiter klicken"
|
||||||
|
assert _probleme(_pruefe("funktion", "testen", text)) \
|
||||||
|
== ["Satzzeichen am Zeilenende, Füllwort „auch“"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_funktion_ohne_jedes_feld_ist_leer():
|
||||||
|
"""Ein bloßer Titel beschreibt nichts — der Prüfer stülpt ihn über alles."""
|
||||||
|
b = struktur.befunde("funktion", {"titel": "Modal steuern", "ziel": "",
|
||||||
|
"regeln": "", "testen": ""})
|
||||||
|
assert _probleme(b) == ["Funktion ohne Ziel, Regeln und Testen"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_ein_einziges_feld_genuegt():
|
||||||
|
for feld in ("ziel", "regeln", "testen"):
|
||||||
|
felder = {"titel": "Modal steuern", "ziel": "", "regeln": "", "testen": ""}
|
||||||
|
felder[feld] = "Etwas tun" if feld == "ziel" else "- Etwas tun"
|
||||||
|
assert struktur.befunde("funktion", felder) == [], feld
|
||||||
|
|
||||||
|
|
||||||
|
def test_einzelnes_feld_loest_nichts_aus():
|
||||||
|
"""Beim Prüfen eines Feldes sagt das Fehlen der anderen nichts."""
|
||||||
|
assert struktur.befunde("funktion", {"titel": "Modal steuern"}) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_kartenebene_darf_leer_sein():
|
||||||
|
assert struktur.befunde("feature", {"titel": "Darkmode", "badge": "kern",
|
||||||
|
"ziel": "", "regeln": ""}) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_platzhalter_ist_kein_inhalt():
|
||||||
|
"""`<testschritt>` erfüllt jede Formregel und sagt trotzdem nichts."""
|
||||||
|
assert struktur._umschreiben("funktion", "testen", "<testschritt>")
|
||||||
|
assert struktur._umschreiben("funktion", "regeln", "Noch offen ...")
|
||||||
|
assert struktur._umschreiben("funktion", "regeln", "TODO klären")
|
||||||
|
|
||||||
|
|
||||||
|
def test_kleiner_als_ist_kein_platzhalter():
|
||||||
|
assert not struktur._umschreiben("funktion", "regeln", "Wert 3 < 5")
|
||||||
|
|||||||
537
daten/MtoVariantConfigurator/.planung/.pruefung.json
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
{
|
||||||
|
"abdeckung": {
|
||||||
|
"einbinden:VariantConfiguratorPlugin": {
|
||||||
|
"abdruck": "823481507a44729d",
|
||||||
|
"funktion": "Konfigurationsdaten bereitstellen",
|
||||||
|
"trug": "5fbc8363b7cffe59"
|
||||||
|
},
|
||||||
|
"einbinden:erweitert @Storefront/storefront/component/buy-widget/configurator.html.twig": {
|
||||||
|
"abdruck": "c6d031d6bc598089",
|
||||||
|
"funktion": "Konfigurator aktivieren",
|
||||||
|
"trug": "6cf5a2b08ce2371a"
|
||||||
|
},
|
||||||
|
"einbinden:override sw-product-detail-variants": {
|
||||||
|
"abdruck": "2fcf518e7333458a",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"einbinden:register mto-vc-variants-card": {
|
||||||
|
"abdruck": "c472feadaac5ac71",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"hoeren:[data-mto-vc-gallery-next].click": {
|
||||||
|
"abdruck": "c402ad6f114e8c3d",
|
||||||
|
"funktion": "Galerie blättern",
|
||||||
|
"trug": "bacf45ebf9dd77b8"
|
||||||
|
},
|
||||||
|
"hoeren:[data-mto-vc-gallery-prev].click": {
|
||||||
|
"abdruck": "f50d4f7effc60a06",
|
||||||
|
"funktion": "Galerie blättern",
|
||||||
|
"trug": "bacf45ebf9dd77b8"
|
||||||
|
},
|
||||||
|
"hoeren:[data-mto-vc-lightbox-close].click": {
|
||||||
|
"abdruck": "89a22850af7694bb",
|
||||||
|
"funktion": "Galerie blättern",
|
||||||
|
"trug": "bacf45ebf9dd77b8"
|
||||||
|
},
|
||||||
|
"hoeren:document.keydown": {
|
||||||
|
"abdruck": "7c018ebbfa346bee",
|
||||||
|
"funktion": "Schritt öffnen",
|
||||||
|
"trug": "efe9634e3080c073"
|
||||||
|
},
|
||||||
|
"hoeren:el.click": {
|
||||||
|
"abdruck": "6a423a062267755c",
|
||||||
|
"funktion": "Nächsten Schritt anzeigen",
|
||||||
|
"trug": "f6b276e3fedf287d"
|
||||||
|
},
|
||||||
|
"hoeren:info.click": {
|
||||||
|
"abdruck": "7a5a70dfb792bf19",
|
||||||
|
"funktion": "Optionsdetail öffnen",
|
||||||
|
"trug": "39fed29af6734cd4"
|
||||||
|
},
|
||||||
|
"hoeren:info.keydown": {
|
||||||
|
"abdruck": "409cecf6fb3bbb4a",
|
||||||
|
"funktion": "Optionsdetail öffnen",
|
||||||
|
"trug": "39fed29af6734cd4"
|
||||||
|
},
|
||||||
|
"hoeren:lightboxSelect.click": {
|
||||||
|
"abdruck": "9add2d2c77b440b5",
|
||||||
|
"funktion": "Ansicht schließen",
|
||||||
|
"trug": "24d52cc6d249d4f5"
|
||||||
|
},
|
||||||
|
"hoeren:next.click": {
|
||||||
|
"abdruck": "d5b0cdcce40afcff",
|
||||||
|
"funktion": "Konfigurator schließen",
|
||||||
|
"trug": "1cc1a6b5fc943e03"
|
||||||
|
},
|
||||||
|
"hoeren:prev.click": {
|
||||||
|
"abdruck": "6b8db8d885c0d6f8",
|
||||||
|
"funktion": "Konfigurator schließen",
|
||||||
|
"trug": "1cc1a6b5fc943e03"
|
||||||
|
},
|
||||||
|
"hoeren:row.click": {
|
||||||
|
"abdruck": "b4ec6f63c613c35f",
|
||||||
|
"funktion": "Option wählen",
|
||||||
|
"trug": "80df89cf0c129387"
|
||||||
|
},
|
||||||
|
"hoeren:thumb.click": {
|
||||||
|
"abdruck": "b4aa2111971f6886",
|
||||||
|
"funktion": "Bild wählen",
|
||||||
|
"trug": "0604a0a6875489b8"
|
||||||
|
},
|
||||||
|
"hoeren:trigger.click": {
|
||||||
|
"abdruck": "e219933925adc81a",
|
||||||
|
"funktion": "Schritt öffnen",
|
||||||
|
"trug": "efe9634e3080c073"
|
||||||
|
},
|
||||||
|
"reaktion:ProductPageLoadedEvent": {
|
||||||
|
"abdruck": "6f808b91c1a0c4f5",
|
||||||
|
"funktion": "Konfigurationsdaten bereitstellen",
|
||||||
|
"trug": "5fbc8363b7cffe59"
|
||||||
|
},
|
||||||
|
"route:get /mto-variant-configurator/resolve/{productId}": {
|
||||||
|
"abdruck": "b14355d6007564a1",
|
||||||
|
"funktion": "Konfigurationsdaten bereitstellen",
|
||||||
|
"trug": "5fbc8363b7cffe59"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"belege": {
|
||||||
|
"[\"auswahl-anstossen\", \"Gewählte Option anzeigen\"]": {
|
||||||
|
"abdruck": "626af3ee300b610a",
|
||||||
|
"beleg": "<span class=\"mto-vc-select-trigger-label\" data-mto-vc-trigger-label>\n {{ mtoVcSelected ? mtoVcSelected.translated.name : 'mto.variantConfigurator.choose'|trans }}\n </span>",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/configurator.html.twig"
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Hinweistext anzeigen\"]": {
|
||||||
|
"abdruck": "b9baf394ca99d4e6",
|
||||||
|
"beleg": "{% if mtoVcBubble != mtoVcBubbleKey and mtoVcBubble is not empty %}\n <p class=\"mto-vc-bubble\">{{ mtoVcBubble|sw_sanitize }}</p>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/configurator.html.twig"
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Schrittnummer anzeigen\"]": {
|
||||||
|
"abdruck": "b9e4deada2c3a6f1",
|
||||||
|
"beleg": "<span class=\"mto-vc-step-badge\">{{ 'mto.variantConfigurator.step'|trans({ '%step%': mtoVcStep })|sw_sanitize }}</span>",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/configurator.html.twig"
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Beratung erreichen\"]": {
|
||||||
|
"abdruck": "fa513f8d265ad713",
|
||||||
|
"beleg": "{% if ctaPhone is not empty %}\n <span class=\"mto-vc-support-icon\" aria-hidden=\"true\"></span>\n <a href=\"tel:{{ ctaPhone|replace({' ': '', '/': '', '-': '', '(': '', ')': ''}) }}\"\n class=\"mto-vc-support-phone\">{{ ctaPhone|sw_sanitize }}</a>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Optionsübersicht öffnen\"]": {
|
||||||
|
"abdruck": "8aba7fb9bf50281b",
|
||||||
|
"beleg": "{% if pdfUrl %}\n <a href=\"{{ pdfUrl }}\" target=\"_blank\" rel=\"noopener\"\n class=\"mto-vc-btn is-outline mto-vc-all-options\">\n {{ 'mto.variantConfigurator.allOptions'|trans|sw_sanitize }}\n </a>\n {% else %}\n <span class=\"mto-vc-actions-spacer\" aria-hidden=\"true\"></span>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Vertrauen zeigen\"]": {
|
||||||
|
"abdruck": "5b204dff7de6f96d",
|
||||||
|
"beleg": "{% if trustLogo %}\n <img src=\"{{ trustLogo }}\" alt=\"\" class=\"mto-vc-trust-logo\">\n{% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Konfigurator aktivieren\"]": {
|
||||||
|
"abdruck": "60a65511f06ae7fe",
|
||||||
|
"beleg": "<sw-switch-field\n v-model:value=\"configuratorActive\"\n class=\"mto-vc-variants-card__active\"\n :disabled=\"!editable\"\n :label=\"$tc('mto-variant-configurator.card.activeLabel')\"\n :help-text=\"$tc('mto-variant-configurator.card.activeHelp')\"\n />",
|
||||||
|
"datei": "src/Resources/app/administration/src/component/mto-vc-variants-card/mto-vc-variants-card.html.twig"
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Optionsübersicht hinterlegen\"]": {
|
||||||
|
"abdruck": "97b421cf540a6cc2",
|
||||||
|
"beleg": "{% if pdfUrl %}\n <a href=\"{{ pdfUrl }}\" target=\"_blank\" rel=\"noopener\"\n class=\"mto-vc-btn is-outline mto-vc-all-options\">\n {{ 'mto.variantConfigurator.allOptions'|trans|sw_sanitize }}\n </a>\n {% else %}\n <span class=\"mto-vc-actions-spacer\" aria-hidden=\"true\"></span>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Trust-Logo hinterlegen\"]": {
|
||||||
|
"abdruck": "1954311a4c3a9cc1",
|
||||||
|
"beleg": "$mediaId = $this->systemConfigService->get(\n MtoVariantConfigurator::CONFIG_TRUST_LOGO,\n $salesChannelId,\n );",
|
||||||
|
"datei": "src/Core/Content/Product/VariantResolveService.php"
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Ansicht schließen\"]": {
|
||||||
|
"abdruck": "6eac493d9a481e30",
|
||||||
|
"beleg": "if (!this._nodes.lightbox.hidden) {\n this._closeLightbox();\n } else {\n this._close();\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Option aus Ansicht wählen\"]": {
|
||||||
|
"abdruck": "b4223f8cf3e2456e",
|
||||||
|
"beleg": "this._nodes.lightboxSelect?.addEventListener('click', () => {\n if (this._lightboxOption) {\n this._selectOption(this.options.groups[this._currentStep], this._lightboxOption.id);\n }\n this._closeLightbox();\n });",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Optionsdetail öffnen\"]": {
|
||||||
|
"abdruck": "59faab581e0fac7f",
|
||||||
|
"beleg": "info.addEventListener('click', open);\n info.addEventListener('keydown', (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n open(event);\n }\n });",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Eigenschaft benennen\"]": {
|
||||||
|
"abdruck": "dd54064a67689e2c",
|
||||||
|
"beleg": "this._nodes.title.textContent = group.name;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Optionen auflisten\"]": {
|
||||||
|
"abdruck": "a71af321f056608d",
|
||||||
|
"beleg": "if (option.media) {\n const visual = document.createElement('span');\n visual.className = 'mto-vc-option-media';\n const img = document.createElement('img');\n img.src = option.media;\n img.alt = '';\n img.loading = 'lazy';\n visual.appendChild(img);\n row.appendChild(visual);\n } else if (option.color) {\n const visual = document.createElement('span');\n visual.className = 'mto-vc-option-swatch';\n visual.style.backgroundColor = option.color;\n row.appendChild(visual);\n } else {\n row.classList.add('has-no-visual');\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Schrittstand anzeigen\"]": {
|
||||||
|
"abdruck": "aa7b8888103f8bd0",
|
||||||
|
"beleg": "this._nodes.counter.textContent = (this._snippets.stepCount || '__step__ / __total__')\n .replace('__step__', String(step))\n .replace('__total__', String(total));\n this._nodes.progress.style.width = `${(step / total) * 100}%`;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Auswahl übernehmen\"]": {
|
||||||
|
"abdruck": "21ccf5bc0286c95c",
|
||||||
|
"beleg": "if (optionId && select.value !== optionId) {\n select.value = optionId;\n lastChanged = select;\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurationsdaten bereitstellen\"]": {
|
||||||
|
"abdruck": "e6e5848381fce5aa",
|
||||||
|
"beleg": "$page->assign([",
|
||||||
|
"datei": "src/Subscriber/ProductPageSubscriber.php"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurator schließen\"]": {
|
||||||
|
"abdruck": "1b7d4aea18cdb896",
|
||||||
|
"beleg": "_close() {\n this._closeLightbox();\n this._modalEl.hidden = true;\n document.body.classList.remove('mto-vc-modal-open');\n document.removeEventListener('keydown', this._keydownHandler);\n this._lastFocus?.focus();\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Nächsten Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "3deee97595984a3e",
|
||||||
|
"beleg": "const isLast = this._nextUsableStep(this._currentStep + 1, 1) === null;\n this._nodes.next.textContent = isLast\n ? this._nodes.next.dataset.finishLabel\n : this._nodes.next.dataset.nextLabel;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Option wählen\"]": {
|
||||||
|
"abdruck": "4b9f7dc844cf7aac",
|
||||||
|
"beleg": "this._selected[group.id] = optionId;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Schritt öffnen\"]": {
|
||||||
|
"abdruck": "94b3aa8d50c53fc8",
|
||||||
|
"beleg": "this._triggers.forEach((trigger) => {\n trigger.addEventListener('click', (event) => {\n event.preventDefault();\n this._open(parseInt(trigger.dataset.mtoVcStepIndex, 10));\n });\n });",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Vorherigen Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "df5e60fe148da5e6",
|
||||||
|
"beleg": "this._nodes.prev.disabled = this._nextUsableStep(this._currentStep - 1, -1) === null;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Bild wählen\"]": {
|
||||||
|
"abdruck": "ab1e74251036541a",
|
||||||
|
"beleg": "thumb.className = `mto-vc-thumb${index === this._galleryIndex ? ' is-active' : ''}`;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Galerie blättern\"]": {
|
||||||
|
"abdruck": "6b28a5866629b878",
|
||||||
|
"beleg": "_stepGallery(delta) {\n if (this._gallery.length < 2) {\n return;\n }\n const count = this._gallery.length;\n this._galleryIndex = (this._galleryIndex + delta + count) % count;\n this._renderGallery();\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Vorschau laden\"]": {
|
||||||
|
"abdruck": "413024bc244f7770",
|
||||||
|
"beleg": "return new JsonResponse($this->variantResolveService->resolveVariant($variantId, $context));",
|
||||||
|
"datei": "src/Storefront/Controller/VariantConfiguratorController.php"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ohne_code": {},
|
||||||
|
"ohne_karte": {},
|
||||||
|
"paare": {
|
||||||
|
"[\"[\\\"auswahl-anstossen\\\", \\\"Gewählte Option anzeigen\\\"]\", \"[\\\"auswahl-anstossen\\\", \\\"Hinweistext anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "450345e8a27f5695",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"auswahl-anstossen\\\", \\\"Gewählte Option anzeigen\\\"]\", \"[\\\"auswahl-anstossen\\\", \\\"Schrittnummer anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "60008fdb6d69349b",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"auswahl-anstossen\\\", \\\"Hinweistext anzeigen\\\"]\", \"[\\\"auswahl-anstossen\\\", \\\"Schrittnummer anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "3eaf19062d6d53a3",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Beratung erreichen\\\"]\", \"[\\\"beratung-anbieten\\\", \\\"Optionsübersicht öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "9d3da3bf8c127ae1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Beratung erreichen\\\"]\", \"[\\\"beratung-anbieten\\\", \\\"Vertrauen zeigen\\\"]\"]": {
|
||||||
|
"abdruck": "2c3c62cb4b935c56",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Optionsübersicht öffnen\\\"]\", \"[\\\"beratung-anbieten\\\", \\\"Vertrauen zeigen\\\"]\"]": {
|
||||||
|
"abdruck": "bf601d5dc7c6c507",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Optionsübersicht öffnen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "bc9dc09e54b78a75",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Konfigurator aktivieren\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsbild am Datensatz pflegen\\\"]\"]": {
|
||||||
|
"abdruck": "5a4d802d668ddd55",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Konfigurator aktivieren\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "eeed78a6284711f1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Konfigurator aktivieren\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Trust-Logo hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "b4944e5aa4df871a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Optionsbild am Datensatz pflegen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "b91bdff82a120a8d",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Optionsbild am Datensatz pflegen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Trust-Logo hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "97268714e2b5d989",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Trust-Logo hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "b52cedd4106ee837",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"option-ansehen\\\", \\\"Ansicht schließen\\\"]\", \"[\\\"option-ansehen\\\", \\\"Option aus Ansicht wählen\\\"]\"]": {
|
||||||
|
"abdruck": "caa5ce4c5ff31774",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"option-ansehen\\\", \\\"Ansicht schließen\\\"]\", \"[\\\"option-ansehen\\\", \\\"Optionsdetail öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "462a46d7a2030874",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"option-ansehen\\\", \\\"Option aus Ansicht wählen\\\"]\", \"[\\\"option-ansehen\\\", \\\"Optionsdetail öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "8f695dfd90060204",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"schritt-zeigen\\\", \\\"Eigenschaft benennen\\\"]\", \"[\\\"schritt-zeigen\\\", \\\"Optionen auflisten\\\"]\"]": {
|
||||||
|
"abdruck": "5372df6b482e9a5c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"schritt-zeigen\\\", \\\"Eigenschaft benennen\\\"]\", \"[\\\"schritt-zeigen\\\", \\\"Schrittstand anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "732d371c2135b53f",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"schritt-zeigen\\\", \\\"Optionen auflisten\\\"]\", \"[\\\"schritt-zeigen\\\", \\\"Schrittstand anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "590d6974e948c69c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\"]": {
|
||||||
|
"abdruck": "34e23fca918e5dc0",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\"]": {
|
||||||
|
"abdruck": "eb521941a5dc979a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "271109c8b42cf5d5",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "618d3c213492555a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "b45252ce7cc39ed9",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "d7ade9bdd7d0c66e",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\"]": {
|
||||||
|
"abdruck": "97ceded85ca2035c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "ba83ab596b72af22",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "6f23248b5fbd7be4",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "7350e93a2a368ac1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "50753ef9f3d5c8b5",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "b49e65e1c60b5307",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "b165f28b36fc3637",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "0bf509fed71f1c25",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "de5acdbf92511e14",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "7c9a06867c701267",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "f45378da83876bae",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "9fc4c9cf8ae1d7fb",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "a71f8b8b92d1025c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "e3270a7176c63620",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "44e775b8e8b106d1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"vorschau-zeigen\\\", \\\"Bild wählen\\\"]\", \"[\\\"vorschau-zeigen\\\", \\\"Galerie blättern\\\"]\"]": {
|
||||||
|
"abdruck": "19851d2901c309a1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"vorschau-zeigen\\\", \\\"Bild wählen\\\"]\", \"[\\\"vorschau-zeigen\\\", \\\"Vorschau laden\\\"]\"]": {
|
||||||
|
"abdruck": "508415e6500f9fca",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"vorschau-zeigen\\\", \\\"Galerie blättern\\\"]\", \"[\\\"vorschau-zeigen\\\", \\\"Vorschau laden\\\"]\"]": {
|
||||||
|
"abdruck": "86aa1f412d5a5dcd",
|
||||||
|
"gleich": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stil": {
|
||||||
|
"[\"auswahl-anstossen\", \"\"]": {
|
||||||
|
"abdruck": "aeec25973dafe92f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Gewählte Option anzeigen\"]": {
|
||||||
|
"abdruck": "59058982f7989af8",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Hinweistext anzeigen\"]": {
|
||||||
|
"abdruck": "b3568e0a2585d71c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Schrittnummer anzeigen\"]": {
|
||||||
|
"abdruck": "117d58f0d4b21973",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"\"]": {
|
||||||
|
"abdruck": "4b0267b165360857",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Beratung erreichen\"]": {
|
||||||
|
"abdruck": "cfdb65d682c3e068",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Optionsübersicht öffnen\"]": {
|
||||||
|
"abdruck": "28d5587c78199c54",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Vertrauen zeigen\"]": {
|
||||||
|
"abdruck": "8c00dec8d3ebf751",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"\"]": {
|
||||||
|
"abdruck": "1e0d2ca5296aabea",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Konfigurator aktivieren\"]": {
|
||||||
|
"abdruck": "dca9bbf7361f5520",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Optionsbild am Datensatz pflegen\"]": {
|
||||||
|
"abdruck": "cc0701aafd02ac3a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Optionsübersicht hinterlegen\"]": {
|
||||||
|
"abdruck": "de6c0e3e80d47496",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Trust-Logo hinterlegen\"]": {
|
||||||
|
"abdruck": "ebd1a72ece157e69",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"\"]": {
|
||||||
|
"abdruck": "ace46c77101e26b3",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Ansicht schließen\"]": {
|
||||||
|
"abdruck": "6bdaf5e60fb3baa9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Option aus Ansicht wählen\"]": {
|
||||||
|
"abdruck": "cc10af5dda4a7857",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Optionsdetail öffnen\"]": {
|
||||||
|
"abdruck": "e00d07ce7b75d8fc",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"\"]": {
|
||||||
|
"abdruck": "f0d53ef421968759",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Eigenschaft benennen\"]": {
|
||||||
|
"abdruck": "ec4828e2e9197083",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Optionen auflisten\"]": {
|
||||||
|
"abdruck": "d8e6daed0f36098f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Schrittstand anzeigen\"]": {
|
||||||
|
"abdruck": "42a1c7f94c30f72d",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"\"]": {
|
||||||
|
"abdruck": "0835908894fa892c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Auswahl übernehmen\"]": {
|
||||||
|
"abdruck": "6683956c63370244",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurationsdaten bereitstellen\"]": {
|
||||||
|
"abdruck": "498f3141e86f8db0",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurator schließen\"]": {
|
||||||
|
"abdruck": "866918b327061449",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Nächsten Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "8d810a760dfec037",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Option wählen\"]": {
|
||||||
|
"abdruck": "ac4eae399ac3a15e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Schritt öffnen\"]": {
|
||||||
|
"abdruck": "40f795456747214e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Vorherigen Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "a63a26a8baea9c07",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"\"]": {
|
||||||
|
"abdruck": "52976631fd09993a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Bild wählen\"]": {
|
||||||
|
"abdruck": "a3e980ef7678a446",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Galerie blättern\"]": {
|
||||||
|
"abdruck": "47ccb441120c1470",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Vorschau laden\"]": {
|
||||||
|
"abdruck": "29e5951f170dba26",
|
||||||
|
"probleme": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verworfen": {}
|
||||||
|
}
|
||||||
122
daten/MtoVariantConfigurator/.planung/.verlauf.json
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"titel": "„Lightbox“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"lightbox": "<!-- titel -->\nLightbox\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nZeigt eine vergrößerte Ansicht der Optionsabbildung mit ihrem Namen\n----------------------------------------\n<!-- regeln -->\n- lightboxSelect bestätigt Auswahl und schließt\n- Escape und x-Icon schließen\n- Tab und Shift-Tab zirkulieren im Fokusring\n- Geschlossen kehrt Fokus zum Auslöser zurück\n- hidden-Attribut entfernt aus dem Barrierefreiheitsbaum\n----------------------------------------\n<!-- funktion -->\nAuswahl bestätigen\n<!-- ziel -->\nWählt eine Option im aktuellen Schritt und berechnet die gültigen Folgeschritte neu\n<!-- regeln -->\n- Bleibt im aktuellen Schritt\n- Invalidiert Folgeschritte mit ungültigen Kombinationen\n- Setzt Fokus auf die gewählte Option\n- Aktualisiert Galerie und Preis\n<!-- testen -->\n- Lightbox mit Option öffnen\n- LMB auf Auswahl bestätigen\n- Option ist markiert\n- Lightbox geschlossen\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- regeln -->\n- Schließt die Lightbox, ohne den Konfigurator zu schließen\n- x-Icon oder Escape beenden die Lightbox\n- Fokus kehrt zum Auslöser in der Optionsliste zurück\n<!-- testen -->\n- Option mit Bildauswahl öffnet die Lightbox\n- Escape drücken\n- Lightbox geschlossen, Modal offen\n----------------------------------------\n<!-- funktion -->\nLightbox schließen\n<!-- ziel -->\nEntfernt die vergrößerte Produktansicht wieder\n<!-- regeln -->\n- x-Icon oder Escape schließt\n- Zurück zum Dialog gibt Focus\n<!-- testen -->\n- Modal öffnen\n- Option mit Bild wählen\n- Leinwand-Icon klicken\n- Lightbox sichtbar\n- Escape drücken\n- Lightbox geschlossen, Dialog fokussiert\n----------------------------------------\n<!-- funktion -->\nLightbox öffnen\n<!-- ziel -->\nZeigt das Produktfoto einer Variante option in Originalgröße\n<!-- regeln -->\n- lightboxImage zeigt option.media\n- lightboxCaption zeigt option.name\n- lightboxSelect erhält Fokus\n<!-- testen -->\n- Modal öffnen\n- Auf Option mit Bild klicken\n- Info-Button der Option klicken\n- Lightbox zeigt das Foto in Originalgröße\n----------------------------------------\n<!-- funktion -->\nTastatur verwenden\n<!-- ziel -->\nBedient Modal und Lightbox per Tastatur\n<!-- regeln -->\n- Escape schließt Lightbox oder Modal\n- Tab zirkuliert im Fokusring vorwärts\n- Shift-Tab zirkuliert im Fokusring rückwärts\n<!-- testen -->\n- Modal öffnen\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen\n- Escape drücken\n- Modal geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Konfigurator“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"konfigurator": "<!-- titel -->\nKonfigurator\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nFührt durch Variantenkonfiguration in Einzelschritten mit Live-Vorschau\n----------------------------------------\n<!-- regeln -->\n- Aktiv nur wenn Produkt mto_variant_configurator_active gesetzt hat\n- Optionen-Bilder nur bis zwei Schritte\n- Galerie und Preis aktualisieren live ohne Seitenreload\n- Body erhält CSS-Klasse mto-vc-modal-open solange Modal offen\n- Escape schließt Modal\n- Schließen stellt Fokus auf vorheriges Element\n- Registriert als mto-vc-variants-card in swProductDetail\n- Einstellungen in customFields des Produkts gespeichert\n- Falls inaktiv: Fallback auf Standard-Konfigurator von Shopware\n- Natives Select pro Gruppe im DOM\n- Schritte ohne gültige Optionen werden beim Navigieren übersprungen\n- Ungültige Kombinationen für Folgeschritte werden beim Wählen verworfen\n- Unbesuchte Schritte werden beim Bestätigen mit erster gültiger Option aufgefüllt\n- Deaktivierte Optionen sind nicht auswählbar\n- Gallery-Container erhält CSS-Klasse has-no-gallery wenn keine Bilder vorhanden\n- Schließen bei Lightbox: fokus auf Lightbox-Auswahl-Button\n----------------------------------------\n<!-- funktion -->\nGeführte Konfiguration aktivieren\n<!-- ziel -->\nFührt Kunden schrittweise durch die Variantenauswahl mit Hinweisen und Modal\n<!-- regeln -->\n- Aktiviert bei Produkten mit aktivem Konfigurator-Layout\n- Zeigt jeden Konfigurationsschritt mit Beschriftung\n- Blendentext je Gruppe über Modal aus\n- Inkrementelle Variantenauflösung über resolveUrl\n<!-- testen -->\n- Produktseite mit aktivem Konfigurator öffnen\n- LMB auf ersten Konfigurationsschritt\n- Option im Modal wählen\n- Modal schließt und nächster Schritt markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurationsdaten abrufen\n<!-- ziel -->\nLiefert Variantendaten für eine Live-Aktualisierung im Konfigurator.\n<!-- regeln -->\n- GET /mto-variant-configurator/resolve/{productId}\n- Query-Parameter switched enthält die aktuell gewechselte Gruppe\n- Query-Parameter options enthält ein JSON-Array gewählter Optionen\n- Ungültiges JSON führt zu leerem options-Array\n- VariantNotFoundException hält productId als Fallback\n- Rückgabe enthält variantId, gallery media und Preis\n- Kein HTTP-Cache wegen kundenspezifischer Preise\n<!-- testen -->\n- GET /mto-variant-configurator/resolve/STRG-PRODUKT-ID?options=[{\"optionId\":\"farbe\",\"option\":\"rot\"}]\n- Antwort enthält variantId\n- Antwort enthält gallery media\n- Antwort enthält Preis\n----------------------------------------\n<!-- funktion -->\nKonfigurator öffnen\n<!-- ziel -->\nÖffnet den Konfigurator ab einem gewählten Schritt\n<!-- regeln -->\n- Erster Aufruf zeigt das Modal\n- Weiterer Aufruf wechselt den Schritt\n- Fokus vorheriges Element wird gespeichert\n- Body erhält CSS-Klasse mto-vc-modal-open\n- Escape-Schließen wird registriert\n- Galerie und Preis werden aktualisiert\n- Fokus springt auf den Next-Button\n<!-- testen -->\n- LMB auf Konfigurator-Trigger klicken\n- Modal erscheint im gewählten Schritt\n----------------------------------------\n<!-- funktion -->\nKonfigurator-Karte registrieren\n<!-- ziel -->\nMacht die Einstellungen des Varianten-Konfigurators auf der Produktdetail-Seite verfügbar\n<!-- regeln -->\n- Registriert als mto-vc-variants-card\n- Nutzt Produkt aus swProductDetail\n- Speichert Einstellungen in customFields des Produkts\n<!-- testen -->\n- Produktdetail-Seite eines Produkts öffnen\n- Tab Variants aufrufen\n- Registrierte mto-vc-variants-card wird angezeigt\n----------------------------------------\n<!-- funktion -->\nPlugin registrieren\n<!-- ziel -->\nMacht das Plugin für Elemente mit dem Selektor verfügbar\n<!-- regeln -->\n- Registriert unter VariantConfiguratorPlugin\n- Nutzt window.PluginManager\n- Bindet an [data-mto-variant-configurator]\n<!-- testen -->\n- PluginManager.register aufrufen\n- Element mit data-mto-variant-configurator erstellen\n- Plugin auf Element angewendet\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Konfigurator“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"konfigurator": "<!-- titel -->\nKonfigurator\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nFührt durch Variantenkonfiguration in Einzelschritten mit Live-Vorschau\n----------------------------------------\n<!-- regeln -->\n- Aktiv nur wenn Produkt mto_variant_configurator_active gesetzt hat\n- Optionen-Bilder nur bis zwei Schritte\n- Galerie und Preis aktualisieren live ohne Seitenreload\n- Body erhält CSS-Klasse mto-vc-modal-open solange Modal offen\n- Escape schließt Modal\n- Schließen stellt Fokus auf vorheriges Element\n- Registriert als mto-vc-variants-card in swProductDetail\n- Einstellungen in customFields des Produkts gespeichert\n- Falls inaktiv: Fallback auf Standard-Konfigurator von Shopware\n- Natives Select pro Gruppe im DOM\n- Schritte ohne gültige Optionen werden beim Navigieren übersprungen\n- Ungültige Kombinationen für Folgeschritte werden beim Wählen verworfen\n- Unbesuchte Schritte werden beim Bestätigen mit erster gültiger Option aufgefüllt\n- Deaktivierte Optionen sind nicht auswählbar\n- Gallery-Container erhält CSS-Klasse has-no-gallery wenn keine Bilder vorhanden\n- Schließen bei Lightbox: fokus auf Lightbox-Auswahl-Button\n----------------------------------------\n<!-- funktion -->\nGeführte Konfiguration aktivieren\n<!-- ziel -->\nFührt Kunden schrittweise durch die Variantenauswahl mit Hinweisen und Modal\n<!-- regeln -->\n- Aktiviert bei Produkten mit aktivem Konfigurator-Layout\n- Zeigt jeden Konfigurationsschritt mit Beschriftung\n- Blendentext je Gruppe über Modal aus\n- Inkrementelle Variantenauflösung über resolveUrl\n<!-- testen -->\n- Produktseite mit aktivem Konfigurator öffnen\n- LMB auf ersten Konfigurationsschritt\n- Option im Modal wählen\n- Modal schließt und nächster Schritt markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurationsdaten abrufen\n<!-- ziel -->\nLiefert Variantendaten für eine Live-Aktualisierung im Konfigurator\n<!-- regeln -->\n- GET /mto-variant-configurator/resolve/{productId}\n- Query-Parameter switched enthält die aktuell gewechselte Gruppe\n- Query-Parameter options enthält ein JSON-Array gewählter Optionen\n- Ungültiges JSON führt zu leerem options-Array\n- VariantNotFoundException hält productId als Fallback\n- Rückgabe enthält variantId, gallery media und Preis\n- Kein HTTP-Cache wegen kundenspezifischer Preise\n<!-- testen -->\n- GET /mto-variant-configurator/resolve/STRG-PRODUKT-ID?options=[{\"optionId\":\"farbe\",\"option\":\"rot\"}]\n- Antwort enthält variantId\n- Antwort enthält gallery media\n- Antwort enthält Preis\n----------------------------------------\n<!-- funktion -->\nKonfigurator öffnen\n<!-- ziel -->\nÖffnet den Konfigurator ab einem gewählten Schritt\n<!-- regeln -->\n- Erster Aufruf zeigt das Modal\n- Weiterer Aufruf wechselt den Schritt\n- Fokus vorheriges Element wird gespeichert\n- Body erhält CSS-Klasse mto-vc-modal-open\n- Escape-Schließen wird registriert\n- Galerie und Preis werden aktualisiert\n- Fokus springt auf den Next-Button\n<!-- testen -->\n- LMB auf Konfigurator-Trigger klicken\n- Modal erscheint im gewählten Schritt\n----------------------------------------\n<!-- funktion -->\nKonfigurator-Karte registrieren\n<!-- ziel -->\nMacht die Einstellungen des Varianten-Konfigurators auf der Produktdetail-Seite verfügbar\n<!-- regeln -->\n- Registriert als mto-vc-variants-card\n- Nutzt Produkt aus swProductDetail\n- Speichert Einstellungen in customFields des Produkts\n<!-- testen -->\n- Produktdetail-Seite eines Produkts öffnen\n- Tab Variants aufrufen\n- Registrierte mto-vc-variants-card wird angezeigt\n----------------------------------------\n<!-- funktion -->\nPlugin registrieren\n<!-- ziel -->\nMacht das Plugin für Elemente mit dem Selektor verfügbar\n<!-- regeln -->\n- Registriert unter VariantConfiguratorPlugin\n- Nutzt window.PluginManager\n- Bindet an [data-mto-variant-configurator]\n<!-- testen -->\n- PluginManager.register aufrufen\n- Element mit data-mto-variant-configurator erstellen\n- Plugin auf Element angewendet\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Konfigurator“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"konfigurator": "<!-- titel -->\nKonfigurator\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nFührt durch Variantenkonfiguration in Einzelschritten mit Live-Vorschau\n----------------------------------------\n<!-- regeln -->\n- Aktiv nur wenn Produkt mto_variant_configurator_active gesetzt hat\n- Optionen-Bilder nur bis zwei Schritte\n- Galerie und Preis aktualisieren live ohne Seitenreload\n- Body erhält CSS-Klasse mto-vc-modal-open solange Modal offen\n- Escape schließt Modal\n- Schließen stellt Fokus auf vorheriges Element\n- Registriert als mto-vc-variants-card in swProductDetail\n- Einstellungen in customFields des Produkts gespeichert\n- Falls inaktiv: Fallback auf Standard-Konfigurator von Shopware\n- Natives Select pro Gruppe im DOM\n- Schritte ohne gültige Optionen werden beim Navigieren übersprungen\n- Ungültige Kombinationen für Folgeschritte werden beim Wählen verworfen\n- Unbesuchte Schritte werden beim Bestätigen mit erster gültiger Option aufgefüllt\n- Deaktivierte Optionen sind nicht auswählbar\n- Gallery-Container erhält CSS-Klasse has-no-gallery wenn keine Bilder vorhanden\n- Schließen bei Lightbox: fokus auf Lightbox-Auswahl-Button\n----------------------------------------\n<!-- funktion -->\nGeführte Konfiguration aktivieren\n<!-- ziel -->\nFührt Kunden schrittweise durch die Variantenauswahl mit Hinweisen und Modal\n<!-- regeln -->\n- Aktiviert bei Produkten mit aktivem Konfigurator-Layout\n- Zeigt jeden Konfigurationsschritt mit Beschriftung\n- Blendentext je Gruppe über Modal aus\n- Inkrementelle Variantenauflösung über resolveUrl\n<!-- testen -->\n- Produktseite mit aktivem Konfigurator öffnen\n- LMB auf ersten Konfigurationsschritt\n- Option im Modal wählen\n- Modal schließt und nächster Schritt markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurationsdaten abrufen\n<!-- ziel -->\nLiefert Variantendaten für eine Live-Aktualisierung im Konfigurator\n<!-- regeln -->\n- Endpoint mto-variant-configurator resolve\n- Query-Parameter switched enthält die aktuell gewechselte Gruppe\n- Query-Parameter options enthält ein JSON-Array gewählter Optionen\n- Ungültiges JSON führt zu leerem options-Array\n- VariantNotFoundException hält productId als Fallback\n- Rückgabe enthält variantId, gallery media und Preis\n- Kein HTTP-Cache wegen kundenspezifischer Preise\n<!-- testen -->\n- GET /mto-variant-configurator/resolve/STRG-PRODUKT-ID?options=[{\"optionId\":\"farbe\",\"option\":\"rot\"}]\n- Antwort enthält variantId\n- Antwort enthält gallery media\n- Antwort enthält Preis\n----------------------------------------\n<!-- funktion -->\nKonfigurator öffnen\n<!-- ziel -->\nÖffnet den Konfigurator ab einem gewählten Schritt\n<!-- regeln -->\n- Erster Aufruf zeigt das Modal\n- Weiterer Aufruf wechselt den Schritt\n- Fokus vorheriges Element wird gespeichert\n- Body erhält CSS-Klasse mto-vc-modal-open\n- Escape-Schließen wird registriert\n- Galerie und Preis werden aktualisiert\n- Fokus springt auf den Next-Button\n<!-- testen -->\n- LMB auf Konfigurator-Trigger klicken\n- Modal erscheint im gewählten Schritt\n----------------------------------------\n<!-- funktion -->\nKonfigurator-Karte registrieren\n<!-- ziel -->\nMacht die Einstellungen des Varianten-Konfigurators auf der Produktdetail-Seite verfügbar\n<!-- regeln -->\n- Registriert als mto-vc-variants-card\n- Nutzt Produkt aus swProductDetail\n- Speichert Einstellungen in customFields des Produkts\n<!-- testen -->\n- Produktdetail-Seite eines Produkts öffnen\n- Tab Variants aufrufen\n- Registrierte mto-vc-variants-card wird angezeigt\n----------------------------------------\n<!-- funktion -->\nPlugin registrieren\n<!-- ziel -->\nMacht das Plugin für Elemente mit dem Selektor verfügbar\n<!-- regeln -->\n- Registriert unter VariantConfiguratorPlugin\n- Nutzt window.PluginManager\n- Bindet an [data-mto-variant-configurator]\n<!-- testen -->\n- PluginManager.register aufrufen\n- Element mit data-mto-variant-configurator erstellen\n- Plugin auf Element angewendet\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Galerie“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"galerie": "<!-- titel -->\nGalerie\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nErmöglicht das Anzeigen und Durchblättern von Produktbildern im Konfigurator-Modal\n----------------------------------------\n<!-- regeln -->\n- Einzelne Galerie pro Modal\n- Navigiert zyklisch\n- Ein Bild deaktiviert Navigation\n- Hauptbild über thumbnails aktualisierbar\n----------------------------------------\n<!-- funktion -->\nGalerie nächste anzeigen\n<!-- ziel -->\nZeigt das nächste Bild der Galerie\n<!-- regeln -->\n- Mindestens 2 Bilder vorhanden\n- Zyklisch von letztem zu erstem Bild\n<!-- testen -->\n- Auf next-Button klicken\n- Nächstes Bild wird angezeigt\n----------------------------------------\n<!-- funktion -->\nGalerie rückwärts blättern\n<!-- ziel -->\nVorheriges Bild der Galerie anzeigen\n<!-- regeln -->\n- Nur mit mindestens zwei Bildern\n- Springt von erstem zum letzten Bild\n- Aktualisiert Miniaturansicht\n<!-- testen -->\n- Vollmodus öffnen\n- Klick auf Vorheriges-Bild-Icon\n- Erstes Bild zeigt\n----------------------------------------\n<!-- funktion -->\nGalerie vorherige anzeigen\n<!-- ziel -->\nBlendet das vorherige Bild der Galerie ein\n<!-- regeln -->\n- mto-vc-gallery-prev-Icon\n- Bei nur einem Bild keine Wirkung\n<!-- testen -->\n- Galerie mit mindestens zwei Bildern öffnen\n- LMB auf mto-vc-gallery-prev-Icon\n- Vorheriges Bild im Stage-Image angezeigt\n----------------------------------------\n<!-- funktion -->\nGalerie vorwärts blättern\n<!-- ziel -->\nZeigt das nächste Bild der Galerie\n<!-- regeln -->\n- Geht bei weniger als zwei Bildern nicht\n- Springt vom letzten zum ersten Bild\n<!-- testen -->\n- Modal öffnen\n- Klick auf gallery-next\n- Nächstes Bild wird angezeigt\n- Klick erneut bei letztem Bild\n- Erstes Bild wird angezeigt\n----------------------------------------\n<!-- funktion -->\nThumbnail auswählen\n<!-- ziel -->\nGreift direkt auf ein Produktbild zu, ohne durch die Galerie zu blättern\n<!-- regeln -->\n- Aktives Thumbnail zeigt x-Icon\n- Klick aktualisiert Hauptbild und Aktivzustand\n<!-- testen -->\n- Galerie mit mehreren Thumbnails öffnen\n- Klick auf Thumbnail\n- Hauptbild entspricht geklicktem Thumbnail\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Lightbox“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"lightbox": "<!-- titel -->\nLightbox\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nZeigt eine vergrößerte Ansicht der Optionsabbildung mit ihrem Namen\n----------------------------------------\n<!-- regeln -->\n- lightboxSelect bestätigt Auswahl und schließt\n- Escape und x-Icon schließen\n- Tab und Shift-Tab zirkulieren im Fokusring\n- Geschlossen kehrt Fokus zum Auslöser zurück\n- hidden-Attribut entfernt aus dem Barrierefreiheitsbaum\n----------------------------------------\n<!-- funktion -->\nAuswahl bestätigen\n<!-- ziel -->\nWählt eine Option im aktuellen Schritt und berechnet die gültigen Folgeschritte neu\n<!-- regeln -->\n- Bleibt im aktuellen Schritt\n- Invalidiert Folgeschritte mit ungültigen Kombinationen\n- Setzt Fokus auf die gewählte Option\n- Aktualisiert Galerie und Preis\n<!-- testen -->\n- Lightbox mit Option öffnen\n- LMB auf Auswahl bestätigen\n- Option markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- regeln -->\n- Schließt die Lightbox, ohne den Konfigurator zu schließen\n- x-Icon oder Escape beenden die Lightbox\n- Fokus kehrt zum Auslöser in der Optionsliste zurück\n<!-- testen -->\n- Option mit Bildauswahl öffnet die Lightbox\n- Escape drücken\n- Lightbox geschlossen, Modal offen\n----------------------------------------\n<!-- funktion -->\nLightbox schließen\n<!-- ziel -->\nEntfernt die vergrößerte Produktansicht wieder\n<!-- regeln -->\n- x-Icon oder Escape schließt\n- Zurück zum Dialog gibt Focus\n<!-- testen -->\n- Modal öffnen\n- Option mit Bild wählen\n- Leinwand-Icon klicken\n- Lightbox sichtbar\n- Escape drücken\n- Lightbox geschlossen, Dialog fokussiert\n----------------------------------------\n<!-- funktion -->\nLightbox öffnen\n<!-- ziel -->\nZeigt das Produktfoto einer Variante option in Originalgröße\n<!-- regeln -->\n- lightboxImage zeigt option.media\n- lightboxCaption zeigt option.name\n- lightboxSelect erhält Fokus\n<!-- testen -->\n- Modal öffnen\n- Auf Option mit Bild klicken\n- Info-Button der Option klicken\n- Lightbox zeigt das Foto in Originalgröße\n----------------------------------------\n<!-- funktion -->\nTastatur verwenden\n<!-- ziel -->\nBedient Modal und Lightbox per Tastatur\n<!-- regeln -->\n- Escape schließt Lightbox oder Modal\n- Tab zirkuliert im Fokusring vorwärts\n- Shift-Tab zirkuliert im Fokusring rückwärts\n<!-- testen -->\n- Modal öffnen\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen\n- Escape drücken\n- Modal geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Produkt“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"produkt": "<!-- titel -->\nProdukt\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nStellt dem Produkttemplate Daten des Variantenkonfigurators bereit\n----------------------------------------\n<!-- regeln -->\n- Inaktiver Konfigurator setzt mtoVariantConfiguratorActive auf false\n- Aktiver Konfigurator weist alle Konfigurator-Variablen zu\n- sw-product-detail-variants wird mit eigener Template-Datei überschrieben\n- mtoVariantConfiguratorTrustLogo fehlt in Regeln und Funktion\n- Produktseite laden: Testschritte beschreiben Ergebnis statt Handlung\n- Variantenansicht erweitern: Funktion hat keine Testschritte, nur Regeln\n----------------------------------------\n<!-- funktion -->\nProduktseite laden\n<!-- ziel -->\nBereitet Variant-Konfigurationsdaten für die Produktseite vor\n<!-- regeln -->\n- mtoVariantConfiguratorActive false bei inaktivem Konfigurator\n- Alle Variablen bei aktivem Konfigurator zugewiesen\n- Initial-Variante für vorausgefülltes Modal geladen\n<!-- testen -->\n- Produkt mit aktivem Konfigurator öffnen\n- Seite vollständig laden\n- Template-Variablen prüfen\n- Seite ohne Konfigurator öffnen\n- mtoVariantConfiguratorActive ist false\n----------------------------------------\n<!-- funktion -->\nVariantenansicht erweitern\n<!-- ziel -->\nFügt der Produktdetail-Variantenansicht zusätzliche Felder oder Funktionen hinzu\n<!-- regeln -->\n- Überschreibt sw-product-detail-variants\n- Eigene Template-Datei wird geladen\n<!-- testen -->\n- Produktdetailseite öffnen\n- Varianten-Bereich aufrufen\n- Erweiterte Felder oder Funktionen sichtbar\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Lightbox“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"lightbox": "<!-- titel -->\nLightbox\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nZeigt eine vergrößerte Ansicht der Optionsabbildung mit ihrem Namen\n----------------------------------------\n<!-- regeln -->\n- lightboxSelect bestätigt Auswahl und schließt\n- Escape und x-Icon schließen\n- Tab und Shift-Tab zirkulieren im Fokusring\n- Geschlossen kehrt Fokus zum Auslöser zurück\n- hidden-Attribut entfernt aus dem Barrierefreiheitsbaum\n----------------------------------------\n<!-- funktion -->\nAuswahl bestätigen\n<!-- ziel -->\nWählt eine Option im aktuellen Schritt und berechnet die gültigen Folgeschritte neu\n<!-- regeln -->\n- Bleibt im aktuellen Schritt\n- Invalidiert Folgeschritte mit ungültigen Kombinationen\n- Setzt Fokus auf die gewählte Option\n- Aktualisiert Galerie und Preis\n<!-- testen -->\n- Lightbox mit Option öffnen\n- LMB auf Auswahl bestätigen\n- Option markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- regeln -->\n- Schließt die Lightbox, ohne den Konfigurator zu schließen\n- x-Icon oder Escape beenden die Lightbox\n- Fokus kehrt zum Auslöser in der Optionsliste zurück\n<!-- testen -->\n- Option mit Bildauswahl öffnet die Lightbox\n- Escape drücken\n- Lightbox geschlossen, Modal offen\n----------------------------------------\n<!-- funktion -->\nLightbox schließen\n<!-- ziel -->\nEntfernt die vergrößerte Produktansicht wieder\n<!-- regeln -->\n- x-Icon oder Escape schließt\n- Zurück zum Dialog gibt Focus\n<!-- testen -->\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen, Dialog fokussiert\n----------------------------------------\n<!-- funktion -->\nLightbox öffnen\n<!-- ziel -->\nZeigt das Produktfoto einer Variante option in Originalgröße\n<!-- regeln -->\n- lightboxImage zeigt option.media\n- lightboxCaption zeigt option.name\n- lightboxSelect erhält Fokus\n<!-- testen -->\n- Modal öffnen\n- Auf Option mit Bild klicken\n- Info-Button der Option klicken\n- Lightbox zeigt das Foto in Originalgröße\n----------------------------------------\n<!-- funktion -->\nTastatur verwenden\n<!-- ziel -->\nBedient Modal und Lightbox per Tastatur\n<!-- regeln -->\n- Escape schließt Lightbox oder Modal\n- Tab zirkuliert im Fokusring vorwärts\n- Shift-Tab zirkuliert im Fokusring rückwärts\n<!-- testen -->\n- Modal öffnen\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen\n- Escape drücken\n- Modal geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Galerie“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"galerie": "<!-- titel -->\nGalerie\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nErmöglicht das Anzeigen und Durchblättern von Produktbildern im Konfigurator-Modal\n----------------------------------------\n<!-- regeln -->\n- Einzelne Galerie pro Modal\n- Navigiert zyklisch\n- Ein Bild deaktiviert Navigation\n- Hauptbild über thumbnails aktualisierbar\n----------------------------------------\n<!-- funktion -->\nGalerie nächste anzeigen\n<!-- ziel -->\nZeigt das nächste Bild der Galerie\n<!-- regeln -->\n- Mindestens 2 Bilder vorhanden\n- Zyklisch von letztem zu erstem Bild\n<!-- testen -->\n- Auf next-Button klicken\n- Nächstes Bild wird angezeigt\n----------------------------------------\n<!-- funktion -->\nGalerie rückwärts blättern\n<!-- ziel -->\nVorheriges Bild der Galerie anzeigen\n<!-- regeln -->\n- Nur mit mindestens zwei Bildern\n- Springt von erstem zum letzten Bild\n- Aktualisiert Miniaturansicht\n<!-- testen -->\n- Vollmodus öffnen\n- Klick auf Vorheriges-Bild-Icon\n- Erstes Bild zeigt\n----------------------------------------\n<!-- funktion -->\nGalerie vorherige anzeigen\n<!-- ziel -->\nBlendet das vorherige Bild der Galerie ein\n<!-- regeln -->\n- mto-vc-gallery-prev-Icon\n- Bei nur einem Bild keine Wirkung\n<!-- testen -->\n- Galerie mit mindestens zwei Bildern öffnen\n- LMB auf mto-vc-gallery-prev-Icon\n- Vorheriges Bild im Stage-Image angezeigt\n----------------------------------------\n<!-- funktion -->\nGalerie vorwärts blättern\n<!-- ziel -->\nZeigt das nächste Bild der Galerie\n<!-- regeln -->\n- Geht bei weniger als zwei Bildern nicht\n- Springt vom letzten zum ersten Bild\n<!-- testen -->\n- Modal öffnen\n- Klick auf gallery-next\n- Nächstes Bild wird angezeigt\n----------------------------------------\n<!-- funktion -->\nThumbnail auswählen\n<!-- ziel -->\nGreift direkt auf ein Produktbild zu, ohne durch die Galerie zu blättern\n<!-- regeln -->\n- Aktives Thumbnail zeigt x-Icon\n- Klick aktualisiert Hauptbild und Aktivzustand\n<!-- testen -->\n- Galerie mit mehreren Thumbnails öffnen\n- Klick auf Thumbnail\n- Hauptbild entspricht geklicktem Thumbnail\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Lightbox“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"lightbox": "<!-- titel -->\nLightbox\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nZeigt eine vergrößerte Ansicht der Optionsabbildung mit ihrem Namen\n----------------------------------------\n<!-- regeln -->\n- lightboxSelect bestätigt Auswahl und schließt\n- Escape und x-Icon schließen\n- Tab und Shift-Tab zirkulieren im Fokusring\n- Geschlossen kehrt Fokus zum Auslöser zurück\n- hidden-Attribut entfernt aus dem Barrierefreiheitsbaum\n----------------------------------------\n<!-- funktion -->\nAuswahl bestätigen\n<!-- ziel -->\nWählt eine Option im aktuellen Schritt und berechnet die gültigen Folgeschritte neu\n<!-- regeln -->\n- Bleibt im aktuellen Schritt\n- Invalidiert Folgeschritte mit ungültigen Kombinationen\n- Setzt Fokus auf die gewählte Option\n- Aktualisiert Galerie und Preis\n<!-- testen -->\n- Lightbox mit Option öffnen\n- LMB auf Auswahl bestätigen\n- Option markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- regeln -->\n- Schließt die Lightbox, ohne den Konfigurator zu schließen\n- x-Icon oder Escape beenden die Lightbox\n- Fokus kehrt zum Auslöser in der Optionsliste zurück\n<!-- testen -->\n- LMB auf Option mit Bildauswahl\n- Escape drücken\n- Lightbox geschlossen, Modal offen\n----------------------------------------\n<!-- funktion -->\nLightbox schließen\n<!-- ziel -->\nEntfernt die vergrößerte Produktansicht wieder\n<!-- regeln -->\n- x-Icon oder Escape schließt\n- Zurück zum Dialog gibt Focus\n<!-- testen -->\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen, Dialog fokussiert\n----------------------------------------\n<!-- funktion -->\nLightbox öffnen\n<!-- ziel -->\nZeigt das Produktfoto einer Variante option in Originalgröße\n<!-- regeln -->\n- lightboxImage zeigt option.media\n- lightboxCaption zeigt option.name\n- lightboxSelect erhält Fokus\n<!-- testen -->\n- Modal öffnen\n- Auf Option mit Bild klicken\n- Info-Button der Option klicken\n- Lightbox zeigt das Foto in Originalgröße\n----------------------------------------\n<!-- funktion -->\nTastatur verwenden\n<!-- ziel -->\nBedient Modal und Lightbox per Tastatur\n<!-- regeln -->\n- Escape schließt Lightbox oder Modal\n- Tab zirkuliert im Fokusring vorwärts\n- Shift-Tab zirkuliert im Fokusring rückwärts\n<!-- testen -->\n- Modal öffnen\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen\n- Escape drücken\n- Modal geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Lightbox“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"lightbox": "<!-- titel -->\nLightbox\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nZeigt eine vergrößerte Ansicht der Optionsabbildung mit ihrem Namen\n----------------------------------------\n<!-- regeln -->\n- lightboxSelect bestätigt Auswahl\n- lightboxSelect schließt\n- Escape schließt\n- x-Icon schließt\n- Geschlossen kehrt Fokus zum Auslöser zurück\n- hidden-Attribut entfernt aus dem Barrierefreiheitsbaum\n----------------------------------------\n<!-- funktion -->\nAuswahl bestätigen\n<!-- ziel -->\nWählt eine Option im aktuellen Schritt und berechnet die gültigen Folgeschritte neu\n<!-- regeln -->\n- Bleibt im aktuellen Schritt\n- Invalidiert Folgeschritte mit ungültigen Kombinationen\n- Setzt Fokus auf die gewählte Option\n- Aktualisiert Galerie und Preis\n<!-- testen -->\n- Lightbox mit Option öffnen\n- LMB auf Auswahl bestätigen\n- Option markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- regeln -->\n- Schließt die Lightbox, ohne den Konfigurator zu schließen\n- x-Icon oder Escape beenden die Lightbox\n- Fokus kehrt zum Auslöser in der Optionsliste zurück\n<!-- testen -->\n- LMB auf Option mit Bildauswahl\n- Escape drücken\n- Lightbox geschlossen, Modal offen\n----------------------------------------\n<!-- funktion -->\nLightbox schließen\n<!-- ziel -->\nEntfernt die vergrößerte Produktansicht wieder\n<!-- regeln -->\n- x-Icon oder Escape schließt\n- Zurück zum Dialog gibt Focus\n<!-- testen -->\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen, Dialog fokussiert\n----------------------------------------\n<!-- funktion -->\nLightbox öffnen\n<!-- ziel -->\nZeigt das Produktfoto einer Variante option in Originalgröße\n<!-- regeln -->\n- lightboxImage zeigt option.media\n- lightboxCaption zeigt option.name\n- lightboxSelect erhält Fokus\n<!-- testen -->\n- Modal öffnen\n- Auf Option mit Bild klicken\n- Info-Button der Option klicken\n- Lightbox zeigt das Foto in Originalgröße\n----------------------------------------\n<!-- funktion -->\nTastatur verwenden\n<!-- ziel -->\nBedient Modal und Lightbox per Tastatur\n<!-- regeln -->\n- Escape schließt Lightbox oder Modal\n- Tab zirkuliert im Fokusring vorwärts\n- Shift-Tab zirkuliert im Fokusring rückwärts\n<!-- testen -->\n- Modal öffnen\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen\n- Escape drücken\n- Modal geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Konfigurator“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"konfigurator": "<!-- titel -->\nKonfigurator\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nFührt durch Variantenkonfiguration in Einzelschritten mit Live-Vorschau\n----------------------------------------\n<!-- regeln -->\n- Aktiv nur wenn Produkt mto_variant_configurator_active gesetzt hat\n- Optionen-Bilder nur bis zwei Schritte\n- Galerie und Preis aktualisieren live ohne Seitenreload\n- Body erhält CSS-Klasse mto-vc-modal-open solange Modal offen\n- Escape schließt Modal\n- Schließen stellt Fokus auf vorheriges Element\n- Registriert als mto-vc-variants-card in swProductDetail\n- Einstellungen in customFields des Produkts gespeichert\n- Falls inaktiv: Fallback auf Standard-Konfigurator von Shopware\n- Natives Select pro Gruppe im DOM\n- Schritte ohne gültige Optionen werden beim Navigieren übersprungen\n- Ungültige Kombinationen für Folgeschritte werden beim Wählen verworfen\n- Unbesuchte Schritte werden beim Bestätigen mit erster gültiger Option aufgefüllt\n- Deaktivierte Optionen sind nicht auswählbar\n- Gallery-Container erhält CSS-Klasse has-no-gallery wenn keine Bilder vorhanden\n- Schließen bei Lightbox: fokus auf Lightbox-Auswahl-Button\n----------------------------------------\n<!-- funktion -->\nGeführte Konfiguration aktivieren\n<!-- ziel -->\nFührt Kunden schrittweise durch die Variantenauswahl mit Hinweisen und Modal\n<!-- regeln -->\n- Aktiviert bei Produkten mit aktivem Konfigurator-Layout\n- Zeigt jeden Konfigurationsschritt mit Beschriftung\n- Blendentext je Gruppe über Modal aus\n- Inkrementelle Variantenauflösung über resolveUrl\n<!-- testen -->\n- Produktseite mit aktivem Konfigurator öffnen\n- LMB auf ersten Konfigurationsschritt\n- Option im Modal wählen\n- Modal schließt und nächster Schritt markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurationsdaten abrufen\n<!-- ziel -->\nLiefert Variantendaten für eine Live-Aktualisierung im Konfigurator\n<!-- regeln -->\n- Endpoint mto-variant-configurator resolve\n- Query-Parameter switched enthält die aktuell gewechselte Gruppe\n- Query-Parameter options enthält ein JSON-Array gewählter Optionen\n- Ungültiges JSON führt zu leerem options-Array\n- VariantNotFoundException hält productId als Fallback\n- Rückgabe enthält variantId, gallery media und Preis\n- Kein HTTP-Cache wegen kundenspezifischer Preise\n<!-- testen -->\n- Resolve-Endpunkt mit Produkt-ID und Option aufrufen\n- Antwort enthält variantId\n- Antwort enthält gallery media\n- Antwort enthält Preis\n----------------------------------------\n<!-- funktion -->\nKonfigurator öffnen\n<!-- ziel -->\nÖffnet den Konfigurator ab einem gewählten Schritt\n<!-- regeln -->\n- Erster Aufruf zeigt das Modal\n- Weiterer Aufruf wechselt den Schritt\n- Fokus vorheriges Element wird gespeichert\n- Body erhält CSS-Klasse mto-vc-modal-open\n- Escape-Schließen wird registriert\n- Galerie und Preis werden aktualisiert\n- Fokus springt auf den Next-Button\n<!-- testen -->\n- LMB auf Konfigurator-Trigger klicken\n- Modal erscheint im gewählten Schritt\n----------------------------------------\n<!-- funktion -->\nKonfigurator-Karte registrieren\n<!-- ziel -->\nMacht die Einstellungen des Varianten-Konfigurators auf der Produktdetail-Seite verfügbar\n<!-- regeln -->\n- Registriert als mto-vc-variants-card\n- Nutzt Produkt aus swProductDetail\n- Speichert Einstellungen in customFields des Produkts\n<!-- testen -->\n- Produktdetail-Seite eines Produkts öffnen\n- Tab Variants aufrufen\n- Registrierte mto-vc-variants-card wird angezeigt\n----------------------------------------\n<!-- funktion -->\nPlugin registrieren\n<!-- ziel -->\nMacht das Plugin für Elemente mit dem Selektor verfügbar\n<!-- regeln -->\n- Registriert unter VariantConfiguratorPlugin\n- Nutzt window.PluginManager\n- Bindet an [data-mto-variant-configurator]\n<!-- testen -->\n- PluginManager.register aufrufen\n- Element mit data-mto-variant-configurator erstellen\n- Plugin auf Element angewendet\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Lightbox“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"lightbox": "<!-- titel -->\nLightbox\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nZeigt eine vergrößerte Ansicht der Optionsabbildung mit ihrem Namen\n----------------------------------------\n<!-- regeln -->\n- lightboxSelect bestätigt Auswahl\n- lightboxSelect schließt\n- Escape schließt\n- x-Icon schließt\n- Geschlossen kehrt Fokus zum Auslöser zurück\n- hidden-Attribut entfernt aus dem Barrierefreiheitsbaum\n----------------------------------------\n<!-- funktion -->\nAuswahl bestätigen\n<!-- ziel -->\nWählt eine Option im aktuellen Schritt und berechnet die gültigen Folgeschritte neu\n<!-- regeln -->\n- Bleibt im aktuellen Schritt\n- Invalidiert Folgeschritte mit ungültigen Kombinationen\n- Setzt Fokus auf die gewählte Option\n- Aktualisiert Galerie und Preis\n<!-- testen -->\n- Lightbox mit Option öffnen\n- LMB auf Auswahl bestätigen\n- Option markiert\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- regeln -->\n- Schließt die Lightbox, ohne den Konfigurator zu schließen\n- x-Icon oder Escape beenden die Lightbox\n- Fokus kehrt zum Auslöser in der Optionsliste zurück\n<!-- testen -->\n- LMB auf Option mit Bildauswahl\n- Escape drücken\n- Lightbox geschlossen, Modal offen\n----------------------------------------\n<!-- funktion -->\nLightbox schließen\n<!-- ziel -->\nEntfernt die vergrößerte Produktansicht wieder\n<!-- regeln -->\n- x-Icon schließt\n- Escape schließt\n- Zurück zum Dialog gibt Focus\n<!-- testen -->\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen, Dialog fokussiert\n----------------------------------------\n<!-- funktion -->\nLightbox öffnen\n<!-- ziel -->\nZeigt das Produktfoto einer Variante option in Originalgröße\n<!-- regeln -->\n- lightboxImage zeigt option.media\n- lightboxCaption zeigt option.name\n- lightboxSelect erhält Fokus\n<!-- testen -->\n- Modal öffnen\n- Auf Option mit Bild klicken\n- Info-Button der Option klicken\n- Lightbox zeigt das Foto in Originalgröße\n----------------------------------------\n<!-- funktion -->\nTastatur verwenden\n<!-- ziel -->\nBedient Modal und Lightbox per Tastatur\n<!-- regeln -->\n- Escape schließt Lightbox oder Modal\n- Tab zirkuliert im Fokusring vorwärts\n- Shift-Tab zirkuliert im Fokusring rückwärts\n<!-- testen -->\n- Modal öffnen\n- Lightbox öffnen\n- Escape drücken\n- Lightbox geschlossen\n- Escape drücken\n- Modal geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Option ansehen“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"option-ansehen": "<!-- titel -->\nOption ansehen\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nDer Kunde prüft eine Option groß, bevor er sie nimmt\n----------------------------------------\n<!-- regeln -->\n- Nur Optionen mit Bild tragen den Hinweis\n- Optionsbilder erscheinen bis zwei Schritte\n- Ansicht liegt über dem Konfigurator\n----------------------------------------\n<!-- funktion -->\nOptionsdetail öffnen\n<!-- ziel -->\nZeigt Bild und Namen einer Option in voller Größe\n<!-- regeln -->\n- i-Icon öffnet die Ansicht\n- Enter öffnet die Ansicht\n- Leertaste öffnet die Ansicht\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf i-Icon einer Option\n- Große Ansicht erscheint\n----------------------------------------\n<!-- funktion -->\nOption aus Ansicht wählen\n<!-- ziel -->\nÜbernimmt die betrachtete Option in die Auswahl\n<!-- regeln -->\n- Knopf übernimmt und schließt die Ansicht\n- Schritt bleibt derselbe\n<!-- testen -->\n- Optionsdetail öffnen\n- LMB auf Wählen-Knopf\n- Option ist gewählt\n----------------------------------------\n<!-- funktion -->\nAnsicht schließen\n<!-- ziel -->\nKehrt zur Optionsliste zurück ohne Auswahl\n<!-- regeln -->\n- x-Icon schließt die Ansicht\n- Escape schließt die Ansicht\n- Konfigurator bleibt offen\n<!-- testen -->\n- Optionsdetail öffnen\n- Escape drücken\n- Optionsliste erscheint wieder\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Varianten wählen“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"varianten-waehlen": "<!-- titel -->\nVarianten wählen\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nDer Kunde findet Schritt für Schritt eine bestellbare Kombination\n----------------------------------------\n<!-- regeln -->\n- Erster Schritt zeigt alle Optionen seiner Eigenschaft\n- Folgeschritte zeigen nur kombinierbare Optionen\n- Reihenfolge der Schritte kommt aus dem Backend\n- Ohne Auswahl greifen die Standardvarianten des Elternartikels\n- Schritte ohne gültige Option werden übersprungen\n- Eine Auswahl wechselt den Schritt nicht\n- Ungültige Folgeauswahl fällt beim Wechsel weg\n- Escape schließt den Konfigurator\n- Tabulator bleibt im Dialog\n----------------------------------------\n<!-- funktion -->\nSchritt öffnen\n<!-- ziel -->\nÖffnet den Konfigurator beim angeklickten Schritt\n<!-- regeln -->\n- LMB auf einen Schritt öffnet das Modal\n- Offener Konfigurator wechselt nur den Schritt\n- Modal hängt am Seitenkörper\n<!-- testen -->\n- Produktseite öffnen\n- LMB auf zweiten Schritt\n- Modal öffnet bei Schritt 2\n----------------------------------------\n<!-- funktion -->\nOption wählen\n<!-- ziel -->\nÜbernimmt eine Option in die laufende Auswahl\n<!-- regeln -->\n- Gewählte Option ist hervorgehoben\n- Nicht kombinierbare Optionen sind gesperrt\n- Auswahl verwirft ungültige Folgeschritte\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf eine Option\n- Option ist hervorgehoben\n----------------------------------------\n<!-- funktion -->\nNächsten Schritt anzeigen\n<!-- ziel -->\nFührt zum nächsten Schritt mit gültigen Optionen\n<!-- regeln -->\n- Weiter-Knopf heißt am letzten Schritt Fertig\n- Leere Schritte werden übersprungen\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf Weiter-Knopf\n- Nächster Schritt erscheint\n----------------------------------------\n<!-- funktion -->\nVorherigen Schritt anzeigen\n<!-- ziel -->\nFührt zurück zum letzten Schritt mit Optionen\n<!-- regeln -->\n- Zurück-Knopf ist am ersten Schritt gesperrt\n- Leere Schritte werden übersprungen\n<!-- testen -->\n- Zum zweiten Schritt gehen\n- LMB auf Zurück-Knopf\n- Erster Schritt erscheint\n----------------------------------------\n<!-- funktion -->\nAuswahl übernehmen\n<!-- ziel -->\nWechselt das Produkt auf die gewählte Variante\n<!-- regeln -->\n- Fertig schreibt die Auswahl in die Auswahlfelder\n- Unbesuchte Schritte bekommen die erste gültige Option\n- Der Variantenwechsel von Shopware führt den Wechsel aus\n<!-- testen -->\n- Alle Schritte durchgehen\n- LMB auf Fertig-Knopf\n- Seite zeigt die gewählte Variante\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- ziel -->\nBeendet die Auswahl ohne Wechsel der Variante\n<!-- regeln -->\n- x-Icon schließt das Modal\n- Escape schließt das Modal\n- Fokus kehrt zum Auslöser zurück\n<!-- testen -->\n- Konfigurator öffnen\n- Escape drücken\n- Modal ist geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Vorschau zeigen“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"vorschau-zeigen": "<!-- titel -->\nVorschau zeigen\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nDer Kunde sieht Bild und Preis der gewählten Kombination\n----------------------------------------\n<!-- regeln -->\n- Preis stimmt mit dem Preis der Produktseite überein\n- Antwort wird nicht zwischengespeichert\n- Späte Antworten überholter Anfragen werden verworfen\n- Ohne eigene Bilder greifen die Bilder des Elternartikels\n----------------------------------------\n<!-- funktion -->\nVorschau laden\n<!-- ziel -->\nHolt Bilder und Preis zur laufenden Auswahl\n<!-- regeln -->\n- Jede Auswahl löst eine neue Anfrage aus\n- Ohne Treffer bleibt die Produkt-ID als Rückfall\n- Erstes Bild ist das Titelbild der Variante\n<!-- testen -->\n- Konfigurator öffnen\n- Andere Option wählen\n- Bild und Preis ändern sich\n----------------------------------------\n<!-- funktion -->\nGalerie blättern\n<!-- ziel -->\nZeigt weitere Bilder der gewählten Variante\n<!-- regeln -->\n- Pfeil-Icons blättern vor und zurück\n- Nach dem letzten Bild folgt das erste\n- Bei einem Bild bleibt die Navigation aus\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf Vor-Icon\n- Nächstes Bild erscheint\n----------------------------------------\n<!-- funktion -->\nBild wählen\n<!-- ziel -->\nWechselt das große Bild über die Vorschaubilder\n<!-- regeln -->\n- Aktives Vorschaubild ist hervorgehoben\n- Großes Bild folgt der Wahl\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf drittes Vorschaubild\n- Großes Bild zeigt dieses Motiv\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Varianten wählen“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"varianten-waehlen": "<!-- titel -->\nVarianten wählen\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nDer Kunde findet Schritt für Schritt eine bestellbare Kombination\n----------------------------------------\n<!-- regeln -->\n- Erster Schritt zeigt alle Optionen seiner Eigenschaft\n- Folgeschritte zeigen nur kombinierbare Optionen\n- Der Ablauf trägt beliebig viele Eigenschaften\n- Reihenfolge der Schritte kommt aus dem Backend\n- Ohne Auswahl greifen die Standardvarianten des Elternartikels\n- Schritte ohne gültige Option werden übersprungen\n- Eine Auswahl wechselt den Schritt nicht\n- Ungültige Folgeauswahl fällt beim Wechsel weg\n- Auf kleinen Bildschirmen bleibt der Ablauf derselbe\n- Escape schließt den Konfigurator\n- Tabulator bleibt im Dialog\n----------------------------------------\n<!-- funktion -->\nSchritt öffnen\n<!-- ziel -->\nÖffnet den Konfigurator beim angeklickten Schritt\n<!-- regeln -->\n- LMB auf einen Schritt öffnet das Modal\n- Offener Konfigurator wechselt nur den Schritt\n- Modal hängt am Seitenkörper\n<!-- testen -->\n- Produktseite öffnen\n- LMB auf zweiten Schritt\n- Modal öffnet bei Schritt 2\n----------------------------------------\n<!-- funktion -->\nOption wählen\n<!-- ziel -->\nÜbernimmt eine Option in die laufende Auswahl\n<!-- regeln -->\n- Gewählte Option ist hervorgehoben\n- Nicht kombinierbare Optionen sind gesperrt\n- Auswahl verwirft ungültige Folgeschritte\n- Schritt bleibt nach der Wahl derselbe\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf eine Option\n- Option ist hervorgehoben\n----------------------------------------\n<!-- funktion -->\nNächsten Schritt anzeigen\n<!-- ziel -->\nFührt zum nächsten Schritt mit gültigen Optionen\n<!-- regeln -->\n- Weiter-Knopf heißt am letzten Schritt Fertig\n- Leere Schritte werden übersprungen\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf Weiter-Knopf\n- Nächster Schritt erscheint\n----------------------------------------\n<!-- funktion -->\nVorherigen Schritt anzeigen\n<!-- ziel -->\nFührt zurück zum letzten Schritt mit Optionen\n<!-- regeln -->\n- Zurück-Knopf ist am ersten Schritt gesperrt\n- Leere Schritte werden übersprungen\n<!-- testen -->\n- Zum zweiten Schritt gehen\n- LMB auf Zurück-Knopf\n- Erster Schritt erscheint\n----------------------------------------\n<!-- funktion -->\nAuswahl übernehmen\n<!-- ziel -->\nWechselt das Produkt auf die gewählte Variante\n<!-- regeln -->\n- Fertig schreibt die Auswahl in die Auswahlfelder\n- Unbesuchte Schritte bekommen die erste gültige Option\n- Der Variantenwechsel von Shopware führt den Wechsel aus\n- Die Seite zeigt danach die Adresse der Variante\n<!-- testen -->\n- Alle Schritte durchgehen\n- LMB auf Fertig-Knopf\n- Produktseite zeigt die gewählte Variante\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- ziel -->\nBeendet die Auswahl ohne Wechsel der Variante\n<!-- regeln -->\n- x-Icon schließt das Modal\n- Escape schließt das Modal\n- Fokus kehrt zum Auslöser zurück\n<!-- testen -->\n- Konfigurator öffnen\n- Escape drücken\n- Modal ist geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Layout zuweisen“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"layout-zuweisen": "<!-- titel -->\nLayout zuweisen\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nEin Händler entscheidet je Produkt, ob die geführte Auswahl erscheint\n----------------------------------------\n<!-- regeln -->\n- Einstellungen liegen auf dem Varianten-Tab des Produkts\n- Werte stehen in den customFields des Produkts\n- Bearbeiten erfordert das Recht product.editor\n- Ohne Zuweisung bleibt das Standardlayout aktiv\n----------------------------------------\n<!-- funktion -->\nKonfigurator aktivieren\n<!-- ziel -->\nSchaltet die geführte Variantenauswahl für ein Produkt frei\n<!-- regeln -->\n- Schalter steht auf dem Varianten-Tab\n- Aktiv ersetzt die Buybox des Produkts\n- Inaktiv zeigt das Standardlayout von Shopware\n<!-- testen -->\n- Produkt im Admin öffnen\n- Varianten-Tab öffnen\n- Schalter aktivieren und speichern\n- Buybox zeigt die geführte Auswahl\n----------------------------------------\n<!-- funktion -->\nOptionsübersicht hinterlegen\n<!-- ziel -->\nHinterlegt ein PDF mit allen Varianten und Kombinationen\n<!-- regeln -->\n- Medienfeld steht auf dem Varianten-Tab\n- Feld hängt am Hauptartikel\n- Ohne PDF bleibt der Knopf unsichtbar\n<!-- testen -->\n- Varianten-Tab öffnen\n- PDF im Medienfeld wählen\n- Produkt speichern\n- Modal zeigt den Knopf für Optionen\n----------------------------------------\n<!-- funktion -->\nTrust-Logo hinterlegen\n<!-- ziel -->\nZeigt ein Vertrauenssiegel neben dem Gesamtpreis\n<!-- regeln -->\n- Logo steht in der Plugin-Konfiguration\n- Gilt für den ganzen Verkaufskanal\n- Ohne Logo erscheint nur der Text\n<!-- testen -->\n- Plugin-Konfiguration öffnen\n- Logo im Medienfeld wählen\n- Modal zeigt das Logo am Preis\n----------------------------------------\n<!-- funktion -->\nOptionsbild zuordnen\n<!-- stand -->\noffen\n<!-- ziel -->\nHinterlegt je Variante ein Farbbild oder Materialbild\n<!-- regeln -->\n- Zuordnung erfolgt auf Variantenebene\n- Bild erscheint neben der Option im Modal\n<!-- testen -->\n- Variante im Admin öffnen\n- Bild der Variante zuordnen\n- Modal zeigt das Bild an der Option\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Varianten wählen“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"varianten-waehlen": "<!-- titel -->\nVarianten wählen\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nDer Kunde findet Schritt für Schritt eine bestellbare Kombination\n----------------------------------------\n<!-- regeln -->\n- Erster Schritt zeigt alle Optionen seiner Eigenschaft\n- Folgeschritte zeigen nur kombinierbare Optionen\n- Der Ablauf trägt beliebig viele Eigenschaften\n- Reihenfolge der Schritte kommt aus dem Backend\n- Ohne Auswahl greifen die Standardvarianten des Elternartikels\n- Schritte ohne gültige Option werden übersprungen\n- Eine Auswahl wechselt den Schritt nicht\n- Ungültige Folgeauswahl fällt beim Wechsel weg\n- Auf kleinen Bildschirmen bleibt der Ablauf derselbe\n- Escape schließt den Konfigurator\n- Tabulator bleibt im Dialog\n----------------------------------------\n<!-- funktion -->\nSchritt öffnen\n<!-- ziel -->\nÖffnet den Konfigurator beim angeklickten Schritt\n<!-- regeln -->\n- LMB auf einen Schritt öffnet das Modal\n- Offener Konfigurator wechselt nur den Schritt\n- Modal hängt am Seitenkörper\n<!-- testen -->\n- Produktseite öffnen\n- LMB auf zweiten Schritt\n- Modal öffnet bei Schritt 2\n----------------------------------------\n<!-- funktion -->\nOption wählen\n<!-- ziel -->\nÜbernimmt eine Option in die laufende Auswahl\n<!-- regeln -->\n- Gewählte Option ist hervorgehoben\n- Nicht kombinierbare Optionen sind gesperrt\n- Auswahl verwirft ungültige Folgeschritte\n- Schritt bleibt nach der Wahl derselbe\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf eine Option\n- Option ist hervorgehoben\n----------------------------------------\n<!-- funktion -->\nNächsten Schritt anzeigen\n<!-- ziel -->\nFührt zum nächsten Schritt mit gültigen Optionen\n<!-- regeln -->\n- Weiter-Knopf heißt am letzten Schritt Fertig\n- Leere Schritte werden übersprungen\n<!-- testen -->\n- Konfigurator öffnen\n- LMB auf Weiter-Knopf\n- Nächster Schritt erscheint\n----------------------------------------\n<!-- funktion -->\nVorherigen Schritt anzeigen\n<!-- ziel -->\nFührt zurück zum letzten Schritt mit Optionen\n<!-- regeln -->\n- Zurück-Knopf ist am ersten Schritt gesperrt\n- Leere Schritte werden übersprungen\n<!-- testen -->\n- Zum zweiten Schritt gehen\n- LMB auf Zurück-Knopf\n- Erster Schritt erscheint\n----------------------------------------\n<!-- funktion -->\nAuswahl übernehmen\n<!-- ziel -->\nWechselt das Produkt auf die gewählte Variante\n<!-- regeln -->\n- Fertig überträgt die Auswahl in die Auswahlfelder\n- Unbesuchte Schritte bekommen die erste gültige Option\n- Shopware führt den Variantenwechsel aus\n<!-- testen -->\n- Alle Schritte durchgehen\n- LMB auf Fertig-Knopf\n- Produktseite zeigt die gewählte Variante\n----------------------------------------\n<!-- funktion -->\nKonfigurator schließen\n<!-- ziel -->\nBeendet die Auswahl ohne Wechsel der Variante\n<!-- regeln -->\n- x-Icon schließt das Modal\n- Escape schließt das Modal\n- Fokus kehrt zum Auslöser zurück\n<!-- testen -->\n- Konfigurator öffnen\n- Escape drücken\n- Modal ist geschlossen\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"titel": "„Schritt zeigen“ geändert",
|
||||||
|
"dateien": {
|
||||||
|
"schritt-zeigen": "<!-- titel -->\nSchritt zeigen\n----------------------------------------\n<!-- art -->\nkern\n----------------------------------------\n<!-- ziel -->\nDer Kunde weiß im Modal, wo er steht und was er wählt\n----------------------------------------\n<!-- regeln -->\n- Optionen stehen rechts, die Bilder links\n- Lange Optionslisten lassen sich scrollen\n----------------------------------------\n<!-- funktion -->\nEigenschaft benennen\n<!-- ziel -->\nZeigt den Namen der Eigenschaft des aktuellen Schritts\n<!-- regeln -->\n- Name steht als Überschrift über der Liste\n- Name kommt aus der Eigenschaftsgruppe\n<!-- testen -->\n- Konfigurator öffnen\n- Überschrift nennt die erste Eigenschaft\n----------------------------------------\n<!-- funktion -->\nSchrittstand anzeigen\n<!-- ziel -->\nZeigt den aktuellen Schritt und die Gesamtzahl\n<!-- regeln -->\n- Zähler nennt Schritt und Gesamtzahl\n- Balken wächst mit dem Fortschritt\n- Text kommt aus einem Textbaustein\n<!-- testen -->\n- Konfigurator öffnen\n- Zähler zeigt 1 von 3\n----------------------------------------\n<!-- funktion -->\nOptionen auflisten\n<!-- ziel -->\nZeigt alle Optionen der Eigenschaft mit Namen und Bild\n<!-- regeln -->\n- Jede Option nennt ihren Namen\n- Bild erscheint nur bis zwei Schritte\n- Ohne Bild erscheint der Farbwert\n- Ohne Farbwert bleibt die Zeile ohne Zeichen\n<!-- testen -->\n- Konfigurator öffnen\n- Jede Option zeigt ihren Namen\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
49
daten/MtoVariantConfigurator/.planung/auswahl-anstossen.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Auswahl anstoßen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde erkennt in der Buybox die Reihenfolge der Schritte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Jede Eigenschaft bildet einen eigenen Schritt
|
||||||
|
- Natives Auswahlfeld bleibt für den Variantenwechsel erhalten
|
||||||
|
- Eigene Darstellung gilt unabhängig vom Anzeigetyp der Eigenschaft
|
||||||
|
- Ohne aktives Layout erscheint die Standard-Buybox
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Schrittnummer anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt neben jeder Eigenschaft ihre Position im Ablauf
|
||||||
|
<!-- regeln -->
|
||||||
|
- Beschriftung nennt das Wort Schritt und die Nummer
|
||||||
|
- Nummer folgt der Reihenfolge aus dem Backend
|
||||||
|
<!-- testen -->
|
||||||
|
- Produktseite öffnen
|
||||||
|
- Erste Eigenschaft zeigt Schritt 1
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Hinweistext anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Erklärt dem Kunden, worauf er im Schritt achtet
|
||||||
|
<!-- regeln -->
|
||||||
|
- Text kommt aus einem Textbaustein je Schritt
|
||||||
|
- Leerer Textbaustein blendet den Hinweis aus
|
||||||
|
- Hinweis hängt an der Nummer statt an der Eigenschaft
|
||||||
|
<!-- testen -->
|
||||||
|
- Textbaustein für Schritt 1 füllen
|
||||||
|
- Produktseite öffnen
|
||||||
|
- Hinweis steht unter der Überschrift
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Gewählte Option anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt am Schritt, welche Option gerade gilt
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ohne Auswahl steht dort Bitte wählen
|
||||||
|
- Vorbelegung erscheint ohne Zutun des Kunden
|
||||||
|
<!-- testen -->
|
||||||
|
- Hauptartikel ohne Variante öffnen
|
||||||
|
- Jeder Schritt nennt eine Option
|
||||||
47
daten/MtoVariantConfigurator/.planung/beratung-anbieten.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Beratung anbieten
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde findet im Konfigurator Vertrauen und einen Kontakt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Alle Texte kommen aus Textbausteinen
|
||||||
|
- Leerer Textbaustein blendet den Bereich aus
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vertrauen zeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt Siegel und Zusage neben dem Gesamtpreis
|
||||||
|
<!-- regeln -->
|
||||||
|
- Logo stammt aus der Plugin-Konfiguration
|
||||||
|
- Ohne Logo bleibt der Text stehen
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Siegel steht neben dem Preis
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Beratung erreichen
|
||||||
|
<!-- ziel -->
|
||||||
|
Bietet eine Telefonnummer für Fragen zur Konfiguration
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nummer ist als Telefonlink klickbar
|
||||||
|
- Leere Nummer blendet den Bereich aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf die Nummer
|
||||||
|
- Telefon-App öffnet sich
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsübersicht öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Öffnet das hinterlegte PDF mit allen Optionen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Knopf erscheint nur mit hinterlegtem PDF
|
||||||
|
- PDF öffnet in einem neuen Tab
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf Optionen-Knopf
|
||||||
|
- PDF öffnet in neuem Tab
|
||||||
69
daten/MtoVariantConfigurator/.planung/layout-zuweisen.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Layout zuweisen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Ein Händler entscheidet je Produkt, ob die geführte Auswahl erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Einstellungen liegen auf dem Varianten-Tab des Produkts
|
||||||
|
- Werte stehen in den customFields des Produkts
|
||||||
|
- Bearbeiten erfordert das Recht product.editor
|
||||||
|
- Ohne Zuweisung bleibt das Standardlayout aktiv
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfigurator aktivieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Schaltet die geführte Variantenauswahl für ein Produkt frei
|
||||||
|
<!-- regeln -->
|
||||||
|
- Schalter steht auf dem Varianten-Tab
|
||||||
|
- Aktiv ersetzt die Buybox des Produkts
|
||||||
|
- Inaktiv zeigt das Standardlayout von Shopware
|
||||||
|
<!-- testen -->
|
||||||
|
- Produkt im Admin öffnen
|
||||||
|
- Varianten-Tab öffnen
|
||||||
|
- Schalter aktivieren und speichern
|
||||||
|
- Buybox zeigt die geführte Auswahl
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsübersicht hinterlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Hinterlegt ein PDF mit allen Varianten und Kombinationen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Medienfeld steht auf dem Varianten-Tab
|
||||||
|
- Feld hängt am Hauptartikel
|
||||||
|
- Ohne PDF bleibt der Knopf unsichtbar
|
||||||
|
<!-- testen -->
|
||||||
|
- Varianten-Tab öffnen
|
||||||
|
- PDF im Medienfeld wählen
|
||||||
|
- Produkt speichern
|
||||||
|
- Modal zeigt den Knopf für Optionen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Trust-Logo hinterlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt ein Vertrauenssiegel neben dem Gesamtpreis
|
||||||
|
<!-- regeln -->
|
||||||
|
- Logo steht in der Plugin-Konfiguration
|
||||||
|
- Gilt für den ganzen Verkaufskanal
|
||||||
|
- Ohne Logo erscheint nur der Text
|
||||||
|
<!-- testen -->
|
||||||
|
- Plugin-Konfiguration öffnen
|
||||||
|
- Logo im Medienfeld wählen
|
||||||
|
- Modal zeigt das Logo am Preis
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsbild am Datensatz pflegen
|
||||||
|
<!-- stand -->
|
||||||
|
offen
|
||||||
|
<!-- ziel -->
|
||||||
|
Hinterlegt das Bild einer Option am Datensatz der Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- Feld liegt im Formular der einzelnen Variante
|
||||||
|
- Heute stammt das Bild aus der Eigenschaftsgruppe
|
||||||
|
<!-- testen -->
|
||||||
|
- Variante im Admin öffnen
|
||||||
|
- Bildfeld der Variante füllen
|
||||||
|
- Variante speichern
|
||||||
51
daten/MtoVariantConfigurator/.planung/option-ansehen.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Option ansehen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde prüft eine Option groß, bevor er sie nimmt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur Optionen mit Bild tragen den Hinweis
|
||||||
|
- Ansicht liegt über dem Konfigurator
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsdetail öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt Bild und Namen einer Option in voller Größe
|
||||||
|
<!-- regeln -->
|
||||||
|
- i-Icon öffnet die Ansicht
|
||||||
|
- Enter öffnet die Ansicht
|
||||||
|
- Leertaste öffnet die Ansicht
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf i-Icon einer Option
|
||||||
|
- Große Ansicht erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Option aus Ansicht wählen
|
||||||
|
<!-- ziel -->
|
||||||
|
Übernimmt die betrachtete Option in die Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Knopf übernimmt die Option
|
||||||
|
- Knopf schließt die Ansicht
|
||||||
|
- Schritt bleibt derselbe
|
||||||
|
<!-- testen -->
|
||||||
|
- Optionsdetail öffnen
|
||||||
|
- LMB auf Wählen-Knopf
|
||||||
|
- Option ist gewählt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ansicht schließen
|
||||||
|
<!-- ziel -->
|
||||||
|
Kehrt zur Optionsliste zurück ohne Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon schließt die Ansicht
|
||||||
|
- Escape schließt die Ansicht
|
||||||
|
- Konfigurator bleibt offen
|
||||||
|
<!-- testen -->
|
||||||
|
- Optionsdetail öffnen
|
||||||
|
- Escape drücken
|
||||||
|
- Optionsliste erscheint wieder
|
||||||
49
daten/MtoVariantConfigurator/.planung/schritt-zeigen.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Schritt zeigen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde weiß im Modal, wo er steht und was er wählt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Optionen stehen rechts
|
||||||
|
- Bilder links
|
||||||
|
- Lange Optionslisten lassen sich scrollen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Eigenschaft benennen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt den Namen der Eigenschaft des aktuellen Schritts
|
||||||
|
<!-- regeln -->
|
||||||
|
- Name steht als Überschrift über der Liste
|
||||||
|
- Name kommt aus der Eigenschaftsgruppe
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Überschrift nennt die erste Eigenschaft
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Schrittstand anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt den aktuellen Schritt und die Gesamtzahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Zähler nennt Schritt und Gesamtzahl
|
||||||
|
- Balken wächst mit dem Fortschritt
|
||||||
|
- Text kommt aus einem Textbaustein
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Zähler zeigt 1 von 3
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionen auflisten
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt alle Optionen der Eigenschaft mit Namen und Bild
|
||||||
|
<!-- regeln -->
|
||||||
|
- Jede Option nennt ihren Namen
|
||||||
|
- Bild erscheint nur bis zwei Schritte
|
||||||
|
- Ohne Bild erscheint der Farbwert
|
||||||
|
- Ohne Farbwert bleibt die Zeile ohne Zeichen
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Jede Option zeigt ihren Namen
|
||||||
109
daten/MtoVariantConfigurator/.planung/varianten-waehlen.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Varianten wählen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde findet Schritt für Schritt eine bestellbare Kombination
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Erster Schritt zeigt alle Optionen seiner Eigenschaft
|
||||||
|
- Folgeschritte zeigen nur kombinierbare Optionen
|
||||||
|
- Der Ablauf trägt beliebig viele Eigenschaften
|
||||||
|
- Reihenfolge der Schritte kommt aus dem Backend
|
||||||
|
- Ohne Auswahl greifen die Standardvarianten des Elternartikels
|
||||||
|
- Schritte ohne gültige Option werden übersprungen
|
||||||
|
- Eine Auswahl wechselt den Schritt nicht
|
||||||
|
- Ungültige Folgeauswahl fällt beim Wechsel weg
|
||||||
|
- Auf kleinen Bildschirmen bleibt der Ablauf derselbe
|
||||||
|
- Escape schließt den Konfigurator
|
||||||
|
- Tabulator bleibt im Dialog
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Schritt öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Öffnet den Konfigurator beim angeklickten Schritt
|
||||||
|
<!-- regeln -->
|
||||||
|
- LMB auf einen Schritt öffnet das Modal
|
||||||
|
- Offener Konfigurator wechselt nur den Schritt
|
||||||
|
- Modal hängt am Seitenkörper
|
||||||
|
<!-- testen -->
|
||||||
|
- Produktseite öffnen
|
||||||
|
- LMB auf zweiten Schritt
|
||||||
|
- Modal öffnet bei Schritt 2
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Option wählen
|
||||||
|
<!-- ziel -->
|
||||||
|
Übernimmt eine Option in die laufende Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gewählte Option ist hervorgehoben
|
||||||
|
- Nicht kombinierbare Optionen sind gesperrt
|
||||||
|
- Auswahl verwirft ungültige Folgeschritte
|
||||||
|
- Schritt bleibt nach der Wahl derselbe
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf eine Option
|
||||||
|
- Option ist hervorgehoben
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Nächsten Schritt anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Führt zum nächsten Schritt mit gültigen Optionen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Weiter-Knopf heißt am letzten Schritt Fertig
|
||||||
|
- Leere Schritte werden übersprungen
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf Weiter-Knopf
|
||||||
|
- Nächster Schritt erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorherigen Schritt anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Führt zurück zum letzten Schritt mit Optionen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Zurück-Knopf ist am ersten Schritt gesperrt
|
||||||
|
- Leere Schritte werden übersprungen
|
||||||
|
<!-- testen -->
|
||||||
|
- Zum zweiten Schritt gehen
|
||||||
|
- LMB auf Zurück-Knopf
|
||||||
|
- Erster Schritt erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Auswahl übernehmen
|
||||||
|
<!-- ziel -->
|
||||||
|
Wechselt das Produkt auf die gewählte Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- Fertig überträgt die Auswahl in die Auswahlfelder
|
||||||
|
- Unbesuchte Schritte bekommen die erste gültige Option
|
||||||
|
- Shopware führt den Variantenwechsel aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Alle Schritte durchgehen
|
||||||
|
- LMB auf Fertig-Knopf
|
||||||
|
- Produktseite zeigt die gewählte Variante
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfigurator schließen
|
||||||
|
<!-- ziel -->
|
||||||
|
Beendet die Auswahl ohne Wechsel der Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon schließt das Modal
|
||||||
|
- Escape schließt das Modal
|
||||||
|
- Fokus kehrt zum Auslöser zurück
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Escape drücken
|
||||||
|
- Modal ist geschlossen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfigurationsdaten bereitstellen
|
||||||
|
<!-- ziel -->
|
||||||
|
Legt der Produktseite alles bei, was der Konfigurator braucht
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kombinationen stammen vom Hauptartikel
|
||||||
|
- Ohne aktives Layout bleibt die Beilage aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Produktseite mit aktivem Layout öffnen
|
||||||
|
- Seite trägt Kombinationen und Vorbelegung
|
||||||
53
daten/MtoVariantConfigurator/.planung/vorschau-zeigen.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Vorschau zeigen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde sieht Bild und Preis der gewählten Kombination
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Preis stimmt mit dem Preis der Produktseite überein
|
||||||
|
- Antwort wird nicht zwischengespeichert
|
||||||
|
- Späte Antworten überholter Anfragen werden verworfen
|
||||||
|
- Ohne eigene Bilder greifen die Bilder des Elternartikels
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorschau laden
|
||||||
|
<!-- ziel -->
|
||||||
|
Holt Bilder und Preis zur laufenden Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Jede Auswahl löst eine neue Anfrage aus
|
||||||
|
- Titelbild der Variante steht an erster Stelle
|
||||||
|
- Ohne Treffer bleibt die Produkt-ID als Rückfall
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Andere Option wählen
|
||||||
|
- Bild und Preis ändern sich
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Galerie blättern
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt weitere Bilder der gewählten Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- Pfeil-Icons blättern vor
|
||||||
|
- Pfeil-Icons blättern zurück
|
||||||
|
- Nach dem letzten Bild folgt das erste
|
||||||
|
- Bei einem Bild bleibt die Navigation aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf Vor-Icon
|
||||||
|
- Nächstes Bild erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Bild wählen
|
||||||
|
<!-- ziel -->
|
||||||
|
Wechselt das große Bild über die Vorschaubilder
|
||||||
|
<!-- regeln -->
|
||||||
|
- Aktives Vorschaubild ist hervorgehoben
|
||||||
|
- Großes Bild folgt der Wahl
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf drittes Vorschaubild
|
||||||
|
- Großes Bild zeigt dieses Motiv
|
||||||
537
daten/MtoVariantConfigurator/.pruefung.json
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
{
|
||||||
|
"abdeckung": {
|
||||||
|
"einbinden:VariantConfiguratorPlugin": {
|
||||||
|
"abdruck": "823481507a44729d",
|
||||||
|
"funktion": "Konfigurationsdaten bereitstellen",
|
||||||
|
"trug": "5fbc8363b7cffe59"
|
||||||
|
},
|
||||||
|
"einbinden:erweitert @Storefront/storefront/component/buy-widget/configurator.html.twig": {
|
||||||
|
"abdruck": "c6d031d6bc598089",
|
||||||
|
"funktion": "Konfigurator aktivieren",
|
||||||
|
"trug": "6cf5a2b08ce2371a"
|
||||||
|
},
|
||||||
|
"einbinden:override sw-product-detail-variants": {
|
||||||
|
"abdruck": "2fcf518e7333458a",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"einbinden:register mto-vc-variants-card": {
|
||||||
|
"abdruck": "c472feadaac5ac71",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"hoeren:[data-mto-vc-gallery-next].click": {
|
||||||
|
"abdruck": "c402ad6f114e8c3d",
|
||||||
|
"funktion": "Galerie blättern",
|
||||||
|
"trug": "bacf45ebf9dd77b8"
|
||||||
|
},
|
||||||
|
"hoeren:[data-mto-vc-gallery-prev].click": {
|
||||||
|
"abdruck": "f50d4f7effc60a06",
|
||||||
|
"funktion": "Galerie blättern",
|
||||||
|
"trug": "bacf45ebf9dd77b8"
|
||||||
|
},
|
||||||
|
"hoeren:[data-mto-vc-lightbox-close].click": {
|
||||||
|
"abdruck": "89a22850af7694bb",
|
||||||
|
"funktion": "Galerie blättern",
|
||||||
|
"trug": "bacf45ebf9dd77b8"
|
||||||
|
},
|
||||||
|
"hoeren:document.keydown": {
|
||||||
|
"abdruck": "7c018ebbfa346bee",
|
||||||
|
"funktion": "Schritt öffnen",
|
||||||
|
"trug": "efe9634e3080c073"
|
||||||
|
},
|
||||||
|
"hoeren:el.click": {
|
||||||
|
"abdruck": "6a423a062267755c",
|
||||||
|
"funktion": "Nächsten Schritt anzeigen",
|
||||||
|
"trug": "f6b276e3fedf287d"
|
||||||
|
},
|
||||||
|
"hoeren:info.click": {
|
||||||
|
"abdruck": "7a5a70dfb792bf19",
|
||||||
|
"funktion": "Optionsdetail öffnen",
|
||||||
|
"trug": "39fed29af6734cd4"
|
||||||
|
},
|
||||||
|
"hoeren:info.keydown": {
|
||||||
|
"abdruck": "409cecf6fb3bbb4a",
|
||||||
|
"funktion": "Optionsdetail öffnen",
|
||||||
|
"trug": "39fed29af6734cd4"
|
||||||
|
},
|
||||||
|
"hoeren:lightboxSelect.click": {
|
||||||
|
"abdruck": "9add2d2c77b440b5",
|
||||||
|
"funktion": "Ansicht schließen",
|
||||||
|
"trug": "24d52cc6d249d4f5"
|
||||||
|
},
|
||||||
|
"hoeren:next.click": {
|
||||||
|
"abdruck": "d5b0cdcce40afcff",
|
||||||
|
"funktion": "Konfigurator schließen",
|
||||||
|
"trug": "1cc1a6b5fc943e03"
|
||||||
|
},
|
||||||
|
"hoeren:prev.click": {
|
||||||
|
"abdruck": "6b8db8d885c0d6f8",
|
||||||
|
"funktion": "Konfigurator schließen",
|
||||||
|
"trug": "1cc1a6b5fc943e03"
|
||||||
|
},
|
||||||
|
"hoeren:row.click": {
|
||||||
|
"abdruck": "b4ec6f63c613c35f",
|
||||||
|
"funktion": "Option wählen",
|
||||||
|
"trug": "80df89cf0c129387"
|
||||||
|
},
|
||||||
|
"hoeren:thumb.click": {
|
||||||
|
"abdruck": "b4aa2111971f6886",
|
||||||
|
"funktion": "Bild wählen",
|
||||||
|
"trug": "0604a0a6875489b8"
|
||||||
|
},
|
||||||
|
"hoeren:trigger.click": {
|
||||||
|
"abdruck": "e219933925adc81a",
|
||||||
|
"funktion": "Schritt öffnen",
|
||||||
|
"trug": "efe9634e3080c073"
|
||||||
|
},
|
||||||
|
"reaktion:ProductPageLoadedEvent": {
|
||||||
|
"abdruck": "6f808b91c1a0c4f5",
|
||||||
|
"funktion": "Konfigurationsdaten bereitstellen",
|
||||||
|
"trug": "5fbc8363b7cffe59"
|
||||||
|
},
|
||||||
|
"route:get /mto-variant-configurator/resolve/{productId}": {
|
||||||
|
"abdruck": "b14355d6007564a1",
|
||||||
|
"funktion": "Konfigurationsdaten bereitstellen",
|
||||||
|
"trug": "5fbc8363b7cffe59"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"belege": {
|
||||||
|
"[\"auswahl-anstossen\", \"Gewählte Option anzeigen\"]": {
|
||||||
|
"abdruck": "626af3ee300b610a",
|
||||||
|
"beleg": "<span class=\"mto-vc-select-trigger-label\" data-mto-vc-trigger-label>\n {{ mtoVcSelected ? mtoVcSelected.translated.name : 'mto.variantConfigurator.choose'|trans }}\n </span>",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/configurator.html.twig"
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Hinweistext anzeigen\"]": {
|
||||||
|
"abdruck": "b9baf394ca99d4e6",
|
||||||
|
"beleg": "{% if mtoVcBubble != mtoVcBubbleKey and mtoVcBubble is not empty %}\n <p class=\"mto-vc-bubble\">{{ mtoVcBubble|sw_sanitize }}</p>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/configurator.html.twig"
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Schrittnummer anzeigen\"]": {
|
||||||
|
"abdruck": "b9e4deada2c3a6f1",
|
||||||
|
"beleg": "<span class=\"mto-vc-step-badge\">{{ 'mto.variantConfigurator.step'|trans({ '%step%': mtoVcStep })|sw_sanitize }}</span>",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/configurator.html.twig"
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Beratung erreichen\"]": {
|
||||||
|
"abdruck": "fa513f8d265ad713",
|
||||||
|
"beleg": "{% if ctaPhone is not empty %}\n <span class=\"mto-vc-support-icon\" aria-hidden=\"true\"></span>\n <a href=\"tel:{{ ctaPhone|replace({' ': '', '/': '', '-': '', '(': '', ')': ''}) }}\"\n class=\"mto-vc-support-phone\">{{ ctaPhone|sw_sanitize }}</a>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Optionsübersicht öffnen\"]": {
|
||||||
|
"abdruck": "8aba7fb9bf50281b",
|
||||||
|
"beleg": "{% if pdfUrl %}\n <a href=\"{{ pdfUrl }}\" target=\"_blank\" rel=\"noopener\"\n class=\"mto-vc-btn is-outline mto-vc-all-options\">\n {{ 'mto.variantConfigurator.allOptions'|trans|sw_sanitize }}\n </a>\n {% else %}\n <span class=\"mto-vc-actions-spacer\" aria-hidden=\"true\"></span>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Vertrauen zeigen\"]": {
|
||||||
|
"abdruck": "5b204dff7de6f96d",
|
||||||
|
"beleg": "{% if trustLogo %}\n <img src=\"{{ trustLogo }}\" alt=\"\" class=\"mto-vc-trust-logo\">\n{% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Konfigurator aktivieren\"]": {
|
||||||
|
"abdruck": "60a65511f06ae7fe",
|
||||||
|
"beleg": "<sw-switch-field\n v-model:value=\"configuratorActive\"\n class=\"mto-vc-variants-card__active\"\n :disabled=\"!editable\"\n :label=\"$tc('mto-variant-configurator.card.activeLabel')\"\n :help-text=\"$tc('mto-variant-configurator.card.activeHelp')\"\n />",
|
||||||
|
"datei": "src/Resources/app/administration/src/component/mto-vc-variants-card/mto-vc-variants-card.html.twig"
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Optionsübersicht hinterlegen\"]": {
|
||||||
|
"abdruck": "97b421cf540a6cc2",
|
||||||
|
"beleg": "{% if pdfUrl %}\n <a href=\"{{ pdfUrl }}\" target=\"_blank\" rel=\"noopener\"\n class=\"mto-vc-btn is-outline mto-vc-all-options\">\n {{ 'mto.variantConfigurator.allOptions'|trans|sw_sanitize }}\n </a>\n {% else %}\n <span class=\"mto-vc-actions-spacer\" aria-hidden=\"true\"></span>\n {% endif %}",
|
||||||
|
"datei": "src/Resources/views/storefront/component/buy-widget/mto-variant-configurator-modal.html.twig"
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Trust-Logo hinterlegen\"]": {
|
||||||
|
"abdruck": "1954311a4c3a9cc1",
|
||||||
|
"beleg": "$mediaId = $this->systemConfigService->get(\n MtoVariantConfigurator::CONFIG_TRUST_LOGO,\n $salesChannelId,\n );",
|
||||||
|
"datei": "src/Core/Content/Product/VariantResolveService.php"
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Ansicht schließen\"]": {
|
||||||
|
"abdruck": "6eac493d9a481e30",
|
||||||
|
"beleg": "if (!this._nodes.lightbox.hidden) {\n this._closeLightbox();\n } else {\n this._close();\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Option aus Ansicht wählen\"]": {
|
||||||
|
"abdruck": "b4223f8cf3e2456e",
|
||||||
|
"beleg": "this._nodes.lightboxSelect?.addEventListener('click', () => {\n if (this._lightboxOption) {\n this._selectOption(this.options.groups[this._currentStep], this._lightboxOption.id);\n }\n this._closeLightbox();\n });",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Optionsdetail öffnen\"]": {
|
||||||
|
"abdruck": "59faab581e0fac7f",
|
||||||
|
"beleg": "info.addEventListener('click', open);\n info.addEventListener('keydown', (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n open(event);\n }\n });",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Eigenschaft benennen\"]": {
|
||||||
|
"abdruck": "dd54064a67689e2c",
|
||||||
|
"beleg": "this._nodes.title.textContent = group.name;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Optionen auflisten\"]": {
|
||||||
|
"abdruck": "a71af321f056608d",
|
||||||
|
"beleg": "if (option.media) {\n const visual = document.createElement('span');\n visual.className = 'mto-vc-option-media';\n const img = document.createElement('img');\n img.src = option.media;\n img.alt = '';\n img.loading = 'lazy';\n visual.appendChild(img);\n row.appendChild(visual);\n } else if (option.color) {\n const visual = document.createElement('span');\n visual.className = 'mto-vc-option-swatch';\n visual.style.backgroundColor = option.color;\n row.appendChild(visual);\n } else {\n row.classList.add('has-no-visual');\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Schrittstand anzeigen\"]": {
|
||||||
|
"abdruck": "aa7b8888103f8bd0",
|
||||||
|
"beleg": "this._nodes.counter.textContent = (this._snippets.stepCount || '__step__ / __total__')\n .replace('__step__', String(step))\n .replace('__total__', String(total));\n this._nodes.progress.style.width = `${(step / total) * 100}%`;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Auswahl übernehmen\"]": {
|
||||||
|
"abdruck": "21ccf5bc0286c95c",
|
||||||
|
"beleg": "if (optionId && select.value !== optionId) {\n select.value = optionId;\n lastChanged = select;\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurationsdaten bereitstellen\"]": {
|
||||||
|
"abdruck": "e6e5848381fce5aa",
|
||||||
|
"beleg": "$page->assign([",
|
||||||
|
"datei": "src/Subscriber/ProductPageSubscriber.php"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurator schließen\"]": {
|
||||||
|
"abdruck": "1b7d4aea18cdb896",
|
||||||
|
"beleg": "_close() {\n this._closeLightbox();\n this._modalEl.hidden = true;\n document.body.classList.remove('mto-vc-modal-open');\n document.removeEventListener('keydown', this._keydownHandler);\n this._lastFocus?.focus();\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Nächsten Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "3deee97595984a3e",
|
||||||
|
"beleg": "const isLast = this._nextUsableStep(this._currentStep + 1, 1) === null;\n this._nodes.next.textContent = isLast\n ? this._nodes.next.dataset.finishLabel\n : this._nodes.next.dataset.nextLabel;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Option wählen\"]": {
|
||||||
|
"abdruck": "4b9f7dc844cf7aac",
|
||||||
|
"beleg": "this._selected[group.id] = optionId;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Schritt öffnen\"]": {
|
||||||
|
"abdruck": "94b3aa8d50c53fc8",
|
||||||
|
"beleg": "this._triggers.forEach((trigger) => {\n trigger.addEventListener('click', (event) => {\n event.preventDefault();\n this._open(parseInt(trigger.dataset.mtoVcStepIndex, 10));\n });\n });",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Vorherigen Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "df5e60fe148da5e6",
|
||||||
|
"beleg": "this._nodes.prev.disabled = this._nextUsableStep(this._currentStep - 1, -1) === null;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Bild wählen\"]": {
|
||||||
|
"abdruck": "ab1e74251036541a",
|
||||||
|
"beleg": "thumb.className = `mto-vc-thumb${index === this._galleryIndex ? ' is-active' : ''}`;",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Galerie blättern\"]": {
|
||||||
|
"abdruck": "6b28a5866629b878",
|
||||||
|
"beleg": "_stepGallery(delta) {\n if (this._gallery.length < 2) {\n return;\n }\n const count = this._gallery.length;\n this._galleryIndex = (this._galleryIndex + delta + count) % count;\n this._renderGallery();\n }",
|
||||||
|
"datei": "src/Resources/app/storefront/src/js/variant-configurator.plugin.js"
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Vorschau laden\"]": {
|
||||||
|
"abdruck": "413024bc244f7770",
|
||||||
|
"beleg": "return new JsonResponse($this->variantResolveService->resolveVariant($variantId, $context));",
|
||||||
|
"datei": "src/Storefront/Controller/VariantConfiguratorController.php"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ohne_code": {},
|
||||||
|
"ohne_karte": {},
|
||||||
|
"paare": {
|
||||||
|
"[\"[\\\"auswahl-anstossen\\\", \\\"Gewählte Option anzeigen\\\"]\", \"[\\\"auswahl-anstossen\\\", \\\"Hinweistext anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "450345e8a27f5695",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"auswahl-anstossen\\\", \\\"Gewählte Option anzeigen\\\"]\", \"[\\\"auswahl-anstossen\\\", \\\"Schrittnummer anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "60008fdb6d69349b",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"auswahl-anstossen\\\", \\\"Hinweistext anzeigen\\\"]\", \"[\\\"auswahl-anstossen\\\", \\\"Schrittnummer anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "3eaf19062d6d53a3",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Beratung erreichen\\\"]\", \"[\\\"beratung-anbieten\\\", \\\"Optionsübersicht öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "9d3da3bf8c127ae1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Beratung erreichen\\\"]\", \"[\\\"beratung-anbieten\\\", \\\"Vertrauen zeigen\\\"]\"]": {
|
||||||
|
"abdruck": "2c3c62cb4b935c56",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Optionsübersicht öffnen\\\"]\", \"[\\\"beratung-anbieten\\\", \\\"Vertrauen zeigen\\\"]\"]": {
|
||||||
|
"abdruck": "bf601d5dc7c6c507",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"beratung-anbieten\\\", \\\"Optionsübersicht öffnen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "bc9dc09e54b78a75",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Konfigurator aktivieren\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsbild am Datensatz pflegen\\\"]\"]": {
|
||||||
|
"abdruck": "5a4d802d668ddd55",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Konfigurator aktivieren\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "eeed78a6284711f1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Konfigurator aktivieren\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Trust-Logo hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "b4944e5aa4df871a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Optionsbild am Datensatz pflegen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "b91bdff82a120a8d",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Optionsbild am Datensatz pflegen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Trust-Logo hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "97268714e2b5d989",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"layout-zuweisen\\\", \\\"Optionsübersicht hinterlegen\\\"]\", \"[\\\"layout-zuweisen\\\", \\\"Trust-Logo hinterlegen\\\"]\"]": {
|
||||||
|
"abdruck": "b52cedd4106ee837",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"option-ansehen\\\", \\\"Ansicht schließen\\\"]\", \"[\\\"option-ansehen\\\", \\\"Option aus Ansicht wählen\\\"]\"]": {
|
||||||
|
"abdruck": "caa5ce4c5ff31774",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"option-ansehen\\\", \\\"Ansicht schließen\\\"]\", \"[\\\"option-ansehen\\\", \\\"Optionsdetail öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "462a46d7a2030874",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"option-ansehen\\\", \\\"Option aus Ansicht wählen\\\"]\", \"[\\\"option-ansehen\\\", \\\"Optionsdetail öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "8f695dfd90060204",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"schritt-zeigen\\\", \\\"Eigenschaft benennen\\\"]\", \"[\\\"schritt-zeigen\\\", \\\"Optionen auflisten\\\"]\"]": {
|
||||||
|
"abdruck": "5372df6b482e9a5c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"schritt-zeigen\\\", \\\"Eigenschaft benennen\\\"]\", \"[\\\"schritt-zeigen\\\", \\\"Schrittstand anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "732d371c2135b53f",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"schritt-zeigen\\\", \\\"Optionen auflisten\\\"]\", \"[\\\"schritt-zeigen\\\", \\\"Schrittstand anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "590d6974e948c69c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\"]": {
|
||||||
|
"abdruck": "34e23fca918e5dc0",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\"]": {
|
||||||
|
"abdruck": "eb521941a5dc979a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "271109c8b42cf5d5",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "618d3c213492555a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "b45252ce7cc39ed9",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Auswahl übernehmen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "d7ade9bdd7d0c66e",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\"]": {
|
||||||
|
"abdruck": "97ceded85ca2035c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "ba83ab596b72af22",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "6f23248b5fbd7be4",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "7350e93a2a368ac1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurationsdaten bereitstellen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "50753ef9f3d5c8b5",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "b49e65e1c60b5307",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "b165f28b36fc3637",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "0bf509fed71f1c25",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Konfigurator schließen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "de5acdbf92511e14",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\"]": {
|
||||||
|
"abdruck": "7c9a06867c701267",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "f45378da83876bae",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Nächsten Schritt anzeigen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "9fc4c9cf8ae1d7fb",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\"]": {
|
||||||
|
"abdruck": "a71f8b8b92d1025c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Option wählen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "e3270a7176c63620",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"varianten-waehlen\\\", \\\"Schritt öffnen\\\"]\", \"[\\\"varianten-waehlen\\\", \\\"Vorherigen Schritt anzeigen\\\"]\"]": {
|
||||||
|
"abdruck": "44e775b8e8b106d1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"vorschau-zeigen\\\", \\\"Bild wählen\\\"]\", \"[\\\"vorschau-zeigen\\\", \\\"Galerie blättern\\\"]\"]": {
|
||||||
|
"abdruck": "19851d2901c309a1",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"vorschau-zeigen\\\", \\\"Bild wählen\\\"]\", \"[\\\"vorschau-zeigen\\\", \\\"Vorschau laden\\\"]\"]": {
|
||||||
|
"abdruck": "508415e6500f9fca",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"vorschau-zeigen\\\", \\\"Galerie blättern\\\"]\", \"[\\\"vorschau-zeigen\\\", \\\"Vorschau laden\\\"]\"]": {
|
||||||
|
"abdruck": "86aa1f412d5a5dcd",
|
||||||
|
"gleich": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stil": {
|
||||||
|
"[\"auswahl-anstossen\", \"\"]": {
|
||||||
|
"abdruck": "aeec25973dafe92f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Gewählte Option anzeigen\"]": {
|
||||||
|
"abdruck": "59058982f7989af8",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Hinweistext anzeigen\"]": {
|
||||||
|
"abdruck": "b3568e0a2585d71c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"auswahl-anstossen\", \"Schrittnummer anzeigen\"]": {
|
||||||
|
"abdruck": "117d58f0d4b21973",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"\"]": {
|
||||||
|
"abdruck": "4b0267b165360857",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Beratung erreichen\"]": {
|
||||||
|
"abdruck": "cfdb65d682c3e068",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Optionsübersicht öffnen\"]": {
|
||||||
|
"abdruck": "28d5587c78199c54",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"beratung-anbieten\", \"Vertrauen zeigen\"]": {
|
||||||
|
"abdruck": "8c00dec8d3ebf751",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"\"]": {
|
||||||
|
"abdruck": "1e0d2ca5296aabea",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Konfigurator aktivieren\"]": {
|
||||||
|
"abdruck": "dca9bbf7361f5520",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Optionsbild am Datensatz pflegen\"]": {
|
||||||
|
"abdruck": "cc0701aafd02ac3a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Optionsübersicht hinterlegen\"]": {
|
||||||
|
"abdruck": "de6c0e3e80d47496",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"layout-zuweisen\", \"Trust-Logo hinterlegen\"]": {
|
||||||
|
"abdruck": "ebd1a72ece157e69",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"\"]": {
|
||||||
|
"abdruck": "ace46c77101e26b3",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Ansicht schließen\"]": {
|
||||||
|
"abdruck": "6bdaf5e60fb3baa9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Option aus Ansicht wählen\"]": {
|
||||||
|
"abdruck": "cc10af5dda4a7857",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"option-ansehen\", \"Optionsdetail öffnen\"]": {
|
||||||
|
"abdruck": "e00d07ce7b75d8fc",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"\"]": {
|
||||||
|
"abdruck": "f0d53ef421968759",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Eigenschaft benennen\"]": {
|
||||||
|
"abdruck": "ec4828e2e9197083",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Optionen auflisten\"]": {
|
||||||
|
"abdruck": "d8e6daed0f36098f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zeigen\", \"Schrittstand anzeigen\"]": {
|
||||||
|
"abdruck": "42a1c7f94c30f72d",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"\"]": {
|
||||||
|
"abdruck": "0835908894fa892c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Auswahl übernehmen\"]": {
|
||||||
|
"abdruck": "6683956c63370244",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurationsdaten bereitstellen\"]": {
|
||||||
|
"abdruck": "498f3141e86f8db0",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Konfigurator schließen\"]": {
|
||||||
|
"abdruck": "866918b327061449",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Nächsten Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "8d810a760dfec037",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Option wählen\"]": {
|
||||||
|
"abdruck": "ac4eae399ac3a15e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Schritt öffnen\"]": {
|
||||||
|
"abdruck": "40f795456747214e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"varianten-waehlen\", \"Vorherigen Schritt anzeigen\"]": {
|
||||||
|
"abdruck": "a63a26a8baea9c07",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"\"]": {
|
||||||
|
"abdruck": "52976631fd09993a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Bild wählen\"]": {
|
||||||
|
"abdruck": "a3e980ef7678a446",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Galerie blättern\"]": {
|
||||||
|
"abdruck": "47ccb441120c1470",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vorschau-zeigen\", \"Vorschau laden\"]": {
|
||||||
|
"abdruck": "29e5951f170dba26",
|
||||||
|
"probleme": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verworfen": {}
|
||||||
|
}
|
||||||
49
daten/MtoVariantConfigurator/auswahl-anstossen.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Auswahl anstoßen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde erkennt in der Buybox die Reihenfolge der Schritte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Jede Eigenschaft bildet einen eigenen Schritt
|
||||||
|
- Natives Auswahlfeld bleibt für den Variantenwechsel erhalten
|
||||||
|
- Eigene Darstellung gilt unabhängig vom Anzeigetyp der Eigenschaft
|
||||||
|
- Ohne aktives Layout erscheint die Standard-Buybox
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Schrittnummer anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt neben jeder Eigenschaft ihre Position im Ablauf
|
||||||
|
<!-- regeln -->
|
||||||
|
- Beschriftung nennt das Wort Schritt und die Nummer
|
||||||
|
- Nummer folgt der Reihenfolge aus dem Backend
|
||||||
|
<!-- testen -->
|
||||||
|
- Produktseite öffnen
|
||||||
|
- Erste Eigenschaft zeigt Schritt 1
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Hinweistext anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Erklärt dem Kunden, worauf er im Schritt achtet
|
||||||
|
<!-- regeln -->
|
||||||
|
- Text kommt aus einem Textbaustein je Schritt
|
||||||
|
- Leerer Textbaustein blendet den Hinweis aus
|
||||||
|
- Hinweis hängt an der Nummer statt an der Eigenschaft
|
||||||
|
<!-- testen -->
|
||||||
|
- Textbaustein für Schritt 1 füllen
|
||||||
|
- Produktseite öffnen
|
||||||
|
- Hinweis steht unter der Überschrift
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Gewählte Option anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt am Schritt, welche Option gerade gilt
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ohne Auswahl steht dort Bitte wählen
|
||||||
|
- Vorbelegung erscheint ohne Zutun des Kunden
|
||||||
|
<!-- testen -->
|
||||||
|
- Hauptartikel ohne Variante öffnen
|
||||||
|
- Jeder Schritt nennt eine Option
|
||||||
47
daten/MtoVariantConfigurator/beratung-anbieten.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Beratung anbieten
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde findet im Konfigurator Vertrauen und einen Kontakt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Alle Texte kommen aus Textbausteinen
|
||||||
|
- Leerer Textbaustein blendet den Bereich aus
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vertrauen zeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt Siegel und Zusage neben dem Gesamtpreis
|
||||||
|
<!-- regeln -->
|
||||||
|
- Logo stammt aus der Plugin-Konfiguration
|
||||||
|
- Ohne Logo bleibt der Text stehen
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Siegel steht neben dem Preis
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Beratung erreichen
|
||||||
|
<!-- ziel -->
|
||||||
|
Bietet eine Telefonnummer für Fragen zur Konfiguration
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nummer ist als Telefonlink klickbar
|
||||||
|
- Leere Nummer blendet den Bereich aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf die Nummer
|
||||||
|
- Telefon-App öffnet sich
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsübersicht öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Öffnet das hinterlegte PDF mit allen Optionen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Knopf erscheint nur mit hinterlegtem PDF
|
||||||
|
- PDF öffnet in einem neuen Tab
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf Optionen-Knopf
|
||||||
|
- PDF öffnet in neuem Tab
|
||||||
69
daten/MtoVariantConfigurator/layout-zuweisen.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Layout zuweisen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Ein Händler entscheidet je Produkt, ob die geführte Auswahl erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Einstellungen liegen auf dem Varianten-Tab des Produkts
|
||||||
|
- Werte stehen in den customFields des Produkts
|
||||||
|
- Bearbeiten erfordert das Recht product.editor
|
||||||
|
- Ohne Zuweisung bleibt das Standardlayout aktiv
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfigurator aktivieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Schaltet die geführte Variantenauswahl für ein Produkt frei
|
||||||
|
<!-- regeln -->
|
||||||
|
- Schalter steht auf dem Varianten-Tab
|
||||||
|
- Aktiv ersetzt die Buybox des Produkts
|
||||||
|
- Inaktiv zeigt das Standardlayout von Shopware
|
||||||
|
<!-- testen -->
|
||||||
|
- Produkt im Admin öffnen
|
||||||
|
- Varianten-Tab öffnen
|
||||||
|
- Schalter aktivieren und speichern
|
||||||
|
- Buybox zeigt die geführte Auswahl
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsübersicht hinterlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Hinterlegt ein PDF mit allen Varianten und Kombinationen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Medienfeld steht auf dem Varianten-Tab
|
||||||
|
- Feld hängt am Hauptartikel
|
||||||
|
- Ohne PDF bleibt der Knopf unsichtbar
|
||||||
|
<!-- testen -->
|
||||||
|
- Varianten-Tab öffnen
|
||||||
|
- PDF im Medienfeld wählen
|
||||||
|
- Produkt speichern
|
||||||
|
- Modal zeigt den Knopf für Optionen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Trust-Logo hinterlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt ein Vertrauenssiegel neben dem Gesamtpreis
|
||||||
|
<!-- regeln -->
|
||||||
|
- Logo steht in der Plugin-Konfiguration
|
||||||
|
- Gilt für den ganzen Verkaufskanal
|
||||||
|
- Ohne Logo erscheint nur der Text
|
||||||
|
<!-- testen -->
|
||||||
|
- Plugin-Konfiguration öffnen
|
||||||
|
- Logo im Medienfeld wählen
|
||||||
|
- Modal zeigt das Logo am Preis
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsbild am Datensatz pflegen
|
||||||
|
<!-- stand -->
|
||||||
|
offen
|
||||||
|
<!-- ziel -->
|
||||||
|
Hinterlegt das Bild einer Option am Datensatz der Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- Feld liegt im Formular der einzelnen Variante
|
||||||
|
- Heute stammt das Bild aus der Eigenschaftsgruppe
|
||||||
|
<!-- testen -->
|
||||||
|
- Variante im Admin öffnen
|
||||||
|
- Bildfeld der Variante füllen
|
||||||
|
- Variante speichern
|
||||||
51
daten/MtoVariantConfigurator/option-ansehen.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Option ansehen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde prüft eine Option groß, bevor er sie nimmt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur Optionen mit Bild tragen den Hinweis
|
||||||
|
- Ansicht liegt über dem Konfigurator
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionsdetail öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt Bild und Namen einer Option in voller Größe
|
||||||
|
<!-- regeln -->
|
||||||
|
- i-Icon öffnet die Ansicht
|
||||||
|
- Enter öffnet die Ansicht
|
||||||
|
- Leertaste öffnet die Ansicht
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf i-Icon einer Option
|
||||||
|
- Große Ansicht erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Option aus Ansicht wählen
|
||||||
|
<!-- ziel -->
|
||||||
|
Übernimmt die betrachtete Option in die Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Knopf übernimmt die Option
|
||||||
|
- Knopf schließt die Ansicht
|
||||||
|
- Schritt bleibt derselbe
|
||||||
|
<!-- testen -->
|
||||||
|
- Optionsdetail öffnen
|
||||||
|
- LMB auf Wählen-Knopf
|
||||||
|
- Option ist gewählt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ansicht schließen
|
||||||
|
<!-- ziel -->
|
||||||
|
Kehrt zur Optionsliste zurück ohne Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon schließt die Ansicht
|
||||||
|
- Escape schließt die Ansicht
|
||||||
|
- Konfigurator bleibt offen
|
||||||
|
<!-- testen -->
|
||||||
|
- Optionsdetail öffnen
|
||||||
|
- Escape drücken
|
||||||
|
- Optionsliste erscheint wieder
|
||||||
49
daten/MtoVariantConfigurator/schritt-zeigen.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Schritt zeigen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde weiß im Modal, wo er steht und was er wählt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Optionen stehen rechts
|
||||||
|
- Bilder links
|
||||||
|
- Lange Optionslisten lassen sich scrollen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Eigenschaft benennen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt den Namen der Eigenschaft des aktuellen Schritts
|
||||||
|
<!-- regeln -->
|
||||||
|
- Name steht als Überschrift über der Liste
|
||||||
|
- Name kommt aus der Eigenschaftsgruppe
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Überschrift nennt die erste Eigenschaft
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Schrittstand anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt den aktuellen Schritt und die Gesamtzahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Zähler nennt Schritt und Gesamtzahl
|
||||||
|
- Balken wächst mit dem Fortschritt
|
||||||
|
- Text kommt aus einem Textbaustein
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Zähler zeigt 1 von 3
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Optionen auflisten
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt alle Optionen der Eigenschaft mit Namen und Bild
|
||||||
|
<!-- regeln -->
|
||||||
|
- Jede Option nennt ihren Namen
|
||||||
|
- Bild erscheint nur bis zwei Schritte
|
||||||
|
- Ohne Bild erscheint der Farbwert
|
||||||
|
- Ohne Farbwert bleibt die Zeile ohne Zeichen
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Jede Option zeigt ihren Namen
|
||||||
109
daten/MtoVariantConfigurator/varianten-waehlen.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Varianten wählen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde findet Schritt für Schritt eine bestellbare Kombination
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Erster Schritt zeigt alle Optionen seiner Eigenschaft
|
||||||
|
- Folgeschritte zeigen nur kombinierbare Optionen
|
||||||
|
- Der Ablauf trägt beliebig viele Eigenschaften
|
||||||
|
- Reihenfolge der Schritte kommt aus dem Backend
|
||||||
|
- Ohne Auswahl greifen die Standardvarianten des Elternartikels
|
||||||
|
- Schritte ohne gültige Option werden übersprungen
|
||||||
|
- Eine Auswahl wechselt den Schritt nicht
|
||||||
|
- Ungültige Folgeauswahl fällt beim Wechsel weg
|
||||||
|
- Auf kleinen Bildschirmen bleibt der Ablauf derselbe
|
||||||
|
- Escape schließt den Konfigurator
|
||||||
|
- Tabulator bleibt im Dialog
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Schritt öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Öffnet den Konfigurator beim angeklickten Schritt
|
||||||
|
<!-- regeln -->
|
||||||
|
- LMB auf einen Schritt öffnet das Modal
|
||||||
|
- Offener Konfigurator wechselt nur den Schritt
|
||||||
|
- Modal hängt am Seitenkörper
|
||||||
|
<!-- testen -->
|
||||||
|
- Produktseite öffnen
|
||||||
|
- LMB auf zweiten Schritt
|
||||||
|
- Modal öffnet bei Schritt 2
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Option wählen
|
||||||
|
<!-- ziel -->
|
||||||
|
Übernimmt eine Option in die laufende Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gewählte Option ist hervorgehoben
|
||||||
|
- Nicht kombinierbare Optionen sind gesperrt
|
||||||
|
- Auswahl verwirft ungültige Folgeschritte
|
||||||
|
- Schritt bleibt nach der Wahl derselbe
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf eine Option
|
||||||
|
- Option ist hervorgehoben
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Nächsten Schritt anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Führt zum nächsten Schritt mit gültigen Optionen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Weiter-Knopf heißt am letzten Schritt Fertig
|
||||||
|
- Leere Schritte werden übersprungen
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf Weiter-Knopf
|
||||||
|
- Nächster Schritt erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorherigen Schritt anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Führt zurück zum letzten Schritt mit Optionen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Zurück-Knopf ist am ersten Schritt gesperrt
|
||||||
|
- Leere Schritte werden übersprungen
|
||||||
|
<!-- testen -->
|
||||||
|
- Zum zweiten Schritt gehen
|
||||||
|
- LMB auf Zurück-Knopf
|
||||||
|
- Erster Schritt erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Auswahl übernehmen
|
||||||
|
<!-- ziel -->
|
||||||
|
Wechselt das Produkt auf die gewählte Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- Fertig überträgt die Auswahl in die Auswahlfelder
|
||||||
|
- Unbesuchte Schritte bekommen die erste gültige Option
|
||||||
|
- Shopware führt den Variantenwechsel aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Alle Schritte durchgehen
|
||||||
|
- LMB auf Fertig-Knopf
|
||||||
|
- Produktseite zeigt die gewählte Variante
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfigurator schließen
|
||||||
|
<!-- ziel -->
|
||||||
|
Beendet die Auswahl ohne Wechsel der Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon schließt das Modal
|
||||||
|
- Escape schließt das Modal
|
||||||
|
- Fokus kehrt zum Auslöser zurück
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Escape drücken
|
||||||
|
- Modal ist geschlossen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfigurationsdaten bereitstellen
|
||||||
|
<!-- ziel -->
|
||||||
|
Legt der Produktseite alles bei, was der Konfigurator braucht
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kombinationen stammen vom Hauptartikel
|
||||||
|
- Ohne aktives Layout bleibt die Beilage aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Produktseite mit aktivem Layout öffnen
|
||||||
|
- Seite trägt Kombinationen und Vorbelegung
|
||||||
53
daten/MtoVariantConfigurator/vorschau-zeigen.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Vorschau zeigen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Der Kunde sieht Bild und Preis der gewählten Kombination
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Preis stimmt mit dem Preis der Produktseite überein
|
||||||
|
- Antwort wird nicht zwischengespeichert
|
||||||
|
- Späte Antworten überholter Anfragen werden verworfen
|
||||||
|
- Ohne eigene Bilder greifen die Bilder des Elternartikels
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorschau laden
|
||||||
|
<!-- ziel -->
|
||||||
|
Holt Bilder und Preis zur laufenden Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Jede Auswahl löst eine neue Anfrage aus
|
||||||
|
- Titelbild der Variante steht an erster Stelle
|
||||||
|
- Ohne Treffer bleibt die Produkt-ID als Rückfall
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- Andere Option wählen
|
||||||
|
- Bild und Preis ändern sich
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Galerie blättern
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt weitere Bilder der gewählten Variante
|
||||||
|
<!-- regeln -->
|
||||||
|
- Pfeil-Icons blättern vor
|
||||||
|
- Pfeil-Icons blättern zurück
|
||||||
|
- Nach dem letzten Bild folgt das erste
|
||||||
|
- Bei einem Bild bleibt die Navigation aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf Vor-Icon
|
||||||
|
- Nächstes Bild erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Bild wählen
|
||||||
|
<!-- ziel -->
|
||||||
|
Wechselt das große Bild über die Vorschaubilder
|
||||||
|
<!-- regeln -->
|
||||||
|
- Aktives Vorschaubild ist hervorgehoben
|
||||||
|
- Großes Bild folgt der Wahl
|
||||||
|
<!-- testen -->
|
||||||
|
- Konfigurator öffnen
|
||||||
|
- LMB auf drittes Vorschaubild
|
||||||
|
- Großes Bild zeigt dieses Motiv
|
||||||
1024
daten/PLANER2GEN/.pruefung.json
Normal file
53
daten/PLANER2GEN/ansicht.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Ansicht
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Umschalten zwischen Ansichtsmodi und Farbschema der Anwendung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Systemänderungen wirken sofort im Auto-Modus
|
||||||
|
- Kompaktmodus zeigt Zusammenfassung
|
||||||
|
- Vollmodus zeigt alle Details
|
||||||
|
- Panel schließen entfernt es aus dem DOM
|
||||||
|
- Auge-Icon nur im Vollmodus
|
||||||
|
- MMB in beiden Modi wechselt die Ansicht
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ansicht wechseln
|
||||||
|
<!-- testen -->
|
||||||
|
- MMB auf Karte drücken
|
||||||
|
- Kompaktmodus wechselt zu Vollmodus
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Darkmode umschalten
|
||||||
|
<!-- ziel -->
|
||||||
|
Passt die Farbgebung der App an die bevorzugte Umgebung an
|
||||||
|
<!-- regeln -->
|
||||||
|
- Zyklus: Auto → Hell → Dunkel → Auto
|
||||||
|
- Auto folgt der Systemeinstellung
|
||||||
|
- Auswahl bleibt nach Neuladen erhalten
|
||||||
|
<!-- testen -->
|
||||||
|
- Klick auf Darkmode-Icon
|
||||||
|
- Modus wechselt zum nächsten im Zyklus
|
||||||
|
- Seite zeigt den gewählten Modus
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Panel schließen
|
||||||
|
<!-- regeln -->
|
||||||
|
- schliesse entfernt das Panel aus dem DOM
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf ✕-Icon
|
||||||
|
- Panel nicht mehr sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Systemfarben nutzen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur wirksam wenn Modus auf Auto steht
|
||||||
|
- Systemänderungen werden sofort übernommen
|
||||||
|
<!-- testen -->
|
||||||
|
- Auto-Modus wählen
|
||||||
|
- Systemfarbschema wechseln
|
||||||
|
- App-Oberfläche passt sich an
|
||||||
83
daten/PLANER2GEN/chat.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Chat
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Beschreibt ein Projekt im Gespräch und erhält Karten als Vorschlag zurück
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Unbekanntes Projekt gibt 404
|
||||||
|
- x-Icon schließt Chat
|
||||||
|
- Prüfung wird geschlossen falls offen
|
||||||
|
- Chat verwerfen nur bei vorhandenem Verlauf
|
||||||
|
- Chat verwerfen ohne Backend-Aufruf
|
||||||
|
- body enthält Pflichtfeld text
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chat leeren
|
||||||
|
<!-- ziel -->
|
||||||
|
Entfernt alle Nachrichten aus dem Chatverlauf
|
||||||
|
<!-- regeln -->
|
||||||
|
- 404 bei unbekanntem Projekt
|
||||||
|
- chat.leeren aufgerufen
|
||||||
|
<!-- testen -->
|
||||||
|
- Chat öffnen
|
||||||
|
- Nachricht senden
|
||||||
|
- Löschanfrage an Chat senden
|
||||||
|
- Antwort {"ok": true}
|
||||||
|
- Chatverlauf leer
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chat schließen
|
||||||
|
<!-- ziel -->
|
||||||
|
Schließt das Chat-Panel und kehrt zur Projektansicht zurück
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon
|
||||||
|
- Schließen ist immer möglich
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf x-Icon
|
||||||
|
- Panel entfernt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chat öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Tauscht die rechte Spalte zwischen Chat und Prüfung aus
|
||||||
|
<!-- regeln -->
|
||||||
|
- Schließt Prüfung, falls offen
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung öffnen
|
||||||
|
- LMB auf Chat-Button
|
||||||
|
- ChatPanel sichtbar, PrüfPanel geschlossen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chat-Nachricht senden
|
||||||
|
<!-- ziel -->
|
||||||
|
Holt eine Antwort des KI-Agenten zum aktuellen Projekt
|
||||||
|
<!-- regeln -->
|
||||||
|
- body enthält Pflichtfeld text
|
||||||
|
- Leere Nachricht wird abgelehnt
|
||||||
|
- Projekt nicht gefunden gibt 404
|
||||||
|
- Agent nicht konfiguriert gibt 400
|
||||||
|
- Antwort enthält gesamten Verlauf
|
||||||
|
<!-- testen -->
|
||||||
|
- POST an Chat-Endpunkt mit Text Frage
|
||||||
|
- Antwort mit Verlauf enthält Nachricht und Antwort
|
||||||
|
- POST mit leerem Text gibt 400
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Nachricht senden
|
||||||
|
<!-- ziel -->
|
||||||
|
Schickt eine Nachricht und zeigt die Antwort der KI
|
||||||
|
<!-- regeln -->
|
||||||
|
- Leere Nachrichten nicht senden
|
||||||
|
- Anfragen werden nacheinander verarbeitet
|
||||||
|
- Eigene Nachricht erscheint sofort vor der Antwort
|
||||||
|
- Fehlgeschlagene Nachrichten werden wiederhergestellt
|
||||||
|
<!-- testen -->
|
||||||
|
- Text ins Eingabefeld eingeben
|
||||||
|
- Enter drücken
|
||||||
|
- Eigene Nachricht erscheint im Verlauf
|
||||||
|
- Ladeanzeige erscheint
|
||||||
|
- Antwort der KI erscheint im Verlauf
|
||||||
260
daten/PLANER2GEN/features.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Features
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Verwaltet Features und Projekte einer Software, einschließlich Bearbeitung, Suche und Überprüfung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Seitenleiste zeigt alle Projekte
|
||||||
|
- Features stehen in Spalten unter dem aktiven Projekt
|
||||||
|
- MMB oder Augen-Icon wechselt Kompakt- und Vollmodus
|
||||||
|
- Fehlerhafte Karte öffnet sich im Vollmodus
|
||||||
|
- Suchergebnis blendet nicht passende Karten aus
|
||||||
|
- Vorgeschlagene Karte als Umriss dargestellt
|
||||||
|
- Feature verschieben nur bei mehreren Projekten
|
||||||
|
- Kontextmenü der Karte nur im Vollmodus
|
||||||
|
- Problemanzahl als Badge auf der Karte
|
||||||
|
- Sammelaktion ab drei vorgeschlagenen Karten
|
||||||
|
- Stand wechselt zwischen offen und gebaut
|
||||||
|
- Funktionen sind über Kontextmenü nach oben und unten verschiebbar
|
||||||
|
- Einzelne Felder lassen sich zu einer Funktion hinzufügen
|
||||||
|
- Verschieben unbekannter Features wirft 404
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Badge umschalten
|
||||||
|
<!-- ziel -->
|
||||||
|
Markiert ein Feature als kern oder optional
|
||||||
|
<!-- regeln -->
|
||||||
|
- Toggelt zwischen kern und optional
|
||||||
|
- Nur im Vollmodus verfügbar
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Badge
|
||||||
|
- Badge zeigt optional statt kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature anlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Legt ein neues Feature im aktuellen Projekt an
|
||||||
|
<!-- regeln -->
|
||||||
|
- Titel ist Pflichtfeld
|
||||||
|
- Erstellt leere Feature-Datei mit Titel
|
||||||
|
- Neue Karte öffnet im Vollmodus
|
||||||
|
- Alphabetisch einsortiert
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf den Bereich
|
||||||
|
- Klick auf "+ Feature erstellen"
|
||||||
|
- Titel eingeben und bestätigen
|
||||||
|
- Karte im Vollmodus sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature bearbeiten
|
||||||
|
<!-- ziel -->
|
||||||
|
Ändert Titel, Ziel, Regeln, Funktion oder Testschritte direkt in der Karte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Titel-Klick öffnet ein Eingabefeld
|
||||||
|
- Nur im Vollmodus verfügbar
|
||||||
|
- Escape verwirft die Änderung
|
||||||
|
- Enter oder Klick außerhalb speichert
|
||||||
|
- Leerer Titel wird verworfen
|
||||||
|
<!-- testen -->
|
||||||
|
- Karte im Vollmodus öffnen
|
||||||
|
- Klick auf Titel
|
||||||
|
- Titel-Feld ist fokussiert und markiert
|
||||||
|
- Text ändern und Enter drücken
|
||||||
|
- Titel aktualisiert
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature löschen
|
||||||
|
<!-- ziel -->
|
||||||
|
Entfernt ein Feature dauerhaft aus dem Projekt
|
||||||
|
<!-- regeln -->
|
||||||
|
- Wird über das Kontextmenü der Karte aufgerufen
|
||||||
|
- Erfragt eine Bestätigung vor dem Entfernen
|
||||||
|
- Löst das Entfernen über ein Event aus
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf die Feature-Karte
|
||||||
|
- Klick auf "Feature entfernen"
|
||||||
|
- Bestätigung im Dialog
|
||||||
|
- Feature entfernt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature sichern
|
||||||
|
<!-- ziel -->
|
||||||
|
Speichert Änderungen an einer Feature-Karte dauerhaft
|
||||||
|
<!-- regeln -->
|
||||||
|
- Akzeptiert rohen Kartentext als Text
|
||||||
|
- Gibt 404 bei unbekanntem Projekt
|
||||||
|
- Gibt 400 bei ungültigem Kartentext
|
||||||
|
<!-- testen -->
|
||||||
|
- PUT api.projekte.name.features.id mit body {"roh": "Neuer Text"}
|
||||||
|
- Antwort 200 mit aktualisierter Karte
|
||||||
|
- Karte enthält neuen Text
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature verschieben
|
||||||
|
<!-- ziel -->
|
||||||
|
Verschiebt ein Feature in ein anderes Projekt
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur möglich wenn mehrere Projekte existieren
|
||||||
|
- Zielprojekt muss existieren
|
||||||
|
- Feature bleibt unter neuer Projektzugehörigkeit erhalten
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Karte
|
||||||
|
- Klick auf "→ Feature verschieben"
|
||||||
|
- Zielprojekt auswählen
|
||||||
|
- Feature im Zielprojekt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt auswählen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt die Features eines anderen Projekts an
|
||||||
|
<!-- regeln -->
|
||||||
|
- Aktives Projekt ist hervorgehoben
|
||||||
|
- Klick auf aktives Projekt bleibt aktiv
|
||||||
|
<!-- testen -->
|
||||||
|
- Klick auf Projekt in der Liste
|
||||||
|
- Projekt ist hervorgehoben
|
||||||
|
- Vorher aktives Projekt ist nicht mehr hervorgehoben
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt entfernen
|
||||||
|
<!-- ziel -->
|
||||||
|
Löscht ein nicht mehr benötigtes Projekt mitsamt aller seiner Features
|
||||||
|
<!-- regeln -->
|
||||||
|
- Fragt zur Bestätigung nach dem Projektnamen
|
||||||
|
- Löscht das Projekt und alle zugehörigen Features
|
||||||
|
- x-Icon bricht ab
|
||||||
|
- Entfernen-Button bestätigt die Löschung
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Projekt in der Seitenleiste
|
||||||
|
- Klick auf ✕ Projekt entfernen
|
||||||
|
- Klick auf Entfernen
|
||||||
|
- Projekt nicht mehr in der Liste
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt erstellen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht das Anlegen eines neuen Projekts über die Seitenleiste
|
||||||
|
<!-- regeln -->
|
||||||
|
- Projektname ist erforderlich
|
||||||
|
- Leerzeichen am Rand werden entfernt
|
||||||
|
- Projektname darf nicht existieren
|
||||||
|
- Erstelltes Projekt wird automatisch ausgewählt
|
||||||
|
<!-- testen -->
|
||||||
|
- Seitenleiste mit RMB öffnen
|
||||||
|
- Klick auf "+ Projekt"
|
||||||
|
- Projektname eingeben
|
||||||
|
- Klick auf "Erstellen"
|
||||||
|
- Modal geschlossen, Projekt ausgewählt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt löschen
|
||||||
|
<!-- regeln -->
|
||||||
|
- 404 bei unbekanntem Projekt
|
||||||
|
- 400 bei ungültigem Wert
|
||||||
|
- Erfolg gibt {"ok": true} zurück
|
||||||
|
<!-- testen -->
|
||||||
|
- api-projekte name per GET aufrufen
|
||||||
|
- Antwort pruefen: 404
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt umbenennen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht das Umbenennen bestehender Projekte direkt in der Seitenleiste
|
||||||
|
<!-- regeln -->
|
||||||
|
- Umbenennung über Kontextmenü
|
||||||
|
- Umbenennung über Enter-Taste
|
||||||
|
- Escape bricht die Umbenennung ab
|
||||||
|
- Leere Eingabe verwirft die Änderung
|
||||||
|
- Gleicher Name wie zuvor verwirft die Änderung
|
||||||
|
- Projekt mit gleichem Namen erzeugt Fehler
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Projekt in der Seitenleiste
|
||||||
|
- Klick auf "Projekt anpassen"
|
||||||
|
- Neuen Namen eingeben
|
||||||
|
- Enter-Taste drücken
|
||||||
|
- Projektname in der Liste aktualisiert
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekte anzeigen
|
||||||
|
<!-- testen -->
|
||||||
|
- Projekte-Endpunkt aufrufen
|
||||||
|
- Rückgabe prüfen
|
||||||
|
- Liste aller Projekte zurück
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projektfeatures anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt alle Features eines Projekts im Rohformat
|
||||||
|
<!-- regeln -->
|
||||||
|
- Rohformat enthält Karte und Funktionen
|
||||||
|
- Fehlendes Projekt wirft 404
|
||||||
|
<!-- testen -->
|
||||||
|
- GET api projekte MeinProjekt features
|
||||||
|
- Antwort enthält Liste der Features
|
||||||
|
- 404 bei unbekanntem Projekt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projektmenü öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Bietet Zugriff auf Anpassung, Quelle und Löschung einzelner Projekte
|
||||||
|
<!-- regeln -->
|
||||||
|
- RMB auf Projektname
|
||||||
|
- Drei Optionen verfügbar
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Projekt in Seitenleiste
|
||||||
|
- Menü erscheint mit drei Einträgen
|
||||||
|
- ✎ Projekt anpassen startet Umbenennung
|
||||||
|
- ◇ Quelle festlegen öffnet Modal zur Pfadwahl
|
||||||
|
- ✕ Projekt entfernen öffnet Bestätigungsdialog
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Quelldaten laden
|
||||||
|
<!-- ziel -->
|
||||||
|
Legt fest, woher ein Projekt seine Daten bezieht
|
||||||
|
<!-- regeln -->
|
||||||
|
- GET gibt den aktuellen Pfad zurück
|
||||||
|
- PUT setzt einen neuen Pfad
|
||||||
|
- Leerer Pfad löscht die Zuordnung
|
||||||
|
<!-- testen -->
|
||||||
|
- PUT quelle mit projektname und pfad data input
|
||||||
|
- GET quelle mit projektname
|
||||||
|
- Rueckgabe enthaelt projektname und pfad data input
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vision entfernen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht das Löschen einer fehlerhaften oder überflüssigen Vision
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon
|
||||||
|
- RMB auf Karte öffnet Menü
|
||||||
|
- Klick außerhalb schließt Menü
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Vision-Karte
|
||||||
|
- Klick auf "Vision entfernen"
|
||||||
|
- Vision-Karte entfernt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vision erstellen
|
||||||
|
<!-- ziel -->
|
||||||
|
Erstellt eine einzelne Vision-Karte für das Projekt
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur eine Vision je Projekt
|
||||||
|
- Vision steht über den Features
|
||||||
|
- Karte vom Typ vision
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf den Bereich
|
||||||
|
- Klick auf + Vision erstellen
|
||||||
|
- Vision-Karte sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Doppeltes Projekt verhindern
|
||||||
|
<!-- testen -->
|
||||||
|
- Projekt mit Namen erstellen
|
||||||
|
- Projekt mit gleichem Namen erstellen
|
||||||
|
- Meldung erscheint
|
||||||
|
- Projekt nicht erstellt
|
||||||
102
daten/PLANER2GEN/funktionen.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Funktionen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Beschreibt einzelne Schritte einer Feature-Karte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Leere Titel werden verworfen
|
||||||
|
- Bearbeiten nur im Vollmodus
|
||||||
|
- Stand wechselt zwischen offen und gebaut
|
||||||
|
- Jede Änderung speichert sofort
|
||||||
|
- Kein Verschieben bei erster oder letzter Funktion
|
||||||
|
- Kontextmenü schließt bei Klick außerhalb
|
||||||
|
- Funktionsmenü in Kompaktmodus öffnet Kartenmenü
|
||||||
|
- Ziel, Regeln und Testen ohne Bearbeitung im Kompaktmodus
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion anlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Beschreibt eine neue Funktion der Karte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Funktion enthält Ziel, Regeln und Testen
|
||||||
|
- Neue Funktion beginnt mit Stand gebaut
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Feature-Karte
|
||||||
|
- Klick auf "+ Funktion erstellen"
|
||||||
|
- Titel im Modal eingeben
|
||||||
|
- Klick auf "Erstellen"
|
||||||
|
- Funktion erscheint in der Liste
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion löschen
|
||||||
|
<!-- ziel -->
|
||||||
|
Entfernt eine nicht mehr benötigte Funktion aus der Liste
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur im Vollmodus verfügbar
|
||||||
|
- x-Icon im Kontextmenü
|
||||||
|
- Modale Rückfrage vor dem Entfernen
|
||||||
|
- Speichert die Änderung
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- RMB auf Funktion
|
||||||
|
- x-Icon betätigen
|
||||||
|
- Funktion entfernt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion nach oben bewegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ändert die Reihenfolge der Funktionen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur verfügbar bei nicht erster Funktion
|
||||||
|
- Vertauscht die Funktionsposition mit der darüberliegenden
|
||||||
|
- Speichert automatisch
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Funktion
|
||||||
|
- ▲ Nach oben wählen
|
||||||
|
- Funktion eine Position höher
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktionsstand wechseln
|
||||||
|
<!-- ziel -->
|
||||||
|
Markiert eine Funktion als bestellt oder gebaut
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur im Vollmodus verfügbar
|
||||||
|
- @click.stop verhindert das Kartenmenü
|
||||||
|
- Toggle zwischen offen und gebaut
|
||||||
|
- Speichert sofort
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Stand-Button einer Funktion
|
||||||
|
- Stand wechselt von offen zu ✓ oder zurück
|
||||||
|
- Änderung gespeichert
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Kontextmenü anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Bietet Zugriff auf kontextbezogene Aktionen einer Karte oder Funktion
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kartenmenü im Kompaktmodus verfügbar
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- RMB auf Funktionszeile
|
||||||
|
- Menü mit Verschieben und Entfernen sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Kontextmenü verbergen
|
||||||
|
<!-- ziel -->
|
||||||
|
Schließt das Kontextmenü, wenn die Person woanders klickt oder Escape drückt
|
||||||
|
<!-- regeln -->
|
||||||
|
- Klick außerhalb des Menüs schließt es
|
||||||
|
- Klick auf Menüeintrag führt Aktion aus
|
||||||
|
- Escape-Taste schließt das Menü
|
||||||
|
<!-- testen -->
|
||||||
|
- Kontextmenü öffnen
|
||||||
|
- LMB außerhalb des Menüs klicken
|
||||||
|
- Menü geschlossen
|
||||||
|
- Kontextmenü erneut öffnen
|
||||||
|
- Escape-Taste drücken
|
||||||
|
- Menü geschlossen
|
||||||
92
daten/PLANER2GEN/pruefung.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Prüfung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft Feature-Karten auf formale Fehler und zeigt Stellen mit Problemen an
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prüfung beenden gibt {"ok": true} zurück
|
||||||
|
- Fehlerstelle wird 1200ms lang hervorgehoben
|
||||||
|
- PruefPanel zeigt eine von vier Prüfarten: stil, feature, code, erzeugen
|
||||||
|
- Leerer Status ohne aktive Prüfung
|
||||||
|
- Eingabe bestätigen erfordert Bereitschaft des Systems
|
||||||
|
- Klick auf ✕ schließt das Panel
|
||||||
|
- Chat öffnen schließt das Prüfungspanel
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Eingabe bestätigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht schnelle Bestätigung der Eingabe per Tastatur
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur auslösbar wenn bereit true ist
|
||||||
|
- Emittiert bestaetigt-Event mit getrimmtem Wert
|
||||||
|
<!-- testen -->
|
||||||
|
- Eingabefeld fokussieren
|
||||||
|
- Text eingeben
|
||||||
|
- Enter drücken
|
||||||
|
- bestaetigt-Event mit Wert
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Fehlerstelle anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Navigiert zur Feature-Karte mit dem Problem
|
||||||
|
<!-- regeln -->
|
||||||
|
- Scrollt zur Feature-Karte
|
||||||
|
- Markiert die Karte mit gesucht
|
||||||
|
- Highlight endet nach 1200ms
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung mit Problemen öffnen
|
||||||
|
- LMB auf Problemeintrag
|
||||||
|
- Karte scrollt in die Ansicht und zeigt Highlight
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Prüfung beenden
|
||||||
|
<!-- ziel -->
|
||||||
|
Bricht die laufende Prüfung ab, ohne Ergebnisse zu übernehmen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Löschaufruf an Endpunkt
|
||||||
|
- Gibt ok true zurück
|
||||||
|
- FileNotFoundError als 404 weitergereicht
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung im Projekt starten
|
||||||
|
- Warten auf Status running
|
||||||
|
- Löschaufruf an Prüfungsendpunkt senden
|
||||||
|
- Prüfung beendet
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Prüfung starten
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft das Projekt auf Probleme und zeigt gefundene Stellen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Vier Prüfarten verfügbar: erzeugen, stil, feature, code
|
||||||
|
- Progress in Prozent während der Prüfung
|
||||||
|
- PruefPanel zeigt Ergebnisse nach Abschluss
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung starten mit Art "stil"
|
||||||
|
- Fortschritt in Prozent sichtbar
|
||||||
|
- PrüfPanel zeigt Ergebnisse
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Prüfungsstatus anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt den aktuellen Zustand einer laufenden Prüfung
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ohne aktive Prüfung wird ein leerer Status zurückgegeben
|
||||||
|
<!-- testen -->
|
||||||
|
- GET-Anfrage an Prüfungsendpunkt senden
|
||||||
|
- Status der Prüfung im Response
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Systemstatus prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob der Backend-Server erreichbar ist
|
||||||
|
<!-- regeln -->
|
||||||
|
- GET api health
|
||||||
|
- Antwortet immer 200 mit {"ok": true}
|
||||||
|
<!-- testen -->
|
||||||
|
- GET api health
|
||||||
|
- Statuscode 200
|
||||||
|
- {"ok": true} im Body
|
||||||
62
daten/PLANER2GEN/verlauf.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Verlauf
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Verfolgt Änderungen und macht sie rückgängig
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- ↺-Icon zeigt letzten Schritt als Tooltip
|
||||||
|
- Leerer Verlauf ist kein Fehlerzustand
|
||||||
|
- ↩-Icon ist ↺-Icon im Code
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Aktion rückgängig machen
|
||||||
|
<!-- ziel -->
|
||||||
|
Nimmt den letzten Schritt im aktiven Projekt zurück
|
||||||
|
<!-- regeln -->
|
||||||
|
- ↺-Schaltfläche bei fehlendem letzterSchritt deaktiviert
|
||||||
|
- Strg+Z gilt nicht in Eingabefeldern
|
||||||
|
- Fehlgeschlagener Aufruf zeigt Meldung
|
||||||
|
<!-- testen -->
|
||||||
|
- Projekt mit Verlauf öffnen
|
||||||
|
- ↺-Schaltfläche anklicken
|
||||||
|
- Letzter Schritt aus Verlauf entfernt
|
||||||
|
- Meldung bleibt leer
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chat-Verlauf abrufen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht das Lesen vergangener Chat-Nachrichten eines Projekts
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gibt den gesamten Verlauf als Liste zurück
|
||||||
|
- 404 bei unbekanntem Projekt
|
||||||
|
<!-- testen -->
|
||||||
|
- GET-Anfrage an Chat-Endpunkt senden
|
||||||
|
- Verlauf als JSON-Array mit Nachrichten
|
||||||
|
- Unbekanntes Projekt gibt 404
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projektverlauf anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt den letzten Stand im Projektverlauf
|
||||||
|
<!-- regeln -->
|
||||||
|
- 404 bei unbekanntem Projekt
|
||||||
|
- Feld "letzter" enthält letzten Verlaufseintrag
|
||||||
|
<!-- testen -->
|
||||||
|
- Endpunkt für Projektverlauf aufrufen
|
||||||
|
- Antwort mit Feld letzter prüfen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Änderung verwerfen
|
||||||
|
<!-- regeln -->
|
||||||
|
- LMB auf ↩-Icon verwirft den letzten Schritt
|
||||||
|
- Strg+Z außerhalb von Eingabefeldern verwirft
|
||||||
|
- Ohne vorherigen Schritt ist das Icon deaktiviert
|
||||||
|
- Nach dem Verwerfen aktualisiert sich die Feature-Liste
|
||||||
|
<!-- testen -->
|
||||||
|
- Projekt mit einer Änderung öffnen
|
||||||
|
- LMB auf ↩-Icon
|
||||||
|
- Änderung in der Liste zurückgesetzt
|
||||||
55
daten/PLANER2GEN/vorschlaege.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Vorschläge
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt Katalogprüfungsergebnisse und erlaubt Übernahme oder Verwerfung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Fehlerhafte Karten öffnen sich im Vollmodus
|
||||||
|
- Nicht anwendbarer Vorschlag bleibt stehen
|
||||||
|
- Verwerfen entfernt das Problem dauerhaft
|
||||||
|
- Vor dem Übernehmen ist der Unterschied sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Alle Vorschläge annehmen
|
||||||
|
<!-- ziel -->
|
||||||
|
Nimmt alle Vorschläge auf einmal an statt einzeln
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kartenentfernung hat Priorität
|
||||||
|
- Nicht anwendbarer Vorschlag wird übersprungen
|
||||||
|
<!-- testen -->
|
||||||
|
- Karte mit N Problemen öffnen
|
||||||
|
- LMB auf Übernehmen-Button
|
||||||
|
- Vorschläge angewendet
|
||||||
|
- Karte gespeichert
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorschlag verwerfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Entfernt einen Vorschlag aus der Anzeige, ohne ihn anzuwenden
|
||||||
|
<!-- regeln -->
|
||||||
|
- Entfernt das Problem aus der Liste
|
||||||
|
- Die Karte bleibt unverändert
|
||||||
|
<!-- testen -->
|
||||||
|
- Karte mit Vorschlag öffnen
|
||||||
|
- Klick auf ✕-Icon
|
||||||
|
- Vorschlag verschwindet, Karte ist unverändert
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorschlag übernehmen
|
||||||
|
<!-- ziel -->
|
||||||
|
Nimmt einen Vorschlag als neue Feature-Karte ins Projekt auf
|
||||||
|
<!-- testen -->
|
||||||
|
- Feature mit Vorschlag öffnen
|
||||||
|
- Klick auf ✓-Icon
|
||||||
|
- Neue Karte im Gitter
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Alle Vorschläge verwerfen
|
||||||
|
<!-- testen -->
|
||||||
|
- Vorschläge erstellen
|
||||||
|
- "Alle verwerfen" betätigen
|
||||||
|
- Alle Vorschläge verworfen
|
||||||
928
daten/Planer/.planung/.pruefung.json
Normal file
@@ -0,0 +1,928 @@
|
|||||||
|
{
|
||||||
|
"abdeckung": {
|
||||||
|
"bedienen:ablehnen": {
|
||||||
|
"abdruck": "e9cce25d92b81ff6",
|
||||||
|
"funktion": "Vorschlag annehmen",
|
||||||
|
"trug": "089e774256dcce89"
|
||||||
|
},
|
||||||
|
"bedienen:alleAblehnen": {
|
||||||
|
"abdruck": "ac9262c7cafe9537",
|
||||||
|
"funktion": "Alle Vorschläge verwerfen",
|
||||||
|
"trug": "6869d68fdbd6d0a7"
|
||||||
|
},
|
||||||
|
"bedienen:alleAnnehmen": {
|
||||||
|
"abdruck": "94f23cae75fdc531",
|
||||||
|
"funktion": "Alle Vorschläge annehmen",
|
||||||
|
"trug": "94ebeec7b03abb70"
|
||||||
|
},
|
||||||
|
"bedienen:annehmen": {
|
||||||
|
"abdruck": "2742ad031b3d3cca",
|
||||||
|
"funktion": "Vorschlag annehmen",
|
||||||
|
"trug": "089e774256dcce89"
|
||||||
|
},
|
||||||
|
"bedienen:auswaehlen": {
|
||||||
|
"abdruck": "d061bf62466085ce",
|
||||||
|
"funktion": "Projekt aufrufen",
|
||||||
|
"trug": "e24c2ab0a27ace24"
|
||||||
|
},
|
||||||
|
"bedienen:bereichsMenue": {
|
||||||
|
"abdruck": "4277536d43c7bcd5",
|
||||||
|
"funktion": "Projekt aufrufen",
|
||||||
|
"trug": "e24c2ab0a27ace24"
|
||||||
|
},
|
||||||
|
"bedienen:bestaetigen": {
|
||||||
|
"abdruck": "db1f954b9ad01749",
|
||||||
|
"funktion": "Eingabe mit Enter bestätigen",
|
||||||
|
"trug": "43fca3b0e83b3701"
|
||||||
|
},
|
||||||
|
"bedienen:erledige": {
|
||||||
|
"abdruck": "1b80ae13958bb11a",
|
||||||
|
"funktion": "Vorgeschlagene Karte anlegen",
|
||||||
|
"trug": "de2b733c447db595"
|
||||||
|
},
|
||||||
|
"bedienen:funktionsMenue": {
|
||||||
|
"abdruck": "bacdd0a7cc9d0f92",
|
||||||
|
"funktion": "Funktion verschieben",
|
||||||
|
"trug": "6a33d1b7b5a7f28c"
|
||||||
|
},
|
||||||
|
"bedienen:karteAnnehmen": {
|
||||||
|
"abdruck": "bf348251763e402a",
|
||||||
|
"funktion": "Vorgeschlagene Karte anlegen",
|
||||||
|
"trug": "de2b733c447db595"
|
||||||
|
},
|
||||||
|
"bedienen:kartenMenue": {
|
||||||
|
"abdruck": "d3ba136a562eb1e9",
|
||||||
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
|
},
|
||||||
|
"bedienen:leeren": {
|
||||||
|
"abdruck": "f2af432a5ae3b255",
|
||||||
|
"funktion": "Gespräch verwerfen",
|
||||||
|
"trug": "1f848fba8b2a5e56"
|
||||||
|
},
|
||||||
|
"bedienen:leistenMenue": {
|
||||||
|
"abdruck": "ed951fbeaaa9bc70",
|
||||||
|
"funktion": "Projekt anlegen",
|
||||||
|
"trug": "44b8a59e1dcb20d4"
|
||||||
|
},
|
||||||
|
"bedienen:oeffneChat": {
|
||||||
|
"abdruck": "9bf171761edca141",
|
||||||
|
"funktion": "Gespräch öffnen",
|
||||||
|
"trug": "c71610a339bb298e"
|
||||||
|
},
|
||||||
|
"bedienen:projektMenue": {
|
||||||
|
"abdruck": "ea73f8c2597c0443",
|
||||||
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
|
},
|
||||||
|
"bedienen:schliesse": {
|
||||||
|
"abdruck": "919b104c012c034d",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"bedienen:schliessen": {
|
||||||
|
"abdruck": "1e12f9ae001e93e1",
|
||||||
|
"funktion": "Gespräch verwerfen",
|
||||||
|
"trug": "1f848fba8b2a5e56"
|
||||||
|
},
|
||||||
|
"bedienen:senden": {
|
||||||
|
"abdruck": "8df3402ed3373acc",
|
||||||
|
"funktion": "Nachricht senden",
|
||||||
|
"trug": "e61022551a98250f"
|
||||||
|
},
|
||||||
|
"bedienen:springe": {
|
||||||
|
"abdruck": "0b134efa50c03bff",
|
||||||
|
"funktion": "Problemstelle anspringen",
|
||||||
|
"trug": "7486526f6867c436"
|
||||||
|
},
|
||||||
|
"bedienen:starte": {
|
||||||
|
"abdruck": "761c2f62c917a3db",
|
||||||
|
"funktion": "Titel anpassen",
|
||||||
|
"trug": "d0b7a430e96e2599"
|
||||||
|
},
|
||||||
|
"bedienen:startePruefung": {
|
||||||
|
"abdruck": "31c3fef23c6319d8",
|
||||||
|
"funktion": "Gespräch öffnen",
|
||||||
|
"trug": "c71610a339bb298e"
|
||||||
|
},
|
||||||
|
"bedienen:umbenennen": {
|
||||||
|
"abdruck": "6185b6b39708bf02",
|
||||||
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
|
},
|
||||||
|
"bedienen:wechsleAnsicht": {
|
||||||
|
"abdruck": "e3193fce50eaa58e",
|
||||||
|
"funktion": "Anzeigemodus umschalten",
|
||||||
|
"trug": "fccea553715951de"
|
||||||
|
},
|
||||||
|
"bedienen:wechsleArt": {
|
||||||
|
"abdruck": "67250a1e8d82e960",
|
||||||
|
"funktion": "Feature einstufen",
|
||||||
|
"trug": "90b6c77ff0c412e6"
|
||||||
|
},
|
||||||
|
"bedienen:wechsleDarkMode": {
|
||||||
|
"abdruck": "b83524eb0e12a7d7",
|
||||||
|
"funktion": "Darkmode umschalten",
|
||||||
|
"trug": "4033fc55c753db3e"
|
||||||
|
},
|
||||||
|
"bedienen:wechsleStand": {
|
||||||
|
"abdruck": "8c2e3bf3be58ec00",
|
||||||
|
"funktion": "Stand umschalten",
|
||||||
|
"trug": "37a3835d1d2a3e6c"
|
||||||
|
},
|
||||||
|
"bedienen:zurueck": {
|
||||||
|
"abdruck": "e378014fbce96d10",
|
||||||
|
"funktion": "Schritt zurücknehmen",
|
||||||
|
"trug": "10e46c458040a5e3"
|
||||||
|
},
|
||||||
|
"hoeren:systemDunkel.change": {
|
||||||
|
"abdruck": "d3ef681b987557dc",
|
||||||
|
"funktion": "Darkmode umschalten",
|
||||||
|
"trug": "4033fc55c753db3e"
|
||||||
|
},
|
||||||
|
"hoeren:window.click": {
|
||||||
|
"abdruck": "620812522ed2d900",
|
||||||
|
"funktion": "Kontextmenü schließen",
|
||||||
|
"trug": "87216235bbe42a1a"
|
||||||
|
},
|
||||||
|
"hoeren:window.keydown": {
|
||||||
|
"abdruck": "c3e1df2d60fcc067",
|
||||||
|
"funktion": "Schritt zurücknehmen",
|
||||||
|
"trug": "10e46c458040a5e3"
|
||||||
|
},
|
||||||
|
"menue:+ Feature erstellen": {
|
||||||
|
"abdruck": "82c40072137d5757",
|
||||||
|
"funktion": "Feature anlegen",
|
||||||
|
"trug": "df7a1452a32da410"
|
||||||
|
},
|
||||||
|
"menue:+ Funktion erstellen": {
|
||||||
|
"abdruck": "b44457d64a5ec9d1",
|
||||||
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
|
},
|
||||||
|
"menue:+ Projekt erstellen": {
|
||||||
|
"abdruck": "2be770390433a2ce",
|
||||||
|
"funktion": "Projekt anlegen",
|
||||||
|
"trug": "44b8a59e1dcb20d4"
|
||||||
|
},
|
||||||
|
"menue:+ Vision erstellen": {
|
||||||
|
"abdruck": "cb22a654070e58b4",
|
||||||
|
"funktion": "Feature anlegen",
|
||||||
|
"trug": "df7a1452a32da410"
|
||||||
|
},
|
||||||
|
"menue:→ Feature verschieben": {
|
||||||
|
"abdruck": "38e6b244668ee164",
|
||||||
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
|
},
|
||||||
|
"menue:▲ Nach oben": {
|
||||||
|
"abdruck": "4e6fd1617d817357",
|
||||||
|
"funktion": "Funktion verschieben",
|
||||||
|
"trug": "6a33d1b7b5a7f28c"
|
||||||
|
},
|
||||||
|
"menue:▼ Nach unten": {
|
||||||
|
"abdruck": "183651450e546c4f",
|
||||||
|
"funktion": "Funktion entfernen",
|
||||||
|
"trug": "885e16a93460f706"
|
||||||
|
},
|
||||||
|
"menue:◇ Quelle festlegen": {
|
||||||
|
"abdruck": "e4bb1781bc375d6d",
|
||||||
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
|
},
|
||||||
|
"menue:✎ Projekt anpassen": {
|
||||||
|
"abdruck": "ad505addca45b06c",
|
||||||
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
|
},
|
||||||
|
"menue:✕ Feature entfernen": {
|
||||||
|
"abdruck": "bc968582260c45b9",
|
||||||
|
"funktion": "Funktion anlegen",
|
||||||
|
"trug": "78d0329cf6eacca9"
|
||||||
|
},
|
||||||
|
"menue:✕ Funktion entfernen": {
|
||||||
|
"abdruck": "d29458b150c55777",
|
||||||
|
"funktion": "Funktion verschieben",
|
||||||
|
"trug": "6a33d1b7b5a7f28c"
|
||||||
|
},
|
||||||
|
"menue:✕ Projekt entfernen": {
|
||||||
|
"abdruck": "aca25c0b5feaf752",
|
||||||
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
|
},
|
||||||
|
"menue:✕ Vision entfernen": {
|
||||||
|
"abdruck": "88520b1f105d3fb5",
|
||||||
|
"funktion": "Anzeigemodus umschalten",
|
||||||
|
"trug": "fccea553715951de"
|
||||||
|
},
|
||||||
|
"route:delete /projekte/{name}": {
|
||||||
|
"abdruck": "b4f871be35d10e5f",
|
||||||
|
"funktion": "Projekt entfernen",
|
||||||
|
"trug": "e91a89b8d78dbd2e"
|
||||||
|
},
|
||||||
|
"route:delete /projekte/{name}/features/{id}": {
|
||||||
|
"abdruck": "c1db81de98835c39",
|
||||||
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
|
},
|
||||||
|
"route:delete /projekte/{name}/pruefung": {
|
||||||
|
"abdruck": "7a480f33ddf55165",
|
||||||
|
"funktion": "Prüfung beenden",
|
||||||
|
"trug": "83435eb3e1394e49"
|
||||||
|
},
|
||||||
|
"route:get /health": {
|
||||||
|
"abdruck": "65257319741edf35",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:get /projekte": {
|
||||||
|
"abdruck": "276556054437c303",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/chat": {
|
||||||
|
"abdruck": "fd2a22c36ea85545",
|
||||||
|
"funktion": "Gespräch öffnen",
|
||||||
|
"trug": "c71610a339bb298e"
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/features": {
|
||||||
|
"abdruck": "122f93cbd0d07881",
|
||||||
|
"funktion": "Projekt aufrufen",
|
||||||
|
"trug": "e24c2ab0a27ace24"
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/pruefung": {
|
||||||
|
"abdruck": "e651ac90b7b2c520",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/quelle": {
|
||||||
|
"abdruck": "834d3af3203ca3a8",
|
||||||
|
"funktion": "Quelle festlegen",
|
||||||
|
"trug": "b8b70dd0ff9817cb"
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/verlauf": {
|
||||||
|
"abdruck": "5a3dcc678733fad8",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:post /projekte": {
|
||||||
|
"abdruck": "fa1628d7e469885d",
|
||||||
|
"funktion": "Projekt anlegen",
|
||||||
|
"trug": "44b8a59e1dcb20d4"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/chat": {
|
||||||
|
"abdruck": "c3edc89cec5d2f6f",
|
||||||
|
"funktion": "Nachricht senden",
|
||||||
|
"trug": "e61022551a98250f"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/features": {
|
||||||
|
"abdruck": "00eb9c16560bac73",
|
||||||
|
"funktion": "Feature anlegen",
|
||||||
|
"trug": "df7a1452a32da410"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/pruefung": {
|
||||||
|
"abdruck": "6d7444a9dfad7d54",
|
||||||
|
"funktion": "Stil prüfen",
|
||||||
|
"trug": "d5019cdafd70efb7"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/pruefung/verworfen": {
|
||||||
|
"abdruck": "a43f7221e01c386f",
|
||||||
|
"funktion": "Alle Vorschläge verwerfen",
|
||||||
|
"trug": "6869d68fdbd6d0a7"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/verlauf/zurueck": {
|
||||||
|
"abdruck": "308fec6d21b01d60",
|
||||||
|
"funktion": "Schritt zurücknehmen",
|
||||||
|
"trug": "10e46c458040a5e3"
|
||||||
|
},
|
||||||
|
"route:put /projekte/{name}": {
|
||||||
|
"abdruck": "75383ac009961cad",
|
||||||
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
|
},
|
||||||
|
"route:put /projekte/{name}/features/{id}": {
|
||||||
|
"abdruck": "b24c7000fd2ec94c",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:put /projekte/{name}/features/{id}/projekt": {
|
||||||
|
"abdruck": "fafc7a8a1656f93b",
|
||||||
|
"funktion": "Feature verschieben",
|
||||||
|
"trug": "cd2cd705298831e0"
|
||||||
|
},
|
||||||
|
"route:put /projekte/{name}/quelle": {
|
||||||
|
"abdruck": "d342055e3163548a",
|
||||||
|
"funktion": "Quelle festlegen",
|
||||||
|
"trug": "b8b70dd0ff9817cb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"belege": {
|
||||||
|
"[\"darkmode\", \"Darkmode umschalten\"]": {
|
||||||
|
"abdruck": "653c08d0647a7f7b",
|
||||||
|
"beleg": "function wechsleDarkMode() {\n modus.value = MODI[(MODI.indexOf(modus.value) + 1) % MODI.length]\n localStorage.setItem('darkModus', modus.value)\n wendeAn()\n}",
|
||||||
|
"datei": "frontend/src/App.vue"
|
||||||
|
},
|
||||||
|
"[\"design\", \"Eingabe mit Enter bestätigen\"]": {
|
||||||
|
"abdruck": "3bace3e9c9f16023",
|
||||||
|
"beleg": "@keydown.enter.exact.prevent=\"senden\"",
|
||||||
|
"datei": "frontend/src/components/ChatPanel.vue"
|
||||||
|
},
|
||||||
|
"[\"design\", \"Kontextmenü schließen\"]": {
|
||||||
|
"abdruck": "671fa86bccb2c2c6",
|
||||||
|
"beleg": "export function schliesseMenue() {\n kontextMenue.value = null\n}",
|
||||||
|
"datei": "frontend/src/composables/kontextmenue.js"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Anzeigemodus umschalten\"]": {
|
||||||
|
"abdruck": "b22ba2dd122887fa",
|
||||||
|
"beleg": "function wechsleAnsicht() {\n gross.value = !gross.value\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature anlegen\"]": {
|
||||||
|
"abdruck": "d2968bea16a06b65",
|
||||||
|
"beleg": "async function erstellen(titel) {",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature durchsuchen\"]": {
|
||||||
|
"abdruck": "4f249c42d46d25b5",
|
||||||
|
"beleg": "function passt(f) {\n const wort = suche.value.trim().toLowerCase()\n if (!wort) return true\n const s = f.sektionen\n const text = [s.titel, s.ziel, s.regeln,\n ...s.funktionen.map(x => `${x.text} ${x.ziel} ${x.regeln} ${x.testen}`)]\n return text.join(' ').toLowerCase().includes(wort)\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature einstufen\"]": {
|
||||||
|
"abdruck": "a0ee6590c24456bb",
|
||||||
|
"beleg": "function wechsleArt() {\n s.value.art = s.value.art === 'kern' ? 'optional' : 'kern'\n speichern()\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature entfernen\"]": {
|
||||||
|
"abdruck": "c6f27381a41a63d7",
|
||||||
|
"beleg": "async function entfernen() {\n const id = modal.value.feature.id\n try {\n await featureLoeschen(props.projekt, id)\n features.value = features.value.filter(f => f.id !== id)\n modal.value = null\n emit('geaendert')\n } catch (e) {\n meldung.value = e.message\n }\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature verschieben\"]": {
|
||||||
|
"abdruck": "a82b97e3d4a1b533",
|
||||||
|
"beleg": "def verschieben(projekt: str, id: str, ziel: str) -> dict:",
|
||||||
|
"datei": "backend/features.py"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Regeln anpassen\"]": {
|
||||||
|
"abdruck": "0af0e4eadcaae560",
|
||||||
|
"beleg": "<div v-else-if=\"s.regeln\" class=\"klickbar\" @click=\"starte('regeln')\">",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Titel anpassen\"]": {
|
||||||
|
"abdruck": "7f727f71dc555492",
|
||||||
|
"beleg": "<h3 v-else :title=\"gross ? 'Klicken zum Bearbeiten' : null\" @click=\"starte('titel')\">{{ s.titel }}</h3>",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Ziel anpassen\"]": {
|
||||||
|
"abdruck": "965b52779fa6aa35",
|
||||||
|
"beleg": "<div v-else-if=\"s.ziel\" class=\"inhalt klickbar\" v-html=\"md(s.ziel)\" @click=\"starte('ziel')\" />",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion anlegen\"]": {
|
||||||
|
"abdruck": "6250215b920cf0f0",
|
||||||
|
"beleg": "async function funktionErstellen(titel) {",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion entfernen\"]": {
|
||||||
|
"abdruck": "20e8d8e507ee6310",
|
||||||
|
"beleg": "eintraege.push({ text: '✕ Funktion entfernen', aktion: () => (modal.value = { art: 'entfernen', i }) })",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion verschieben\"]": {
|
||||||
|
"abdruck": "8435119f0dd5b891",
|
||||||
|
"beleg": "async function schiebe(i, richtung) {",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Regeln anpassen\"]": {
|
||||||
|
"abdruck": "303d6988603e3d61",
|
||||||
|
"beleg": "async function starte(feld, i, j) {\n if (!gross.value) return // Bearbeiten nur in der ausführlichen Ansicht\n const original = wert(feld, i, j)\n aktiv.value = { feld, i, j, original }\n entwurf.value = original\n await nextTick()\n const el = Array.isArray(feldRef.value) ? feldRef.value[0] : feldRef.value\n el?.focus()\n if (feld === 'titel') el?.select()\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Stand umschalten\"]": {
|
||||||
|
"abdruck": "b66ea5f488899d8f",
|
||||||
|
"beleg": "@click.stop=\"wechsleStand(i)\"",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Testschritte anpassen\"]": {
|
||||||
|
"abdruck": "e303ebeaece423de",
|
||||||
|
"beleg": "<div v-else class=\"klickbar\" @click=\"starte('testen', i)\">",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Titel anpassen\"]": {
|
||||||
|
"abdruck": "3ceaa19290c793b4",
|
||||||
|
"beleg": "s.value.funktionen[a.i].text = neu",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Ziel anpassen\"]": {
|
||||||
|
"abdruck": "4cb617031cbf6ef4",
|
||||||
|
"beleg": "<div v-else class=\"inhalt klickbar\" v-html=\"md(f.ziel)\" @click=\"starte('ziel', i)\" />",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch schließen\"]": {
|
||||||
|
"abdruck": "d6de2c46300fef26",
|
||||||
|
"beleg": "<button class=\"icon-knopf\" title=\"Gespräch schließen\" @click=\"emit('schliessen')\">✕</button>",
|
||||||
|
"datei": "frontend/src/components/ChatPanel.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch verwerfen\"]": {
|
||||||
|
"abdruck": "9e74be976a4d0e15",
|
||||||
|
"beleg": "@router.delete(\"/projekte/{name}/chat\")\nasync def chat_leeren(name: str):\n try:\n chat.leeren(name)\n except FileNotFoundError as e:\n raise HTTPException(404, str(e))\n return {\"ok\": True}",
|
||||||
|
"datei": "backend/routes.py"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch öffnen\"]": {
|
||||||
|
"abdruck": "c84f9a14428a019e",
|
||||||
|
"beleg": "<button\n class=\"icon-knopf\"\n :class=\"{ an: chatOffen }\"\n title=\"Karten im Gespräch entwickeln\"\n @click=\"oeffneChat\"\n >✎</button>",
|
||||||
|
"datei": "frontend/src/App.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Nachricht senden\"]": {
|
||||||
|
"abdruck": "308218d682fe3dd9",
|
||||||
|
"beleg": "async def frage(projekt: str, text: str) -> dict:",
|
||||||
|
"datei": "backend/chat.py"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Vorgeschlagene Karte anlegen\"]": {
|
||||||
|
"abdruck": "15aa862d2e0f6f6e",
|
||||||
|
"beleg": "async function karteAnnehmen(p) {",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Alle Vorschläge annehmen\"]": {
|
||||||
|
"abdruck": "52fb2acd3d6945fa",
|
||||||
|
"beleg": "async function alleAnnehmen() {\n props.probleme.forEach(p => { if (anwenden(p)) erledige(p) })\n await speichern()\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Alle Vorschläge verwerfen\"]": {
|
||||||
|
"abdruck": "f98fd3fc005b9e56",
|
||||||
|
"beleg": "<button\n class=\"icon-knopf rot\"\n :title=\"`Alle ${probleme.length} Vorschläge verwerfen`\"\n @click=\"alleAblehnen\"\n >✕</button>",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Code in Features prüfen\"]": {
|
||||||
|
"abdruck": "daf5820ada5ee059",
|
||||||
|
"beleg": "async def code_im_feature(features: list, punkte: list, stand: dict,",
|
||||||
|
"datei": "backend/inhalt.py"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Features im Code prüfen\"]": {
|
||||||
|
"abdruck": "acc652c172671790",
|
||||||
|
"beleg": "ANKLAGE_AB = 4",
|
||||||
|
"datei": "backend/inhalt.py"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Funktion abnehmen\"]": {
|
||||||
|
"abdruck": "21095461d6190dec",
|
||||||
|
"beleg": "if beleg:\n stand[\"belege\"][schluessel] = beleg\n if x.get(\"stand\") == parser.OFFEN:\n # Abnahme: die Karte hat es bestellt, jetzt steht es im Code\n return {\"feature\": feature[\"id\"], \"titel\": x[\"text\"], \"art\": \"gebaut\",\n \"grund\": f'{beleg[\"datei\"]}: {beleg[\"beleg\"].strip()}'}",
|
||||||
|
"datei": "backend/inhalt.py"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Problemstelle anspringen\"]": {
|
||||||
|
"abdruck": "340a32321fd23834",
|
||||||
|
"beleg": "karte.classList.add('gesucht')",
|
||||||
|
"datei": "frontend/src/components/PruefPanel.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Prüfung beenden\"]": {
|
||||||
|
"abdruck": "7400070de29ce762",
|
||||||
|
"beleg": "<button class=\"icon-knopf\" title=\"Schließen\" @click=\"schliesse\">✕</button>",
|
||||||
|
"datei": "frontend/src/components/PruefPanel.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Stil prüfen\"]": {
|
||||||
|
"abdruck": "86c4530f5dd0bd52",
|
||||||
|
"beleg": "async def starte(projekt: str, art: str = \"stil\") -> dict:",
|
||||||
|
"datei": "backend/pruefung.py"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Vorschlag annehmen\"]": {
|
||||||
|
"abdruck": "e4b81d800fd23fec",
|
||||||
|
"beleg": "async function annehmen(p) {\n if (anwenden(p)) erledige(p)\n await speichern()\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Vorschlag verwerfen\"]": {
|
||||||
|
"abdruck": "b37b74ea369ed66c",
|
||||||
|
"beleg": "export function erledige(problem) {\n probleme.value = probleme.value.filter(p => p !== problem)\n}",
|
||||||
|
"datei": "frontend/src/composables/pruefung.js"
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt anlegen\"]": {
|
||||||
|
"abdruck": "9b3598c9081155b2",
|
||||||
|
"beleg": "def anlegen(name: str):",
|
||||||
|
"datei": "backend/projekte.py"
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt aufrufen\"]": {
|
||||||
|
"abdruck": "1071109545496535",
|
||||||
|
"beleg": "@click=\"emit('auswaehlen', p.name)\"",
|
||||||
|
"datei": "frontend/src/components/SeitenLeiste.vue"
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt entfernen\"]": {
|
||||||
|
"abdruck": "e413063557fa0f13",
|
||||||
|
"beleg": "shutil.rmtree(ordner)",
|
||||||
|
"datei": "backend/projekte.py"
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt umbenennen\"]": {
|
||||||
|
"abdruck": "d4e71f8ef907393c",
|
||||||
|
"beleg": "function projektMenue(e, name) {\n oeffneMenue(e, [\n { text: '✎ Projekt anpassen', aktion: () => starteUmbenennen(name) },\n { text: '◇ Quelle festlegen', aktion: () => starteQuelle(name) },\n { text: '✕ Projekt entfernen', aktion: () => oeffneModal({ art: 'entfernen', name }) },\n ])\n}",
|
||||||
|
"datei": "frontend/src/components/SeitenLeiste.vue"
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Quelle festlegen\"]": {
|
||||||
|
"abdruck": "fc6c20f4e8d74fa6",
|
||||||
|
"beleg": "if ziel == Path(ziel.anchor) or ziel == Path.home():",
|
||||||
|
"datei": "backend/quelle.py"
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Nächsten Rückschritt anzeigen\"]": {
|
||||||
|
"abdruck": "6768d8a0b83537a2",
|
||||||
|
"beleg": "letzterSchritt.value = (await fetchVerlauf(aktivesProjekt.value)).letzter",
|
||||||
|
"datei": "frontend/src/App.vue"
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Schritt zurücknehmen\"]": {
|
||||||
|
"abdruck": "c49332e7a679dfda",
|
||||||
|
"beleg": "def zurueck(projekt: str) -> str:\n \"\"\"Den letzten Schritt zurücknehmen. Gibt zurück, was zurückgenommen wurde.\"\"\"\n schritte = lade(projekt)\n if not schritte:\n raise ValueError(\"Nichts zum Zurücknehmen\")\n schritt = schritte.pop()",
|
||||||
|
"datei": "backend/verlauf.py"
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Anzeigemodus umschalten\"]": {
|
||||||
|
"abdruck": "b983b82c02343560",
|
||||||
|
"beleg": "const gross = ref(false)",
|
||||||
|
"datei": "frontend/src/components/VisionKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision anlegen\"]": {
|
||||||
|
"abdruck": "58ceb9502d1754bf",
|
||||||
|
"beleg": "<div class=\"bereich\" @contextmenu.prevent=\"bereichsMenue\">",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision anpassen\"]": {
|
||||||
|
"abdruck": "dd527b57b4c9685e",
|
||||||
|
"beleg": "async function fertig() {\n if (!aktiv.value) return\n aktiv.value = false\n const neu = entwurf.value.trim()\n if (neu === text.value) return\n text.value = neu\n await speichern()\n}",
|
||||||
|
"datei": "frontend/src/components/VisionKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision entfernen\"]": {
|
||||||
|
"abdruck": "0aeb156d13b32b7e",
|
||||||
|
"beleg": "function kartenMenue(e) {\n oeffneMenue(e, [{ text: '✕ Vision entfernen', aktion: () => emit('entfernen') }])\n}",
|
||||||
|
"datei": "frontend/src/components/VisionKarte.vue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ohne_code": {},
|
||||||
|
"ohne_karte": {
|
||||||
|
"route:delete /projekte/{name}/chat": {
|
||||||
|
"abdruck": "4ba7a240985a83b4",
|
||||||
|
"beleg": "backend/routes.py:182",
|
||||||
|
"feature": "karten-besprechen",
|
||||||
|
"karten": "41f55589fcff4959",
|
||||||
|
"stelle": "route:delete /projekte/{name}/chat",
|
||||||
|
"titel": "Chatverlauf leeren"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paare": {
|
||||||
|
"[\"[\\\"features\\\", \\\"Anzeigemodus umschalten\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Anzeigemodus umschalten\\\"]\"]": {
|
||||||
|
"abdruck": "d23706be97d6369a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature anlegen\\\"]\", \"[\\\"features\\\", \\\"Feature entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "586059671bbfa75b",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature anlegen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\"]": {
|
||||||
|
"abdruck": "3eb9956887005cc4",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature anlegen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt anlegen\\\"]\"]": {
|
||||||
|
"abdruck": "66a7175c58b703c4",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\"]": {
|
||||||
|
"abdruck": "97a9dde3e968b1eb",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "9717a5c6eab69f22",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "65c43ee10d0d8b4a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "60434d422bbd2ca3",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"features\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "75e90ff6d5453c2d",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"features\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "a9e849a044a7ee41",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "827215ea68dcbada",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "ea7185c230fbdd2f",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "bc79858745a7d262",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Titel anpassen\\\"]\", \"[\\\"features\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "69360dc1502aa01f",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Titel anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "01fbbfc1ca91d32a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Titel anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "414ddf4e7bff9896",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Ziel anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "83a0a87e978c859a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Ziel anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "5e97e3c6fe9df287",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "3e58148f974b6b34",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt anlegen\\\"]\"]": {
|
||||||
|
"abdruck": "202f51c53a73b452",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Funktion entfernen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "a9d2c4dd5e70fa2c",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Funktion entfernen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "4090ee6826466fa3",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "c8321ad2b769d77f",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "7ce003da82080259",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"karten-pruefen\\\", \\\"Funktion abnehmen\\\"]\"]": {
|
||||||
|
"abdruck": "d2f80684e1be6287",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Testschritte anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "7a51922f327ba28b",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Testschritte anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "97a70c7ecfbb9f30",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "17b00db94f54b62d",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "d17a7b7bad8d947a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "e25b21ca4183d372",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"karten-pruefen\\\", \\\"Alle Vorschläge annehmen\\\"]\", \"[\\\"karten-pruefen\\\", \\\"Alle Vorschläge verwerfen\\\"]\"]": {
|
||||||
|
"abdruck": "b9c707879cffb7cd",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"karten-pruefen\\\", \\\"Alle Vorschläge verwerfen\\\"]\", \"[\\\"karten-pruefen\\\", \\\"Vorschlag verwerfen\\\"]\"]": {
|
||||||
|
"abdruck": "aed348a776637c4a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"projekte\\\", \\\"Projekt anlegen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "957ebec8e3096ce6",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"projekte\\\", \\\"Projekt entfernen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "8757fb8b0f89d5a6",
|
||||||
|
"gleich": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stil": {
|
||||||
|
"[\"darkmode\", \"\"]": {
|
||||||
|
"abdruck": "a5b0fd59f25c55ae",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"darkmode\", \"Darkmode umschalten\"]": {
|
||||||
|
"abdruck": "0dbcca6954cc4d16",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"design\", \"\"]": {
|
||||||
|
"abdruck": "1ae7985ebcfe6c6a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"design\", \"Eingabe mit Enter bestätigen\"]": {
|
||||||
|
"abdruck": "b961f168017fb01e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"design\", \"Kontextmenü schließen\"]": {
|
||||||
|
"abdruck": "a3717ea52a012784",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"\"]": {
|
||||||
|
"abdruck": "b4f6fe4cd065e9e9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Anzeigemodus umschalten\"]": {
|
||||||
|
"abdruck": "8d468a73bcec5521",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature anlegen\"]": {
|
||||||
|
"abdruck": "7d4d6e36b4f03318",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature durchsuchen\"]": {
|
||||||
|
"abdruck": "3acde68ab76b942c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature einstufen\"]": {
|
||||||
|
"abdruck": "10e74d934dafacc9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature entfernen\"]": {
|
||||||
|
"abdruck": "509a1153b2e6382f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature verschieben\"]": {
|
||||||
|
"abdruck": "5169bc85a796b6a9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Regeln anpassen\"]": {
|
||||||
|
"abdruck": "6b4eeed76c631333",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Titel anpassen\"]": {
|
||||||
|
"abdruck": "de6e2788e24aa230",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Ziel anpassen\"]": {
|
||||||
|
"abdruck": "f26e4426663b8265",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"\"]": {
|
||||||
|
"abdruck": "adcc38efdb393cf6",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion anlegen\"]": {
|
||||||
|
"abdruck": "11c59974712619a0",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion entfernen\"]": {
|
||||||
|
"abdruck": "e55d43e078f1020e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion verschieben\"]": {
|
||||||
|
"abdruck": "1bdf3be9b53fdb99",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Regeln anpassen\"]": {
|
||||||
|
"abdruck": "e354caf2701ee79c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Stand umschalten\"]": {
|
||||||
|
"abdruck": "f39b25d34cc6e4c4",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Testschritte anpassen\"]": {
|
||||||
|
"abdruck": "8fb282300c19c3c2",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Titel anpassen\"]": {
|
||||||
|
"abdruck": "63e9a78a02d890fe",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Ziel anpassen\"]": {
|
||||||
|
"abdruck": "a72ce3656c787475",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"\"]": {
|
||||||
|
"abdruck": "8d330c5a71e25e0c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch schließen\"]": {
|
||||||
|
"abdruck": "d1c043cd26a05d69",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch verwerfen\"]": {
|
||||||
|
"abdruck": "e6d601409eb48f7a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch öffnen\"]": {
|
||||||
|
"abdruck": "b9a3f8340aa9e266",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Nachricht senden\"]": {
|
||||||
|
"abdruck": "c3ec1453a9aff541",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Vorgeschlagene Karte anlegen\"]": {
|
||||||
|
"abdruck": "ca0a5b02f10bbd1e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"\"]": {
|
||||||
|
"abdruck": "43f92e591142fa95",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Alle Vorschläge annehmen\"]": {
|
||||||
|
"abdruck": "5afe1e8ec40d8b82",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Alle Vorschläge verwerfen\"]": {
|
||||||
|
"abdruck": "c44bbe11908637e9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Code in Features prüfen\"]": {
|
||||||
|
"abdruck": "ed5da2461b99ddf3",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Features im Code prüfen\"]": {
|
||||||
|
"abdruck": "c31fcfa6dbc75232",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Funktion abnehmen\"]": {
|
||||||
|
"abdruck": "dd3cf177204bcf25",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Problemstelle anspringen\"]": {
|
||||||
|
"abdruck": "0656bbf0d6554c85",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Prüfung beenden\"]": {
|
||||||
|
"abdruck": "af40481c06d2fd97",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Stil prüfen\"]": {
|
||||||
|
"abdruck": "8326e792e8383b53",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Vorschlag annehmen\"]": {
|
||||||
|
"abdruck": "6f8d17b80787b7aa",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Vorschlag verwerfen\"]": {
|
||||||
|
"abdruck": "cf5bda968428df9f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"\"]": {
|
||||||
|
"abdruck": "a59f6fdfa58188fa",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt anlegen\"]": {
|
||||||
|
"abdruck": "2c3ab4452996ffaf",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt aufrufen\"]": {
|
||||||
|
"abdruck": "2ca7a3ea9e79fc6e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt entfernen\"]": {
|
||||||
|
"abdruck": "a0c52ceb9605a805",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt umbenennen\"]": {
|
||||||
|
"abdruck": "d41292ff010e7fd3",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Quelle festlegen\"]": {
|
||||||
|
"abdruck": "990ee354f1aa70fa",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"\"]": {
|
||||||
|
"abdruck": "f4ecff18e05ff058",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Nächsten Rückschritt anzeigen\"]": {
|
||||||
|
"abdruck": "2d944c3424b6f851",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Schritt zurücknehmen\"]": {
|
||||||
|
"abdruck": "28825333acb1e107",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"\"]": {
|
||||||
|
"abdruck": "e83e9ad5470aa4b1",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Anzeigemodus umschalten\"]": {
|
||||||
|
"abdruck": "87c494e1e1b0a7b2",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision anlegen\"]": {
|
||||||
|
"abdruck": "7826b26956ba51d2",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision anpassen\"]": {
|
||||||
|
"abdruck": "378ea30c154f7994",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision entfernen\"]": {
|
||||||
|
"abdruck": "340343e51583e86a",
|
||||||
|
"probleme": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verworfen": {
|
||||||
|
"route:delete /projekte/{name}/chat": {
|
||||||
|
"abdruck": "4ba7a240985a83b4",
|
||||||
|
"titel": "Gespräch schließen\nChatverlauf leeren"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
daten/Planer/.planung/darkmode.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Darkmode
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Passt die Anwendung ans Umgebungslicht an
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Auto folgt der Systemeinstellung
|
||||||
|
- Zustand überlebt das Neuladen
|
||||||
|
- Kein Flackern beim Laden
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Darkmode umschalten
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nacht-Icon: ☾
|
||||||
|
- Tag-Icon: ☀
|
||||||
|
- Auto-Icon: A
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Icon in der Kopfzeile
|
||||||
|
- Modus gewechselt
|
||||||
44
daten/Planer/.planung/design.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Design
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Hält die Oberfläche lesbar und ruhig
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Karten stapeln sich lückenlos in Spalten
|
||||||
|
- Lesefeld ist in der Breite gedeckelt
|
||||||
|
- Leisten scrollen nicht mit
|
||||||
|
- Schmale Fenster stapeln die Bereiche
|
||||||
|
- Klickflächen sind mindestens 28 Pixel
|
||||||
|
- Textkontrast erfüllt die Lesbarkeitsschwelle
|
||||||
|
- Kontextmenü bleibt im Fenster
|
||||||
|
- Modal setzt den Fokus ins Eingabefeld
|
||||||
|
- Escape schließt das Modal
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Eingabe mit Enter bestätigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Spart den Weg zum Knopf
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gilt in einzeiligen Feldern
|
||||||
|
- Escape verwirft stattdessen
|
||||||
|
<!-- testen -->
|
||||||
|
- Modal öffnen
|
||||||
|
- Text eingeben
|
||||||
|
- Enter drücken
|
||||||
|
- Eingabe übernommen
|
||||||
|
<!-- funktion -->
|
||||||
|
Kontextmenü schließen
|
||||||
|
<!-- ziel -->
|
||||||
|
Räumt ein offenes Menü aus dem Weg
|
||||||
|
<!-- regeln -->
|
||||||
|
- Klick daneben schließt es
|
||||||
|
- Escape schließt es
|
||||||
|
- Ein neues Menü verdrängt das alte
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf eine Karte
|
||||||
|
- LMB daneben
|
||||||
|
- Menü geschlossen
|
||||||
109
daten/Planer/.planung/features.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Features
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Hält fest, was ein Projekt können soll
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Eine Markdown-Datei je Feature
|
||||||
|
- Die Datei ist die Wahrheit
|
||||||
|
- Titel bestimmt den Dateinamen
|
||||||
|
- Namensgleichheit bekommt eine Nummer
|
||||||
|
- Fremde Marker bleiben erhalten
|
||||||
|
- Alphabetisch sortiert
|
||||||
|
- Anpassen nur im Vollmodus
|
||||||
|
- Verlassen speichert
|
||||||
|
- Escape verwirft
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Anzeigemodus umschalten
|
||||||
|
<!-- ziel -->
|
||||||
|
Kompaktmodus zum Lesen, Vollmodus zum Anpassen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Standard ist Kompaktmodus
|
||||||
|
- Vollmodus zeigt alle Felder
|
||||||
|
- Augen-Icon rechts oben
|
||||||
|
<!-- testen -->
|
||||||
|
- MMB auf Featurecard
|
||||||
|
- Modus umgeschaltet
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature anlegen
|
||||||
|
<!-- regeln -->
|
||||||
|
- +-Icon
|
||||||
|
- Feature öffnet im Vollmodus
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Hauptbereich
|
||||||
|
- Klick auf "Feature erstellen"
|
||||||
|
- Namen im Modal eingeben
|
||||||
|
- Bestätigen mit LMB
|
||||||
|
- Feature angelegt
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature einstufen
|
||||||
|
<!-- ziel -->
|
||||||
|
Trennt tragende von verzichtbaren Features
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kern blau umrandet
|
||||||
|
- Optional grün umrandet
|
||||||
|
- Badge zeigt die Einstufung
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Badge
|
||||||
|
- Einstufung gewechselt
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature durchsuchen
|
||||||
|
<!-- ziel -->
|
||||||
|
Findet die Karte, ohne alle zu lesen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Sucht in allen Feldern
|
||||||
|
- Zählt Ausgeblendete mit
|
||||||
|
<!-- testen -->
|
||||||
|
- Suchwort eingeben
|
||||||
|
- Nur passende Karten bleiben
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature entfernen
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Featurecard
|
||||||
|
- Klick auf "Feature entfernen"
|
||||||
|
- Bestätigen im Modal
|
||||||
|
- Feature entfernt
|
||||||
|
<!-- funktion -->
|
||||||
|
Feature verschieben
|
||||||
|
<!-- ziel -->
|
||||||
|
Hängt ein Feature in ein anderes Projekt um
|
||||||
|
<!-- regeln -->
|
||||||
|
- Datei wandert
|
||||||
|
- Datei wird nicht kopiert
|
||||||
|
- Nur bei mehreren Projekten
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Featurecard
|
||||||
|
- Klick auf "Feature verschieben"
|
||||||
|
- Zielprojekt wählen
|
||||||
|
- Feature verschoben
|
||||||
|
<!-- funktion -->
|
||||||
|
Regeln anpassen
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Regeln der Karte
|
||||||
|
- Text ändern
|
||||||
|
- Regeln angepasst
|
||||||
|
<!-- funktion -->
|
||||||
|
Titel anpassen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Aktualisiert den Dateinamen
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Titel der Karte
|
||||||
|
- Text ändern
|
||||||
|
- Titel angepasst
|
||||||
|
<!-- funktion -->
|
||||||
|
Ziel anpassen
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Ziel der Karte
|
||||||
|
- Text ändern
|
||||||
|
- Ziel angepasst
|
||||||
98
daten/Planer/.planung/funktionen.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Funktionen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Zerlegt ein Feature in prüfbare Stücke
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gehört zu genau einem Feature
|
||||||
|
- Offen heißt bestellt, nicht gebaut
|
||||||
|
- Neue Funktionen von Hand gelten als gebaut
|
||||||
|
- Reihenfolge frei wählbar
|
||||||
|
- Ziel bleibt freiwillig
|
||||||
|
- Regeln bleiben freiwillig
|
||||||
|
- Testen bleibt freiwillig
|
||||||
|
- Anpassen nur im Vollmodus
|
||||||
|
- Verlassen speichert
|
||||||
|
- Escape verwirft
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion anlegen
|
||||||
|
<!-- regeln -->
|
||||||
|
- +-Icon
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- RMB auf Featurecard
|
||||||
|
- Klick auf "Funktion erstellen"
|
||||||
|
- Namen im Modal eingeben
|
||||||
|
- Bestätigen mit LMB
|
||||||
|
- Funktion angelegt
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion entfernen
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- RMB auf Funktion
|
||||||
|
- Klick auf "Funktion entfernen"
|
||||||
|
- Bestätigen im Modal
|
||||||
|
- Funktion entfernt
|
||||||
|
<!-- funktion -->
|
||||||
|
Stand umschalten
|
||||||
|
<!-- ziel -->
|
||||||
|
Trennt Bestelltes von Gebautem
|
||||||
|
<!-- regeln -->
|
||||||
|
- Marke rechts vom Titel
|
||||||
|
- Offen ist gelb
|
||||||
|
- Gebaut ist ein Haken
|
||||||
|
- Kompakt erscheint nur die offene Marke
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf die Marke
|
||||||
|
- Stand gewechselt
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion verschieben
|
||||||
|
<!-- ziel -->
|
||||||
|
Bringt die Schritte in ihre natürliche Folge
|
||||||
|
<!-- regeln -->
|
||||||
|
- Reihenfolge steht in der Datei
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- RMB auf Funktion
|
||||||
|
- Klick auf "Nach oben"
|
||||||
|
- Funktion verschoben
|
||||||
|
<!-- funktion -->
|
||||||
|
Titel anpassen
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Funktionstitel
|
||||||
|
- Text ändern
|
||||||
|
- Titel angepasst
|
||||||
|
<!-- funktion -->
|
||||||
|
Ziel anpassen
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Ziel der Funktion
|
||||||
|
- Text ändern
|
||||||
|
- Ziel angepasst
|
||||||
|
<!-- funktion -->
|
||||||
|
Regeln anpassen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Eine Regel je Zeile
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Regeln der Funktion
|
||||||
|
- Text ändern
|
||||||
|
- Regeln angepasst
|
||||||
|
<!-- funktion -->
|
||||||
|
Testschritte anpassen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ein Schritt je Zeile
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf Testen der Funktion
|
||||||
|
- Text ändern
|
||||||
|
- Testschritte angepasst
|
||||||
BIN
daten/Planer/.planung/k1-1024.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
daten/Planer/.planung/k1-1280.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
daten/Planer/.planung/k1-1600.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
daten/Planer/.planung/k1-390.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
daten/Planer/.planung/k1-640.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
daten/Planer/.planung/k1-820.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
61
daten/Planer/.planung/karten-besprechen.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Karten besprechen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Entwickelt Karten im Gespräch mit der KI
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Die KI schreibt nie selbst in eine Karte
|
||||||
|
- Sie beschreibt den Sollzustand
|
||||||
|
- Was sich ändert, rechnet der Code aus
|
||||||
|
- Vorschläge sehen aus wie bei der Prüfung
|
||||||
|
- Neue Funktionen gelten als offen
|
||||||
|
- Das Gespräch überlebt den Neustart
|
||||||
|
- Die letzten 30 Wortmeldungen bleiben
|
||||||
|
- Gespräch und Prüfung teilen eine Spalte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Gespräch öffnen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Stift-Icon in der Kopfzeile
|
||||||
|
- Icon leuchtet bei offenem Gespräch
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Stift-Icon
|
||||||
|
- Gespräch geöffnet
|
||||||
|
<!-- funktion -->
|
||||||
|
Nachricht senden
|
||||||
|
<!-- ziel -->
|
||||||
|
Sagt der KI, was das Projekt können soll
|
||||||
|
<!-- regeln -->
|
||||||
|
- Enter sendet
|
||||||
|
- Eigene Zeile erscheint sofort
|
||||||
|
- Text bleibt bei einem Fehler stehen
|
||||||
|
<!-- testen -->
|
||||||
|
- Text eingeben
|
||||||
|
- Enter drücken
|
||||||
|
- Antwort und Vorschläge erscheinen
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorgeschlagene Karte anlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Macht aus einem Umriss eine echte Karte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gestrichelter Rand vor dem Annehmen
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Haken am Umriss
|
||||||
|
- Karte angelegt
|
||||||
|
<!-- funktion -->
|
||||||
|
Gespräch verwerfen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Löscht nur das Gespräch
|
||||||
|
- Karten bleiben unberührt
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Papierkorb-Icon
|
||||||
|
- Gespräch geleert
|
||||||
|
<!-- funktion -->
|
||||||
|
Gespräch schließen
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf x-Icon
|
||||||
|
- Spalte wieder frei
|
||||||
82
daten/Planer/.planung/karten-pruefen.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Karten prüfen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Meldet, wo eine Karte vom Soll abweicht
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ein Lauf je Projekt
|
||||||
|
- Ergebnis überlebt keinen Neustart
|
||||||
|
- Zugang steht in .env
|
||||||
|
- Fehlender Zugang bricht sichtbar ab
|
||||||
|
- Jeder Vorschlag ist für sich anwendbar
|
||||||
|
- Jeder Vorschlag nennt seinen Beleg
|
||||||
|
- Eine Katalogdatei je Feld im Ordner stil
|
||||||
|
- Neue Katalogdatei wirkt ohne Codeänderung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Alle Vorschläge annehmen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Haken-Icon oben in der Karte
|
||||||
|
- Zahl nennt die Menge
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung durchlaufen lassen
|
||||||
|
- LMB auf Haken-Icon der Karte
|
||||||
|
- Alle Vorschläge übernommen
|
||||||
|
<!-- funktion -->
|
||||||
|
Alle Vorschläge verwerfen
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon oben in der Karte
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung durchlaufen lassen
|
||||||
|
- LMB auf x-Icon der Karte
|
||||||
|
- Alle Vorschläge verworfen
|
||||||
|
<!-- funktion -->
|
||||||
|
Problemstelle anspringen
|
||||||
|
<!-- ziel -->
|
||||||
|
Führt vom Panel-Eintrag zur betroffenen Karte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Karte blitzt kurz auf
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung durchlaufen lassen
|
||||||
|
- LMB auf Eintrag im Panel
|
||||||
|
- Karte im Editor sichtbar
|
||||||
|
<!-- funktion -->
|
||||||
|
Prüfung beenden
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon oben im Panel
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung durchlaufen lassen
|
||||||
|
- LMB auf x-Icon im Panel
|
||||||
|
- Panel geschlossen
|
||||||
|
<!-- funktion -->
|
||||||
|
Stil prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Form gegen den Katalog
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prüf-Icon in der Kopfzeile
|
||||||
|
- Wortzahl und Satzzeichen misst der Code
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Prüf-Icon
|
||||||
|
- Fortschritt unter dem Icon
|
||||||
|
- Fehlerhafte Karten rot markiert
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorschlag annehmen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Haken-Icon: ✓
|
||||||
|
- Übrige Vorschläge bleiben stehen
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung durchlaufen lassen
|
||||||
|
- LMB auf Haken-Icon
|
||||||
|
- Vorschlag in der Datei
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorschlag verwerfen
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon: ✕
|
||||||
|
<!-- testen -->
|
||||||
|
- Prüfung durchlaufen lassen
|
||||||
|
- LMB auf x-Icon
|
||||||
|
- Vorschlag verschwunden
|
||||||
BIN
daten/Planer/.planung/komfort.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
daten/Planer/.planung/offen.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
53
daten/Planer/.planung/projekte.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Projekte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Gruppiert Features für ein Projekt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ordner unter daten ist das Projekt
|
||||||
|
- Alphabetisch sortiert
|
||||||
|
- Aktives Projekt ist hervorgehoben
|
||||||
|
- Auswahl überlebt das Neuladen
|
||||||
|
- Entfernen nimmt die Features mit
|
||||||
|
- Verlassen speichert
|
||||||
|
- Escape bricht ab
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt anlegen
|
||||||
|
<!-- regeln -->
|
||||||
|
- +-Icon
|
||||||
|
- Projekt wird direkt aufgerufen
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Sidebar
|
||||||
|
- Klick auf "Projekt erstellen"
|
||||||
|
- Namen im Modal eingeben
|
||||||
|
- Bestätigen mit LMB
|
||||||
|
- Projekt angelegt
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt aufrufen
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Projektname
|
||||||
|
- Features angezeigt
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt umbenennen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Stift-Icon
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Projektname
|
||||||
|
- Klick auf "Projekt anpassen"
|
||||||
|
- Namen ändern
|
||||||
|
- Außerhalb klicken
|
||||||
|
- Name angepasst
|
||||||
|
<!-- funktion -->
|
||||||
|
Projekt entfernen
|
||||||
|
<!-- regeln -->
|
||||||
|
- x-Icon
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Projektname
|
||||||
|
- Klick auf "Projekt entfernen"
|
||||||
|
- Bestätigen im Modal
|
||||||
|
- Projekt entfernt
|
||||||
53
daten/Planer/.planung/vision-festhalten.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Vision festhalten
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Hält fest, wie es gedacht ist
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Höchstens eine je Projekt
|
||||||
|
- Goldener Rand
|
||||||
|
- Steht als erste Karte
|
||||||
|
- Nur Titel und ein Textfeld
|
||||||
|
- Text wird als Markdown gezeigt
|
||||||
|
- Keine Prüfung ändert sie
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Anzeigemodus umschalten
|
||||||
|
<!-- ziel -->
|
||||||
|
Kompaktmodus zum Lesen, Vollmodus zum Anpassen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Standard ist Kompaktmodus
|
||||||
|
- Kompakt zeigt nur die obersten Punkte
|
||||||
|
- Augen-Icon rechts oben
|
||||||
|
- Vollmodus geht über alle Spalten
|
||||||
|
<!-- testen -->
|
||||||
|
- MMB auf die Vision
|
||||||
|
- Modus umgeschaltet
|
||||||
|
<!-- funktion -->
|
||||||
|
Vision anlegen
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Hauptbereich
|
||||||
|
- Klick auf "Vision erstellen"
|
||||||
|
- Vision angelegt
|
||||||
|
<!-- funktion -->
|
||||||
|
Vision anpassen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Anpassen nur im Vollmodus
|
||||||
|
- Verlassen speichert
|
||||||
|
- Escape verwirft
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf den Text
|
||||||
|
- Text ändern
|
||||||
|
- Vision angepasst
|
||||||
|
<!-- funktion -->
|
||||||
|
Vision entfernen
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf die Vision
|
||||||
|
- Klick auf "Vision entfernen"
|
||||||
|
- Bestätigen im Modal
|
||||||
|
- Vision entfernt
|
||||||
19
daten/Planer/.planung/vision.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Vision
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
vision
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
- (Basis) Projekte und ihre Features manuell verwalten
|
||||||
|
- Mit Hilfe von KI-Chat Live Projekte entwickeln und Features erweitern
|
||||||
|
- Ich schreibe mit der KI und definiere meine Idee
|
||||||
|
- Die KI zeigt mir Live wie sich die Features ändern
|
||||||
|
- Ich verbessere und die KI passt Live an
|
||||||
|
- Wenn ich zufrieden bin, dann lasse ich die Features von der KI implementieren
|
||||||
|
- (später) Zu einem ausgewählten Projekt analysiert die KI alle kompakten Informationen dazu
|
||||||
|
- Ich wähle ein Verzeichnis das als Quelle dient
|
||||||
|
- Die Quelle wird durch ein System geschickt, dass kompakte Informationen extrahiert
|
||||||
|
- Die kompakten Informationen enthalten das Wissen zu dem WIE vom Projekt
|
||||||
|
- Also ich brauche die Funktionen und Regeln zum Projekt, nicht wie es Implementiert wird
|
||||||
|
- Es werden Features zum Projekt generiert, die es komplett abbilden
|
||||||
@@ -1,172 +1,319 @@
|
|||||||
{
|
{
|
||||||
"abdeckung": {
|
"abdeckung": {
|
||||||
"bedienen:ablehnen": {
|
"bedienen:ablehnen": {
|
||||||
"abdruck": "61a0e2e5f8a4c439",
|
"abdruck": "e9cce25d92b81ff6",
|
||||||
"funktion": "Vorschlag annehmen"
|
"funktion": "Vorschlag annehmen",
|
||||||
|
"trug": "089e774256dcce89"
|
||||||
},
|
},
|
||||||
"bedienen:alleAblehnen": {
|
"bedienen:alleAblehnen": {
|
||||||
"abdruck": "9d613f6b111b9b67",
|
"abdruck": "ac9262c7cafe9537",
|
||||||
"funktion": "Alle Vorschläge verwerfen"
|
"funktion": "Alle Vorschläge verwerfen",
|
||||||
|
"trug": "6869d68fdbd6d0a7"
|
||||||
},
|
},
|
||||||
"bedienen:alleAnnehmen": {
|
"bedienen:alleAnnehmen": {
|
||||||
"abdruck": "9456d726b83144c9",
|
"abdruck": "94f23cae75fdc531",
|
||||||
"funktion": "Alle Vorschläge annehmen"
|
"funktion": "Alle Vorschläge annehmen",
|
||||||
|
"trug": "94ebeec7b03abb70"
|
||||||
},
|
},
|
||||||
"bedienen:annehmen": {
|
"bedienen:annehmen": {
|
||||||
"abdruck": "97dc5220725e77ee",
|
"abdruck": "2742ad031b3d3cca",
|
||||||
"funktion": "Vorschlag annehmen"
|
"funktion": "Vorschlag annehmen",
|
||||||
|
"trug": "089e774256dcce89"
|
||||||
},
|
},
|
||||||
"bedienen:auswaehlen": {
|
"bedienen:auswaehlen": {
|
||||||
"abdruck": "31f34c41cbd1bd93",
|
"abdruck": "d061bf62466085ce",
|
||||||
"funktion": "Projekt aufrufen"
|
"funktion": "Projekt aufrufen",
|
||||||
|
"trug": "e24c2ab0a27ace24"
|
||||||
},
|
},
|
||||||
"bedienen:bereichsMenue": {
|
"bedienen:bereichsMenue": {
|
||||||
"abdruck": "421627feeb92867c",
|
"abdruck": "4277536d43c7bcd5",
|
||||||
"funktion": "Projekt aufrufen"
|
"funktion": "Projekt aufrufen",
|
||||||
|
"trug": "e24c2ab0a27ace24"
|
||||||
},
|
},
|
||||||
"bedienen:bestaetigen": {
|
"bedienen:bestaetigen": {
|
||||||
"abdruck": "b1ce0152c68722c9",
|
"abdruck": "db1f954b9ad01749",
|
||||||
"funktion": "Feature anlegen"
|
"funktion": "Eingabe mit Enter bestätigen",
|
||||||
|
"trug": "43fca3b0e83b3701"
|
||||||
|
},
|
||||||
|
"bedienen:erledige": {
|
||||||
|
"abdruck": "1b80ae13958bb11a",
|
||||||
|
"funktion": "Vorgeschlagene Karte anlegen",
|
||||||
|
"trug": "de2b733c447db595"
|
||||||
},
|
},
|
||||||
"bedienen:funktionsMenue": {
|
"bedienen:funktionsMenue": {
|
||||||
"abdruck": "67bf62e64b2af11f",
|
"abdruck": "bacdd0a7cc9d0f92",
|
||||||
"funktion": "Funktion entfernen"
|
"funktion": "Funktion verschieben",
|
||||||
|
"trug": "6a33d1b7b5a7f28c"
|
||||||
|
},
|
||||||
|
"bedienen:karteAnnehmen": {
|
||||||
|
"abdruck": "bf348251763e402a",
|
||||||
|
"funktion": "Vorgeschlagene Karte anlegen",
|
||||||
|
"trug": "de2b733c447db595"
|
||||||
},
|
},
|
||||||
"bedienen:kartenMenue": {
|
"bedienen:kartenMenue": {
|
||||||
"abdruck": "bc47fd3a97b2027c",
|
"abdruck": "d3ba136a562eb1e9",
|
||||||
"funktion": "Feature entfernen"
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
|
},
|
||||||
|
"bedienen:leeren": {
|
||||||
|
"abdruck": "f2af432a5ae3b255",
|
||||||
|
"funktion": "Gespräch verwerfen",
|
||||||
|
"trug": "1f848fba8b2a5e56"
|
||||||
},
|
},
|
||||||
"bedienen:leistenMenue": {
|
"bedienen:leistenMenue": {
|
||||||
"abdruck": "90a66ccbe71ddc74",
|
"abdruck": "ed951fbeaaa9bc70",
|
||||||
"funktion": "Projekt anlegen"
|
"funktion": "Projekt anlegen",
|
||||||
|
"trug": "44b8a59e1dcb20d4"
|
||||||
|
},
|
||||||
|
"bedienen:oeffneChat": {
|
||||||
|
"abdruck": "9bf171761edca141",
|
||||||
|
"funktion": "Gespräch öffnen",
|
||||||
|
"trug": "c71610a339bb298e"
|
||||||
},
|
},
|
||||||
"bedienen:projektMenue": {
|
"bedienen:projektMenue": {
|
||||||
"abdruck": "58ca0dac4286c61d",
|
"abdruck": "ea73f8c2597c0443",
|
||||||
"funktion": "Projekt umbenennen"
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
},
|
},
|
||||||
"bedienen:schliesse": {
|
"bedienen:schliesse": {
|
||||||
"abdruck": "f78739ce74d876f5",
|
"abdruck": "919b104c012c034d",
|
||||||
"funktion": ""
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"bedienen:schliessen": {
|
||||||
|
"abdruck": "1e12f9ae001e93e1",
|
||||||
|
"funktion": "Gespräch verwerfen",
|
||||||
|
"trug": "1f848fba8b2a5e56"
|
||||||
|
},
|
||||||
|
"bedienen:senden": {
|
||||||
|
"abdruck": "8df3402ed3373acc",
|
||||||
|
"funktion": "Nachricht senden",
|
||||||
|
"trug": "e61022551a98250f"
|
||||||
},
|
},
|
||||||
"bedienen:springe": {
|
"bedienen:springe": {
|
||||||
"abdruck": "67b827c9bba0c05a",
|
"abdruck": "0b134efa50c03bff",
|
||||||
"funktion": "Problemstelle anspringen"
|
"funktion": "Problemstelle anspringen",
|
||||||
|
"trug": "7486526f6867c436"
|
||||||
},
|
},
|
||||||
"bedienen:starte": {
|
"bedienen:starte": {
|
||||||
"abdruck": "06e7ff1e70efc603",
|
"abdruck": "761c2f62c917a3db",
|
||||||
"funktion": "Titel anpassen"
|
"funktion": "Titel anpassen",
|
||||||
|
"trug": "d0b7a430e96e2599"
|
||||||
},
|
},
|
||||||
"bedienen:startePruefung": {
|
"bedienen:startePruefung": {
|
||||||
"abdruck": "1f9ac6d1bb70cda8",
|
"abdruck": "31c3fef23c6319d8",
|
||||||
"funktion": "Stil prüfen"
|
"funktion": "Gespräch öffnen",
|
||||||
|
"trug": "c71610a339bb298e"
|
||||||
},
|
},
|
||||||
"bedienen:umbenennen": {
|
"bedienen:umbenennen": {
|
||||||
"abdruck": "af404032e5814872",
|
"abdruck": "6185b6b39708bf02",
|
||||||
"funktion": "Projekt umbenennen"
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
},
|
},
|
||||||
"bedienen:wechsleAnsicht": {
|
"bedienen:wechsleAnsicht": {
|
||||||
"abdruck": "ee50f2dbfa66f779",
|
"abdruck": "e3193fce50eaa58e",
|
||||||
"funktion": "Anzeigemodus umschalten"
|
"funktion": "Anzeigemodus umschalten",
|
||||||
|
"trug": "fccea553715951de"
|
||||||
},
|
},
|
||||||
"bedienen:wechsleArt": {
|
"bedienen:wechsleArt": {
|
||||||
"abdruck": "3fcd27f68677ed3a",
|
"abdruck": "67250a1e8d82e960",
|
||||||
"funktion": "Feature einstufen"
|
"funktion": "Feature einstufen",
|
||||||
|
"trug": "90b6c77ff0c412e6"
|
||||||
},
|
},
|
||||||
"bedienen:wechsleDarkMode": {
|
"bedienen:wechsleDarkMode": {
|
||||||
"abdruck": "51e5efb1e9196f3b",
|
"abdruck": "b83524eb0e12a7d7",
|
||||||
"funktion": "Darkmode umschalten"
|
"funktion": "Darkmode umschalten",
|
||||||
|
"trug": "4033fc55c753db3e"
|
||||||
},
|
},
|
||||||
"lauscher:window.click": {
|
"bedienen:wechsleStand": {
|
||||||
"abdruck": "5dd03db00120c133",
|
"abdruck": "8c2e3bf3be58ec00",
|
||||||
"funktion": "Kontextmenü schließen"
|
"funktion": "Stand umschalten",
|
||||||
|
"trug": "37a3835d1d2a3e6c"
|
||||||
},
|
},
|
||||||
"lauscher:window.keydown": {
|
"bedienen:zurueck": {
|
||||||
"abdruck": "70b3f36549535bfc",
|
"abdruck": "e378014fbce96d10",
|
||||||
"funktion": ""
|
"funktion": "Schritt zurücknehmen",
|
||||||
|
"trug": "10e46c458040a5e3"
|
||||||
|
},
|
||||||
|
"hoeren:systemDunkel.change": {
|
||||||
|
"abdruck": "d3ef681b987557dc",
|
||||||
|
"funktion": "Darkmode umschalten",
|
||||||
|
"trug": "4033fc55c753db3e"
|
||||||
|
},
|
||||||
|
"hoeren:window.click": {
|
||||||
|
"abdruck": "620812522ed2d900",
|
||||||
|
"funktion": "Kontextmenü schließen",
|
||||||
|
"trug": "87216235bbe42a1a"
|
||||||
|
},
|
||||||
|
"hoeren:window.keydown": {
|
||||||
|
"abdruck": "c3e1df2d60fcc067",
|
||||||
|
"funktion": "Schritt zurücknehmen",
|
||||||
|
"trug": "10e46c458040a5e3"
|
||||||
},
|
},
|
||||||
"menue:+ Feature erstellen": {
|
"menue:+ Feature erstellen": {
|
||||||
"abdruck": "a9a3fefd653555c7",
|
"abdruck": "82c40072137d5757",
|
||||||
"funktion": "Feature anlegen"
|
"funktion": "Feature anlegen",
|
||||||
|
"trug": "df7a1452a32da410"
|
||||||
},
|
},
|
||||||
"menue:+ Funktion erstellen": {
|
"menue:+ Funktion erstellen": {
|
||||||
"abdruck": "0f968068851c2f7a",
|
"abdruck": "b44457d64a5ec9d1",
|
||||||
"funktion": "Feature entfernen"
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
},
|
},
|
||||||
"menue:+ Projekt erstellen": {
|
"menue:+ Projekt erstellen": {
|
||||||
"abdruck": "dbbe1573ebe97251",
|
"abdruck": "2be770390433a2ce",
|
||||||
"funktion": "Projekt entfernen"
|
"funktion": "Projekt anlegen",
|
||||||
|
"trug": "44b8a59e1dcb20d4"
|
||||||
},
|
},
|
||||||
"menue:+ Vision erstellen": {
|
"menue:+ Vision erstellen": {
|
||||||
"abdruck": "1d814205e510b035",
|
"abdruck": "cb22a654070e58b4",
|
||||||
"funktion": "Feature anlegen"
|
"funktion": "Feature anlegen",
|
||||||
|
"trug": "df7a1452a32da410"
|
||||||
|
},
|
||||||
|
"menue:→ Feature verschieben": {
|
||||||
|
"abdruck": "38e6b244668ee164",
|
||||||
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
|
},
|
||||||
|
"menue:▲ Nach oben": {
|
||||||
|
"abdruck": "4e6fd1617d817357",
|
||||||
|
"funktion": "Funktion verschieben",
|
||||||
|
"trug": "6a33d1b7b5a7f28c"
|
||||||
|
},
|
||||||
|
"menue:▼ Nach unten": {
|
||||||
|
"abdruck": "183651450e546c4f",
|
||||||
|
"funktion": "Funktion entfernen",
|
||||||
|
"trug": "885e16a93460f706"
|
||||||
|
},
|
||||||
|
"menue:◇ Quelle festlegen": {
|
||||||
|
"abdruck": "e4bb1781bc375d6d",
|
||||||
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
},
|
},
|
||||||
"menue:✎ Projekt anpassen": {
|
"menue:✎ Projekt anpassen": {
|
||||||
"abdruck": "cd7390c2f6852257",
|
"abdruck": "ad505addca45b06c",
|
||||||
"funktion": "Projekt umbenennen"
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
},
|
},
|
||||||
"menue:✕ Feature entfernen": {
|
"menue:✕ Feature entfernen": {
|
||||||
"abdruck": "e773a996f84a8d7b",
|
"abdruck": "bc968582260c45b9",
|
||||||
"funktion": "Feature entfernen"
|
"funktion": "Funktion anlegen",
|
||||||
|
"trug": "78d0329cf6eacca9"
|
||||||
},
|
},
|
||||||
"menue:✕ Funktion entfernen": {
|
"menue:✕ Funktion entfernen": {
|
||||||
"abdruck": "99495f37aa040550",
|
"abdruck": "d29458b150c55777",
|
||||||
"funktion": "Funktion entfernen"
|
"funktion": "Funktion verschieben",
|
||||||
|
"trug": "6a33d1b7b5a7f28c"
|
||||||
},
|
},
|
||||||
"menue:✕ Projekt entfernen": {
|
"menue:✕ Projekt entfernen": {
|
||||||
"abdruck": "bc7b8c360ac3c403",
|
"abdruck": "aca25c0b5feaf752",
|
||||||
"funktion": "Projekt anlegen"
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
},
|
},
|
||||||
"menue:✕ Vision entfernen": {
|
"menue:✕ Vision entfernen": {
|
||||||
"abdruck": "ec65c57f36ff14d0",
|
"abdruck": "88520b1f105d3fb5",
|
||||||
"funktion": "Anzeigemodus umschalten"
|
"funktion": "Anzeigemodus umschalten",
|
||||||
|
"trug": "fccea553715951de"
|
||||||
},
|
},
|
||||||
"route:delete /projekte/{name}": {
|
"route:delete /projekte/{name}": {
|
||||||
"abdruck": "9014a4b884cca27a",
|
"abdruck": "b4f871be35d10e5f",
|
||||||
"funktion": "Projekt entfernen"
|
"funktion": "Projekt entfernen",
|
||||||
|
"trug": "e91a89b8d78dbd2e"
|
||||||
},
|
},
|
||||||
"route:delete /projekte/{name}/features/{id}": {
|
"route:delete /projekte/{name}/features/{id}": {
|
||||||
"abdruck": "ff8d0773ef483606",
|
"abdruck": "c1db81de98835c39",
|
||||||
"funktion": "Feature entfernen"
|
"funktion": "Feature entfernen",
|
||||||
|
"trug": "f486bf09c64ebb3a"
|
||||||
},
|
},
|
||||||
"route:delete /projekte/{name}/pruefung": {
|
"route:delete /projekte/{name}/pruefung": {
|
||||||
"abdruck": "79f2bdc0413ea88a",
|
"abdruck": "7a480f33ddf55165",
|
||||||
"funktion": "Prüfung beenden"
|
"funktion": "Prüfung beenden",
|
||||||
|
"trug": "83435eb3e1394e49"
|
||||||
},
|
},
|
||||||
"route:get /health": {
|
"route:get /health": {
|
||||||
"abdruck": "3214361386c64911",
|
"abdruck": "65257319741edf35",
|
||||||
"funktion": ""
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
},
|
},
|
||||||
"route:get /projekte": {
|
"route:get /projekte": {
|
||||||
"abdruck": "2f6756caafbe76b8",
|
"abdruck": "276556054437c303",
|
||||||
"funktion": ""
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/chat": {
|
||||||
|
"abdruck": "fd2a22c36ea85545",
|
||||||
|
"funktion": "Gespräch öffnen",
|
||||||
|
"trug": "c71610a339bb298e"
|
||||||
},
|
},
|
||||||
"route:get /projekte/{name}/features": {
|
"route:get /projekte/{name}/features": {
|
||||||
"abdruck": "509119bcecfd63e6",
|
"abdruck": "122f93cbd0d07881",
|
||||||
"funktion": "Projekt aufrufen"
|
"funktion": "Projekt aufrufen",
|
||||||
|
"trug": "e24c2ab0a27ace24"
|
||||||
},
|
},
|
||||||
"route:get /projekte/{name}/pruefung": {
|
"route:get /projekte/{name}/pruefung": {
|
||||||
"abdruck": "f7d46d8a5be26212",
|
"abdruck": "e651ac90b7b2c520",
|
||||||
"funktion": "Stil prüfen"
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/quelle": {
|
||||||
|
"abdruck": "834d3af3203ca3a8",
|
||||||
|
"funktion": "Quelle festlegen",
|
||||||
|
"trug": "b8b70dd0ff9817cb"
|
||||||
|
},
|
||||||
|
"route:get /projekte/{name}/verlauf": {
|
||||||
|
"abdruck": "5a3dcc678733fad8",
|
||||||
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
},
|
},
|
||||||
"route:post /projekte": {
|
"route:post /projekte": {
|
||||||
"abdruck": "c3e70befc5eb772b",
|
"abdruck": "fa1628d7e469885d",
|
||||||
"funktion": "Projekt anlegen"
|
"funktion": "Projekt anlegen",
|
||||||
|
"trug": "44b8a59e1dcb20d4"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/chat": {
|
||||||
|
"abdruck": "c3edc89cec5d2f6f",
|
||||||
|
"funktion": "Nachricht senden",
|
||||||
|
"trug": "e61022551a98250f"
|
||||||
},
|
},
|
||||||
"route:post /projekte/{name}/features": {
|
"route:post /projekte/{name}/features": {
|
||||||
"abdruck": "df08920d8f5e5d4d",
|
"abdruck": "00eb9c16560bac73",
|
||||||
"funktion": "Feature anlegen"
|
"funktion": "Feature anlegen",
|
||||||
|
"trug": "df7a1452a32da410"
|
||||||
},
|
},
|
||||||
"route:post /projekte/{name}/pruefung": {
|
"route:post /projekte/{name}/pruefung": {
|
||||||
"abdruck": "9fb8d6c9260477c7",
|
"abdruck": "6d7444a9dfad7d54",
|
||||||
"funktion": "Stil prüfen"
|
"funktion": "Stil prüfen",
|
||||||
|
"trug": "d5019cdafd70efb7"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/pruefung/verworfen": {
|
||||||
|
"abdruck": "a43f7221e01c386f",
|
||||||
|
"funktion": "Alle Vorschläge verwerfen",
|
||||||
|
"trug": "6869d68fdbd6d0a7"
|
||||||
|
},
|
||||||
|
"route:post /projekte/{name}/verlauf/zurueck": {
|
||||||
|
"abdruck": "308fec6d21b01d60",
|
||||||
|
"funktion": "Schritt zurücknehmen",
|
||||||
|
"trug": "10e46c458040a5e3"
|
||||||
},
|
},
|
||||||
"route:put /projekte/{name}": {
|
"route:put /projekte/{name}": {
|
||||||
"abdruck": "6030731add729040",
|
"abdruck": "75383ac009961cad",
|
||||||
"funktion": "Projekt umbenennen"
|
"funktion": "Projekt umbenennen",
|
||||||
|
"trug": "40d6954a44fa3240"
|
||||||
},
|
},
|
||||||
"route:put /projekte/{name}/features/{id}": {
|
"route:put /projekte/{name}/features/{id}": {
|
||||||
"abdruck": "e95aea276bf795cd",
|
"abdruck": "b24c7000fd2ec94c",
|
||||||
"funktion": ""
|
"funktion": "",
|
||||||
|
"trug": ""
|
||||||
|
},
|
||||||
|
"route:put /projekte/{name}/features/{id}/projekt": {
|
||||||
|
"abdruck": "fafc7a8a1656f93b",
|
||||||
|
"funktion": "Feature verschieben",
|
||||||
|
"trug": "cd2cd705298831e0"
|
||||||
|
},
|
||||||
|
"route:put /projekte/{name}/quelle": {
|
||||||
|
"abdruck": "d342055e3163548a",
|
||||||
|
"funktion": "Quelle festlegen",
|
||||||
|
"trug": "b8b70dd0ff9817cb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"belege": {
|
"belege": {
|
||||||
@@ -175,9 +322,14 @@
|
|||||||
"beleg": "function wechsleDarkMode() {\n modus.value = MODI[(MODI.indexOf(modus.value) + 1) % MODI.length]\n localStorage.setItem('darkModus', modus.value)\n wendeAn()\n}",
|
"beleg": "function wechsleDarkMode() {\n modus.value = MODI[(MODI.indexOf(modus.value) + 1) % MODI.length]\n localStorage.setItem('darkModus', modus.value)\n wendeAn()\n}",
|
||||||
"datei": "frontend/src/App.vue"
|
"datei": "frontend/src/App.vue"
|
||||||
},
|
},
|
||||||
|
"[\"design\", \"Eingabe mit Enter bestätigen\"]": {
|
||||||
|
"abdruck": "3bace3e9c9f16023",
|
||||||
|
"beleg": "@keydown.enter.exact.prevent=\"senden\"",
|
||||||
|
"datei": "frontend/src/components/ChatPanel.vue"
|
||||||
|
},
|
||||||
"[\"design\", \"Kontextmenü schließen\"]": {
|
"[\"design\", \"Kontextmenü schließen\"]": {
|
||||||
"abdruck": "519c5a56bf4b9810",
|
"abdruck": "671fa86bccb2c2c6",
|
||||||
"beleg": "schliesseMenue()",
|
"beleg": "export function schliesseMenue() {\n kontextMenue.value = null\n}",
|
||||||
"datei": "frontend/src/composables/kontextmenue.js"
|
"datei": "frontend/src/composables/kontextmenue.js"
|
||||||
},
|
},
|
||||||
"[\"features\", \"Anzeigemodus umschalten\"]": {
|
"[\"features\", \"Anzeigemodus umschalten\"]": {
|
||||||
@@ -190,14 +342,24 @@
|
|||||||
"beleg": "async function erstellen(titel) {",
|
"beleg": "async function erstellen(titel) {",
|
||||||
"datei": "frontend/src/components/FeatureListe.vue"
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
},
|
},
|
||||||
|
"[\"features\", \"Feature durchsuchen\"]": {
|
||||||
|
"abdruck": "4f249c42d46d25b5",
|
||||||
|
"beleg": "function passt(f) {\n const wort = suche.value.trim().toLowerCase()\n if (!wort) return true\n const s = f.sektionen\n const text = [s.titel, s.ziel, s.regeln,\n ...s.funktionen.map(x => `${x.text} ${x.ziel} ${x.regeln} ${x.testen}`)]\n return text.join(' ').toLowerCase().includes(wort)\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
"[\"features\", \"Feature einstufen\"]": {
|
"[\"features\", \"Feature einstufen\"]": {
|
||||||
"abdruck": "de473e39c7232637",
|
"abdruck": "a0ee6590c24456bb",
|
||||||
"beleg": "function wechsleArt() {\n s.value.art = s.value.art === 'kern' ? 'optional' : 'kern'\n speichern()\n}",
|
"beleg": "function wechsleArt() {\n s.value.art = s.value.art === 'kern' ? 'optional' : 'kern'\n speichern()\n}",
|
||||||
"datei": "frontend/src/components/FeatureKarte.vue"
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
},
|
},
|
||||||
"[\"features\", \"Feature entfernen\"]": {
|
"[\"features\", \"Feature entfernen\"]": {
|
||||||
"abdruck": "c6f27381a41a63d7",
|
"abdruck": "c6f27381a41a63d7",
|
||||||
"beleg": "def loeschen(projekt: str, id: str):\n _datei(projekt, id).unlink()",
|
"beleg": "async function entfernen() {\n const id = modal.value.feature.id\n try {\n await featureLoeschen(props.projekt, id)\n features.value = features.value.filter(f => f.id !== id)\n modal.value = null\n emit('geaendert')\n } catch (e) {\n meldung.value = e.message\n }\n}",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature verschieben\"]": {
|
||||||
|
"abdruck": "a82b97e3d4a1b533",
|
||||||
|
"beleg": "def verschieben(projekt: str, id: str, ziel: str) -> dict:",
|
||||||
"datei": "backend/features.py"
|
"datei": "backend/features.py"
|
||||||
},
|
},
|
||||||
"[\"features\", \"Regeln anpassen\"]": {
|
"[\"features\", \"Regeln anpassen\"]": {
|
||||||
@@ -222,7 +384,12 @@
|
|||||||
},
|
},
|
||||||
"[\"funktionen\", \"Funktion entfernen\"]": {
|
"[\"funktionen\", \"Funktion entfernen\"]": {
|
||||||
"abdruck": "20e8d8e507ee6310",
|
"abdruck": "20e8d8e507ee6310",
|
||||||
"beleg": "{ text: '✕ Funktion entfernen', aktion: () => (modal.value = { art: 'entfernen', i }) },",
|
"beleg": "eintraege.push({ text: '✕ Funktion entfernen', aktion: () => (modal.value = { art: 'entfernen', i }) })",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion verschieben\"]": {
|
||||||
|
"abdruck": "8435119f0dd5b891",
|
||||||
|
"beleg": "async function schiebe(i, richtung) {",
|
||||||
"datei": "frontend/src/components/FeatureKarte.vue"
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
},
|
},
|
||||||
"[\"funktionen\", \"Regeln anpassen\"]": {
|
"[\"funktionen\", \"Regeln anpassen\"]": {
|
||||||
@@ -230,6 +397,11 @@
|
|||||||
"beleg": "async function starte(feld, i, j) {\n if (!gross.value) return // Bearbeiten nur in der ausführlichen Ansicht\n const original = wert(feld, i, j)\n aktiv.value = { feld, i, j, original }\n entwurf.value = original\n await nextTick()\n const el = Array.isArray(feldRef.value) ? feldRef.value[0] : feldRef.value\n el?.focus()\n if (feld === 'titel') el?.select()\n}",
|
"beleg": "async function starte(feld, i, j) {\n if (!gross.value) return // Bearbeiten nur in der ausführlichen Ansicht\n const original = wert(feld, i, j)\n aktiv.value = { feld, i, j, original }\n entwurf.value = original\n await nextTick()\n const el = Array.isArray(feldRef.value) ? feldRef.value[0] : feldRef.value\n el?.focus()\n if (feld === 'titel') el?.select()\n}",
|
||||||
"datei": "frontend/src/components/FeatureKarte.vue"
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
},
|
},
|
||||||
|
"[\"funktionen\", \"Stand umschalten\"]": {
|
||||||
|
"abdruck": "b66ea5f488899d8f",
|
||||||
|
"beleg": "@click.stop=\"wechsleStand(i)\"",
|
||||||
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
|
},
|
||||||
"[\"funktionen\", \"Testschritte anpassen\"]": {
|
"[\"funktionen\", \"Testschritte anpassen\"]": {
|
||||||
"abdruck": "e303ebeaece423de",
|
"abdruck": "e303ebeaece423de",
|
||||||
"beleg": "<div v-else class=\"klickbar\" @click=\"starte('testen', i)\">",
|
"beleg": "<div v-else class=\"klickbar\" @click=\"starte('testen', i)\">",
|
||||||
@@ -245,6 +417,31 @@
|
|||||||
"beleg": "<div v-else class=\"inhalt klickbar\" v-html=\"md(f.ziel)\" @click=\"starte('ziel', i)\" />",
|
"beleg": "<div v-else class=\"inhalt klickbar\" v-html=\"md(f.ziel)\" @click=\"starte('ziel', i)\" />",
|
||||||
"datei": "frontend/src/components/FeatureKarte.vue"
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
},
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch schließen\"]": {
|
||||||
|
"abdruck": "d6de2c46300fef26",
|
||||||
|
"beleg": "<button class=\"icon-knopf\" title=\"Gespräch schließen\" @click=\"emit('schliessen')\">✕</button>",
|
||||||
|
"datei": "frontend/src/components/ChatPanel.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch verwerfen\"]": {
|
||||||
|
"abdruck": "9e74be976a4d0e15",
|
||||||
|
"beleg": "@router.delete(\"/projekte/{name}/chat\")\nasync def chat_leeren(name: str):\n try:\n chat.leeren(name)\n except FileNotFoundError as e:\n raise HTTPException(404, str(e))\n return {\"ok\": True}",
|
||||||
|
"datei": "backend/routes.py"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch öffnen\"]": {
|
||||||
|
"abdruck": "c84f9a14428a019e",
|
||||||
|
"beleg": "<button\n class=\"icon-knopf\"\n :class=\"{ an: chatOffen }\"\n title=\"Karten im Gespräch entwickeln\"\n @click=\"oeffneChat\"\n >✎</button>",
|
||||||
|
"datei": "frontend/src/App.vue"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Nachricht senden\"]": {
|
||||||
|
"abdruck": "308218d682fe3dd9",
|
||||||
|
"beleg": "async def frage(projekt: str, text: str) -> dict:",
|
||||||
|
"datei": "backend/chat.py"
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Vorgeschlagene Karte anlegen\"]": {
|
||||||
|
"abdruck": "15aa862d2e0f6f6e",
|
||||||
|
"beleg": "async function karteAnnehmen(p) {",
|
||||||
|
"datei": "frontend/src/components/FeatureListe.vue"
|
||||||
|
},
|
||||||
"[\"karten-pruefen\", \"Alle Vorschläge annehmen\"]": {
|
"[\"karten-pruefen\", \"Alle Vorschläge annehmen\"]": {
|
||||||
"abdruck": "52fb2acd3d6945fa",
|
"abdruck": "52fb2acd3d6945fa",
|
||||||
"beleg": "async function alleAnnehmen() {\n props.probleme.forEach(p => { if (anwenden(p)) erledige(p) })\n await speichern()\n}",
|
"beleg": "async function alleAnnehmen() {\n props.probleme.forEach(p => { if (anwenden(p)) erledige(p) })\n await speichern()\n}",
|
||||||
@@ -252,7 +449,7 @@
|
|||||||
},
|
},
|
||||||
"[\"karten-pruefen\", \"Alle Vorschläge verwerfen\"]": {
|
"[\"karten-pruefen\", \"Alle Vorschläge verwerfen\"]": {
|
||||||
"abdruck": "f98fd3fc005b9e56",
|
"abdruck": "f98fd3fc005b9e56",
|
||||||
"beleg": "function alleAblehnen() {\n props.probleme.forEach(erledige)\n}",
|
"beleg": "<button\n class=\"icon-knopf rot\"\n :title=\"`Alle ${probleme.length} Vorschläge verwerfen`\"\n @click=\"alleAblehnen\"\n >✕</button>",
|
||||||
"datei": "frontend/src/components/FeatureKarte.vue"
|
"datei": "frontend/src/components/FeatureKarte.vue"
|
||||||
},
|
},
|
||||||
"[\"karten-pruefen\", \"Code in Features prüfen\"]": {
|
"[\"karten-pruefen\", \"Code in Features prüfen\"]": {
|
||||||
@@ -265,6 +462,11 @@
|
|||||||
"beleg": "ANKLAGE_AB = 4",
|
"beleg": "ANKLAGE_AB = 4",
|
||||||
"datei": "backend/inhalt.py"
|
"datei": "backend/inhalt.py"
|
||||||
},
|
},
|
||||||
|
"[\"karten-pruefen\", \"Funktion abnehmen\"]": {
|
||||||
|
"abdruck": "21095461d6190dec",
|
||||||
|
"beleg": "if beleg:\n stand[\"belege\"][schluessel] = beleg\n if x.get(\"stand\") == parser.OFFEN:\n # Abnahme: die Karte hat es bestellt, jetzt steht es im Code\n return {\"feature\": feature[\"id\"], \"titel\": x[\"text\"], \"art\": \"gebaut\",\n \"grund\": f'{beleg[\"datei\"]}: {beleg[\"beleg\"].strip()}'}",
|
||||||
|
"datei": "backend/inhalt.py"
|
||||||
|
},
|
||||||
"[\"karten-pruefen\", \"Problemstelle anspringen\"]": {
|
"[\"karten-pruefen\", \"Problemstelle anspringen\"]": {
|
||||||
"abdruck": "340a32321fd23834",
|
"abdruck": "340a32321fd23834",
|
||||||
"beleg": "karte.classList.add('gesucht')",
|
"beleg": "karte.classList.add('gesucht')",
|
||||||
@@ -307,9 +509,24 @@
|
|||||||
},
|
},
|
||||||
"[\"projekte\", \"Projekt umbenennen\"]": {
|
"[\"projekte\", \"Projekt umbenennen\"]": {
|
||||||
"abdruck": "d4e71f8ef907393c",
|
"abdruck": "d4e71f8ef907393c",
|
||||||
"beleg": "function projektMenue(e, name) {\n oeffneMenue(e, [\n { text: '✎ Projekt anpassen', aktion: () => starteUmbenennen(name) },\n { text: '✕ Projekt entfernen', aktion: () => oeffneModal({ art: 'entfernen', name }) },\n ])\n}",
|
"beleg": "function projektMenue(e, name) {\n oeffneMenue(e, [\n { text: '✎ Projekt anpassen', aktion: () => starteUmbenennen(name) },\n { text: '◇ Quelle festlegen', aktion: () => starteQuelle(name) },\n { text: '✕ Projekt entfernen', aktion: () => oeffneModal({ art: 'entfernen', name }) },\n ])\n}",
|
||||||
"datei": "frontend/src/components/SeitenLeiste.vue"
|
"datei": "frontend/src/components/SeitenLeiste.vue"
|
||||||
},
|
},
|
||||||
|
"[\"projekte\", \"Quelle festlegen\"]": {
|
||||||
|
"abdruck": "fc6c20f4e8d74fa6",
|
||||||
|
"beleg": "if ziel == Path(ziel.anchor) or ziel == Path.home():",
|
||||||
|
"datei": "backend/quelle.py"
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Nächsten Rückschritt anzeigen\"]": {
|
||||||
|
"abdruck": "6768d8a0b83537a2",
|
||||||
|
"beleg": "letzterSchritt.value = (await fetchVerlauf(aktivesProjekt.value)).letzter",
|
||||||
|
"datei": "frontend/src/App.vue"
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Schritt zurücknehmen\"]": {
|
||||||
|
"abdruck": "c49332e7a679dfda",
|
||||||
|
"beleg": "def zurueck(projekt: str) -> str:\n \"\"\"Den letzten Schritt zurücknehmen. Gibt zurück, was zurückgenommen wurde.\"\"\"\n schritte = lade(projekt)\n if not schritte:\n raise ValueError(\"Nichts zum Zurücknehmen\")\n schritt = schritte.pop()",
|
||||||
|
"datei": "backend/verlauf.py"
|
||||||
|
},
|
||||||
"[\"vision-festhalten\", \"Anzeigemodus umschalten\"]": {
|
"[\"vision-festhalten\", \"Anzeigemodus umschalten\"]": {
|
||||||
"abdruck": "b983b82c02343560",
|
"abdruck": "b983b82c02343560",
|
||||||
"beleg": "const gross = ref(false)",
|
"beleg": "const gross = ref(false)",
|
||||||
@@ -332,24 +549,69 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ohne_code": {},
|
"ohne_code": {},
|
||||||
"ohne_karte": {},
|
"ohne_karte": {
|
||||||
|
"route:delete /projekte/{name}/chat": {
|
||||||
|
"abdruck": "4ba7a240985a83b4",
|
||||||
|
"beleg": "backend/routes.py:182",
|
||||||
|
"feature": "karten-besprechen",
|
||||||
|
"karten": "41f55589fcff4959",
|
||||||
|
"stelle": "route:delete /projekte/{name}/chat",
|
||||||
|
"titel": "Chatverlauf leeren"
|
||||||
|
}
|
||||||
|
},
|
||||||
"paare": {
|
"paare": {
|
||||||
"[\"[\\\"features\\\", \\\"Anzeigemodus umschalten\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Anzeigemodus umschalten\\\"]\"]": {
|
"[\"[\\\"features\\\", \\\"Anzeigemodus umschalten\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Anzeigemodus umschalten\\\"]\"]": {
|
||||||
"abdruck": "d23706be97d6369a",
|
"abdruck": "d23706be97d6369a",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
},
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature anlegen\\\"]\", \"[\\\"features\\\", \\\"Feature entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "586059671bbfa75b",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
"[\"[\\\"features\\\", \\\"Feature anlegen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\"]": {
|
"[\"[\\\"features\\\", \\\"Feature anlegen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\"]": {
|
||||||
"abdruck": "3eb9956887005cc4",
|
"abdruck": "3eb9956887005cc4",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
},
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature anlegen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt anlegen\\\"]\"]": {
|
||||||
|
"abdruck": "66a7175c58b703c4",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\"]": {
|
||||||
|
"abdruck": "97a9dde3e968b1eb",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion entfernen\\\"]\"]": {
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion entfernen\\\"]\"]": {
|
||||||
"abdruck": "9717a5c6eab69f22",
|
"abdruck": "9717a5c6eab69f22",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
},
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "65c43ee10d0d8b4a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Feature entfernen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "60434d422bbd2ca3",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"features\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "75e90ff6d5453c2d",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"features\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "a9e849a044a7ee41",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\"]": {
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\"]": {
|
||||||
"abdruck": "827215ea68dcbada",
|
"abdruck": "827215ea68dcbada",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
},
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "ea7185c230fbdd2f",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"features\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "bc79858745a7d262",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
"[\"[\\\"features\\\", \\\"Titel anpassen\\\"]\", \"[\\\"features\\\", \\\"Ziel anpassen\\\"]\"]": {
|
"[\"[\\\"features\\\", \\\"Titel anpassen\\\"]\", \"[\\\"features\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
"abdruck": "69360dc1502aa01f",
|
"abdruck": "69360dc1502aa01f",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
@@ -370,6 +632,10 @@
|
|||||||
"abdruck": "5e97e3c6fe9df287",
|
"abdruck": "5e97e3c6fe9df287",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
},
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\", \"[\\\"funktionen\\\", \\\"Funktion entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "3e58148f974b6b34",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
"[\"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt anlegen\\\"]\"]": {
|
"[\"[\\\"funktionen\\\", \\\"Funktion anlegen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt anlegen\\\"]\"]": {
|
||||||
"abdruck": "202f51c53a73b452",
|
"abdruck": "202f51c53a73b452",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
@@ -382,9 +648,281 @@
|
|||||||
"abdruck": "4090ee6826466fa3",
|
"abdruck": "4090ee6826466fa3",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
},
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "c8321ad2b769d77f",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "7ce003da82080259",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Regeln anpassen\\\"]\", \"[\\\"karten-pruefen\\\", \\\"Funktion abnehmen\\\"]\"]": {
|
||||||
|
"abdruck": "d2f80684e1be6287",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Testschritte anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "7a51922f327ba28b",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Testschritte anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "97a70c7ecfbb9f30",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
"[\"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
"[\"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\", \"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\"]": {
|
||||||
"abdruck": "17b00db94f54b62d",
|
"abdruck": "17b00db94f54b62d",
|
||||||
"gleich": false
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Titel anpassen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "d17a7b7bad8d947a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"funktionen\\\", \\\"Ziel anpassen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision anpassen\\\"]\"]": {
|
||||||
|
"abdruck": "e25b21ca4183d372",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"karten-pruefen\\\", \\\"Alle Vorschläge annehmen\\\"]\", \"[\\\"karten-pruefen\\\", \\\"Alle Vorschläge verwerfen\\\"]\"]": {
|
||||||
|
"abdruck": "b9c707879cffb7cd",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"karten-pruefen\\\", \\\"Alle Vorschläge verwerfen\\\"]\", \"[\\\"karten-pruefen\\\", \\\"Vorschlag verwerfen\\\"]\"]": {
|
||||||
|
"abdruck": "aed348a776637c4a",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"projekte\\\", \\\"Projekt anlegen\\\"]\", \"[\\\"projekte\\\", \\\"Projekt entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "957ebec8e3096ce6",
|
||||||
|
"gleich": false
|
||||||
|
},
|
||||||
|
"[\"[\\\"projekte\\\", \\\"Projekt entfernen\\\"]\", \"[\\\"vision-festhalten\\\", \\\"Vision entfernen\\\"]\"]": {
|
||||||
|
"abdruck": "8757fb8b0f89d5a6",
|
||||||
|
"gleich": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stil": {
|
||||||
|
"[\"darkmode\", \"\"]": {
|
||||||
|
"abdruck": "a5b0fd59f25c55ae",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"darkmode\", \"Darkmode umschalten\"]": {
|
||||||
|
"abdruck": "0dbcca6954cc4d16",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"design\", \"\"]": {
|
||||||
|
"abdruck": "1ae7985ebcfe6c6a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"design\", \"Eingabe mit Enter bestätigen\"]": {
|
||||||
|
"abdruck": "b961f168017fb01e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"design\", \"Kontextmenü schließen\"]": {
|
||||||
|
"abdruck": "a3717ea52a012784",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"\"]": {
|
||||||
|
"abdruck": "b4f6fe4cd065e9e9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Anzeigemodus umschalten\"]": {
|
||||||
|
"abdruck": "8d468a73bcec5521",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature anlegen\"]": {
|
||||||
|
"abdruck": "7d4d6e36b4f03318",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature durchsuchen\"]": {
|
||||||
|
"abdruck": "3acde68ab76b942c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature einstufen\"]": {
|
||||||
|
"abdruck": "10e74d934dafacc9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature entfernen\"]": {
|
||||||
|
"abdruck": "509a1153b2e6382f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Feature verschieben\"]": {
|
||||||
|
"abdruck": "5169bc85a796b6a9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Regeln anpassen\"]": {
|
||||||
|
"abdruck": "6b4eeed76c631333",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Titel anpassen\"]": {
|
||||||
|
"abdruck": "de6e2788e24aa230",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"features\", \"Ziel anpassen\"]": {
|
||||||
|
"abdruck": "f26e4426663b8265",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"\"]": {
|
||||||
|
"abdruck": "adcc38efdb393cf6",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion anlegen\"]": {
|
||||||
|
"abdruck": "11c59974712619a0",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion entfernen\"]": {
|
||||||
|
"abdruck": "e55d43e078f1020e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Funktion verschieben\"]": {
|
||||||
|
"abdruck": "1bdf3be9b53fdb99",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Regeln anpassen\"]": {
|
||||||
|
"abdruck": "e354caf2701ee79c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Stand umschalten\"]": {
|
||||||
|
"abdruck": "f39b25d34cc6e4c4",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Testschritte anpassen\"]": {
|
||||||
|
"abdruck": "8fb282300c19c3c2",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Titel anpassen\"]": {
|
||||||
|
"abdruck": "63e9a78a02d890fe",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"funktionen\", \"Ziel anpassen\"]": {
|
||||||
|
"abdruck": "a72ce3656c787475",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"\"]": {
|
||||||
|
"abdruck": "8d330c5a71e25e0c",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch schließen\"]": {
|
||||||
|
"abdruck": "d1c043cd26a05d69",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch verwerfen\"]": {
|
||||||
|
"abdruck": "e6d601409eb48f7a",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Gespräch öffnen\"]": {
|
||||||
|
"abdruck": "b9a3f8340aa9e266",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Nachricht senden\"]": {
|
||||||
|
"abdruck": "c3ec1453a9aff541",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-besprechen\", \"Vorgeschlagene Karte anlegen\"]": {
|
||||||
|
"abdruck": "ca0a5b02f10bbd1e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"\"]": {
|
||||||
|
"abdruck": "43f92e591142fa95",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Alle Vorschläge annehmen\"]": {
|
||||||
|
"abdruck": "5afe1e8ec40d8b82",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Alle Vorschläge verwerfen\"]": {
|
||||||
|
"abdruck": "c44bbe11908637e9",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Code in Features prüfen\"]": {
|
||||||
|
"abdruck": "ed5da2461b99ddf3",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Features im Code prüfen\"]": {
|
||||||
|
"abdruck": "c31fcfa6dbc75232",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Funktion abnehmen\"]": {
|
||||||
|
"abdruck": "dd3cf177204bcf25",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Problemstelle anspringen\"]": {
|
||||||
|
"abdruck": "0656bbf0d6554c85",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Prüfung beenden\"]": {
|
||||||
|
"abdruck": "af40481c06d2fd97",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Stil prüfen\"]": {
|
||||||
|
"abdruck": "8326e792e8383b53",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Vorschlag annehmen\"]": {
|
||||||
|
"abdruck": "6f8d17b80787b7aa",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"karten-pruefen\", \"Vorschlag verwerfen\"]": {
|
||||||
|
"abdruck": "cf5bda968428df9f",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"\"]": {
|
||||||
|
"abdruck": "a59f6fdfa58188fa",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt anlegen\"]": {
|
||||||
|
"abdruck": "2c3ab4452996ffaf",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt aufrufen\"]": {
|
||||||
|
"abdruck": "2ca7a3ea9e79fc6e",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt entfernen\"]": {
|
||||||
|
"abdruck": "a0c52ceb9605a805",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Projekt umbenennen\"]": {
|
||||||
|
"abdruck": "d41292ff010e7fd3",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"projekte\", \"Quelle festlegen\"]": {
|
||||||
|
"abdruck": "990ee354f1aa70fa",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"\"]": {
|
||||||
|
"abdruck": "f4ecff18e05ff058",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Nächsten Rückschritt anzeigen\"]": {
|
||||||
|
"abdruck": "2d944c3424b6f851",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"schritt-zuruecknehmen\", \"Schritt zurücknehmen\"]": {
|
||||||
|
"abdruck": "28825333acb1e107",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"\"]": {
|
||||||
|
"abdruck": "e83e9ad5470aa4b1",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Anzeigemodus umschalten\"]": {
|
||||||
|
"abdruck": "87c494e1e1b0a7b2",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision anlegen\"]": {
|
||||||
|
"abdruck": "7826b26956ba51d2",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision anpassen\"]": {
|
||||||
|
"abdruck": "378ea30c154f7994",
|
||||||
|
"probleme": []
|
||||||
|
},
|
||||||
|
"[\"vision-festhalten\", \"Vision entfernen\"]": {
|
||||||
|
"abdruck": "340343e51583e86a",
|
||||||
|
"probleme": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verworfen": {
|
||||||
|
"route:delete /projekte/{name}/chat": {
|
||||||
|
"abdruck": "4ba7a240985a83b4",
|
||||||
|
"titel": "Gespräch schließen\nChatverlauf leeren"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ Darkmode umschalten
|
|||||||
- Tag-Icon: ☀
|
- Tag-Icon: ☀
|
||||||
- Auto-Icon: A
|
- Auto-Icon: A
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- LMB auf Sidebar-Icon
|
- LMB auf Icon in der Kopfzeile
|
||||||
- Modus gewechselt
|
- Modus gewechselt
|
||||||
|
|||||||
@@ -19,11 +19,24 @@ Hält die Oberfläche lesbar und ruhig
|
|||||||
- Escape schließt das Modal
|
- Escape schließt das Modal
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
|
Eingabe mit Enter bestätigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Spart den Weg zum Knopf
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gilt in einzeiligen Feldern
|
||||||
|
- Escape verwirft stattdessen
|
||||||
|
<!-- testen -->
|
||||||
|
- Modal öffnen
|
||||||
|
- Text eingeben
|
||||||
|
- Enter drücken
|
||||||
|
- Eingabe übernommen
|
||||||
|
<!-- funktion -->
|
||||||
Kontextmenü schließen
|
Kontextmenü schließen
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Räumt ein offenes Menü aus dem Weg
|
Räumt ein offenes Menü aus dem Weg
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Klick daneben schließt es
|
- Klick daneben schließt es
|
||||||
|
- Escape schließt es
|
||||||
- Ein neues Menü verdrängt das alte
|
- Ein neues Menü verdrängt das alte
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- RMB auf eine Karte
|
- RMB auf eine Karte
|
||||||
|
|||||||
@@ -50,9 +50,19 @@ Trennt tragende von verzichtbaren Features
|
|||||||
- Badge zeigt die Einstufung
|
- Badge zeigt die Einstufung
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
- LMB auf Badge klicken
|
- LMB auf Badge
|
||||||
- Einstufung gewechselt
|
- Einstufung gewechselt
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
|
Feature durchsuchen
|
||||||
|
<!-- ziel -->
|
||||||
|
Findet die Karte, ohne alle zu lesen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Sucht in allen Feldern
|
||||||
|
- Zählt Ausgeblendete mit
|
||||||
|
<!-- testen -->
|
||||||
|
- Suchwort eingeben
|
||||||
|
- Nur passende Karten bleiben
|
||||||
|
<!-- funktion -->
|
||||||
Feature entfernen
|
Feature entfernen
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- x-Icon
|
- x-Icon
|
||||||
@@ -62,6 +72,19 @@ Feature entfernen
|
|||||||
- Bestätigen im Modal
|
- Bestätigen im Modal
|
||||||
- Feature entfernt
|
- Feature entfernt
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
|
Feature verschieben
|
||||||
|
<!-- ziel -->
|
||||||
|
Hängt ein Feature in ein anderes Projekt um
|
||||||
|
<!-- regeln -->
|
||||||
|
- Datei wandert
|
||||||
|
- Datei wird nicht kopiert
|
||||||
|
- Nur bei mehreren Projekten
|
||||||
|
<!-- testen -->
|
||||||
|
- RMB auf Featurecard
|
||||||
|
- Klick auf "Feature verschieben"
|
||||||
|
- Zielprojekt wählen
|
||||||
|
- Feature verschoben
|
||||||
|
<!-- funktion -->
|
||||||
Regeln anpassen
|
Regeln anpassen
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ Zerlegt ein Feature in prüfbare Stücke
|
|||||||
----------------------------------------
|
----------------------------------------
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Gehört zu genau einem Feature
|
- Gehört zu genau einem Feature
|
||||||
- Alphabetisch sortiert
|
- Offen heißt bestellt, nicht gebaut
|
||||||
|
- Neue Funktionen von Hand gelten als gebaut
|
||||||
|
- Reihenfolge frei wählbar
|
||||||
- Ziel bleibt freiwillig
|
- Ziel bleibt freiwillig
|
||||||
- Regeln bleiben freiwillig
|
- Regeln bleiben freiwillig
|
||||||
- Testen bleibt freiwillig
|
- Testen bleibt freiwillig
|
||||||
@@ -39,6 +41,30 @@ Funktion entfernen
|
|||||||
- Bestätigen im Modal
|
- Bestätigen im Modal
|
||||||
- Funktion entfernt
|
- Funktion entfernt
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
|
Stand umschalten
|
||||||
|
<!-- ziel -->
|
||||||
|
Trennt Bestelltes von Gebautem
|
||||||
|
<!-- regeln -->
|
||||||
|
- Marke rechts vom Titel
|
||||||
|
- Offen ist gelb
|
||||||
|
- Gebaut ist ein Haken
|
||||||
|
- Kompakt erscheint nur die offene Marke
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- LMB auf die Marke
|
||||||
|
- Stand gewechselt
|
||||||
|
<!-- funktion -->
|
||||||
|
Funktion verschieben
|
||||||
|
<!-- ziel -->
|
||||||
|
Bringt die Schritte in ihre natürliche Folge
|
||||||
|
<!-- regeln -->
|
||||||
|
- Reihenfolge steht in der Datei
|
||||||
|
<!-- testen -->
|
||||||
|
- Vollmodus öffnen
|
||||||
|
- RMB auf Funktion
|
||||||
|
- Klick auf "Nach oben"
|
||||||
|
- Funktion verschoben
|
||||||
|
<!-- funktion -->
|
||||||
Titel anpassen
|
Titel anpassen
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- Vollmodus öffnen
|
- Vollmodus öffnen
|
||||||
|
|||||||
BIN
daten/Planer/k1-1024.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
daten/Planer/k1-1280.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
daten/Planer/k1-1600.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
daten/Planer/k1-390.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
daten/Planer/k1-640.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
daten/Planer/k1-820.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
61
daten/Planer/karten-besprechen.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Karten besprechen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
optional
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Entwickelt Karten im Gespräch mit der KI
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Die KI schreibt nie selbst in eine Karte
|
||||||
|
- Sie beschreibt den Sollzustand
|
||||||
|
- Was sich ändert, rechnet der Code aus
|
||||||
|
- Vorschläge sehen aus wie bei der Prüfung
|
||||||
|
- Neue Funktionen gelten als offen
|
||||||
|
- Das Gespräch überlebt den Neustart
|
||||||
|
- Die letzten 30 Wortmeldungen bleiben
|
||||||
|
- Gespräch und Prüfung teilen eine Spalte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Gespräch öffnen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Stift-Icon in der Kopfzeile
|
||||||
|
- Icon leuchtet bei offenem Gespräch
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Stift-Icon
|
||||||
|
- Gespräch geöffnet
|
||||||
|
<!-- funktion -->
|
||||||
|
Nachricht senden
|
||||||
|
<!-- ziel -->
|
||||||
|
Sagt der KI, was das Projekt können soll
|
||||||
|
<!-- regeln -->
|
||||||
|
- Enter sendet
|
||||||
|
- Eigene Zeile erscheint sofort
|
||||||
|
- Text bleibt bei einem Fehler stehen
|
||||||
|
<!-- testen -->
|
||||||
|
- Text eingeben
|
||||||
|
- Enter drücken
|
||||||
|
- Antwort und Vorschläge erscheinen
|
||||||
|
<!-- funktion -->
|
||||||
|
Vorgeschlagene Karte anlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Macht aus einem Umriss eine echte Karte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gestrichelter Rand vor dem Annehmen
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Haken am Umriss
|
||||||
|
- Karte angelegt
|
||||||
|
<!-- funktion -->
|
||||||
|
Gespräch verwerfen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Löscht nur das Gespräch
|
||||||
|
- Karten bleiben unberührt
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf Papierkorb-Icon
|
||||||
|
- Gespräch geleert
|
||||||
|
<!-- funktion -->
|
||||||
|
Gespräch schließen
|
||||||
|
<!-- testen -->
|
||||||
|
- LMB auf x-Icon
|
||||||
|
- Spalte wieder frei
|
||||||
@@ -35,26 +35,6 @@ Alle Vorschläge verwerfen
|
|||||||
- LMB auf x-Icon der Karte
|
- LMB auf x-Icon der Karte
|
||||||
- Alle Vorschläge verworfen
|
- Alle Vorschläge verworfen
|
||||||
<!-- funktion -->
|
<!-- funktion -->
|
||||||
Code in Features prüfen
|
|
||||||
<!-- ziel -->
|
|
||||||
Findet Bedienstellen ohne Funktion auf einer Karte
|
|
||||||
<!-- regeln -->
|
|
||||||
- Bedienstellen kommen aus dem Code
|
|
||||||
- Fünf von sechs Stimmen entscheiden
|
|
||||||
<!-- testen -->
|
|
||||||
- LMB auf Aufwärts-Icon
|
|
||||||
- Fehlende Funktionen gelistet
|
|
||||||
<!-- funktion -->
|
|
||||||
Features im Code prüfen
|
|
||||||
<!-- ziel -->
|
|
||||||
Findet Funktionen ohne Entsprechung im Code
|
|
||||||
<!-- regeln -->
|
|
||||||
- Sechs Stimmen suchen den Beleg
|
|
||||||
- Ein Fund genügt zum Freispruch
|
|
||||||
<!-- testen -->
|
|
||||||
- LMB auf Abwärts-Icon
|
|
||||||
- Funktionen ohne Code gelistet
|
|
||||||
<!-- funktion -->
|
|
||||||
Problemstelle anspringen
|
Problemstelle anspringen
|
||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Führt vom Panel-Eintrag zur betroffenen Karte
|
Führt vom Panel-Eintrag zur betroffenen Karte
|
||||||
@@ -77,7 +57,7 @@ Stil prüfen
|
|||||||
<!-- ziel -->
|
<!-- ziel -->
|
||||||
Prüft die Form gegen den Katalog
|
Prüft die Form gegen den Katalog
|
||||||
<!-- regeln -->
|
<!-- regeln -->
|
||||||
- Prüf-Icon in der Sidebar
|
- Prüf-Icon in der Kopfzeile
|
||||||
- Wortzahl und Satzzeichen misst der Code
|
- Wortzahl und Satzzeichen misst der Code
|
||||||
<!-- testen -->
|
<!-- testen -->
|
||||||
- LMB auf Prüf-Icon
|
- LMB auf Prüf-Icon
|
||||||
|
|||||||
BIN
daten/Planer/komfort.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
daten/Planer/offen.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
@@ -11,7 +11,7 @@ vision
|
|||||||
- Die KI zeigt mir Live wie sich die Features ändern
|
- Die KI zeigt mir Live wie sich die Features ändern
|
||||||
- Ich verbessere und die KI passt Live an
|
- Ich verbessere und die KI passt Live an
|
||||||
- Wenn ich zufrieden bin, dann lasse ich die Features von der KI implementieren
|
- Wenn ich zufrieden bin, dann lasse ich die Features von der KI implementieren
|
||||||
- Zu einem ausgewählten Projekt analysiert die KI alle kompakten Informationen dazu
|
- (später) Zu einem ausgewählten Projekt analysiert die KI alle kompakten Informationen dazu
|
||||||
- Ich wähle ein Verzeichnis das als Quelle dient
|
- Ich wähle ein Verzeichnis das als Quelle dient
|
||||||
- Die Quelle wird durch ein System geschickt, dass kompakte Informationen extrahiert
|
- Die Quelle wird durch ein System geschickt, dass kompakte Informationen extrahiert
|
||||||
- Die kompakten Informationen enthalten das Wissen zu dem WIE vom Projekt
|
- Die kompakten Informationen enthalten das Wissen zu dem WIE vom Projekt
|
||||||
|
|||||||
2735
daten/RAG/.pruefung.json
Normal file
93
daten/RAG/admin.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Admin
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Bietet Zugriff auf Enterprise-Betriebsdaten und Systemüberwachung für Administratoren
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- POST-Operationen erfordern gültiges CSRF-Token
|
||||||
|
- Wiederherstellung erfordert ROLE_SUPER_ADMIN
|
||||||
|
- Ergebnis jeder Operation als Flash-Meldung
|
||||||
|
- Backup-Download erfordert ROLE_SUPER_ADMIN
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Betrieb einsehen
|
||||||
|
<!-- ziel -->
|
||||||
|
Verschafft Admins einen Überblick über den Betriebszustand aller Systeme
|
||||||
|
<!-- regeln -->
|
||||||
|
- Backup- und Restore-Bereitschaft zeigt Status
|
||||||
|
- Sicherheit und Datenschutz zeigt Bereitschaft
|
||||||
|
- Deployment-Scheduler zeigt Bereitschaft
|
||||||
|
- Backup-Übersicht zeigt vorhandene Sicherungen
|
||||||
|
- Operator-Prüfprotokoll zeigt letzte 8 Prüfungen
|
||||||
|
- Übersicht kombiniert alle Berichte
|
||||||
|
<!-- testen -->
|
||||||
|
- Admin-Dashboard öffnen
|
||||||
|
- Backup- und Restore-Bereitschaft anzeigen
|
||||||
|
- Sicherheit und Datenschutz anzeigen
|
||||||
|
- Deployment-Scheduler anzeigen
|
||||||
|
- Backup-Übersicht mit vorhandenen Sicherungen
|
||||||
|
- Operator-Prüfprotokoll mit letzten 8 Einträgen
|
||||||
|
- Kombinierte Übersicht aller Berichte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Dashboard anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt Systemzustand und Konfiguration im Admin-Bereich
|
||||||
|
<!-- regeln -->
|
||||||
|
- Rendered admin dashboard index html twig
|
||||||
|
- Enthält Chunk-Zähler und Chunk-Limit
|
||||||
|
- Enthält Vektor- und Tag-Gesundheitsdaten
|
||||||
|
- Enthält Reset-Konfiguration
|
||||||
|
<!-- testen -->
|
||||||
|
- Admin-Dashboard aufrufen
|
||||||
|
- HTTP 200 zurückerhalten
|
||||||
|
- admin-dashboard-index-html-twigt Template prüfen
|
||||||
|
- Chunk-Zähler im Response vorhanden
|
||||||
|
- Vektor-Gesundheitsdaten im Response vorhanden
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Logout einleiten
|
||||||
|
<!-- ziel -->
|
||||||
|
Beendet die aktive Adminsession sicher
|
||||||
|
<!-- regeln -->
|
||||||
|
- GET oder POST an Admin-Logout startet Logout
|
||||||
|
- Wird vom Framework-Sicherheitssystem abgefangen
|
||||||
|
- Leere Methoden-Signatur
|
||||||
|
- Löscht Session und Authentifizierungsdaten
|
||||||
|
<!-- testen -->
|
||||||
|
- Klick auf Logout-Button im Admin
|
||||||
|
- Weiterleitung zur Login-Seite
|
||||||
|
- Session beendet
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Wiederherstellung testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prueft ob ein Backup sich vollständig wiederherstellen laesst
|
||||||
|
<!-- regeln -->
|
||||||
|
- Benoetigt ROLE_SUPER_ADMIN
|
||||||
|
- POST-Request mit CSRF-Token
|
||||||
|
- Prueft angegebene Backup-ID gegen Muster retriex-backup-YYYYMMDD-HHMMSS
|
||||||
|
- Trockenlauf ohne tatsaechliche Wiederherstellung
|
||||||
|
- Ergebnis als Flash-Meldung nach Startseite
|
||||||
|
<!-- testen -->
|
||||||
|
- Admin-Enterprise-Seite aufrufen
|
||||||
|
- Dry-Run-Wiederherstellung mit gueltigem CSRF-Token absenden
|
||||||
|
- Rueckleitung auf Admin-Enterprise mit Flash-Meldung pruefen
|
||||||
|
- Flash-Meldung zeigt Warnungen und Fehler oder Erfolg
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Zum Dashboard weiterleiten
|
||||||
|
<!-- ziel -->
|
||||||
|
Leitet admin und admin- auf das Dashboard um
|
||||||
|
<!-- regeln -->
|
||||||
|
- Admin leitet zum Dashboard um
|
||||||
|
- Verhindert doppelten Inhalt
|
||||||
|
<!-- testen -->
|
||||||
|
- admin aufrufen
|
||||||
|
- Weiterleitung zu dashboard prüfen
|
||||||
|
- admin-Abschluss aufrufen
|
||||||
|
- Weiterleitung zu dashboard prüfen
|
||||||
286
daten/RAG/agenten.md
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Agenten
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Steuert Agentenaktivitaet und prueft deren Komponenten automatisiert
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- JSON-Ausgabe bei gesetztem --json Flag
|
||||||
|
- Self-TestsFuehrenKeineMCPTransportAufrufeAus
|
||||||
|
- AgentenaktivitaetSeiteAusQueryParameterPage
|
||||||
|
- LimitZwischen1Und200Standardmaessig50
|
||||||
|
- StreamingGibtJedesTokenSofortAus
|
||||||
|
- Inspector liefert paths, meta und ndjson
|
||||||
|
- paths zeigen konfigurierte Agentenlogikdateien
|
||||||
|
- meta enthält Statistiken zur Agentenaktivität
|
||||||
|
- ndjson enthält paginierte Aktivitätseinträge
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Agentenaktivität anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt eine liste der agentenaktivitäten
|
||||||
|
<!-- regeln -->
|
||||||
|
- Seite aus Query-Parameter page
|
||||||
|
- Limit zwischen 1 und 200
|
||||||
|
- Standardlimit 50
|
||||||
|
<!-- testen -->
|
||||||
|
- Admin-System-Agent abrufen
|
||||||
|
- Antwort ist 200
|
||||||
|
- Debug-Count zeigt Itemanzahl
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chat-Auswahl-Guard testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Validiert dass der Chat-Auswahl-Guard korrekt funktioniert
|
||||||
|
<!-- regeln -->
|
||||||
|
- Command mto:agent:knowledge-space:chat-selection-guard-self-test
|
||||||
|
- json gibt strukturierte Daten aus
|
||||||
|
- Exit-Code 0 bei Status OK, sonst 1
|
||||||
|
<!-- testen -->
|
||||||
|
- Command mit --json Flag ausführen
|
||||||
|
- JSON mit status summary cases enthält
|
||||||
|
- Exit-Code entspricht Status
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Fähigkeiten ermitteln
|
||||||
|
<!-- ziel -->
|
||||||
|
Listet verfügbare Resources, Tools und Prompts eines Admin-MCP-Servers auf
|
||||||
|
<!-- regeln -->
|
||||||
|
- Benötigt gespeicherten Admin-MCP-Server
|
||||||
|
- read liest Resource-URIs metadaten
|
||||||
|
- json maschinelles Format
|
||||||
|
- Status ready bei erfolgreicher Erkennung
|
||||||
|
- Runtime-Calls gezählt
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl mit gespeichertem Servernamen ausführen
|
||||||
|
- Ausgabe auf Status ready prüfen
|
||||||
|
- Resources, Tools und Prompts in Ausgabe
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Hybrid-Tool-Auswahl testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Stellt sicher, dass LLM-Vorschlag und RetrieX-Validierung bei der Tool-Auswahl zusammenarbeiten
|
||||||
|
<!-- regeln -->
|
||||||
|
- Planner besteht eigenen Self-Test
|
||||||
|
- AgentRunner nutzt LLM für Vorschlag vor RetrieX-Validierung
|
||||||
|
- Loader zeigt Status beim Tool-Auswählen
|
||||||
|
- Model-Config enthält dediziertes mcp_tool_selection-Modell
|
||||||
|
<!-- testen -->
|
||||||
|
- mto:agent:mcp:hybrid-tool-selection-planner-self-test --json ausführen
|
||||||
|
- JSON-Ausgabe auf status OK prüfen
|
||||||
|
- Alle vier Cases mit Status PASS vorhanden
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
MCP-Gateway trocken ausführen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob eine Resource die Gateway-Policy erfüllt und welches Evidence erzeugt wird
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kein MCP-Transport wird geöffnet
|
||||||
|
- fixture nutzt eingebaute Testdaten
|
||||||
|
- Exit-Code 0 bei Status ready
|
||||||
|
- json gibt Ergebnis als JSON aus
|
||||||
|
<!-- testen -->
|
||||||
|
- bin console mto agent mcp gateway-dry-run --fixture ausführen
|
||||||
|
- Ausgabe zeigt status ready
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
MCP-Laufzeit-Gate prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob ein MCP-Read-Only-Zugriff erlaubt ist, ohne Transport zu öffnen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gate ohne Transport öffnen
|
||||||
|
- fixture nutzt eingebaute Testdaten
|
||||||
|
- assume-enabled simuliert aktivierte MCP-Konfiguration
|
||||||
|
- JSON-Ausgabe verfügbar
|
||||||
|
<!-- testen -->
|
||||||
|
- Console-Befehl mit --json ausführen
|
||||||
|
- Exit-Code auf SUCCESS prüfen
|
||||||
|
- JSON auf Status ready und Fehler prüfen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
MCP-Laufzeitgrenze testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Validiert, dass MCP-Server nur Admin-gesteuert und begrenzt in der Antwort-Runtime verwendet werden
|
||||||
|
<!-- regeln -->
|
||||||
|
- McpDataRoomStore nur in Admin, CLI, ReadModels referenziert
|
||||||
|
- Keine externen MCP-Abrufe in Runtime-Boundary
|
||||||
|
- Keine Chat-, Retrieval- oder Evidence-Beruehrung
|
||||||
|
- Tool-Nachweise unterscheiden sichtbare Tool-Ausfuehrung
|
||||||
|
- Tool-first-Inhalts-Fallback bleibt im Statusprotokoll sichtbar
|
||||||
|
- Live-Inhalts-Fallback buendelt MCP-JSON-RPC-Resource-Reads
|
||||||
|
<!-- testen -->
|
||||||
|
- Rueckgabewert pruefen: Exitcode 0 bestaetigt OK
|
||||||
|
- Rueckgabewert pruefen: Exitcode 1 bestaetigt FAIL
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
MCP-Quelle prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft eine gespeicherte Admin-MCP-Quelle als registrierungskompatible Richtlinie
|
||||||
|
<!-- regeln -->
|
||||||
|
- Lokale Prüfung ohne MCP-Transport
|
||||||
|
- server wählt die gespeicherte Quelle
|
||||||
|
- audit-all prüft alle Quellen
|
||||||
|
- online führt einen Nur-Lese-Netzwerktest durch
|
||||||
|
- json gibt nur Metadaten aus
|
||||||
|
<!-- testen -->
|
||||||
|
- mto:agent:mcp:admin-source-check --server=my_source
|
||||||
|
- Ergebnis zeigt Status, Label und Fehler
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Mit Agent chatten
|
||||||
|
<!-- ziel -->
|
||||||
|
Stellt Fragen an den KI-Agenten und empfängt Antworten in Echtzeit
|
||||||
|
<!-- regeln -->
|
||||||
|
- Streaming-Ausgabe jedes Tokens sofort
|
||||||
|
- Beendet auf Eingabe von exit
|
||||||
|
- Beendet auf Strg+C
|
||||||
|
- Leerer Prompt beendet
|
||||||
|
- user-id Standardwert ist cli
|
||||||
|
<!-- testen -->
|
||||||
|
- mto:agent:chat ausführen
|
||||||
|
- Frage eingeben
|
||||||
|
- Antwort tokenweise empfangen
|
||||||
|
- exit eingeben
|
||||||
|
- CLI beendet
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Performance-Cache leeren
|
||||||
|
<!-- ziel -->
|
||||||
|
Leert alle RetrieX Runtime-Caches und den Python Vector-Service-Query-Cache
|
||||||
|
<!-- regeln -->
|
||||||
|
- Löscht den PHP Runtime-Cache
|
||||||
|
- Löscht optional den Vector-Service-Query-Cache
|
||||||
|
- skip-vector-service überspringt den Vector-Service-Cache
|
||||||
|
- json gibt maschinenlesbare Ausgabe aus
|
||||||
|
<!-- testen -->
|
||||||
|
- php bin console mto:agent:performance:cache:clear json
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Produktlabel-Formatierer testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Validiert die p465-Formatter-Extraktion ohne Laufzeiteffekte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest nur lokale Quelldateien
|
||||||
|
- Ruft keine Chat-, Retrieval-, Shop-, MCP- oder LLM-Dienste auf
|
||||||
|
- Prüft AgentRunner-Delegation an ProductLabelFormatter
|
||||||
|
- Prüft Formatter-Besitz reiner Anzeige-Methoden
|
||||||
|
- Prüft Formatter-Besitz reiner Label-Methoden
|
||||||
|
- Prüft Abwesenheit von Runtime-Dependencies im Formatter
|
||||||
|
- Prüft Abwesenheit von p465-Methoden im AgentRunner
|
||||||
|
- Prüft Dokumentationsabdeckung in allen Referenzdateien
|
||||||
|
<!-- testen -->
|
||||||
|
- Produktlabel-Formatter-Selbsttest mit --json ausführen
|
||||||
|
- JSON zeigt status OK und 11 PASS
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Release-Selbsttest bereitstellen
|
||||||
|
<!-- ziel -->
|
||||||
|
Validiert den v1.10.8 Release-Lock ohne Seiteneffekte im Chat-Pipeline, Agent-Runtime, Retrieval, Ranking, Shop, Followup, Evidence oder Truth Layer.
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prüft 22 Release-Artefakte
|
||||||
|
- Prüft 8 zentrale Produktdokumente auf v1.10.8
|
||||||
|
- Prüft 16 Required-Release-Phrasen
|
||||||
|
- Liest nur lokale Dateien
|
||||||
|
- Führt keine Chat-, LLM- oder MCP-Live-Aufrufe aus
|
||||||
|
- Führt keine Retrieval-, Shop- oder Netzaufrufe aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Ausgabe zeigt Status PASS
|
||||||
|
- JSON zeigt status OK und runtime_calls 0
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ressource abrufen
|
||||||
|
<!-- ziel -->
|
||||||
|
Liest eine erlaubte MCP-Ressource über die CLI ab
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur allowlisted Ressourcen abrufbar
|
||||||
|
- MIME-Typ geprüft
|
||||||
|
- Ergebnis enthält Status, Metadaten und Fehler
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl mit Optionen ausführen
|
||||||
|
- Kurzformat prüfen
|
||||||
|
- json Flag verwenden
|
||||||
|
- Status ready und Ressource vorhanden
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ressource validieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht das Prüfen, ob eine MCP-Ressource_URI auf der Allowlist steht
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prüft URI gegen konfigurierten Allowlist-Eintrag
|
||||||
|
- Prüft MIME-Type gegen erlaubte Typen des Eintrags
|
||||||
|
- Gibt Fehler und Warnungen aus
|
||||||
|
- CLI-Exit-Code spiegelt Entscheidung wider
|
||||||
|
<!-- testen -->
|
||||||
|
- CLI-Befehl mit --server und --resource-uri ausführen
|
||||||
|
- Option --mime-type ergänzen
|
||||||
|
- Exit-Code zeigt Ergebnis an
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Rollencode-Zuweisung prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Validiert die p465b-Rollencode-Zuweisungs-Grenzen ohne Laufzeiteffekte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prüft statische Artefakte nur
|
||||||
|
- Keine Chat-, Retrieval- oder LLM-Laufzeit
|
||||||
|
- Keine Shop-Suche
|
||||||
|
- Keine MCP-Aufrufe
|
||||||
|
- ProductLabelFormatter bleibt frei von Shop, Retrieval, Evidence und LLM
|
||||||
|
- AgentRunner bleibt Orchestrator
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl ausführen: mto:agent:architecture:product-role-code-assignment-guard-self-test
|
||||||
|
- Ausgabe auf Status OK prüfen
|
||||||
|
- Ergebnis: status = OK oder FAIL mit Details
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Stabilitäts-Selbsttest bereitstellen
|
||||||
|
<!-- ziel -->
|
||||||
|
Validierung der Shop- und Follow-up-Ankerwächter vor dem Deployment
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prüft REQUIRED_METHODS in AgentRunner.php
|
||||||
|
- Prüft keine FORBIDDEN_GUARD_MARKERS in Ankerwächter-Methoden
|
||||||
|
- Liest PATCH_MANIFEST.md und patch_history-Dateien
|
||||||
|
- Erzeugt Report ohne Runtime-Seiteneffekte
|
||||||
|
- json gibt maschinenlesbares Format aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Binconsole mto:agent:shop-followup-anchor-stability-self-test
|
||||||
|
- Status OK prüfen
|
||||||
|
- passed größer 0 prüfen
|
||||||
|
- failed gleich 0 prüfen
|
||||||
|
- Binconsole mit --json
|
||||||
|
- JSON-Status OK prüfen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
System rebuild bereitstellen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vektordienst steuern
|
||||||
|
<!-- ziel -->
|
||||||
|
Steuert den Vektorsuchdienst über die Kommandozeile
|
||||||
|
<!-- regeln -->
|
||||||
|
- Rufzeit 300 Sekunden
|
||||||
|
- Standard-Host 0.0.0.0
|
||||||
|
- Standard-Port 8090
|
||||||
|
- Ruft Vector-Kontrollskript auf
|
||||||
|
<!-- testen -->
|
||||||
|
- Status wird angezeigt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Wissensbatch finalisieren testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Verknüpfung von Batch-Abschluss und Bericht ohne Seiteneffekte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prüft ohne Import, LLM-Aufruf oder Index-Rebuild
|
||||||
|
- Neun Prüfungen für Routing, Report und Abhängigkeiten
|
||||||
|
- Gibt FAIL bei einer einzigen fehlgeschlagenen Prüfung
|
||||||
|
- JSON-Ausgabe enthält status, scope, checks und failed
|
||||||
|
<!-- testen -->
|
||||||
|
- Command ausführen: mto:agent:knowledge:batch-finalize-self-test
|
||||||
|
- Ausgabe zeigt status OK oder FAIL
|
||||||
|
- JSON-Flag setzen: --json
|
||||||
|
- JSON enthält status und failed-Liste
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
MCP-Allowlist prüfen
|
||||||
43
daten/RAG/anleitungen.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Anleitungen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt Anleitungen als gerendertes Markdown im Admin-Bereich
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Erste H1 als Titel
|
||||||
|
- Ohne H1 wird Slug als Titel verwendet
|
||||||
|
- H1 erscheint doppelt: als Titel und im gerenderten Inhalt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Anleitung anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Liest eine Markdown-Datei und zeigt sie als HTML gerendert an
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ungültiger Slug wirft 404
|
||||||
|
- Fehlende Datei wirft 404
|
||||||
|
- Titel aus erster H1 oder aus Slug
|
||||||
|
- Markdown als HTML gerendert
|
||||||
|
<!-- testen -->
|
||||||
|
- admin guides beispiel-seite aufrufen
|
||||||
|
- Titel und HTML-Inhalt sehen
|
||||||
|
- admin guides ungueltig aufrufen
|
||||||
|
- 404-Fehlerseite
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Anleitungsübersicht öffnen
|
||||||
|
<!-- ziel -->
|
||||||
|
Listet alle verfügbaren Anleitungen zur schnellen Auswahl auf
|
||||||
|
<!-- regeln -->
|
||||||
|
- Markdown-Dateien aus bestimmtem Ordner
|
||||||
|
- Erste Überschrift als Titel
|
||||||
|
- Ohne Ordner wird leere Liste angezeigt
|
||||||
|
- Alphabetisch nach Titel sortiert
|
||||||
|
- Ungültige Dateinamen ignoriert
|
||||||
|
<!-- testen -->
|
||||||
|
- Admin-Bereich öffnen
|
||||||
|
- Klick auf Anleitungen
|
||||||
|
- Anleitungen alphabetisch aufgelistet
|
||||||
94
daten/RAG/auftraege.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Aufträge
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Verarbeitet asynchrone Aufgaben mit Verfolgung von Status und Ereignissen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- ROLE_ADMIN_AREA für Admin-Statusabruf erforderlich
|
||||||
|
- Admin-Bereich für Ereignisse gilt ohne Events-Endpunkt
|
||||||
|
- Antwort enthält lastEventId, updatedAt, startedAt, completedAt, runningStaleAfterSeconds
|
||||||
|
- Vollständiger Kontext muss explizit angefordert werden, Standard ist eingeschränkter Kontext
|
||||||
|
- Abgeschlossene, fehlgeschlagene und abgebrochene Ingest-Jobs werden mit Exitcode SUCCESS beendet
|
||||||
|
- Dry-Run gibt im Output einen Hinweis zum Zwischenspeicher-Status aus
|
||||||
|
- Ingest-Job-Verarbeitung leert PHP-Laufzeit- und Vector-Service-Cache nach Abschluss
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Auftrag starten
|
||||||
|
<!-- ziel -->
|
||||||
|
Übermittelt einen Prompt zur asynchronen Verarbeitung im Hintergrund
|
||||||
|
<!-- regeln -->
|
||||||
|
- Leere Prompts werden abgelehnt
|
||||||
|
- Prompts über dem Zeichenlimit werden abgelehnt
|
||||||
|
- Ungültige Upload-IDs werden abgelehnt
|
||||||
|
- Job-ID besteht aus 48 Hexadezimalzeichen
|
||||||
|
- Erstellt Job mit Status pending
|
||||||
|
<!-- testen -->
|
||||||
|
- POST ask-jobs mit gültigem Prompt
|
||||||
|
- Antwort enthält jobId
|
||||||
|
- Statuscode ist 200
|
||||||
|
- POST ask-jobs mit leerem Prompt
|
||||||
|
- Statuscode ist 400
|
||||||
|
- POST ask-jobs mit zu langem Prompt
|
||||||
|
- Statuscode ist 413
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Auftragsereignisse abrufen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht dem Client, Auftragsereignisse zu pollen
|
||||||
|
<!-- regeln -->
|
||||||
|
- Unbekannte Auftrags-ID gibt HTTP 404 zurück
|
||||||
|
- Veraltete laufende Aufträge werden als fehlgeschlagen markiert
|
||||||
|
- Ereignisse nach after werden zurückgegeben
|
||||||
|
<!-- testen -->
|
||||||
|
- GET ask-jobs jobId events after 0
|
||||||
|
- Antwort enthält status, events, lastEventId
|
||||||
|
- Unbekannte jobId gibt HTTP 404
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Auftragsstatus anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht dem Admin, den aktuellen Status eines Auftrags ohne Seitenreload zu prüfen
|
||||||
|
<!-- regeln -->
|
||||||
|
- ROLE_ADMIN_AREA erforderlich
|
||||||
|
- JSON mit id, type, status, startedAt, finishedAt, errorMessage, logPath
|
||||||
|
- 404 bei unbekannter Auftrags-ID
|
||||||
|
<!-- testen -->
|
||||||
|
- Detailseite eines Auftrags öffnen
|
||||||
|
- Statusabfrage per AJAX auslösen
|
||||||
|
- JSON mit status und Zeitstempeln empfangen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Auftragsstatus prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht das Prüfen des aktuellen Zustands eines Auftrags
|
||||||
|
<!-- regeln -->
|
||||||
|
- jobId ist eine 48-stellige Hexadezimalzahl
|
||||||
|
- Ungültige jobId liefert Status missing
|
||||||
|
- Abgelaufene Aufträge werden als failed markiert
|
||||||
|
- Antwort enthält status, message, timestamps und serverTime
|
||||||
|
<!-- testen -->
|
||||||
|
- ask-jobs-Endpunkt mit jobId aufrufen
|
||||||
|
- JSON-Antwort mit Status prüfen
|
||||||
|
- Status pending, running, completed, interrupted oder failed zurückgegeben
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ingest-Job starten
|
||||||
|
<!-- ziel -->
|
||||||
|
Startet einen bereitgestellten Ingest-Job über seine UUID
|
||||||
|
<!-- regeln -->
|
||||||
|
- Job-ID als Pflichtargument erforderlich
|
||||||
|
- Job-Status muss ausführbar sein
|
||||||
|
- Abgeschlossene Jobs werden nicht erneut ausgeführt
|
||||||
|
- Dry-Run vermeidet Seiteneffekte
|
||||||
|
- Fehler führen zu FAILURE-Exitcode
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl mit gültiger Job-ID ausführen
|
||||||
|
- Job wird verarbeitet
|
||||||
|
- Erfolgsmeldung erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Ingest-Jobs auflisten
|
||||||
97
daten/RAG/backup.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Backup
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Erstellt, prüft und stellt Backups bereit
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- POST-Routen erfordern CSRF-Token
|
||||||
|
- Super Admin darf Restore-Prüfungen ausführen
|
||||||
|
- Flash-Meldung zeigt Status nach jeder Aktion
|
||||||
|
- Alle Prüfungen werden protokolliert
|
||||||
|
- Backup-IDs folgen dem Muster retriex-backup-Datum-Uhrzeit
|
||||||
|
- json gibt maschinenlesbare Ausgabe zurück
|
||||||
|
- Ungueltiges CSRF-Token zeigt Flash-Meldung
|
||||||
|
- Backup herunterladen mit "Neueste" waehlt aktuelles Backup
|
||||||
|
- Operator-Report als JSON exportieren
|
||||||
|
- Deployment-Scheduler-Pruefung mit Beschreibung in Flash-Meldung
|
||||||
|
- Sicherheit-und-Datenschutz-Pruefung mit Beschreibung in Flash-Meldung
|
||||||
|
- Backup-Dry-Run erwähnt fehlende Dateischreibweise in Flash-Meldung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Backup herunterladen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht dem Super Admin das Herunterladen einer Sicherungsdatei
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur Super Admin
|
||||||
|
- Backup-ID aus Liste oder "Neueste"
|
||||||
|
- Fehlgeschlagener Download zeigt Flash-Meldung
|
||||||
|
- Erfolgreicher Download protokolliert OperatorCheckProtocol
|
||||||
|
<!-- testen -->
|
||||||
|
- admin_enterprise_operations öffnen
|
||||||
|
- Backup-ID aus Liste auswählen
|
||||||
|
- Klick auf Download-Button
|
||||||
|
- Datei im Browser heruntergeladen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Backup starten
|
||||||
|
<!-- ziel -->
|
||||||
|
Erstellt eine Sicherungskopie mit allen konfigurierten Quellen
|
||||||
|
<!-- regeln -->
|
||||||
|
- CSRF-Token erforderlich
|
||||||
|
- Ablauf im Operator-Protokoll
|
||||||
|
- Flash-Meldung je nach Status
|
||||||
|
- Database-Dump als Option
|
||||||
|
<!-- testen -->
|
||||||
|
- Backup-Endpunkt per POST aufrufen
|
||||||
|
- _token und include_database_dump senden
|
||||||
|
- Flash-Meldung zeigt Status
|
||||||
|
- Backup erstellt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Backup-Tauglichkeit prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob das System alle Voraussetzungen für Backup und Restore erfüllt
|
||||||
|
<!-- regeln -->
|
||||||
|
- JSON-Ausgabe mit --json
|
||||||
|
- Tabelle zeigt alle Testfälle
|
||||||
|
- Rückgabe SUCCESS bei Status OK
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl mto:agent:enterprise:backup-restore-readiness ausführen
|
||||||
|
- Tabelle mit Testfällen prüfen
|
||||||
|
- Rückgabe SUCCESS bei Status OK
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Backup-Trockenlauf prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Wiederherstellbarkeit eines Backups ohne tatsächlichen Restore
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur Super-Admin darf Funktion ausführen
|
||||||
|
- CSRF-Token wird geprüft
|
||||||
|
- Report wird im Prüfprotokoll aufgezeichnet
|
||||||
|
- Flash-Nachricht zeigt Status, Backup-ID und Anzahl Warnungen sowie Fehler
|
||||||
|
<!-- testen -->
|
||||||
|
- POST an admin enterprise restore dry-run latest
|
||||||
|
- Erfolg: Redirect zur Übersicht mit Flash-Nachricht
|
||||||
|
- Fehler: Redirect zur Übersicht mit Fehlermeldung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Backup-Wiederherstellung prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob Backup-Pfad, Restore-Nachweis und Betreiberhinweise vorhanden sind
|
||||||
|
<!-- regeln -->
|
||||||
|
- CSRF-Token erforderlich
|
||||||
|
- Flash-Meldung zeigt Zusammenfassung
|
||||||
|
- Protokolliert den Check
|
||||||
|
<!-- testen -->
|
||||||
|
- POST an /admin/enterprise/check/backup-restore
|
||||||
|
- ungültiges CSRF-Token senden
|
||||||
|
- Weiterleitung mit Fehlermeldung
|
||||||
|
- gültiges CSRF-Token senden
|
||||||
|
- Weiterleitung mit Zusammenfassung
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Operatorbericht als JSON exportieren
|
||||||
89
daten/RAG/benutzer.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Benutzer
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht Super-Admins die Verwaltung aller Benutzer des Systems
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- POST-Requests erfordern gültiges CSRF-Token
|
||||||
|
- Mindestens eine Rolle erforderlich
|
||||||
|
- Statusfilter akzeptiert nur all, active, inactive
|
||||||
|
- Statusfilter default bei ungueltigem Wert ist all
|
||||||
|
- Rollefilter default bei ungueltiger Rolle ist all
|
||||||
|
- Vorname ist optional
|
||||||
|
- Abteilung ist optional
|
||||||
|
- Passwort ist erforderlich
|
||||||
|
- is_active ist standardmaessig inaktiv
|
||||||
|
- Rollen werden als Array uebergeben
|
||||||
|
- Bearbeiten erfordert aktuellen Benutzer
|
||||||
|
- Benutzer-ID muss UUID-Format haben
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Benutzer aktivieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Aktiviert oder deaktiviert einen Benutzer sofort
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur ROLE_SUPER_ADMIN hat Zugriff
|
||||||
|
- POST mit gültigem CSRF-Token nötig
|
||||||
|
- Umschalten zwischen Aktiv und Inaktiv
|
||||||
|
- Redirect zur Übersicht nach Umschalten
|
||||||
|
<!-- testen -->
|
||||||
|
- Admin-Übersicht öffnen
|
||||||
|
- LMB auf Toggle-Button eines aktiven Benutzers
|
||||||
|
- Benutzer ist inaktiv
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Benutzer anlegen
|
||||||
|
<!-- ziel -->
|
||||||
|
Legt einen Anwendungsbenutzer über die Konsole an
|
||||||
|
<!-- regeln -->
|
||||||
|
- E-Mail gültig und einmalig
|
||||||
|
- Passwort mindestens 8 Zeichen
|
||||||
|
- Passwort zweimal identisch eingegeben
|
||||||
|
- Mindestens eine Rolle zugewiesen
|
||||||
|
<!-- testen -->
|
||||||
|
- mto-agent-user-create aufrufen
|
||||||
|
- Ungültige E-Mail eingeben
|
||||||
|
- Validierungsfehler erwarten
|
||||||
|
- Gültige E-Mail eingeben
|
||||||
|
- Zu kurzes Passwort eingeben
|
||||||
|
- Validierungsfehler erwarten
|
||||||
|
- Passwort eingeben
|
||||||
|
- Anderes Passwort wiederholen
|
||||||
|
- Validierungsfehler erwarten
|
||||||
|
- Passwort wiederholen
|
||||||
|
- Rollen auswählen
|
||||||
|
- Benutzeraktivierung bestätigen
|
||||||
|
- Ausgabe zeigt E-Mail Rolle Aktiv
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Benutzer auflisten
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt admin-seitig alle Benutzer mit Filteroptionen an
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur Super-Admin darf zugreifen
|
||||||
|
- Filterung nach Suchbegriff, Status und Rolle
|
||||||
|
- Standardfilterung ohne Parameter zeigt alle
|
||||||
|
<!-- testen -->
|
||||||
|
- Seite /admin/users aufrufen
|
||||||
|
- Seite zeigt Benutzerliste mit Rollen und Status
|
||||||
|
- Filter auf Status "aktiv" setzen
|
||||||
|
- Liste zeigt nur aktive Benutzer
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Benutzer bearbeiten
|
||||||
|
<!-- ziel -->
|
||||||
|
Änderungen an Benutzerdaten speichern
|
||||||
|
<!-- regeln -->
|
||||||
|
- Super Admin erforderlich
|
||||||
|
- CSRF-Token erforderlich
|
||||||
|
- E-Mail muss gültig sein
|
||||||
|
- Neues Passwort optional
|
||||||
|
<!-- testen -->
|
||||||
|
- Bearbeitungsseite für Benutzer öffnen
|
||||||
|
- Formular mit geänderten Daten absenden
|
||||||
|
- Erfolgsmeldung anzeigen
|
||||||
|
- Zur Übersicht redirect
|
||||||
90
daten/RAG/berichte.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Berichte
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Berichte aus Enterprise-Diensten generieren, herunterladen und löschen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Rolle SUPER_ADMIN statt EDITOR für Bericht als JSON herunterladen erforderlich
|
||||||
|
- Akzeptanzbericht generieren fehlt Ausgabe als Markdown für Nachweisprotokoll
|
||||||
|
- FAIL-Status: Exitcode 1 nur im JSON-Modus
|
||||||
|
- Operatorbericht als Markdown herunterladen fehlt als Funktion
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Akzeptanzbericht generieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt zusammengefasste Ergebnisse der Operator-Prüfungen im Terminal
|
||||||
|
<!-- regeln -->
|
||||||
|
- markdown-Option gibt Markdown aus
|
||||||
|
- json-Option gibt JSON aus
|
||||||
|
- Standardausgabe formatiert als Tabelle
|
||||||
|
<!-- testen -->
|
||||||
|
- Kommando mit markdown-Option ausführen
|
||||||
|
- Markdown-Tabelle erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Bericht als JSON herunterladen
|
||||||
|
<!-- ziel -->
|
||||||
|
Exportiert einen Batch-Bericht als JSON-Datei zum Speichern
|
||||||
|
<!-- regeln -->
|
||||||
|
- Rolle EDITOR erforderlich
|
||||||
|
- Format muss json oder md sein
|
||||||
|
- Datei muss existieren
|
||||||
|
<!-- testen -->
|
||||||
|
- Batch-Übersicht öffnen
|
||||||
|
- LMB auf JSON-Download-Link eines Batch-Berichts
|
||||||
|
- Datei wird heruntergeladen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Bericht löschen
|
||||||
|
<!-- ziel -->
|
||||||
|
Entfernt einen nicht mehr benötigten Batch-Bericht dauerhaft
|
||||||
|
<!-- regeln -->
|
||||||
|
- POST-Methode erforderlich
|
||||||
|
- CSRF-Token obligatorisch
|
||||||
|
- Nur ROLE_EDITOR hat Zugriff
|
||||||
|
- Erfolgs- oder Warnmeldung als Flash
|
||||||
|
- Zur Berichte-Übersicht weitergeleitet
|
||||||
|
<!-- testen -->
|
||||||
|
- Batch-Bericht öffnen
|
||||||
|
- Löschen-Schaltfläche klicken
|
||||||
|
- Bestätigungsdialog bestätigen
|
||||||
|
- Zur Übersicht weitergeleitet
|
||||||
|
- Flash-Meldung 'gelöscht' sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Berichte auflisten
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt alle Batch-Importe zur Übersicht
|
||||||
|
<!-- regeln -->
|
||||||
|
- listReports liefert alle Berichte
|
||||||
|
- defaultReportDir wird übergeben
|
||||||
|
<!-- testen -->
|
||||||
|
- GET admin knowledge-batches aufrufen
|
||||||
|
- Seite mit Berichtsliste wird angezeigt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Übersicht rendern
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt den Betriebszustand aller Enterprise-Komponenten auf einen Blick
|
||||||
|
<!-- regeln -->
|
||||||
|
- Baut Berichte aller Enterprise-Dienste
|
||||||
|
- json gibt maschinenlesbares JSON aus
|
||||||
|
- Ohne Flag formatiert SymfonyStyle-Ausgabe
|
||||||
|
- FAIL-Status gibt Exitcode 1 zurück
|
||||||
|
<!-- testen -->
|
||||||
|
- php bin console mto:agent:enterprise:operations-overview --json
|
||||||
|
- JSON-Ausgabe enthält Felder status, progress, components, primary_action
|
||||||
|
- Exitcode ist 1 bei Status FAIL
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Operatorbericht als Markdown herunterladen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Batch-Bericht hochladen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Batch-Bericht anzeigen
|
||||||
53
daten/RAG/chat.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Chat
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht authentifizierten Nutzern, Prompts zu senden und Antworten als Event-Stream zu empfangen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Eigener Login-Bereich
|
||||||
|
- Upload-IDs werden gegen Client-ID validiert
|
||||||
|
- CHAT_HANDBOOK.md wird aus Projektverzeichnis gelesen
|
||||||
|
- Fehlende oder leere CHAT_HANDBOOK.md als nicht verfügbar
|
||||||
|
- Anonym bei fehlendem Namen
|
||||||
|
- Handbook maximal 512 KB
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Abmelden ermöglichen
|
||||||
|
<!-- testen -->
|
||||||
|
- Abmelden wählen
|
||||||
|
- Anmeldung beendet
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chat streamen
|
||||||
|
<!-- ziel -->
|
||||||
|
Liefert eine Chat-Antwort als kontinuierlichen Datenstrom im Browser
|
||||||
|
<!-- regeln -->
|
||||||
|
- POST mit Prompt im Body
|
||||||
|
- Prompt zu lang: Stream mit error-Event
|
||||||
|
- Prompt leer: Stream mit error-Event
|
||||||
|
- Upload-IDs werden validiert
|
||||||
|
- Antwort im Format text event-stream
|
||||||
|
- retry: 3000 bei Verbindungsproblemen
|
||||||
|
<!-- testen -->
|
||||||
|
- Ask-SSE-Endpunkt mit gültigem Prompt ansprechen
|
||||||
|
- Daten-Chunks aus Stream empfangen
|
||||||
|
- event: done als Abschluss
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Chatseite anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Zeigt die Chatseite mit Prompt-Benutzername und Wissensraum-Auswahl
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest CHAT_HANDBOOK.md aus dem Projektverzeichnis
|
||||||
|
- Fehlende oder leere Datei wird als nicht verfügbar behandelt
|
||||||
|
- Eigene Vorlage: chat index.html.twig
|
||||||
|
<!-- testen -->
|
||||||
|
- GET chat aufrufen
|
||||||
|
- Seite rendert ohne Fehler
|
||||||
|
- Prompt-Benutzername ist Anonym
|
||||||
|
- Wissensraum-Auswahl wird angezeigt
|
||||||
|
- Handbuch-Verfügbarkeit wird angezeigt
|
||||||
386
daten/RAG/diagnose.md
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
<!-- titel -->
|
||||||
|
Diagnose
|
||||||
|
----------------------------------------
|
||||||
|
<!-- art -->
|
||||||
|
kern
|
||||||
|
----------------------------------------
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht Betreibern die Überprüfung von Systemzustand, Konfiguration und Datenqualität ohne produktive Änderungen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- regeln -->
|
||||||
|
- Alle Diagnosekommandos bieten --json-Ausgabe
|
||||||
|
- Exit-Code 0 spiegelt Status OK wider
|
||||||
|
- Exit-Code 1 zeigt Fehler oder Befunde an
|
||||||
|
- Diagnoseoperationen verändern keine Daten
|
||||||
|
- Kein Diagnosebefehl ruft LLM während der Prüfung auf
|
||||||
|
- MAX_LINE_BYTES = 2097152
|
||||||
|
- limit-Option: Standardwert 10
|
||||||
|
- Diagnostic-only-Hinweis im Footer
|
||||||
|
- Patchline-Pruefung bei Architecture Release Lock
|
||||||
|
- Genau ein AgentRunner-Boundary-Call sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Abdeckung prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob gespeicherte Ingest-Facts eine Anfrage abdecken
|
||||||
|
<!-- regeln -->
|
||||||
|
- Query muss nicht leer sein
|
||||||
|
- index.ndjson muss existieren
|
||||||
|
- Treffer nach State-Rang und Score sortiert
|
||||||
|
- json gibt maschinenlesbare Daten zurück
|
||||||
|
<!-- testen -->
|
||||||
|
- Command mit Query ausführen
|
||||||
|
- Diagnoseausgabe mit Abdeckungsdaten prüfen
|
||||||
|
- Command mit --json ausführen
|
||||||
|
- JSON-Struktur mit rows und summary prüfen
|
||||||
|
- Command mit --min-score und --limit ausführen
|
||||||
|
- Gefilterte Treffer nach Score geordnet
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Abrufgrenzen prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob Retrieval-Architekturgrenzen eingehalten werden
|
||||||
|
<!-- regeln -->
|
||||||
|
- Required-Dateien sind vorhanden
|
||||||
|
- AgentRunner besitzt Retrieval-Orchestrierung
|
||||||
|
- Genau ein Boundary-Call in AgentRunner
|
||||||
|
- Boundary-Service bleibt dünn
|
||||||
|
- Flow bleibt trace-only
|
||||||
|
- ReadModel bleibt beschränkt
|
||||||
|
- PostCallFocus erweitert keinen Constructor
|
||||||
|
- RetrieverInterface bleibt minimal
|
||||||
|
- HybridRetriever bleibt in Retrieval-Layer
|
||||||
|
- Dokumentation sichert Architecture Release Lock
|
||||||
|
<!-- testen -->
|
||||||
|
- Command ausführen mit --json
|
||||||
|
- Ausgabe zeigt Status OK
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Bereitschaftsbericht ausgeben
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft Bereitschaft von Deployment und Scheduler und zeigt Ergebnisse an
|
||||||
|
<!-- regeln -->
|
||||||
|
- json-Option gibt maschinenlesbares JSON aus
|
||||||
|
- Standardausgabe zeigt DefinitionList und Tabelle
|
||||||
|
- Exit-Code 0 bei Status OK
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl mit --json ausführen
|
||||||
|
- JSON-Ausgabe validieren
|
||||||
|
- Befehl ohne Option ausführen
|
||||||
|
- Tabelle mit Status OK prüfen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Datenqualität prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Qualität von Text-Chunks im Original und nach Markdown-Umsetzung auf Datenverluste
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest ausschließlich ohne Index-Schreibzugriff
|
||||||
|
- Kein Ranking und keine Antwortlogik
|
||||||
|
- Argument document-id ist erforderlich
|
||||||
|
- Option version wählt eine bestimmte Dokumentversion
|
||||||
|
- Option json liefert maschinenlesbare Ausgabe
|
||||||
|
<!-- testen -->
|
||||||
|
- php bin/console mto:agent:ingest:chunk-quality {uuid}
|
||||||
|
- Ausgabe zeigt Titel und ausgewählte Version
|
||||||
|
- Abschnitt Original zeigt Chunk-Profil
|
||||||
|
- Abschnitt Markdown zeigt Chunk-Profil
|
||||||
|
- Abschnitt Vergleich zeigt Kennzahlen und Delta
|
||||||
|
- Abschnitt Interpretation zeigt strukturierte Hinweise
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Diagnose anzeigen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht dem Administrator die MCP-Anbindungen und Wissensraum-Inhalte zu prüfen und zu verwalten
|
||||||
|
<!-- regeln -->
|
||||||
|
- Nur Super-Administratoren haben Zugriff
|
||||||
|
- Vier Ansichten: Übersicht, Anbindung, Wissensräume, System
|
||||||
|
- Vorgangsvarianten werden als Flash-Meldungen zurückgemeldet
|
||||||
|
- Formularaktionen erfordern gültiges CSRF-Token
|
||||||
|
- Transienter Zustand wird über POST-GET-Redirect erhalten
|
||||||
|
<!-- testen -->
|
||||||
|
- Admin-MCP-Seite aufrufen
|
||||||
|
- Route antwortet mit Status 200
|
||||||
|
- Übersicht der Anbindungen sichtbar
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Genre-Hotspots prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Entdeckt genrespezifische Begriffe im Kerncode, die das Sprachmodell beeinflussen können
|
||||||
|
<!-- regeln -->
|
||||||
|
- Ergebnis ist schreibgeschützt
|
||||||
|
- json gibt maschinenlesbare Daten aus
|
||||||
|
- strict gibt Nicht-Null-Exit-Code bei Befunden aus
|
||||||
|
- details zeigt alle Ergebniszeilen
|
||||||
|
<!-- testen -->
|
||||||
|
- Terminal öffnen
|
||||||
|
- Befehl ohne Parameter ausführen
|
||||||
|
- Zusammenfassung mit Befunden erscheint
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Guard-Mechanismus prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Guard-Implementierung ohne Seiteneffekte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest nur lokale Dateien
|
||||||
|
- Keine Runtime-Aufrufe
|
||||||
|
- Prüft 8 Testfälle
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl ausführen: mto:agent:proofing:unsupported-tool-term-guard-self-test
|
||||||
|
- Status OK in JSON-Ausgabe prüfen
|
||||||
|
- Alle 8 Testfälle bestanden
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Hygieneregeln prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob Batch-Hygieneregeln korrekt implementiert und nicht in die Antwortlaufzeit verdrahtet sind
|
||||||
|
<!-- regeln -->
|
||||||
|
- source_noise_cleaner_entfernt_PDF_Restzeichen
|
||||||
|
- validator_entfernt_QUELLSEGMENT_Echo
|
||||||
|
- directory_cleanup_entfernt_leere_Admin-Upload_Ordner
|
||||||
|
- directory_cleanup_verschiebt_Legacy_failed_Eintraege
|
||||||
|
- batch_reports_sind_loeschbar_mit_CSRF_Schutz
|
||||||
|
- document_delete_bereinigt_physische_Dateien
|
||||||
|
- failed_skipped_resolvt_ausserhalb_inbox
|
||||||
|
- keine_Hygiene_Dienste_in_Antwortlaufzeit_verdrahtet
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl `mto:agent:knowledge:batch-hygiene-self-test` ausfuehren
|
||||||
|
- JSON-Ausgabe pruefen
|
||||||
|
- Alle Faelle Status OK
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Indizierungszustand prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft automatisch, ob der verzögerte Indizierungsvertrag in allen Dateien korrekt umgesetzt ist
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest Controller, Services, Repositories und Templates ein
|
||||||
|
- Prüft 19 Vertragsfälle zum verzögerten Indizierungsworkflow
|
||||||
|
- Gibt Zusammenfassung oder strukturiertes JSON aus
|
||||||
|
- Gibt Erfolgscode bei allen bestandenen Fällen zurück
|
||||||
|
<!-- testen -->
|
||||||
|
- Befehl mit --json ausführen
|
||||||
|
- Ausgabe auf "OK" oder "FAILED" prüfen
|
||||||
|
- FEHLGESCHLAGEN: Case entfernt ungültigen startJob-Aufruf in activateVersion
|
||||||
|
- FEHLGESCHLAGEN: Case entfernt verzögertes setCurrentVersion in documentService
|
||||||
|
- Alle Cases bestanden: Rückgabewert Command::SUCCESS
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfiguration prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob alle RetrieX-Einstellungen gültig sind und zeigt Fehler oder Warnungen an
|
||||||
|
<!-- regeln -->
|
||||||
|
- mto:agent:config:validate als Konsolenbefehl verfügbar
|
||||||
|
- Gibt Fehler und Warnungen aus
|
||||||
|
- json gibt maschinenlesbares Ergebnis aus
|
||||||
|
- Rückgabewert 0 bei gültiger Konfiguration
|
||||||
|
- Rückgabewert 1 bei ungültiger Konfiguration
|
||||||
|
- Regression Guardrails werden geprüft
|
||||||
|
<!-- testen -->
|
||||||
|
- php bin/console mto:agent:config:validate ausführen
|
||||||
|
- Ergebnis auf Fehler und Warnungen prüfen
|
||||||
|
- Exitcode prüfen
|
||||||
|
- Konfiguration gültig: Exitcode 0
|
||||||
|
- php bin/console mto:agent:config:validate --json ausführen
|
||||||
|
- JSON-Ausgabe enthält status, errors, warnings
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Provenienz prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob Provenienzdiagnostik ohne Ranking- oder Antwortwirkung funktioniert
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest Ingest-Service, Admin-Inspektor, Template und Styles
|
||||||
|
- Prüft Provenienz-Metadaten in NDJSON
|
||||||
|
- Prüft Diagnostik-Metadaten in NDJSON
|
||||||
|
- Prüft Sidecar-Lesen ohne LLM-Aufruf
|
||||||
|
- Prüft Admin-Ansicht mit Provenienz und Heading-Pfad
|
||||||
|
- Prüft Read-only-Label in UI
|
||||||
|
- Prüft Kein Antwort-Runtime-Einfluss
|
||||||
|
- Prüft Genre-Neutralität der Diagnostik
|
||||||
|
<!-- testen -->
|
||||||
|
- mto agent ingest markdown-derived-chunk-diagnostics-self-test ausführen
|
||||||
|
- Kommando endet erfolgreich
|
||||||
|
- Ausgabe zeigt status OK
|
||||||
|
- Alle 8 Cases zeigen status OK
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Retrieval-Pipeline testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Ermöglicht die Überprüfung der Retrieval-Pipeline mit detailliertem Debug-Output
|
||||||
|
<!-- regeln -->
|
||||||
|
- Prompt als Argument erforderlich
|
||||||
|
- Leerer Prompt erzeugt Fehler
|
||||||
|
- json gibt Rohdaten ohne Formatierung aus
|
||||||
|
- show-text zeigt ungekürzte Chunk-Texte
|
||||||
|
<!-- testen -->
|
||||||
|
- mto:agent:retrieval:test mit Prompt ausführen
|
||||||
|
- Ausgabe auf Pipeline-Summary prüfen
|
||||||
|
- mto:agent:retrieval:test --json ausführen
|
||||||
|
- JSON-Struktur mit duration_ms und results vorhanden
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Retrieval-Qualität testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob die Konfiguration passende Inhalte findet
|
||||||
|
<!-- regeln -->
|
||||||
|
- Erfordert ROLE_KNOWLEDGE_ADMIN
|
||||||
|
- GET zeigt leere Maske
|
||||||
|
- POST führt TestRetrieval aus
|
||||||
|
<!-- testen -->
|
||||||
|
- Seite test-retrieval öffnen
|
||||||
|
- Prompt eingeben und absenden
|
||||||
|
- Ergebnisse werden angezeigt
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Segmentierung prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft ob Segmentierung korrekt ausgeführt wird
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest alle relevanten Dateien ein
|
||||||
|
- Prüft required_files_present
|
||||||
|
- Prüft execution_uses_complete_segment_plan
|
||||||
|
- Prüft segment_prompt_is_fragment_only
|
||||||
|
- Prüft llm_called_per_segment_with_long_transport
|
||||||
|
- Prüft sidecar_records_segment_processing_without_raw_payloads
|
||||||
|
- Prüft generated_version_remains_inactive
|
||||||
|
- Prüft no_index_or_answer_runtime_wiring
|
||||||
|
- Prüft no_genre_runtime_gate_markers
|
||||||
|
- Report enthält Status und Zusammenfassung
|
||||||
|
<!-- testen -->
|
||||||
|
- mto:agent:ingest:segmented-markdown-normalization-self-test ausführen
|
||||||
|
- Status OK oder FAIL prüfen
|
||||||
|
- Testläufe als Zusammenfassung sehen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Shop-Suche testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Shop-Suche mit detaillierter Ausgabe von Suchvorschau, Ergebnissen und optionalem Repair
|
||||||
|
<!-- regeln -->
|
||||||
|
- Standard-Query: 'zeige mir produkte unter 5000 euro'
|
||||||
|
- repair aktiviert zusätzliche Search-Repair-Suche
|
||||||
|
- json gibt maschinenlesbare Ausgabe aus
|
||||||
|
- Primärsuche wird immer ausgeführt
|
||||||
|
<!-- testen -->
|
||||||
|
- mto:agent:test:shop-search ausführen
|
||||||
|
- Suchvorschau und Primärergebnisse prüfen
|
||||||
|
- mto:agent:test:shop-search laptop --repair ausführen
|
||||||
|
- Repair-Auswertung mit Ergebnissen prüfen
|
||||||
|
- mto:agent:test:shop-search laptop --json ausführen
|
||||||
|
- JSON-Ausgabe mit query, preview und repair prüfen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Sicherheitsstatus prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft den Security- und Datenschutz-Bereitschaftsgrad ohne Seiteneffekte
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kommandoname: mto:agent:enterprise:security-data-protection-readiness
|
||||||
|
- json gibt maschinenlesbares JSON aus
|
||||||
|
- Erfolg bei Status OK
|
||||||
|
- Fehler bei Status nicht OK
|
||||||
|
<!-- testen -->
|
||||||
|
- php bin/console mto:agent:enterprise:security-data-protection-readiness
|
||||||
|
- Tabelle mit Cases wird ausgegeben
|
||||||
|
- php bin/console mto:agent:enterprise:security-data-protection-readiness --json
|
||||||
|
- JSON-Objekt wird ausgegeben
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Stapel-Upload verifizieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Integrität des Batch-Upload-Systems ohne Dokumente zu importieren
|
||||||
|
<!-- regeln -->
|
||||||
|
- Trockenlauf ohne Dokumentenimport
|
||||||
|
- Ruft kein LLM auf
|
||||||
|
- Ändert keine Daten im Wissensspeicher
|
||||||
|
- Gibt maschinenlesbares JSON aus
|
||||||
|
- Prüft CSRF-Schutz, Dienstintegration und Fortschrittsanzeige
|
||||||
|
- Prüft PDF-Schutz, Speichergrenzen und Leerraum-Bereinigung
|
||||||
|
- Prüft CLI-Progress und keine Agent-Verschaltung
|
||||||
|
<!-- testen -->
|
||||||
|
- PHP-Befehl aus src Command KnowledgeBatchAdminUploadSelfTestCommand.php ausführen
|
||||||
|
- Alle 10 Cases in der Tabelle prüfen
|
||||||
|
- Jeder Case zeigt Status OK oder FAIL
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Systemzustand prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft NDJSON und FAISS-Konsistenz
|
||||||
|
<!-- regeln -->
|
||||||
|
- Gibt JSON aus
|
||||||
|
- OK-Status gibt SUCCESS zurück
|
||||||
|
- Fehler-Status gibt FAILURE zurück
|
||||||
|
<!-- testen -->
|
||||||
|
- php bin console mto:agent:vector:health
|
||||||
|
- Konsistente Indices: OK-Status in JSON
|
||||||
|
- Fehler: FAILURE und Fehlermeldung in JSON
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Tag-Index-Gesundheit prüfen
|
||||||
|
<!-- ziel -->
|
||||||
|
Prüft die Konsistenz zwischen Tag-Daten und FAISS-Index
|
||||||
|
<!-- regeln -->
|
||||||
|
- summary zeigt Zusammenfassung statt JSON
|
||||||
|
- Exitcode 0 bei Status OK oder OK_EMPTY
|
||||||
|
- Exitcode 1 bei allen anderen Status
|
||||||
|
- Prüft tags_ndjson_exists, vector_exists, meta_valid
|
||||||
|
<!-- testen -->
|
||||||
|
- bin console mto:agent:tag:health ausführen
|
||||||
|
- LMB auf --summary
|
||||||
|
- Zusammenfassung mit Status, Zählern und Gültigkeit
|
||||||
|
- Befehl mit Exitcode 0 beendet
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Tags neu indizieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Echtzeitaktualisierung des Neuindizierungsfortschritts im Browser
|
||||||
|
<!-- regeln -->
|
||||||
|
- Lädt alle 2 Sekunden neuen Status
|
||||||
|
- Sendet Ereignis nur bei Hash-Änderung
|
||||||
|
- Sendet Keepalive alle 10 Sekunden
|
||||||
|
- Endet bei Verbindungsabbruch
|
||||||
|
<!-- testen -->
|
||||||
|
- Stream-Endpunkt aufrufen
|
||||||
|
- MIME-Typ text event stream prüfen
|
||||||
|
- Antwortet mit Stream
|
||||||
|
- Event-Stream antwortet
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Verankerung testen
|
||||||
|
<!-- ziel -->
|
||||||
|
Diagnostiziert die Verankerung von Nachfolgedialogen in der Historie
|
||||||
|
<!-- regeln -->
|
||||||
|
- Kein Laufzeitverhalten geändert
|
||||||
|
- Report als Text oder JSON ausgebbar
|
||||||
|
- sample führt integrierten Smoke-Test aus
|
||||||
|
- Historie aus Datei, STDIN oder Option lesbar
|
||||||
|
<!-- testen -->
|
||||||
|
- Sample-Dry-Run ausführen
|
||||||
|
- Text-Report auf erkennbaren Anker prüfen
|
||||||
|
- Kernprodukt und Nachfolgedialog identifizieren
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Vergleichsgrenzen validieren
|
||||||
|
<!-- ziel -->
|
||||||
|
Prueft, ob alle Dateien und Muster fuer die Chunk-Qualitaetsvalidierung vorhanden sind
|
||||||
|
<!-- regeln -->
|
||||||
|
- Liest src Service Admin DocumentChunkQualityComparisonService php
|
||||||
|
- Liest src Command IngestChunkQualityCommand php
|
||||||
|
- Liest src Controller Admin DocumentController php
|
||||||
|
- Liest templates admin document show html twig
|
||||||
|
- Liest public assets styles base css
|
||||||
|
- Liest DocumentMarkdownSegmentationPlanService php
|
||||||
|
- Liest AgentRunner php und KnowledgeIngestService php
|
||||||
|
- Prueft Dateien auf vorhandene Strings mit str_contains
|
||||||
|
- Prueft Dateien auf abwesende Strings mit !str_contains
|
||||||
|
- Gibt FAIL zurueck sobald ein Fall fehlschlaegt
|
||||||
|
- x-Icon
|
||||||
|
<!-- testen -->
|
||||||
|
- php bin console mto agent ingest chunk-quality-self-test ausfuehren
|
||||||
|
- Rueckgabe auf vollstaendige Faelle pruefen
|
||||||
|
- Status OK bei allen gueltigen Implementierungen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Titelvorschläge-Selbsttest bereitstellen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Konfigurationsquellen prüfen
|
||||||
|
----------------------------------------
|
||||||
|
<!-- funktion -->
|
||||||
|
Batch-Markdown-Normalisierung testen
|
||||||