-
+
+
+
+
+
Prüfe Abschnitt…
+
+ {{ vorschlaege[i].fehler }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
.fokus-col { overflow-y: visible; }
.fokus-col.right { width: auto; }
}
+
+/* Block-Prüfen: Rechtsklick-Menü + Vorschlag unter dem Abschnitt */
+.md-block { border-radius: 6px; transition: background 0.15s; }
+.md-block:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
+
+.menu-overlay { position: fixed; inset: 0; z-index: 60; }
+.block-menu {
+ position: fixed;
+ min-width: 8rem;
+ padding: 0.25rem;
+ background: var(--panel);
+ border: 1px solid var(--border-strong);
+ border-radius: 8px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
+}
+.bm-item {
+ display: block;
+ width: 100%;
+ padding: 0.4rem 0.7rem;
+ text-align: left;
+ font-size: 0.9rem;
+ border: none;
+ border-radius: 5px;
+ background: transparent;
+ color: var(--text);
+ cursor: pointer;
+}
+.bm-item:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
+
+.block-vorschlag {
+ margin: 0.5rem 0 1rem;
+ padding: 0.75rem 1rem;
+ border: 1px solid var(--accent);
+ border-left: 3px solid var(--accent);
+ border-radius: 8px;
+ background: color-mix(in srgb, var(--accent) 6%, var(--panel));
+}
+.bv-status { font-size: 0.9rem; color: var(--text-soft, #888); }
+.bv-fehler { color: var(--danger, #dc2626); font-size: 0.88rem; margin: 0 0 0.5rem; }
+.bv-neu > :first-child { margin-top: 0; }
+.bv-neu > :last-child { margin-bottom: 0; }
+.bv-aktionen { display: flex; gap: 0.4rem; margin-top: 0.6rem; }
+.bv-btn {
+ min-width: 2rem; height: 2rem; padding: 0 0.5rem;
+ font-size: 0.95rem;
+ border: 1px solid var(--border-strong);
+ border-radius: 6px;
+ background: var(--panel);
+ color: var(--text);
+ cursor: pointer;
+}
+.bv-btn:hover { border-color: var(--accent); }
+.bv-btn.aktiv { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--panel)); }
+.bv-btn.ja { border-color: var(--success-border); color: var(--success); }
+.bv-edit { display: flex; gap: 0.4rem; margin-top: 0.5rem; }
+.bv-input {
+ flex: 1;
+ padding: 0.4rem 0.6rem;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ background: var(--panel);
+ color: var(--text);
+ font-size: 0.9rem;
+}
diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue
index 90b34e3..772ca44 100644
--- a/frontend/src/components/BausteinPanel.vue
+++ b/frontend/src/components/BausteinPanel.vue
@@ -24,7 +24,7 @@ const props = defineProps({
const emit = defineEmits(['statusChanged', 'openFokus', 'setAnsicht'])
// Lernstufen relativ zum cap (stufen.js): grün/blau/lila/gold @ 20/40/60/100 %.
-// Prüfung IMMER zufällig aus 5 Formen (mit Muster); cap = 2×relevante Subbausteine.
+// Prüfung IMMER zufällig aus 5 Formen (mit Muster); cap = 4×relevante Subbausteine.
const MODI = ['quiz_leicht', 'quiz_schwer', 'lueck_leicht', 'lueck_schwer', 'erklaeren']
const st = computed(() => props.status || { gute_antworten: 0, streak: 0, cap: props.cap })
const score = computed(() => st.value.gute_antworten || 0)
diff --git a/frontend/src/components/TopicDetail.vue b/frontend/src/components/TopicDetail.vue
index 9cf553e..6cdba7d 100644
--- a/frontend/src/components/TopicDetail.vue
+++ b/frontend/src/components/TopicDetail.vue
@@ -95,6 +95,19 @@ function onBausteinStatus(baustein, status) {
if (neu !== alt) emit('progressChanged') // Stufenwechsel → Locks/Stats neu laden
}
+// Section nach Prüfen/Beheben/Neu-Schreiben im Content ersetzen + Markdown-Cache leeren.
+function onSectionUpdated({ title, kompakt, md }) {
+ if (!content.value) return
+ for (const ch of content.value.chapters) {
+ for (const s of ch.sections) {
+ if (s.title !== title) continue
+ s.md = md
+ s.kompakt = kompakt
+ for (const k of [...mdCache.keys()]) if (k.endsWith(`:${s.num}`)) mdCache.delete(k)
+ }
+ }
+}
+
// --- Vollbild-Fokus: ein Baustein groß, Guide links + Prüfung rechts ---
const fokusIndex = ref(null) // Index in der flachen Baustein-Liste; null = zu
const fokusTab = ref('pruefung')
@@ -137,7 +150,7 @@ const fortschritt = computed(() => {
return z
})
-// cap je Baustein = 2×relevante Subbausteine (vom Backend in lernstand[title].cap geliefert).
+// cap je Baustein = 4×relevante Subbausteine (vom Backend in lernstand[title].cap geliefert).
function capVon(title) {
return lernstand.value[title]?.cap || 10
}
@@ -287,6 +300,7 @@ function extractContext() {
v-if="fokusBaustein"
:baustein="fokusBaustein"
:topic="previewGuide.topic"
+ :guide-id="previewGuide.id"
:provider="provider"
:fortschritt="fortschritt"
:status="lernstand[fokusBaustein.title]"
@@ -301,6 +315,7 @@ function extractContext() {
@set-ansicht="$emit('setAnsicht', $event)"
@status-changed="(st) => onBausteinStatus(st.baustein, st)"
@open-sidebar="$emit('openSidebar')"
+ @section-updated="onSectionUpdated"
/>
diff --git a/frontend/src/markdown.js b/frontend/src/markdown.js
index 2d3fc77..24de19b 100644
--- a/frontend/src/markdown.js
+++ b/frontend/src/markdown.js
@@ -71,3 +71,12 @@ export function renderMarkdown(text) {
export function renderMarkdownInline(text) {
return DOMPurify.sanitize(marked.parseInline(text || ''))
}
+
+// Markdown in Top-Level-Blöcke zerlegen: je Block { raw (exakte Quelle), html }.
+// raw ist verlustfrei (tokens.map(raw).join('') === Original) → Block-genaues Ersetzen.
+export function renderBloecke(text) {
+ const tokens = marked.lexer(text || '')
+ return tokens
+ .filter((t) => t.type !== 'space' && (t.raw || '').trim())
+ .map((t) => ({ raw: t.raw, html: DOMPurify.sanitize(marked.parser([t])) }))
+}
diff --git a/frontend/src/stufen.js b/frontend/src/stufen.js
index ecab6ec..bc71e8c 100644
--- a/frontend/src/stufen.js
+++ b/frontend/src/stufen.js
@@ -1,5 +1,5 @@
// Lernstufen je Baustein — identisch zum Backend (lernen.py STUFEN).
-// Floor in % des cap (cap = clamp(2×relevante Subbausteine, 10, 50)).
+// Floor in % des cap (cap = clamp(4×relevante Subbausteine, 10, 50)).
// grün=Anfänger 20% · blau=Fortgeschritten 40% · lila=Experte 60% · gold=Meister 100%.
export const STUFEN = [
{ key: 'anfaenger', label: 'Anfänger', kurz: '✓', floor: 0.2, farbe: 'var(--stufe-anfaenger)' },
diff --git a/templates/Format/Section.md b/templates/Format/Section.md
index 48141aa..c0282dc 100644
--- a/templates/Format/Section.md
+++ b/templates/Format/Section.md
@@ -35,6 +35,12 @@ Markdown im Section-Body: erklärende Absätze in normalem Text, Aufzählungen a
Mathematik IMMER als LaTeX schreiben: inline zwischen `$…$` (z. B. `$\Sigma^*$`, `$L \subseteq U$`, `$k = 3$`), abgesetzte Formeln zwischen `$$…$$`. KEINE Unicode-Sonderzeichen als Mathe-Ersatz (nicht `x₁`, `¬`, `∨`, `≤` — stattdessen `$x_1$`, `$\neg$`, `$\lor$`, `$\le$`) und keine nackten Formeln ohne `$`. Außerhalb von Mathe normaler Text.
+Formeln brechen NICHT automatisch um — lange Mathe läuft sonst über den Rand. Darum:
+- **Inline `$…$` nur für KURZE Symbole/Terme** — einzelne Variablen, Mengen, kurze Relationen (`$\Sigma^*$`, `$k = 3$`, `$x \notin L$`). NIE einen ganzen Ausdruck mit mehreren Teilen oder Sätzen inline (auch nicht in einem Listenpunkt).
+- **Lange oder mehrteilige Formeln IMMER abgesetzt zwischen `$$…$$` auf eigener Zeile** — besonders Mengen-/Set-Builder-Definitionen (`\{ … \mid … \}`). Nicht inline, nicht in eine Aufzählung quetschen.
+- **KEINE Prosa in `\text{…}`.** Bedingungen und Erklärungen als normalen deutschen Text NEBEN oder UNTER die Formel, nicht in sie hinein. Also nicht `$$L = \text{CLIQUE} = \{ u\#v \mid u \text{ kodiert die Adjazenzmatrix …}\}$$`, sondern die Formel knapp (`$$L = \{\, u\#v \mid \dots \,\}$$`) und die Bedeutung von `u`, `v` im Fließtext erklären.
+- Lange Definitionen oder Gleichungsketten mit `$$\begin{aligned} … \\ … \end{aligned}$$` über mehrere Zeilen umbrechen.
+
Beispiel einer fertigen Section (Code-Thema, nur der Body):
Arrays lösen ein simples Problem: Du willst viele Werte unter einem Namen halten, statt für jeden eine eigene Variable. In PHP gibt es zwei Sorten. Indizierte Arrays nummerieren die Werte durch (`[0 => 'a']`). Assoziative Arrays geben jedem Wert einen eigenen Schlüssel (`['key' => 'wert']`) — praktisch, wenn die Position egal ist, der Name aber zählt. Intern sind beide dasselbe: geordnete Hashmaps.
diff --git a/templates/Prompt/Block-Pruefen.md b/templates/Prompt/Block-Pruefen.md
new file mode 100644
index 0000000..adcf212
--- /dev/null
+++ b/templates/Prompt/Block-Pruefen.md
@@ -0,0 +1,25 @@
+Prüfe EINEN Abschnitt (Markdown-Block) einer Section eines Lern-Guides zum Thema "{topic}" gegen die Guide-Regeln und gib ihn KORRIGIERT zurück. Zielgruppe: Anfänger ohne Vorwissen.
+
+{facts}
+
+SECTION-SPEZIFIKATION (Soll-Zustand):
+{spec}
+
+SUBBAUSTEINE der Section (Kontext):
+{subbausteine}
+
+GANZE SECTION (nur Kontext — NICHT mit ausgeben):
+{kontext}
+
+ZU PRÜFENDER ABSCHNITT:
+{block}
+
+{hinweis}
+
+Prüfe den Abschnitt auf echte Mängel und behebe sie — was in Ordnung ist, bleibt unverändert:
+- Fachliche Fehler, falsche oder erfundene Fakten/Werte.
+- Verständlichkeit für Anfänger; Lesbarkeit (Sätze über ~25 Wörter, Schachtelsätze, Textwand, eine Aufzählung als Fließtext-Satz, unerklärte Fachbegriffe).
+- Beispiel passend und korrekt, Format laut Spezifikation.
+- Formeln laut Spezifikation (lange/mehrteilige als abgesetzte `$$…$$`, keine Prosa in `\text{{…}}`); sauberes Markdown.
+
+Gib NUR den korrigierten Abschnitt als Markdown zurück — kein weiterer Text, keine Erklärung, keine Marker. Ist nichts zu ändern, gib den Abschnitt unverändert zurück.