Darstellung: Inline-Code-Chips fuer Dark Mode, 58 Seiten auf .mdx (Komponenten rendern jetzt)

Befund 1 (Nutzer): Inline-Code war im Dark Mode kaum lesbar.
- src/styles/custom.css: eigene Chip-Farben (--tibi-inline-*), Rahmen, nowrap in der ersten Tabellenspalte
- gemessen: Text/Chip 10,2:1 dunkel, 12,4:1 hell

Befund 2 (beim Nachmessen gefunden): 58 Seiten waren .md - Starlight-Komponenten wurden NICHT gerendert
(rohe <Aside>/<Steps>-Tags, import-Zeile als sichtbarer Text auf 57 Seiten).
- alle 58 Seiten auf .mdx umgestellt und MDX-konform gemacht (Imports, Block-Tags in eigenen Zeilen,
  HTML-Kommentare -> {/* */}, geschweifte Klammern escaped)
- nachher: 428 Asides, 87 Steps, 158 Tab-Gruppen gerendert, 0 geleakte Imports
- fehlender Import in reference/hooks-daten ergaenzt

Regressionsschutz: audit-docs.mjs prueft jetzt Komponente-ohne-Import, .md-mit-Komponente,
geleakte Imports und rohe Tags im gebauten dist/. AGENTS.md 3 dokumentiert die .mdx-Regel.

Nebenfund: de.json mit ASCII-Umschreibungen -> Issue tibi-admin-nova #6
Build: 71 Seiten gruen, Audit 7 legitime Platzhalter-Marker
This commit is contained in:
apairon committed 2026-10-17 13:54:52 +00:00
1 parent 06c5f0d8ea
commit dcdb6ee637
64 files changed
+246 -104

No files matched your search

+9
View File
@@ -38,6 +38,12 @@ Jede Seite nennt ihre Quelle-der-Wahrheit (Code zuerst), hat einen Stempel am En
## 3. Neue Seite anlegen ## 3. Neue Seite anlegen
- Datei `src/content/docs/<bereich>/<slug>.md` mit Frontmatter `title` + `description`. - Datei `src/content/docs/<bereich>/<slug>.md` mit Frontmatter `title` + `description`.
- **Sobald die Seite eine Starlight-Komponente nutzt (`<Aside>`, `<Steps>`, `<Tabs>`, `<Card>`, `<LinkCard>`, `<Badge>`, `<FileTree>`), muss die Datei `.mdx` heißen.** In reinem Markdown (`.md`) werden Komponenten-Tags **nicht** gerendert: sie landen als unbekannte HTML-Tags im Ergebnis, und die `import`-Zeile erscheint als sichtbarer Text auf der Seite (`scripts/audit-docs.mjs` prüft das gegen `dist/`).
- MDX-Regeln, die die Konvertierung erfüllen muss:
- `import …` steht als erste Zeile **vor** dem Inhalt (nach dem Frontmatter).
- Block-Komponenten: öffnendes Tag in eigener Zeile, Inhalt, schließendes Tag in eigener Zeile (nicht `Text</Aside>` in einer Zeile).
- Kommentar-Hinweise als `{/* … */}` schreiben, **nicht** als `<!-- … -->`.
- Geschweifte Klammern im Fließtext escapen (`\{count\}`) — in Code-Spans und Codeblöcken nicht nötig.
- Sidebar: jede Gruppe explizit in `astro.config.mjs` eintragen (beim Neuaufbau pro Bereich ergänzen; `autogenerate` erst nutzen, sobald der Ordner gepflegt befüllt ist). - Sidebar: jede Gruppe explizit in `astro.config.mjs` eintragen (beim Neuaufbau pro Bereich ergänzen; `autogenerate` erst nutzen, sobald der Ordner gepflegt befüllt ist).
- Bilder nach `src/assets/`, max. 200 KB, deutsche Alt-Texte. - Bilder nach `src/assets/`, max. 200 KB, deutsche Alt-Texte.
- Screenshots: nur Mock-Projekt `corporate` (nie `t1`), Ablauf unter `plans/t-shots-arbeitsauftrag.md`. - Screenshots: nur Mock-Projekt `corporate` (nie `t1`), Ablauf unter `plans/t-shots-arbeitsauftrag.md`.
@@ -57,6 +63,9 @@ npm run build # muss grün durchlaufen (prüft interne Links NIC
- `scripts/audit-docs.mjs` meldet tote Links/Anker, kaputte Tabellen, fehlende Alt-Texte und offene - `scripts/audit-docs.mjs` meldet tote Links/Anker, kaputte Tabellen, fehlende Alt-Texte und offene
Platzhalter-Marker. Ergebnis **muss** „0 tote Links, 0 kaputte Tabellen" sein; verbleibende Marker Platzhalter-Marker. Ergebnis **muss** „0 tote Links, 0 kaputte Tabellen" sein; verbleibende Marker
sind zu begründen. sind zu begründen.
- Zusätzlich prüft das Skript das **gebaute** `dist/`: geleakte `import`-Zeilen, rohe `<Aside>`/`<Steps>`-Tags
und fehlende Komponenten-Klassen. Erst `npm run build`, dann `node scripts/audit-docs.mjs` — so fällt auf,
wenn eine Seite als `.md` statt `.mdx` angelegt wurde.
- `scripts/gen-schema-matrix.mjs` und `reference/schema-properties.md` sind ein Paar: Seite nie von Hand - `scripts/gen-schema-matrix.mjs` und `reference/schema-properties.md` sind ein Paar: Seite nie von Hand
editieren, Schemas nie in der Doku nachpflegen — nur `npm run gen:schemas` laufen lassen. editieren, Schemas nie in der Doku nachpflegen — nur `npm run gen:schemas` laufen lassen.
- `scripts/sync-server-docs.mjs` ist **stillgelegt** (der Server-Sync erzeugte verwaiste Seiten ohne - `scripts/sync-server-docs.mjs` ist **stillgelegt** (der Server-Sync erzeugte verwaiste Seiten ohne
+50 -50
View File
@@ -1,5 +1,5 @@
{ {
"generatedAt": "2026-10-17T13:19:19.983Z", "generatedAt": "2026-10-17T13:54:38.079Z",
"stats": { "stats": {
"files": 70, "files": 70,
"byDir": { "byDir": {
@@ -27,60 +27,60 @@
"emptyFenceLang": 0, "emptyFenceLang": 0,
"proseWalls": [], "proseWalls": [],
"asciiUmlauts": [ "asciiUmlauts": [
"src/content/docs/admin/aktionen.md", "src/content/docs/admin/aktionen.mdx",
"src/content/docs/anwender/aktionen.md", "src/content/docs/anwender/aktionen.mdx",
"src/content/docs/projekt/felder-widgets.md", "src/content/docs/projekt/felder-widgets.mdx",
"src/content/docs/projekt/mcp-nutzen.md", "src/content/docs/projekt/mcp-nutzen.mdx",
"src/content/docs/projekt/navigation-i18n.md", "src/content/docs/projekt/navigation-i18n.mdx",
"src/content/docs/projekt/ssr-website.md", "src/content/docs/projekt/ssr-website.mdx",
"src/content/docs/reference/schema-properties.md" "src/content/docs/reference/schema-properties.md"
], ],
"hollowMarkers": [ "hollowMarkers": [
"src/content/docs/admin/exporte.md:37 Platzhalter", "src/content/docs/admin/exporte.mdx:37 Platzhalter",
"src/content/docs/anwender/seiten-bauen.md:45 Platzhalter", "src/content/docs/anwender/seiten-bauen.mdx:45 Platzhalter",
"src/content/docs/einstieg/neues-projekt.md:26 Platzhalter", "src/content/docs/einstieg/neues-projekt.mdx:26 Platzhalter",
"src/content/docs/einstieg/schnellstart.md:48 noch nicht", "src/content/docs/einstieg/schnellstart.mdx:48 noch nicht",
"src/content/docs/einstieg/schnellstart.md:21 Platzhalter", "src/content/docs/einstieg/schnellstart.mdx:21 Platzhalter",
"src/content/docs/einstieg/was-ist-tibi.md:11 Platzhalter", "src/content/docs/einstieg/was-ist-tibi.mdx:11 Platzhalter",
"src/content/docs/projekt/deploy.md:26 Platzhalter" "src/content/docs/projekt/deploy.mdx:26 Platzhalter"
], ],
"t1Refs": [ "t1Refs": [
"src/content/docs/adr/0003-selbstlaeufer.md" "src/content/docs/adr/0003-selbstlaeufer.md"
], ],
"tibiAdminRefs": [ "tibiAdminRefs": [
"src/content/docs/admin/aktionen.md (2×)", "src/content/docs/admin/aktionen.mdx (2×)",
"src/content/docs/admin/ansichten-listen.md (2×)", "src/content/docs/admin/ansichten-listen.mdx (2×)",
"src/content/docs/admin/benutzer-rechte.md (7×)", "src/content/docs/admin/benutzer-rechte.mdx (7×)",
"src/content/docs/admin/customizing.md (2×)", "src/content/docs/admin/customizing.mdx (2×)",
"src/content/docs/admin/eintrags-editor.md (2×)", "src/content/docs/admin/eintrags-editor.mdx (2×)",
"src/content/docs/admin/exporte.md (2×)", "src/content/docs/admin/exporte.mdx (2×)",
"src/content/docs/admin/ki-funktionen.md (3×)", "src/content/docs/admin/ki-funktionen.mdx (3×)",
"src/content/docs/admin/konzept.md (4×)", "src/content/docs/admin/konzept.mdx (4×)",
"src/content/docs/admin/medien.md (2×)", "src/content/docs/admin/medien.mdx (2×)",
"src/content/docs/admin/setup.md (5×)", "src/content/docs/admin/setup.mdx (5×)",
"src/content/docs/adr/0001-starlight.md (1×)", "src/content/docs/adr/0001-starlight.md (1×)",
"src/content/docs/adr/0002-code-schlaegt-doku.md (2×)", "src/content/docs/adr/0002-code-schlaegt-doku.md (2×)",
"src/content/docs/adr/0003-selbstlaeufer.md (1×)", "src/content/docs/adr/0003-selbstlaeufer.md (1×)",
"src/content/docs/anwender/anmelden.md (5×)", "src/content/docs/anwender/anmelden.mdx (5×)",
"src/content/docs/anwender/benutzer-teams.md (1×)", "src/content/docs/anwender/benutzer-teams.mdx (1×)",
"src/content/docs/anwender/exportieren.md (3×)", "src/content/docs/anwender/exportieren.mdx (3×)",
"src/content/docs/anwender/ki-nutzen.md (1×)", "src/content/docs/anwender/ki-nutzen.mdx (1×)",
"src/content/docs/anwender/lesen-suchen-filtern.md (1×)", "src/content/docs/anwender/lesen-suchen-filtern.mdx (1×)",
"src/content/docs/anwender/medien.md (1×)", "src/content/docs/anwender/medien.mdx (1×)",
"src/content/docs/anwender/orientierung.md (2×)", "src/content/docs/anwender/orientierung.mdx (2×)",
"src/content/docs/anwender/seiten-bauen.md (1×)", "src/content/docs/anwender/seiten-bauen.mdx (1×)",
"src/content/docs/anwender/sichern.md (1×)", "src/content/docs/anwender/sichern.mdx (1×)",
"src/content/docs/einstieg/begriffe.md (4×)", "src/content/docs/einstieg/begriffe.md (4×)",
"src/content/docs/einstieg/neues-projekt.md (1×)", "src/content/docs/einstieg/neues-projekt.mdx (1×)",
"src/content/docs/einstieg/schnellstart.md (1×)", "src/content/docs/einstieg/schnellstart.mdx (1×)",
"src/content/docs/einstieg/was-ist-tibi.md (3×)", "src/content/docs/einstieg/was-ist-tibi.mdx (3×)",
"src/content/docs/projekt/audit-backup.md (2×)", "src/content/docs/projekt/audit-backup.mdx (2×)",
"src/content/docs/projekt/jobs-actions.md (1×)", "src/content/docs/projekt/jobs-actions.mdx (1×)",
"src/content/docs/projekt/navigation-i18n.md (1×)", "src/content/docs/projekt/navigation-i18n.mdx (1×)",
"src/content/docs/projekt/pagebuilder-dev.md (1×)", "src/content/docs/projekt/pagebuilder-dev.mdx (1×)",
"src/content/docs/projekt/sammlungen-anlegen.md (1×)", "src/content/docs/projekt/sammlungen-anlegen.mdx (1×)",
"src/content/docs/projekt/suche.md (2×)", "src/content/docs/projekt/suche.mdx (2×)",
"src/content/docs/reference/tastatur-kurzbefehle.md (2×)" "src/content/docs/reference/tastatur-kurzbefehle.mdx (2×)"
], ],
"fm_hero": 1, "fm_hero": 1,
"fm_template": 1 "fm_template": 1
@@ -92,43 +92,43 @@
}, },
"findings": [ "findings": [
{ {
"file": "src/content/docs/admin/exporte.md", "file": "src/content/docs/admin/exporte.mdx",
"kind": "hollow", "kind": "hollow",
"msg": "Marker \"Platzhalter\": Stehen im Export-Dialog eigene Reiter **vor** dem Standard-Tab, hat das Projekt fertige Berichte in `collectio", "msg": "Marker \"Platzhalter\": Stehen im Export-Dialog eigene Reiter **vor** dem Standard-Tab, hat das Projekt fertige Berichte in `collectio",
"line": 37 "line": 37
}, },
{ {
"file": "src/content/docs/anwender/seiten-bauen.md", "file": "src/content/docs/anwender/seiten-bauen.mdx",
"kind": "hollow", "kind": "hollow",
"msg": "Marker \"Platzhalter\": Die kleine Vorschau benötigt nach der Bildauswahl einen Moment. Ein Platzhalterbild ist in dieser Phase normal", "msg": "Marker \"Platzhalter\": Die kleine Vorschau benötigt nach der Bildauswahl einen Moment. Ein Platzhalterbild ist in dieser Phase normal",
"line": 45 "line": 45
}, },
{ {
"file": "src/content/docs/einstieg/neues-projekt.md", "file": "src/content/docs/einstieg/neues-projekt.mdx",
"kind": "hollow", "kind": "hollow",
"msg": "Marker \"Platzhalter\": ## 2. Platzhalter und Starter-Reste", "msg": "Marker \"Platzhalter\": ## 2. Platzhalter und Starter-Reste",
"line": 26 "line": 26
}, },
{ {
"file": "src/content/docs/einstieg/schnellstart.md", "file": "src/content/docs/einstieg/schnellstart.mdx",
"kind": "hollow", "kind": "hollow",
"msg": "Marker \"noch nicht\": - `project not found` → Projektdateien liegen da, aber das Projekt ist im laufenden tibi-server noch nicht reg", "msg": "Marker \"noch nicht\": - `project not found` → Projektdateien liegen da, aber das Projekt ist im laufenden tibi-server noch nicht reg",
"line": 48 "line": 48
}, },
{ {
"file": "src/content/docs/einstieg/schnellstart.md", "file": "src/content/docs/einstieg/schnellstart.mdx",
"kind": "hollow", "kind": "hollow",
"msg": "Marker \"Platzhalter\": ## 2. Platzhalter ersetzen", "msg": "Marker \"Platzhalter\": ## 2. Platzhalter ersetzen",
"line": 21 "line": 21
}, },
{ {
"file": "src/content/docs/einstieg/was-ist-tibi.md", "file": "src/content/docs/einstieg/was-ist-tibi.mdx",
"kind": "hollow", "kind": "hollow",
"msg": "Marker \"Platzhalter\": - **Projekt-Website (aus `tibi-svelte-starter` geklont):** eigenständige Website auf Tibi-Basis — normale klei", "msg": "Marker \"Platzhalter\": - **Projekt-Website (aus `tibi-svelte-starter` geklont):** eigenständige Website auf Tibi-Basis — normale klei",
"line": 11 "line": 11
}, },
{ {
"file": "src/content/docs/projekt/deploy.md", "file": "src/content/docs/projekt/deploy.mdx",
"kind": "hollow", "kind": "hollow",
"msg": "Marker \"Platzhalter\": Im Starter stehen Platzhalter (`__PROJECT_NAME__`, `_CUSTOMER_ID_`, `LIVE_URL=https://www`).", "msg": "Marker \"Platzhalter\": Im Starter stehen Platzhalter (`__PROJECT_NAME__`, `_CUSTOMER_ID_`, `LIVE_URL=https://www`).",
"line": 26 "line": 26
+2 -1
View File
@@ -57,7 +57,7 @@ Jede Fähigkeit von tibi-server / tibi-admin braucht genau eine Dokustelle. Der
| jobs (Cron) + actions | projekt/jobs-actions | ✅ | | jobs (Cron) + actions | projekt/jobs-actions | ✅ |
| snapshots + backup/restore | projekt/audit-backup | ✅ | | snapshots + backup/restore | projekt/audit-backup | ✅ |
| search (Textindex, RRF-Hybrid, Regenerate) | projekt/suche (+ projekt/sammlungen-anlegen für Index-YAML) | ✅ | | search (Textindex, RRF-Hybrid, Regenerate) | projekt/suche (+ projekt/sammlungen-anlegen für Index-YAML) | ✅ |
| embedding + vectorindex | projekt/suche (Kurzüberblick) | ✅ | | embedding + vectorindex (inkl. Failover-Gruppen, Startprüfung/Quarantäne/Rückkehr, `goldenPath`, Status-Endpunkte) | server/konfiguration (Gruppen im Detail), server/betrieb (Status/Störungen), projekt/suche (Kurzüberblick) | ✅ |
| rankfusion (RRF-Formel) | projekt/suche | ✅ | | rankfusion (RRF-Formel) | projekt/suche | ✅ |
| permissions (public/user/token, Orgs/Teams-Matrix) | projekt/rechte-tokens | ✅ | | permissions (public/user/token, Orgs/Teams-Matrix) | projekt/rechte-tokens | ✅ |
| filecleanup (automatisch, keine Config-Fläche) | kein eigener Artikel — Einzeiler in server/betrieb prüfen (T-FINISH) | ⏳ | | filecleanup (automatisch, keine Config-Fläche) | kein eigener Artikel — Einzeiler in server/betrieb prüfen (T-FINISH) | ⏳ |
@@ -182,6 +182,7 @@ Vollständige Prüfung von Code und Doku, dann Abarbeitung in Wellen. Übersicht
| F | P8 Meta (AGENTS.md, Werkzeuge, Issues) | ✅ | | F | P8 Meta (AGENTS.md, Werkzeuge, Issues) | ✅ |
| — | P9 Repo-Hygiene: 5 Alt-Kopien gelöscht (~6,1 GB), 6 mit unpushed Commits behalten | ✅ | | — | P9 Repo-Hygiene: 5 Alt-Kopien gelöscht (~6,1 GB), 6 mit unpushed Commits behalten | ✅ |
| H | Ton: Fehlerabschnitte auf Handbuch-Sprache (Regel in AGENTS.md §1, 12 Anwender-Seiten + 5 Stellen) | ✅ | | H | Ton: Fehlerabschnitte auf Handbuch-Sprache (Regel in AGENTS.md §1, 12 Anwender-Seiten + 5 Stellen) | ✅ |
| I | Darstellung: Inline-Code-Chips für Dark Mode, 58 Seiten auf `.mdx` (Komponenten rendern jetzt), Render-Prüfung im Audit-Skript | ✅ |
| — | Issues in den Nachbar-Repos (13) | ✅ tibi-server #42–#48, tibi-admin-nova #2–#5, tibi-types #3/#4 | | — | Issues in den Nachbar-Repos (13) | ✅ tibi-server #42–#48, tibi-admin-nova #2–#5, tibi-types #3/#4 |
**Neue Werkzeuge:** `scripts/audit-docs.mjs` (Struktur-/Link-/Bildprüfung, `plans/audit-report.json`), **Neue Werkzeuge:** `scripts/audit-docs.mjs` (Struktur-/Link-/Bildprüfung, `plans/audit-report.json`),
+3 -1
View File
@@ -1,6 +1,6 @@
# Offene Punkte in den Nachbar-Repos (bereit zum Anlegen als Issue) # Offene Punkte in den Nachbar-Repos (bereit zum Anlegen als Issue)
Stand: 2026-10-17 · **alle 13 Issues sind angelegt** (Gitea, Repos `cms/tibi-server`, `cms/tibi-admin-nova`, `cms/tibi-types`); Nummer je Zeile in der ersten Spalte. Stand: 2026-10-17 · **alle 14 Issues sind angelegt** (Gitea, Repos `cms/tibi-server`, `cms/tibi-admin-nova`, `cms/tibi-types`); Nummer je Zeile in der ersten Spalte.
Stand: 2026-10-17 (erste Fassung). Alle Punkte sind am Code belegt und aus dem Doku-Audit entstanden Stand: 2026-10-17 (erste Fassung). Alle Punkte sind am Code belegt und aus dem Doku-Audit entstanden
(`plans/doku-audit.md`). Je Eintrag: Titel für das Issue, Repo, Beleg, Auswirkung, Vorschlag. (`plans/doku-audit.md`). Je Eintrag: Titel für das Issue, Repo, Beleg, Auswirkung, Vorschlag.
@@ -26,6 +26,8 @@ Stand: 2026-10-17 (erste Fassung). Alle Punkte sind am Code belegt und aus dem D
| A3 **#4** | Wiederherstellen-Dialog nur bei Änderungen sichtbar | `AuditRestoreDialog.svelte` — Feldauswahl erscheint nur bei ungespeicherten Änderungen, sonst „Keine Änderungen" | Doku muss den Sonderfall erklären; im Audit erwähnt | Hinweistext im UI prüfen | | A3 **#4** | Wiederherstellen-Dialog nur bei Änderungen sichtbar | `AuditRestoreDialog.svelte` — Feldauswahl erscheint nur bei ungespeicherten Änderungen, sonst „Keine Änderungen" | Doku muss den Sonderfall erklären; im Audit erwähnt | Hinweistext im UI prüfen |
| A4 **#3** | Actions fehlen im Mock-Projekt `corporate` | `frontend/assets/mock/_config/corporate/config.yml` hatte keinen `actions`-Block (nur `t1`) | Screenshots/Reviews ohne aussagekräftiges Demo-Projekt (erledigt in der Doku-Arbeit, s. u.) | übernommen: `actions/reindex-search.yml` + `meta.exports` in `corporate` ergänzt | | A4 **#3** | Actions fehlen im Mock-Projekt `corporate` | `frontend/assets/mock/_config/corporate/config.yml` hatte keinen `actions`-Block (nur `t1`) | Screenshots/Reviews ohne aussagekräftiges Demo-Projekt (erledigt in der Doku-Arbeit, s. u.) | übernommen: `actions/reindex-search.yml` + `meta.exports` in `corporate` ergänzt |
| A5 **#6** | ASCII-Umschreibungen in UI-Texten | `frontend/src/lib/i18n/locales/de.json:178` („ausfuehrbare Methode fuer diese Aktion"), `:194` („vor dem Ausfuehren") | Rechtschreibfehler im deutschen UI; Doku zitiert die Texte wörtlich | Umlaute korrigieren (`ausführbare`, `für`, `Ausführen`) |
## tibi-types ## tibi-types
| # | Titel | Beleg | Auswirkung | Vorschlag | | # | Titel | Beleg | Auswirkung | Vorschlag |
+35 -3
View File
@@ -179,6 +179,18 @@ for (const f of files) {
if (!im[1].trim()) add(f, 'image', 'Bild ohne Alt-Text', line); if (!im[1].trim()) add(f, 'image', 'Bild ohne Alt-Text', line);
} }
// Komponente genutzt, aber nicht importiert (rendert dann als rohes HTML)
const usedComponents = new Set(
[...text.matchAll(/<(Aside|Steps|Tabs|TabItem|Card|CardGrid|LinkCard|Badge|FileTree)\b/g)].map((m) => m[1]),
);
const imported = new Set(
([...text.matchAll(/^import\s*\{([^}]*)\}/gm)].flatMap((m) => m[1].split(',').map((s) => s.trim()))),
);
for (const c of usedComponents) {
if (!imported.has(c) && f.endsWith('.mdx')) add(f, 'import', `Komponente <${c}> genutzt, aber nicht importiert`);
if (!f.endsWith('.mdx')) add(f, 'import', `Komponente <${c}> in einer .md-Datei — Datei muss .mdx sein`);
}
// Starlight-Komponenten // Starlight-Komponenten
for (const c of new Set(STARLIGHT_COMPONENTS)) { for (const c of new Set(STARLIGHT_COMPONENTS)) {
const n = (text.match(new RegExp(`<${c}[\\s/>]`, 'g')) ?? []).length; const n = (text.match(new RegExp(`<${c}[\\s/>]`, 'g')) ?? []).length;
@@ -248,6 +260,26 @@ for (const f of files) {
if (badAdmin) stats.tibiAdminRefs.push(`${rel(f)} (${badAdmin.length}×)`); if (badAdmin) stats.tibiAdminRefs.push(`${rel(f)} (${badAdmin.length}×)`);
} }
// --- Render-Prüfung gegen dist/ (Komponenten müssen echt gerendert sein) ----
const DIST = path.join(ROOT, 'dist');
const renderFindings = [];
if (fs.existsSync(DIST)) {
const htmlFiles = walk(DIST).filter((f) => f.endsWith('.html'));
const RAW_TAG = /<(Aside|Steps|Tabs|TabItem|Card|CardGrid|LinkCard|Badge|FileTree)[\s/>]/;
for (const f of htmlFiles) {
const html = fs.readFileSync(f, 'utf8');
if (html.includes('astrojs/starlight/components')) {
renderFindings.push({ file: rel(f), kind: 'render', msg: 'import-Zeile steht sichtbar im HTML (Seite ist .md statt .mdx)', line: null });
}
const m = RAW_TAG.exec(html);
if (m) {
renderFindings.push({ file: rel(f), kind: 'render', msg: `Komponenten-Tag <${m[1]}> ungerendert im HTML (Seite ist .md statt .mdx)`, line: null });
}
}
} else {
renderFindings.push({ file: 'dist/', kind: 'render', msg: 'dist/ fehlt — zuerst `npm run build` laufen lassen', line: null });
}
// --- Sidebar-Abgleich ----------------------------------------------------- // --- Sidebar-Abgleich -----------------------------------------------------
const cfg = fs.readFileSync(path.join(ROOT, 'astro.config.mjs'), 'utf8'); const cfg = fs.readFileSync(path.join(ROOT, 'astro.config.mjs'), 'utf8');
const sidebarSlugs = [...cfg.matchAll(/slug:\s*'([^']+)'/g)].map((m) => m[1]); const sidebarSlugs = [...cfg.matchAll(/slug:\s*'([^']+)'/g)].map((m) => m[1]);
@@ -259,10 +291,10 @@ const report = {
generatedAt: new Date().toISOString(), generatedAt: new Date().toISOString(),
stats, stats,
sidebar: { count: sidebarSlugs.length, notInSidebar, sidebarMissing }, sidebar: { count: sidebarSlugs.length, notInSidebar, sidebarMissing },
findings, findings: [...findings, ...renderFindings],
summary: { summary: {
total: findings.length, total: findings.length + renderFindings.length,
byKind: findings.reduce((a, f) => ({ ...a, [f.kind]: (a[f.kind] ?? 0) + 1 }), {}), byKind: [...findings, ...renderFindings].reduce((a, f) => ({ ...a, [f.kind]: (a[f.kind] ?? 0) + 1 }), {}),
}, },
}; };
@@ -45,7 +45,10 @@ meta:
| `execution.cancellable` | Schema-seitig vorgesehen, im Frontend **ohne Verwendung** (kein Abbruch-Knopf; `grep` über `frontend/src` findet keine Auswertung) — lang laufende Actions brechen nur über SSE-Ende oder Netzwerkfehler ab | | `execution.cancellable` | Schema-seitig vorgesehen, im Frontend **ohne Verwendung** (kein Abbruch-Knopf; `grep` über `frontend/src` findet keine Auswertung) — lang laufende Actions brechen nur über SSE-Ende oder Netzwerkfehler ab |
| Upload-Fortschritt | Enthält `data` Datei-Bytes (`hasInlineUploadPayload`), erzeugt `runAction` einen Byte-Toast (`createByteProgressToast`, `ActionView.svelte:350-357`) und füttert ihn per `onUploadProgress` (`ActionView.svelte:377`) — Erfolgs-Toast erst nach Server-Antwort | | Upload-Fortschritt | Enthält `data` Datei-Bytes (`hasInlineUploadPayload`), erzeugt `runAction` einen Byte-Toast (`createByteProgressToast`, `ActionView.svelte:350-357`) und füttert ihn per `onUploadProgress` (`ActionView.svelte:377`) — Erfolgs-Toast erst nach Server-Antwort |
<Aside type="note">Fortschritt während der Ausführung kommt ausschließlich aus SSE-Events plus `output.mapping.progress` (siehe Reiter oben) — `execution` steuert daran nichts.</Aside> <Aside type="note">
Fortschritt während der Ausführung kommt ausschließlich aus SSE-Events plus `output.mapping.progress` (siehe Reiter oben) — `execution` steuert daran nichts.
</Aside>
![Action-Ansicht im tibi-admin: Methoden-Reiter GET und POST, Eingabeformular mit den Feldern Kollektion und Versteckte einschliessen sowie Ausführen-Knopf](../../../assets/admin/admin-aktionen.webp) ![Action-Ansicht im tibi-admin: Methoden-Reiter GET und POST, Eingabeformular mit den Feldern Kollektion und Versteckte einschliessen sowie Ausführen-Knopf](../../../assets/admin/admin-aktionen.webp)
@@ -85,7 +88,10 @@ Liefert die Action Live-Events (`Content-Type: text/event-stream`), zeigt `Actio
- Status- und Meldungstext aus `progress.statusField` / `progress.messageField`. - Status- und Meldungstext aus `progress.statusField` / `progress.messageField`.
- Optionale Zusatzfelder (`progress.fields`) aus jedem Event. - Optionale Zusatzfelder (`progress.fields`) aus jedem Event.
<Aside type="note">Ohne `output.mapping.progress` in der Action-Meta gibt es bewusst **keinen** Balken — ein 0-%-Balken bei jeder alten Action wäre irreführend. Dann bleibt JSON das Ergebnis.</Aside> <Aside type="note">
Ohne `output.mapping.progress` in der Action-Meta gibt es bewusst **keinen** Balken — ein 0-%-Balken bei jeder alten Action wäre irreführend. Dann bleibt JSON das Ergebnis.
</Aside>
</TabItem> </TabItem>
</Tabs> </Tabs>
@@ -105,7 +111,10 @@ Formular (query + data) → Ausführen → [SSE-Events → Balken] → Ergebnis
| `permission` / `not found` | Falscher Kopf oder Action gelöscht | [Token-Köpfe](/reference/token-koepfe/) und Action-Namen prüfen | | `permission` / `not found` | Falscher Kopf oder Action gelöscht | [Token-Köpfe](/reference/token-koepfe/) und Action-Namen prüfen |
| Netzwerkfehler | Server nicht erreichbar | [Admin einrichten](/admin/setup/) prüfen, erneut versuchen | | Netzwerkfehler | Server nicht erreichbar | [Admin einrichten](/admin/setup/) prüfen, erneut versuchen |
<Aside type="caution">Actions können Daten ändern. Starte eine unbekannte Action erst in einer Testumgebung — der Verlauf (`_actions/<name>` im Audit) zeigt nur, was bereits lief.</Aside> <Aside type="caution">
Actions können Daten ändern. Starte eine unbekannte Action erst in einer Testumgebung — der Verlauf (`_actions/<name>` im Audit) zeigt nur, was bereits lief.
</Aside>
Klick-Abläufe dazu stehen im Test-Plan `docs/test-plans/40-actions.md` (Methoden-Tabs, ausführbar vs. gesperrt, Ergebnis und Fortschritt). Klick-Abläufe dazu stehen im Test-Plan `docs/test-plans/40-actions.md` (Methoden-Tabs, ausführbar vs. gesperrt, Ergebnis und Fortschritt).
@@ -121,7 +121,10 @@ admin:
allowSnapshots: true allowSnapshots: true
``` ```
<Aside type="caution">Fehlt ein Flag, scheitert die Operation serverseitig — der Admin zeigt dann einen Rechte-Fehler. Vergib nur, was das Projekt wirklich braucht.</Aside> <Aside type="caution">
Fehlt ein Flag, scheitert die Operation serverseitig — der Admin zeigt dann einen Rechte-Fehler. Vergib nur, was das Projekt wirklich braucht.
</Aside>
## Stellvertretung (Impersonation) ## Stellvertretung (Impersonation)
@@ -133,7 +136,10 @@ Admins können sich als ein Benutzerkonto anmelden, um dessen Sicht zu prüfen:
| Erkennen | Banner/Status zeigt die Stellvertretung (`AdminLayout`, `AdminHub`, `UserDetailDrawer`) | | Erkennen | Banner/Status zeigt die Stellvertretung (`AdminLayout`, `AdminHub`, `UserDetailDrawer`) |
| Beenden | `POST /login/impersonate?logout=1` → Server löscht den Stellvertretungs-Cookie und stellt die Admin-Sitzung wieder her | | Beenden | `POST /login/impersonate?logout=1` → Server löscht den Stellvertretungs-Cookie und stellt die Admin-Sitzung wieder her |
<Aside type="caution">Stellvertretung nur mit Freigabe nutzen — du handelst mit den Rechten der vertretenen Person, und der Vorgang wird protokolliert (Audit). Währenddessen keine Token erstellen.</Aside> <Aside type="caution">
Stellvertretung nur mit Freigabe nutzen — du handelst mit den Rechten der vertretenen Person, und der Vorgang wird protokolliert (Audit). Währenddessen keine Token erstellen.
</Aside>
## Fehler ## Fehler
@@ -71,7 +71,10 @@ Volle Kontext-Tabelle in der Repo-Doku `custom-export-script.md`; Bedienung unte
</TabItem> </TabItem>
</Tabs> </Tabs>
<Aside type="caution">Anpassungen an Sammlungs-Config und Skripten erst im lokalen Stack ([einrichten](/admin/setup/)) testen — falsche `meta`-Einträge blenden Ansichten oder Export-Reiter ersatzlos aus.</Aside> <Aside type="caution">
Anpassungen an Sammlungs-Config und Skripten erst im lokalen Stack ([einrichten](/admin/setup/)) testen — falsche `meta`-Einträge blenden Ansichten oder Export-Reiter ersatzlos aus.
</Aside>
![Angepasste Listenansicht im tibi-admin: Seitenliste mit Filter-Reitern und geöffnetem Export-Dialog mit den eigenen Export-Reitern Seiten-Report und Seiten-CSV](../../../assets/admin/admin-customizing.webp) ![Angepasste Listenansicht im tibi-admin: Seitenliste mit Filter-Reitern und geöffnetem Export-Dialog mit den eigenen Export-Reitern Seiten-Report und Seiten-CSV](../../../assets/admin/admin-customizing.webp)
File renamed without changes.
@@ -42,7 +42,7 @@ Was die KI dabei sieht (`aiController.ts`, `AiChat.svelte`): Neben dem UI-Snapsh
Das **„Kontextfenster“** liest der Chat aus dem Modellkatalog (Endpunkt `/api/ai/models`, `configured.chat.contextWindow`); antwortet der Katalog nicht, gelten 96 000 Token (`MODEL_CONTEXT_WINDOW`). Aktuell meldet der Proxy 262 144 (siehe `config/ai-proxy.config.json`, `chat.contextWindow`). Das **„Kontextfenster“** liest der Chat aus dem Modellkatalog (Endpunkt `/api/ai/models`, `configured.chat.contextWindow`); antwortet der Katalog nicht, gelten 96 000 Token (`MODEL_CONTEXT_WINDOW`). Aktuell meldet der Proxy 262 144 (siehe `config/ai-proxy.config.json`, `chat.contextWindow`).
Tempo der Cursor-Animation: **„Nova-Speed“** mit **„Hyperspeed“** (0, alles sofort), **„Schnell“** (1, Standard), **„Mittel“** (2), **„Langsam“** (3) — als Befehle (**„Nova-Speed: …“**) und Kennung `ai-animation-speed` in `localStorage`. Bei zu vielen Schritten stoppt Nova von selbst: „Automatisch gestoppt – zu viele aufeinanderfolgende Aktionen. Nova wurde nach {count} Schritten angehalten …“ — dann Anfrage neu formulieren oder Felder manuell prüfen. Tempo der Cursor-Animation: **„Nova-Speed“** mit **„Hyperspeed“** (0, alles sofort), **„Schnell“** (1, Standard), **„Mittel“** (2), **„Langsam“** (3) — als Befehle (**„Nova-Speed: …“**) und Kennung `ai-animation-speed` in `localStorage`. Bei zu vielen Schritten stoppt Nova von selbst: „Automatisch gestoppt – zu viele aufeinanderfolgende Aktionen. Nova wurde nach \{count\} Schritten angehalten …“ — dann Anfrage neu formulieren oder Felder manuell prüfen.
## Zusammenfassen ## Zusammenfassen
@@ -57,8 +57,8 @@ Wächst ein Gespräch Richtung Kontextgrenze, komprimiert `lib/aiSummarizer.ts`
- Reserviert sind 10 000 Token für System-Prompt, Werkzeug-Definitionen und Antwort (`RESERVED_TOKENS`). - Reserviert sind 10 000 Token für System-Prompt, Werkzeug-Definitionen und Antwort (`RESERVED_TOKENS`).
- Zusammenfassungs-Modell: `proxy-managed` — auch hier entscheidet der Proxy, nicht das Frontend. Der Aufruf geht an den Zusammenfassungs-Endpunkt mit Profil `summarizer` und liest einen SSE-Strom (inkl. Zähler); Antwort leer oder Netzfehler → Rückfall auf hartes Kürzen. - Zusammenfassungs-Modell: `proxy-managed` — auch hier entscheidet der Proxy, nicht das Frontend. Der Aufruf geht an den Zusammenfassungs-Endpunkt mit Profil `summarizer` und liest einen SSE-Strom (inkl. Zähler); Antwort leer oder Netzfehler → Rückfall auf hartes Kürzen.
- Hartes Kürzen hält Werkzeug-Paare zusammen: Verwaiste `tool`-Antworten und `assistant`-Nachrichten ohne vollständige Ergebnisse fliegen raus (`hardTruncate`, `sanitizeWireMessages`) — sonst würde die API den Turn ablehnen. - Hartes Kürzen hält Werkzeug-Paare zusammen: Verwaiste `tool`-Antworten und `assistant`-Nachrichten ohne vollständige Ergebnisse fliegen raus (`hardTruncate`, `sanitizeWireMessages`) — sonst würde die API den Turn ablehnen.
- UI: Währenddessen steht **„Kontext wird zusammengefasst…“**, danach **„Kontext zusammengefasst“** bzw. **„Zusammenfassung aktiv“** mit **„{count} Nachrichten komprimiert · ~{tokens} Tokens gespart“**. - UI: Währenddessen steht **„Kontext wird zusammengefasst…“**, danach **„Kontext zusammengefasst“** bzw. **„Zusammenfassung aktiv“** mit **„\{count\} Nachrichten komprimiert · ~\{tokens\} Tokens gespart“**.
- Themenwechsel: Schlägt Nova einen neuen Chat vor (**„Neuen Chat starten?“** — **„Neuen Chat starten“** oder **„Nein, weiter“**), fasst `summarizeForTransfer` den ganzen Verlauf zusammen und stellt deine letzte Nachricht wörtlich unter „LATEST REQUEST“. Im neuen Chat erscheint das als **„Kontext aus vorherigem Chat“** (**„{count} Nachrichten aus vorherigem Chat · ~{tokens} Tokens übertragen“**). - Themenwechsel: Schlägt Nova einen neuen Chat vor (**„Neuen Chat starten?“** — **„Neuen Chat starten“** oder **„Nein, weiter“**), fasst `summarizeForTransfer` den ganzen Verlauf zusammen und stellt deine letzte Nachricht wörtlich unter „LATEST REQUEST“. Im neuen Chat erscheint das als **„Kontext aus vorherigem Chat“** (**„\{count\} Nachrichten aus vorherigem Chat · ~\{tokens\} Tokens übertragen“**).
## Stimme ## Stimme
@@ -84,7 +84,7 @@ Antworten anhören statt lesen (`lib/aiTTS.ts`, Endpunkt `/api/ai/tts`) — Knop
| Tempo | **„Geschwindigkeit“** 0,5–2,0, Schritt 0,1, Standard **1,3** (**„Zurücksetzen“** stellt den Standard wieder her) | | Tempo | **„Geschwindigkeit“** 0,5–2,0, Schritt 0,1, Standard **1,3** (**„Zurücksetzen“** stellt den Standard wieder her) |
| Format | MP3 (`responseFormat: mp3`) — Opus im WebM, wenn dein Browser es kann (`preferredTTSFormat`) | | Format | MP3 (`responseFormat: mp3`) — Opus im WebM, wenn dein Browser es kann (`preferredTTSFormat`) |
Verhalten: Quelle zuerst Browser-**„Systemstimme auf diesem Gerät“** (passend zu deiner Dokumentsprache, Standard `de-DE`), sonst **„KI-Stimme“** (Proxy-Modell `qwen3-tts-0.6b-base`) — Anzeige **„Stimme: {voice}“**. Beim Streamen spricht Nova satzweise mit: erster Block ab ~60 Zeichen, danach ~120-Zeichen-Bündel, bis zu 2 parallele Anfragen, lückenlos aneinandergereiht. Schalter **„Sprachausgabe“** / **„Sprachausgabe aus“**; Zeichen und Kosten meldet der Server per Kopf (`X-AI-Characters`, `X-AI-Cost-Usd`) und landen unter **„Sprachausgabe“** in **„Konversation gesamt“**. Verhalten: Quelle zuerst Browser-**„Systemstimme auf diesem Gerät“** (passend zu deiner Dokumentsprache, Standard `de-DE`), sonst **„KI-Stimme“** (Proxy-Modell `qwen3-tts-0.6b-base`) — Anzeige **„Stimme: \{voice\}“**. Beim Streamen spricht Nova satzweise mit: erster Block ab ~60 Zeichen, danach ~120-Zeichen-Bündel, bis zu 2 parallele Anfragen, lückenlos aneinandergereiht. Schalter **„Sprachausgabe“** / **„Sprachausgabe aus“**; Zeichen und Kosten meldet der Server per Kopf (`X-AI-Characters`, `X-AI-Cost-Usd`) und landen unter **„Sprachausgabe“** in **„Konversation gesamt“**.
</TabItem> </TabItem>
</Tabs> </Tabs>
@@ -93,7 +93,7 @@ Zusatz: **Vision** (`/api/ai/vision`) beschreibt Bilder für CMS-Felder („erfi
## Werkzeuge ## Werkzeuge
Die KI ruft UI-Werkzeuge statt zu raten (`aiTools.ts` → `aiController.ts`). Labels wörtlich aus `de.json` (`admin.ai.tools.*`); Status dazu: **„Arbeitet…“** währenddessen, danach **„1 Aktion ausgeführt“** bzw. **„{count} Aktionen ausgeführt“**, **„Details“** öffnet den **„Tool-Inspector“** (**„Tool-Aufruf“** / **„Tool-Rückgabe“**, **„Formatiert“** / **„Roh“**, **„Alle Schritte kopieren“**): Die KI ruft UI-Werkzeuge statt zu raten (`aiTools.ts` → `aiController.ts`). Labels wörtlich aus `de.json` (`admin.ai.tools.*`); Status dazu: **„Arbeitet…“** währenddessen, danach **„1 Aktion ausgeführt“** bzw. **„\{count\} Aktionen ausgeführt“**, **„Details“** öffnet den **„Tool-Inspector“** (**„Tool-Aufruf“** / **„Tool-Rückgabe“**, **„Formatiert“** / **„Roh“**, **„Alle Schritte kopieren“**):
| Gruppe | Werkzeuge (UI-Name) | | Gruppe | Werkzeuge (UI-Name) |
|---|---| |---|---|
@@ -113,7 +113,10 @@ Die KI ruft UI-Werkzeuge statt zu raten (`aiTools.ts` → `aiController.ts`). La
Stapel-Limits (`aiTools.ts`): `run_tool_batch` fasst bis zu 40 ändernde Schritte (100 bei rein lesenden wie **„Kontext abrufen“**, **„Projekte auflisten“**, **„Benutzer auflisten“**) — darüber verweigert der Batch. `get_context` (**„Kontext abrufen“**) und `get_entry_context` lesen gezielt nach: Standardablauf ist erst Eintrag öffnen, dann `get_entry_context`, dann die Feld-Updates möglichst als ein Batch. Stapel-Limits (`aiTools.ts`): `run_tool_batch` fasst bis zu 40 ändernde Schritte (100 bei rein lesenden wie **„Kontext abrufen“**, **„Projekte auflisten“**, **„Benutzer auflisten“**) — darüber verweigert der Batch. `get_context` (**„Kontext abrufen“**) und `get_entry_context` lesen gezielt nach: Standardablauf ist erst Eintrag öffnen, dann `get_entry_context`, dann die Feld-Updates möglichst als ein Batch.
<Aside type="note">Alle Aktionen sind nicht destruktiv angelegt; `prepare_bulk_delete` bereitet nur vor und `restore_from_audit` (**„Aus Audit übernehmen"**) fragt vorher bei dir nach. Zwei Debug-Ansichten: `AiDebugContext.svelte` (Befehlspalette: **„Debug: KI-Kontext anzeigen"**) zeigt, welchen Kontext die KI gerade sieht; `AiVisionOverlay.svelte` (Befehlspalette: **„Debug: KI-Vision"**, Schalter **„Debug: KI-Vision (steuerbare Elemente anzeigen)"**) legt ein Röntgen-Overlay über alle steuerbaren Elemente mit ihren Kennungen.</Aside> <Aside type="note">
Alle Aktionen sind nicht destruktiv angelegt; `prepare_bulk_delete` bereitet nur vor und `restore_from_audit` (**„Aus Audit übernehmen"**) fragt vorher bei dir nach. Zwei Debug-Ansichten: `AiDebugContext.svelte` (Befehlspalette: **„Debug: KI-Kontext anzeigen"**) zeigt, welchen Kontext die KI gerade sieht; `AiVisionOverlay.svelte` (Befehlspalette: **„Debug: KI-Vision"**, Schalter **„Debug: KI-Vision (steuerbare Elemente anzeigen)"**) legt ein Röntgen-Overlay über alle steuerbaren Elemente mit ihren Kennungen.
</Aside>
## Grenzen und Kosten ## Grenzen und Kosten
@@ -137,7 +140,7 @@ Wichtig zur Einordnung: Der Admin-Chat läuft über den Proxy (`/api/ai/chat`, `
| **„Rate-Limit erreicht. Bitte warte einen Moment und versuche es erneut.“** | 429 am Proxy | Kurz warten, erneut versuchen | | **„Rate-Limit erreicht. Bitte warte einen Moment und versuche es erneut.“** | 429 am Proxy | Kurz warten, erneut versuchen |
| **„Der KI-Dienst ist vorübergehend nicht erreichbar. Bitte versuche es später erneut.“** | 5xx am Proxy | Später erneut versuchen | | **„Der KI-Dienst ist vorübergehend nicht erreichbar. Bitte versuche es später erneut.“** | 5xx am Proxy | Später erneut versuchen |
| **„Mikrofon-Zugriff verweigert. …“** | Browser-Recht fehlt | Mikrofon in den Browser-Einstellungen freigeben | | **„Mikrofon-Zugriff verweigert. …“** | Browser-Recht fehlt | Mikrofon in den Browser-Einstellungen freigeben |
| **„Transkription fehlgeschlagen: {error}“** | Rate-Limit, Netz oder Proxy-Fehler | Kürzeren Abschnitt sprechen, erneut versuchen | | **„Transkription fehlgeschlagen: \{error\}“** | Rate-Limit, Netz oder Proxy-Fehler | Kürzeren Abschnitt sprechen, erneut versuchen |
| Stimme klingt falsch | Andere Stimme aktiv | Stimme in den KI-Einstellungen zurück auf `eva-de` setzen | | Stimme klingt falsch | Andere Stimme aktiv | Stimme in den KI-Einstellungen zurück auf `eva-de` setzen |
![Geöffneter Nova-Chat im tibi-admin: Begrüßungsnachricht, Eingabefeld mit Mikrofon-Knopf und Sammlungsübersicht des Corporate-Projekts dahinter](../../../assets/admin/admin-ki-funktionen.webp) ![Geöffneter Nova-Chat im tibi-admin: Begrüßungsnachricht, Eingabefeld mit Mikrofon-Knopf und Sammlungsübersicht des Corporate-Projekts dahinter](../../../assets/admin/admin-ki-funktionen.webp)
File renamed without changes.
File renamed without changes.
@@ -102,7 +102,10 @@ yarn build # esbuild-Bundle, muss fehlerfrei durchlaufen
yarn test:e2e # Playwright E2E yarn test:e2e # Playwright E2E
``` ```
<Aside type="tip">Läuft `yarn build` grün und zeigt der Admin nach Login die Projektliste, ist das Setup fertig. Weiter geht es mit [Admin anpassen](/admin/customizing/).</Aside> <Aside type="tip">
Läuft `yarn build` grün und zeigt der Admin nach Login die Projektliste, ist das Setup fertig. Weiter geht es mit [Admin anpassen](/admin/customizing/).
</Aside>
![Projektliste im tibi-admin nach dem Login: Karten für Blog Platform, Corporate Website, Event Portal, Intranet, Online Shop und Test Core mit Online-Status](../../../assets/admin/admin-setup.webp) ![Projektliste im tibi-admin nach dem Login: Karten für Blog Platform, Corporate Website, Event Portal, Intranet, Online Shop und Test Core mit Online-Status](../../../assets/admin/admin-setup.webp)
@@ -16,7 +16,7 @@ import { Aside, Steps } from '@astrojs/starlight/components';
1. Öffne das Projekt und wähle die Aktion aus der Liste. 1. Öffne das Projekt und wähle die Aktion aus der Liste.
2. Lies oben, was die Aktion tut (Abschnitt **Methoden**). 2. Lies oben, was die Aktion tut (Abschnitt **Methoden**).
3. Fülle unter **Eingaben** alle benötigten Felder aus. 3. Fülle unter **Eingaben** alle benötigten Felder aus.
4. Korrigiere markierte Felder, solange die Meldung **Bitte korrigiere {count} Feldfehler vor dem Ausfuehren.** steht. 4. Korrigiere markierte Felder, solange die Meldung **Bitte korrigiere \{count\} Feldfehler vor dem Ausfuehren.** steht.
5. Klicke auf **Ausführen**. 5. Klicke auf **Ausführen**.
6. Warte, bis statt **Läuft…** wieder **Ausführen** dasteht. 6. Warte, bis statt **Läuft…** wieder **Ausführen** dasteht.
@@ -30,7 +30,7 @@ Steht statt **Ausführen** der Knopf **Gesperrt**, fehlt dem Konto die Berechtig
## Fortschritt und Ergebnis lesen ## Fortschritt und Ergebnis lesen
Während der **Ausführung** zeigt der Bereich **Fortschritt** einen Balken mit **{percent}% abgeschlossen** oder einer Zählung wie **{index} von {count}**. Danach steht das Ergebnis unter **Letztes Ergebnis** mit **Zusammenfassung** und **Details**. Während der **Ausführung** zeigt der Bereich **Fortschritt** einen Balken mit **\{percent\}% abgeschlossen** oder einer Zählung wie **\{index\} von \{count\}**. Danach steht das Ergebnis unter **Letztes Ergebnis** mit **Zusammenfassung** und **Details**.
<Steps> <Steps>
@@ -47,7 +47,7 @@ Die Anmeldeadresse erhältst du von der Administration; speichere sie anschließ
Kurz vor dem automatischen Abmelden erscheint der Hinweis **Sitzung läuft ab** mit der Meldung „Du wirst wegen Inaktivität gleich automatisch abgemeldet.". Wähle **Angemeldet bleiben**, um weiterzuarbeiten, oder **Jetzt abmelden**, um die Sitzung sofort zu beenden. Reagierst du nicht, meldet dich tibi-admin automatisch ab und es erscheint der Hinweis **Sitzung abgelaufen – bitte neu anmelden**. Kurz vor dem automatischen Abmelden erscheint der Hinweis **Sitzung läuft ab** mit der Meldung „Du wirst wegen Inaktivität gleich automatisch abgemeldet.". Wähle **Angemeldet bleiben**, um weiterzuarbeiten, oder **Jetzt abmelden**, um die Sitzung sofort zu beenden. Reagierst du nicht, meldet dich tibi-admin automatisch ab und es erscheint der Hinweis **Sitzung abgelaufen – bitte neu anmelden**.
<!-- Screenshot (T-SHOTS): Hinweisdialog Sitzung läuft ab mit Angemeldet bleiben und Jetzt abmelden, Mock-Projekt corporate, Alt-Text deutsch --> {/* Screenshot (T-SHOTS): Hinweisdialog Sitzung läuft ab mit Angemeldet bleiben und Jetzt abmelden, Mock-Projekt corporate, Alt-Text deutsch */}
<Steps> <Steps>
@@ -26,7 +26,7 @@ Ohne Administrator-Recht sperrt die Ansicht mit **Keine Administrator-Berechtigu
## Analyse lesen ## Analyse lesen
Das **Analyse-Ergebnis** nennt zuerst die Zahl der gefundenen Sammlungen (**{count} Collections gefunden**) sowie Quell-Projekt und Erstelldatum der Datei. Die Tabelle darunter listet pro **Collection** die Spalten **Dokumente**, **Medien** und **Hinweise**. Das **Analyse-Ergebnis** nennt zuerst die Zahl der gefundenen Sammlungen (**\{count\} Collections gefunden**) sowie Quell-Projekt und Erstelldatum der Datei. Die Tabelle darunter listet pro **Collection** die Spalten **Dokumente**, **Medien** und **Hinweise**.
<Steps> <Steps>
@@ -48,10 +48,10 @@ Lies die Analyse vollständig, bevor du fortfährst. Bei ausgeschalteter Zusamme
<Steps> <Steps>
1. Klicke auf **Wiederherstellung starten** (mit Auswahl: **Wiederherstellung starten ({n} Collections)**). 1. Klicke auf **Wiederherstellung starten** (mit Auswahl: **Wiederherstellung starten (\{n\} Collections)**).
2. Verfolge den Fortschritt (**Stelle {name} wieder her…**) und warte auf **Wiederherstellung abgeschlossen**. 2. Verfolge den Fortschritt (**Stelle \{name\} wieder her…**) und warte auf **Wiederherstellung abgeschlossen**.
3. Lies die Zusammenfassung: **{docs} Dokumente in {n} Collections wiederhergestellt**, dazu ggf. **{n} Mediendateien kopiert**. 3. Lies die Zusammenfassung: **\{docs\} Dokumente in \{n\} Collections wiederhergestellt**, dazu ggf. **\{n\} Mediendateien kopiert**.
4. Beachte übersprungene Sammlungen (**{n} Collections übersprungen (existieren nicht im Ziel)**). 4. Beachte übersprungene Sammlungen (**\{n\} Collections übersprungen (existieren nicht im Ziel)**).
5. Öffne danach die betroffenen Sammlungen und prüfe Stichproben. 5. Öffne danach die betroffenen Sammlungen und prüfe Stichproben.
</Steps> </Steps>
File renamed without changes.
@@ -57,7 +57,7 @@ Liefert die Suche keine Treffer, gehe wie folgt vor:
Arbeitet deine Sammlung mit Hintergrundsuche, baut tibi-admin den **Suchindex** im Hintergrund auf. Solange Einträge offen sind, zeigt ein Hinweisbanner über der Liste „wird im Hintergrund aktualisiert" mit der Zahl der offenen Einträge. Du kannst normal weiterarbeiten; sobald der Zähler auf null steht, verschwindet das Banner von selbst. Über **Details ein-/ausblenden** siehst du den Stand je Sammlung, bei Problemen stößt **Neu aufbauen** den Aufbau erneut an. Arbeitet deine Sammlung mit Hintergrundsuche, baut tibi-admin den **Suchindex** im Hintergrund auf. Solange Einträge offen sind, zeigt ein Hinweisbanner über der Liste „wird im Hintergrund aktualisiert" mit der Zahl der offenen Einträge. Du kannst normal weiterarbeiten; sobald der Zähler auf null steht, verschwindet das Banner von selbst. Über **Details ein-/ausblenden** siehst du den Stand je Sammlung, bei Problemen stößt **Neu aufbauen** den Aufbau erneut an.
<!-- Screenshot (T-SHOTS): Suchindex-Hinweisbanner im Projekt-/Sammlungs-Header, Mock-Projekt corporate, Alt-Text deutsch --> {/* Screenshot (T-SHOTS): Suchindex-Hinweisbanner im Projekt-/Sammlungs-Header, Mock-Projekt corporate, Alt-Text deutsch */}
## Filtern und sortieren ## Filtern und sortieren
@@ -16,7 +16,7 @@ Die **Massenbearbeitung** ändert viele Einträge in einem Durchgang — zum Bei
1. Öffne die Sammlung mit den Einträgen. 1. Öffne die Sammlung mit den Einträgen.
2. Markiere die Einträge einzeln, oder nutze **Alle auswählen** für die ganze Seite. 2. Markiere die Einträge einzeln, oder nutze **Alle auswählen** für die ganze Seite.
3. Klicke auf **Bearbeiten** (Taste `e` geht auch). 3. Klicke auf **Bearbeiten** (Taste `e` geht auch).
4. Der Dialog zeigt oben **Massenoperationen** und wie viele Einträge betroffen sind (**{count} ausgewählt**). 4. Der Dialog zeigt oben **Massenoperationen** und wie viele Einträge betroffen sind (**\{count\} ausgewählt**).
</Steps> </Steps>
@@ -33,11 +33,11 @@ Ohne Auswahl betrifft die Aktion alle Einträge der Sammlung. Brich in dem Fall
3. Lege unter **Operationen** fest, was passieren soll (**Operation hinzufügen**, Feld wählen und Aktion wie **Setzen auf** oder **Suchen & Ersetzen** bestimmen). 3. Lege unter **Operationen** fest, was passieren soll (**Operation hinzufügen**, Feld wählen und Aktion wie **Setzen auf** oder **Suchen & Ersetzen** bestimmen).
4. Öffne die **Vorschau** und lies, wie viele der ausgewählten Einträge betroffen sind. 4. Öffne die **Vorschau** und lies, wie viele der ausgewählten Einträge betroffen sind.
5. Klicke auf **Änderungen anwenden** und bestätige die Nachfrage. 5. Klicke auf **Änderungen anwenden** und bestätige die Nachfrage.
6. Warte die Meldung ab: **{count} Einträge aktualisiert** bedeutet Erfolg. 6. Warte die Meldung ab: **\{count\} Einträge aktualisiert** bedeutet Erfolg.
</Steps> </Steps>
Die Nachfrage warnt ehrlich: **{count} Einträge werden aktualisiert. Diese Aktion kann nicht rückgängig gemacht werden.** Lies sie, bevor du bestätigst. Die Nachfrage warnt ehrlich: **\{count\} Einträge werden aktualisiert. Diese Aktion kann nicht rückgängig gemacht werden.** Lies sie, bevor du bestätigst.
![Massenbearbeitung-Dialog mit einer Bedingung, zwei Operationen und geöffneter Vorschau](../../../assets/anwender/massenbearbeitung-vorschau.webp) ![Massenbearbeitung-Dialog mit einer Bedingung, zwei Operationen und geöffneter Vorschau](../../../assets/anwender/massenbearbeitung-vorschau.webp)
@@ -62,9 +62,9 @@ Die Massenbearbeitung lässt sich nicht rückgängig machen. Prüfe die **Vorsch
| Meldung | Bedeutung (Ursache) | Vorgehen | | Meldung | Bedeutung (Ursache) | Vorgehen |
| ------- | ------------------- | -------- | | ------- | ------------------- | -------- |
| „{done} von {total} aktualisiert, {failed} fehlgeschlagen" | Einzelne Einträge sind gescheitert (z. B. Pflichtfeld leer, fehlende Berechtigung). | Prüfe die gescheiterten Einträge einzeln. | | „\{done\} von \{total\} aktualisiert, \{failed\} fehlgeschlagen" | Einzelne Einträge sind gescheitert (z. B. Pflichtfeld leer, fehlende Berechtigung). | Prüfe die gescheiterten Einträge einzeln. |
| **Füge mindestens eine Operation hinzu.** | Es ist noch keine **Operation** angelegt. | Lege unter **Operationen** mindestens eine Operation an. | | **Füge mindestens eine Operation hinzu.** | Es ist noch keine **Operation** angelegt. | Lege unter **Operationen** mindestens eine Operation an. |
Zum Löschen vieler Einträge wechsele oben auf **Löschen** — dort heißt der Knopf **Löschen** statt **Änderungen anwenden**, und die Nachfrage lautet **Möchtest du {count} Einträge wirklich unwiderruflich löschen?**. Zum Löschen vieler Einträge wechsele oben auf **Löschen** — dort heißt der Knopf **Löschen** statt **Änderungen anwenden**, und die Nachfrage lautet **Möchtest du \{count\} Einträge wirklich unwiderruflich löschen?**.
Verifiziert am 2b091d8 2026-10-17 (tibi-admin-nova) Verifiziert am 2b091d8 2026-10-17 (tibi-admin-nova)
@@ -80,7 +80,7 @@ Ohne **OK** geht die Anpassung verloren. Klicke zuerst auf **OK** und speichere
## Direkt bearbeiten und KI-Beschreibung ## Direkt bearbeiten und KI-Beschreibung
In der Upload-Warteschlange („{done} von {total} hochgeladen") bearbeitest du Einträge vor dem Hochladen: **Bearbeiten & hochladen** öffnet den Eintrag im Dialog (**Medieneintrag bearbeiten**), **Schnell-Upload** lädt ohne Bearbeitung. Große Bilder verkleinert tibi-admin automatisch („Bild auf {width}×{height} verkleinert"). In der Upload-Warteschlange („\{done\} von \{total\} hochgeladen") bearbeitest du Einträge vor dem Hochladen: **Bearbeiten & hochladen** öffnet den Eintrag im Dialog (**Medieneintrag bearbeiten**), **Schnell-Upload** lädt ohne Bearbeitung. Große Bilder verkleinert tibi-admin automatisch („Bild auf \{width\}×\{height\} verkleinert").
Fehlt die Bildbeschreibung, erstellt die KI sie selbst („KI beschreibt das Bild…" → „KI-Beschreibung bereit"). Bereits ausgefüllte Beschreibungen bleiben stehen. Bei Bedarf brichst du ab (**KI-Beschreibung abbrechen**) oder stößt sie über **Erneut versuchen** erneut an. Fehlt die Bildbeschreibung, erstellt die KI sie selbst („KI beschreibt das Bild…" → „KI-Beschreibung bereit"). Bereits ausgefüllte Beschreibungen bleiben stehen. Bei Bedarf brichst du ab (**KI-Beschreibung abbrechen**) oder stößt sie über **Erneut versuchen** erneut an.
File renamed without changes.
@@ -10,9 +10,18 @@ Drei Ebenen: Das Audit-Log protokolliert Änderungen, Snapshots frieren Datenst
Suchbegriffe: Änderungshistorie, Verlauf, Wiederherstellen, Snapshot, Datensicherung. Suchbegriffe: Änderungshistorie, Verlauf, Wiederherstellen, Snapshot, Datensicherung.
<CardGrid> <CardGrid>
<Card title="Audit-Log">Protokolliert Änderungen — wer, wann, vorher/nachher.</Card> <Card title="Audit-Log">
<Card title="Snapshots">Frieren Datenstände im Ordner `snapshotPath` ein.</Card> Protokolliert Änderungen — wer, wann, vorher/nachher.
<Card title="Backups">Voll-Export als ZIP für Umzug und Ernstfall.</Card>
</Card>
<Card title="Snapshots">
Frieren Datenstände im Ordner `snapshotPath` ein.
</Card>
<Card title="Backups">
Voll-Export als ZIP für Umzug und Ernstfall.
</Card>
</CardGrid> </CardGrid>
## Audit einschalten und lesen ## Audit einschalten und lesen
File renamed without changes.
@@ -10,8 +10,14 @@ Beides sind JavaScript-Programme in der goja-Laufzeit des **tibi-server** — wi
Suchbegriffe: Cron-Job, geplante Aufgabe, Action-Endpunkt, Kontaktformular-Backend. Suchbegriffe: Cron-Job, geplante Aufgabe, Action-Endpunkt, Kontaktformular-Backend.
<CardGrid> <CardGrid>
<Card title="Jobs">Laufen nach Zeitplan (Cron) — zum Beispiel Aufräumen alle 5 Minuten.</Card> <Card title="Jobs">
<Card title="Aktionen">Laufen auf Abruf per HTTP — zum Beispiel Kontaktformular oder Webhook.</Card> Laufen nach Zeitplan (Cron) — zum Beispiel Aufräumen alle 5 Minuten.
</Card>
<Card title="Aktionen">
Laufen auf Abruf per HTTP — zum Beispiel Kontaktformular oder Webhook.
</Card>
</CardGrid> </CardGrid>
<Aside type="note"> <Aside type="note">
@@ -201,7 +201,7 @@ Design-Regeln aus dem Starter (`api/config.yml`, `meta.agent.designGuide`): Svel
| `closeTitle` | Schließen-Knopf mit ESC-Hinweis („Schließen (ESC)") | `PagebuilderOverlay.svelte:348`, `PagebuilderOverviewOverlay.svelte:725`, `de.json:596` | | `closeTitle` | Schließen-Knopf mit ESC-Hinweis („Schließen (ESC)") | `PagebuilderOverlay.svelte:348`, `PagebuilderOverviewOverlay.svelte:725`, `de.json:596` |
| `viewportDesktop` / `viewportTablet` / `viewportPhone` | Breiten-Presets 1280 / 768 / 375 in der Viewport-Steuerung | `PagebuilderViewportControl.svelte:23-25`, `de.json:605-607` | | `viewportDesktop` / `viewportTablet` / `viewportPhone` | Breiten-Presets 1280 / 768 / 375 in der Viewport-Steuerung | `PagebuilderViewportControl.svelte:23-25`, `de.json:605-607` |
| Drill-Down-Modi `overlay` / `inline` | `overlay` (Standard): Fullscreen-Overlay mit Layer-Stack; `inline`: direkte Blockliste im Formular bei `meta.drillDown: false` | `PagebuilderOverviewOverlay.svelte:4-8,34,61,83`, `PagebuilderWidget.svelte:205,238` | | Drill-Down-Modi `overlay` / `inline` | `overlay` (Standard): Fullscreen-Overlay mit Layer-Stack; `inline`: direkte Blockliste im Formular bei `meta.drillDown: false` | `PagebuilderOverviewOverlay.svelte:4-8,34,61,83`, `PagebuilderWidget.svelte:205,238` |
| `blueprintMoreFields` | „+{count} weitere" — eingeklappte Restfelder auf der Blueprint-Karte | `BlueprintCard.svelte:125`, `de.json:611` | | `blueprintMoreFields` | „+\{count\} weitere" — eingeklappte Restfelder auf der Blueprint-Karte | `BlueprintCard.svelte:125`, `de.json:611` |
| `blueprintChildren` | Kind-Zähler („1 Kind" / „n Kinder") | `BlueprintCard.svelte:101`, `de.json:610` | | `blueprintChildren` | Kind-Zähler („1 Kind" / „n Kinder") | `BlueprintCard.svelte:101`, `de.json:610` |
| `blueprintNoData` | Leertext „Keine Daten" bei fehlenden Block-Daten | `BlueprintCard.svelte:132`, `de.json:612` | | `blueprintNoData` | Leertext „Keine Daten" bei fehlenden Block-Daten | `BlueprintCard.svelte:132`, `de.json:612` |
| ESC / Blur / Drilldown | `Escape` schließt nur den obersten Layer (`isTopLayer`), vorher `activeElement.blur()`; Drilldown-Navigation übernimmt Blur selbst | `PagebuilderOverlay.svelte:190-198,244`, `PagebuilderOverviewOverlay.svelte:378-382` | | ESC / Blur / Drilldown | `Escape` schließt nur den obersten Layer (`isTopLayer`), vorher `activeElement.blur()`; Drilldown-Navigation übernimmt Blur selbst | `PagebuilderOverlay.svelte:190-198,244`, `PagebuilderOverviewOverlay.svelte:378-382` |
@@ -133,12 +133,12 @@ Quelldatei der Vorlage ist `frontend/spa.html`. `ci-modify-config.sh` ersetzt `_
| Marker | Inhalt | | Marker | Inhalt |
|---|---| |---|---|
| `<!--HEAD-->` | Head aus dem SSR-Render | | `{/*HEAD*/}` | Head aus dem SSR-Render |
| `<!--HTML-->` | Gerendertes App-HTML | | `{/*HTML*/}` | Gerendertes App-HTML |
| `<!--PRELOAD-->` | Preload-Hinweise (`scripts/preload-meta.sh`) | | `{/*PRELOAD*/}` | Preload-Hinweise (`scripts/preload-meta.sh`) |
| `__TIMESTAMP__` | Build-Stempel für `/dist`-Assets (Cache-Buster, schon beim Kopieren ersetzt) | | `__TIMESTAMP__` | Build-Stempel für `/dist`-Assets (Cache-Buster, schon beim Kopieren ersetzt) |
| `<!--SSR.ERROR-->` | Render-Fehler als Kommentar (leer im Normalfall) | | `{/*SSR.ERROR*/}` | Render-Fehler als Kommentar (leer im Normalfall) |
| `<!--SSR.COMMENT-->` | Diagnose (`url`, `deps`) als Kommentar | | `{/*SSR.COMMENT*/}` | Diagnose (`url`, `deps`) als Kommentar |
| `<html lang>` | Sprache aus dem URL-Präfix (`de`/`en`, sonst `de`) | | `<html lang>` | Sprache aus dem URL-Präfix (`de`/`en`, sonst `de`) |
Die Sammlung `ssr` (`api/collections/ssr.yml`) hält einen Eintrag je URL: Die Sammlung `ssr` (`api/collections/ssr.yml`) hält einen Eintrag je URL:
File renamed without changes.
File renamed without changes.
@@ -3,6 +3,8 @@ title: 'Hook-Referenz: Daten'
description: 'db, cache, request, response und fs im Hook-JS: Signaturen aus internal/hook, je ein Beispiel und je ein belegter Fehlerfall.' description: 'db, cache, request, response und fs im Hook-JS: Signaturen aus internal/hook, je ein Beispiel und je ein belegter Fehlerfall.'
--- ---
import { Aside } from '@astrojs/starlight/components';
In Hooks sprichst du diese fünf Pakete über `context` an, z. B. `context.db.find(...)` oder `context.request()`. Die Signaturen stammen aus den `// args:`- bzw. `// JS signature:`-Kommentaren in `internal/hook/context_*.go` im tibi-server; wo so ein Kommentar fehlt, ist die Signatur aus Funktionsrumpf und `_test`-Datei abgeleitet und entsprechend markiert. Jede Funktion zeigt genau einen Fehlerfall mit dem originalen Error-Text aus Code oder Tests (Status-Code aus den Hook-Tests, meist `500`). In Hooks sprichst du diese fünf Pakete über `context` an, z. B. `context.db.find(...)` oder `context.request()`. Die Signaturen stammen aus den `// args:`- bzw. `// JS signature:`-Kommentaren in `internal/hook/context_*.go` im tibi-server; wo so ein Kommentar fehlt, ist die Signatur aus Funktionsrumpf und `_test`-Datei abgeleitet und entsprechend markiert. Jede Funktion zeigt genau einen Fehlerfall mit dem originalen Error-Text aus Code oder Tests (Status-Code aus den Hook-Tests, meist `500`).
## db – Collections lesen und schreiben ## db – Collections lesen und schreiben
@@ -12,12 +12,12 @@ Jedes dieser fünf Modelle hat dieselben fünf Routen; dazu kommen Meta- und Sco
| Methode + Pfad | Zweck | Rechte | | Methode + Pfad | Zweck | Rechte |
|---|---|---| |---|---|---|
| `GET /api/v1/user`, `POST /api/v1/user`, `GET /api/v1/user/:id`, `PUT /api/v1/user/:id`, `DELETE /api/v1/user/:id` | Nutzer anlegen, lesen, ändern, löschen (Passwort nie in Antworten) | Nutzer-Scope (`user.read` …) bzw. Admin-Token; Org-/Team-Zugehörigkeit schränkt ein | | `GET /api/v1/user`, `POST /api/v1/user`, `GET /api/v1/user/:id`, `PUT /api/v1/user/:id`, `DELETE /api/v1/user/:id` | Nutzer anlegen, lesen, ändern, löschen (Passwort nie in Antworten) | Nutzer-Scope (z. B. `user.read`) bzw. Admin-Token; Org-/Team-Zugehörigkeit schränkt ein |
| `GET /api/v1/project`, `POST /api/v1/project`, `GET /api/v1/project/:id`, `PUT /api/v1/project/:id`, `DELETE /api/v1/project/:id` | Projekte registrieren und konfigurieren | Projekt-Scope (`project.read` …); Fremd-Orgs sind unsichtbar | | `GET /api/v1/project`, `POST /api/v1/project`, `GET /api/v1/project/:id`, `PUT /api/v1/project/:id`, `DELETE /api/v1/project/:id` | Projekte registrieren und konfigurieren | Projekt-Scope (z. B. `project.read`); Fremd-Orgs sind unsichtbar |
| `GET /api/v1/org`, `POST /api/v1/org`, `GET /api/v1/org/:id`, `PUT /api/v1/org/:id`, `DELETE /api/v1/org/:id` | Mandanten (Orgs) verwalten | Org-Scope (`org.read` …); nur eigene Org | | `GET /api/v1/org`, `POST /api/v1/org`, `GET /api/v1/org/:id`, `PUT /api/v1/org/:id`, `DELETE /api/v1/org/:id` | Mandanten (Orgs) verwalten | Org-Scope (z. B. `org.read`); nur eigene Org |
| `GET /api/v1/team`, `POST /api/v1/team`, `GET /api/v1/team/:id`, `PUT /api/v1/team/:id`, `DELETE /api/v1/team/:id` | Gruppen in einer Org verwalten | Team-Scope (`team.read` …) | | `GET /api/v1/team`, `POST /api/v1/team`, `GET /api/v1/team/:id`, `PUT /api/v1/team/:id`, `DELETE /api/v1/team/:id` | Gruppen in einer Org verwalten | Team-Scope (z. B. `team.read`) |
| `GET /api/v1/token`, `POST /api/v1/token`, `GET /api/v1/token/:id`, `PUT /api/v1/token/:id`, `DELETE /api/v1/token/:id` | Eigene Personal-Access-Tokens (PAT) verwalten | Login-Sitzung nötig — ein PAT darf **nicht** seine eigene Verwaltung aufrufen | | `GET /api/v1/token`, `POST /api/v1/token`, `GET /api/v1/token/:id`, `PUT /api/v1/token/:id`, `DELETE /api/v1/token/:id` | Eigene Personal-Access-Tokens (PAT) verwalten | Login-Sitzung nötig — ein PAT darf **nicht** seine eigene Verwaltung aufrufen |
| `GET /api/v1/token/_scopes` | Katalog aller Scopes, die du aktuell vergeben darfst (gleiche Quelle wie die Anlage-Prüfung) | Nur Nutzer-Login (`X-Auth-Token`/Session); PAT → `403` | | `GET /api/v1/token/_scopes` | Katalog aller Scopes, die du aktuell vergeben darfst (gleiche Quelle wie die Anlage-Prüfung) | Nur Nutzer-Login (`X-Auth-Token` bzw. Session-Cookie); PAT → `403` |
| `GET /api/v1/_meta` | Feld-Metadaten **aller** System-Modelle als `{models: {user: […], …}}` | Wie die Modell-Routen (System-Ebene) | | `GET /api/v1/_meta` | Feld-Metadaten **aller** System-Modelle als `{models: {user: […], …}}` | Wie die Modell-Routen (System-Ebene) |
| `GET /api/v1/_meta/<modell>` | Feld-Metadaten eines Modells (`user`, `project`, `org`, `team`, `token`) | Wie oben | | `GET /api/v1/_meta/<modell>` | Feld-Metadaten eines Modells (`user`, `project`, `org`, `team`, `token`) | Wie oben |
File renamed without changes.
@@ -43,7 +43,9 @@ curl -s -X POST "$SERVER_URL/api/v1/project" \
</Tabs> </Tabs>
<Aside type="note" title="Zwei Login-Pfade, ein Login"> <Aside type="note" title="Zwei Login-Pfade, ein Login">
Direkt am Server heißt die Route `/api/v1/login` (`setup.go`: Gruppe `/api/v1` + `/login`). Im Starter-Stack proxyt der Admin-Host `/api/...` auf den Server — deshalb steht im Beispiel `$ADMIN_URL/api/login`. Beide meinen denselben Login, nur auf verschiedenen Wegen.</Aside> Direkt am Server heißt die Route `/api/v1/login` (`setup.go`: Gruppe `/api/v1` + `/login`). Im Starter-Stack proxyt der Admin-Host `/api/...` auf den Server — deshalb steht im Beispiel `$ADMIN_URL/api/login`. Beide meinen denselben Login, nur auf verschiedenen Wegen.
</Aside>
## Verwechslungs-Fallen ## Verwechslungs-Fallen
@@ -54,7 +54,19 @@ Die Volltextsuche läuft pro Projekt in einem Worker. Nach Config-Änderungen mu
| `POST /api/v1/_/:project/_/admin/reload` | Lädt Projekt-Config neu (Recht `project.reload`) | | `POST /api/v1/_/:project/_/admin/reload` | Lädt Projekt-Config neu (Recht `project.reload`) |
| `POST /api/v1/_/:project/_/admin/regenerate-search` | Baut Suchindex neu auf (`project.regenerate-search`) | | `POST /api/v1/_/:project/_/admin/regenerate-search` | Baut Suchindex neu auf (`project.regenerate-search`) |
| `GET /api/v1/_/:project/_/admin/search-worker/status` | Status des Such-Workers | | `GET /api/v1/_/:project/_/admin/search-worker/status` | Status des Such-Workers |
| `GET /api/v1/_/:project/_/admin/embedding/status` | Status der Embedding-Provider | | `GET /api/v1/_/:project/_/admin/embedding/status` | Status der Embedding-Provider/Gruppen (`project.regenerate-search`; Fehlertexte der Mitglieder sind redigiert) |
| `GET /api/v1/admin/embedding/status` | Derselbe Status serverweit (`server.shutdown`, mit vollen Fehlertexten) |
Der Embedding-Status nennt je Gruppe die Referenz und je Mitglied `state` und `breaker`:
| `state` | Bedeutung | Vorgehen |
|---|---|---|
| `live` | Mitglied antwortet | — |
| `degraded` | Sicherung offen, letzte Aufrufe scheiterten | Log-Zeile des Mitglieds prüfen; `COOLDOWN_AFTER_UNHEALTHY` und `CHECK_INTERVAL` steuern die Rückkehr |
| `quarantined` | Startprüfung fehlgeschlagen (Vektoren weichen ab, Sparse-Form anders, nicht deterministisch) | Modell/Version/Gewichte der Gegenstelle prüfen; mit `CHECK_INTERVAL > 0` kommt sie automatisch zurück, sonst Neustart |
| `offline` | Gruppe hat kein verifiziertes Mitglied (oder Config-Fehler) | Warnzeile `embedding group … offline` im Log; Ursache in `MEMBERS`/`PREFERRED_ORDER`/Provider-Config beheben |
Ist die Gruppe weg, bleiben neue Dokumente ohne Vektoren liegen (sie werden beim nächsten Schreibvorgang oder per `regenerate-search` nachgezogen) — Suchanfragen auf solche Sammlungen brechen mit einer Meldung ab, die die Gruppe nennt.
```bash title="Nach Config-Änderung: Reload plus Suchindex neu aufbauen" ```bash title="Nach Config-Änderung: Reload plus Suchindex neu aufbauen"
curl -X POST http://localhost:8080/api/v1/_/meinprojekt/_/admin/reload \ curl -X POST http://localhost:8080/api/v1/_/meinprojekt/_/admin/reload \
@@ -104,4 +116,4 @@ Waisen findest du per Dateisuche im Upload-Pfad (`media/` bzw. `uploadPath` der
Begriffe wie Collection, Hook oder Namespace klärt [Begriffe](/einstieg/begriffe/). Zum Einrichten siehe [Schnellstart](/einstieg/schnellstart/). Begriffe wie Collection, Hook oder Namespace klärt [Begriffe](/einstieg/begriffe/). Zum Einrichten siehe [Schnellstart](/einstieg/schnellstart/).
Verifiziert am 68b4278 2026-10-15 (tibi-server); Compose-Profile am e3f92eb 2026-10-14 (tibi-svelte-starter). Datei-Cleanup aus `internal/filecleanup/cleanup.go` und Aufrufern (`collection_delete.go`, `collection_bulk_delete.go`, `collection_put.go`, `hook/context_db.go`) Verifiziert am d272041 2026-10-17 (tibi-server); Compose-Profile am e3f92eb 2026-10-14 (tibi-svelte-starter). Datei-Cleanup aus `internal/filecleanup/cleanup.go` und Aufrufern (`collection_delete.go`, `collection_bulk_delete.go`, `collection_put.go`, `hook/context_db.go`). Embedding-Status aus `internal/controller/admin_embedding_status.go` und `internal/embedding/registry.go` (Snapshot/Health-Loop).
@@ -149,10 +149,24 @@ EMBEDDING_GROUP_MAIN_POLICY=prefer-in-order
EMBEDDING_GROUP_MAIN_TIMEOUT_TOTAL=30s EMBEDDING_GROUP_MAIN_TIMEOUT_TOTAL=30s
``` ```
#### Was eine Gruppe beim Start und im Betrieb tut
Die vollständige Fassung steht in `tibi-server/docs/02-configuration.md`; hier die Betreiber-Kurzfassung:
- **Prüfung beim Start.** Referenz ist das erste Mitglied aus `PREFERRED_ORDER`, das antwortet (zwei Versuche). Jedes weitere Mitglied muss dieselben Vektoren liefern: Kosinus ≥ 0.999, zweimal identisch (deterministische Inferenz), gleiche Sparse-Form (beide mit oder beide ohne Sparse-Vektoren) und — wenn eine Gold-Datei verankert ist — auch dagegen passen. Abweichende Mitglieder sind `quarantined`; ohne ein einziges verifiziertes Mitglied ist die Gruppe `offline`. Der Server startet trotzdem.
- **Rückkehr.** Mit `CHECK_INTERVAL > 0` prüft der Health-Loop Quarantänisierte neu gegen die aktuelle Referenz und nimmt sie zurück, sobald sie wieder passen — auch eine `offline` Gruppe kommt so zurück. Ohne `CHECK_INTERVAL` half bisher nur ein Neustart. Nach `COOLDOWN_AFTER_UNHEALTHY` öffnet außerdem die nächste Anfrage das Mitglied wieder halb (nur ein Versuch gleichzeitig).
- **Fehlkonfiguration** (unbekanntes Mitglied, Tippfehler in `PREFERRED_ORDER`, doppelter Gruppen- oder Mitgliedsname, Namensgleichheit mit einem Provider, abweichende Modell-Identität) macht die Gruppe `offline` plus Warnzeile im Log — nie den ganzen Server.
- **Timeouts.** Der bevorzugte Member läuft in das Gruppen-`TIMEOUT_PER_ATTEMPT`; ein Ausweich-Member bekommt `max(Provider-Timeout, Gruppen-Timeout)`, damit ein kurzes Gruppen-Timeout einen kalten lokalen Fallback nicht abschneidet — beim Bulk-Schreiben genauso. Bei `POLICY=fastest-wins` oder `HEDGE_AFTER_SLOW` immer `TIMEOUT_TOTAL` setzen. Sind alle Sicherungen offen, antwortet die Gruppe sofort mit Fehler, statt die Gegenstellen zu bombardieren.
- **Dense-only-Endpoints bleiben kompatibel:** fehlen Sparse-Vektoren, obwohl `SPARSE` aktiv ist, liefert der Provider weiter dense-only Treffer (Hybrid-Suche fällt auf dense zurück) und schreibt einmalig eine Warnung; `SPARSE=false` schaltet sie stumm. Innerhalb einer **Gruppe** lehnt die Prüfung ein Mitglied mit abweichender Sparse-Form ab, damit ein Failover nie still Sparse-Vektoren verliert.
- **Während die Gruppe weg ist**, bleiben Dokumente liegen: Der Batch-Weg meldet sie dem Aufrufer als offen, einzelne Schreibvorgänge lassen den Such-Eintrag stehen. Nachziehen per nächstem Schreibvorgang, `regenerate-search` oder `autoRegenerate`; Suchanfragen brechen mit einer Meldung ab, die die Gruppe beim Namen nennt.
Provider-Env zum Vergleich (Provider `<NAME>` → `EMBEDDING_<NAME>_…`, ebenfalls Bindestrich zu Unterstrich): Provider-Env zum Vergleich (Provider `<NAME>` → `EMBEDDING_<NAME>_…`, ebenfalls Bindestrich zu Unterstrich):
`APIKEY`, `TYPE`, `MODEL`, `MODELPATH`, `DIMENSIONS`, `BASEURL`, `DOCUMENTPREFIX`, `QUERYPREFIX`, `REVISION`, `TIMEOUTPERATTEMPT`, `SLOWTHRESHOLD`, `SPARSE` — `APIKEY`, `TYPE`, `MODEL`, `MODELPATH`, `DIMENSIONS`, `BASEURL`, `DOCUMENTPREFIX`, `QUERYPREFIX`, `REVISION`, `TIMEOUTPERATTEMPT`, `SLOWTHRESHOLD`, `SPARSE` —
z. B. `EMBEDDING_BGE_M3_MODELPATH`, `EMBEDDING_BGE_M3_DIMENSIONS`. z. B. `EMBEDDING_BGE_M3_MODELPATH`, `EMBEDDING_BGE_M3_DIMENSIONS`.
Unbekannte `POLICY`/`DISTRIBUTION` oder ungültige Durations lassen die Gruppe mit Fehler im Log scheitern; unpassende `WEIGHTS`/`RACE_*`-Kombis geben nur eine Warnung und werden ignoriert. Unbekannte `POLICY`/`DISTRIBUTION` oder ungültige Durations lassen die Gruppe mit Fehler im Log scheitern; unpassende `WEIGHTS`/`RACE_*`-Kombis geben nur eine Warnung und werden ignoriert.
Provider- und Gruppen-Env gelten auch mit Bindestrich im Namen (`EMBEDDING_BGE-M3_TYPE`): der Server übernimmt solche Alt-Schlüssel beim Start auf die Unterstrich-Form und schreibt eine Hinweiszeile ins Log.
Wo die **Gold-Dateien** für die Mitglieds-Prüfung liegen, setzt du global: `embedding.goldenPath` bzw. `EMBEDDING_GOLDEN_PATH` (Vorgabe `testdata/embedding-golden` relativ zum Arbeitsverzeichnis); erzeugen mit `go run ./cmd/embed-probe-golden`. Ohne verankerte Gold-Datei protokolliert die Gruppe das beim Start und prüft nur gegen die Referenz.
## Admin-Tokens ## Admin-Tokens
@@ -185,4 +199,4 @@ api:
Weiter: [Betrieb und Beobachtung](/server/betrieb/) zeigt Profile und Metriken. [Sicherheit](/server/sicherheit/) erklärt Token-Arten und Ratelimit. Weiter: [Betrieb und Beobachtung](/server/betrieb/) zeigt Profile und Metriken. [Sicherheit](/server/sicherheit/) erklärt Token-Arten und Ratelimit.
Verifiziert am 68b4278 2026-10-15 (tibi-server); Starter-Compose am e3f92eb 2026-10-14 (tibi-svelte-starter); Go-Version aus `go.mod`. Upload-Kaskade aus `internal/controller/collection.go` (resolveAllowedMimeTypes), Gruppen-Env aus `internal/config/server.go` und `internal/config/embedding_group.go`. Verifiziert am d272041 2026-10-17 (tibi-server); Starter-Compose am e3f92eb 2026-10-14 (tibi-svelte-starter); Go-Version aus `go.mod`. Upload-Kaskade aus `internal/controller/collection.go` (resolveAllowedMimeTypes), Provider-/Gruppen-Env und `goldenPath` aus `internal/config/server.go`, Gruppen-Regeln aus `internal/config/embedding_group.go`, Prüfung/Quarantäne/Rückkehr/Timeouts aus `internal/embedding/{registry,probe,group}.go`, Pending-Verhalten aus `internal/utils/search.go`.
File renamed without changes.
File renamed without changes.
+29
View File
@@ -18,6 +18,11 @@
--tibi-sidebar: #f8f9fa; --tibi-sidebar: #f8f9fa;
--tibi-topbar: #ffffff; --tibi-topbar: #ffffff;
--tibi-card-border: #e5e7eb; --tibi-card-border: #e5e7eb;
/* Inline-Code-Chips (hell): Grau mit Rahmen statt Flaeche ohne Kante */
--tibi-inline-bg: #e9ecf1;
--tibi-inline-border: #d3d7de;
--tibi-inline-text: #1f2937;
} }
:root[data-theme='dark'] { :root[data-theme='dark'] {
@@ -29,6 +34,11 @@
--tibi-sidebar: #16181d; --tibi-sidebar: #16181d;
--tibi-topbar: #16181d; --tibi-topbar: #16181d;
--tibi-card-border: #333a44; --tibi-card-border: #333a44;
/* Inline-Code-Chips (dunkel): deutlich abgesetzt vom Seitenhintergrund (#16181d) */
--tibi-inline-bg: #333945;
--tibi-inline-border: #525b6b;
--tibi-inline-text: #eef1f6;
} }
/* ---------- Flächen in Nova-Nähe ---------- */ /* ---------- Flächen in Nova-Nähe ---------- */
@@ -114,3 +124,22 @@
.sl-link-card:hover { .sl-link-card:hover {
border-color: var(--tibi-nova); border-color: var(--tibi-nova);
} }
/* ---------- Inline-Code: eigenstaendige Chips, auch im Dark Mode lesbar ----------
Starlight faerbt Inline-Code nur mit --sl-color-bg-inline-code (gray-5/gray-6).
Im Dark Mode liegt das zu nah am Seitenhintergrund, deshalb fester Rahmen,
eigene Textfarbe und mehr Innenabstand. Betrifft nur Inline-Code — Codebloecke
(Expressive Code) bleiben unberuehrt, weil `pre > code` nicht adressiert wird. */
.sl-markdown-content :not(pre) > code:not(:where(.not-content *)) {
background-color: var(--tibi-inline-bg);
border: 1px solid var(--tibi-inline-border);
border-radius: 0.3rem;
color: var(--tibi-inline-text);
font-weight: 500;
padding: 0.12em 0.4em;
}
/* Methoden-/Status-Spalten (etwa `GET`, `POST`, `404`) umbrechen nicht mitten im Wort */
.sl-markdown-content td:first-child code:not(:where(.not-content *)) {
white-space: nowrap;
}