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

+132
View File
@@ -0,0 +1,132 @@
---
title: 'Suche'
description: 'Textindex, search-Blöcke, Hybrid-Suche, Neuaufbau des Index aus Projektsicht.'
---
import { Aside, Steps } from '@astrojs/starlight/components';
Die Suche läuft über `search`-Blöcke in der Sammlungs-YAML plus Abfrage per `q` und `qName`.
Suchbegriffe: Volltextsuche, Hybrid-Suche, Suchindex neu aufbauen.
## Index anlegen
`content.yml` definiert drei Indexe:
| Index | Schlüssel | Zweck |
|---|---|---|
| `content_text` | `$text:$**`, `defaultLanguage: none` | Volltext über alle Felder |
| `path_lang` | `path` + `lang`, unique | Pfad je Sprache eindeutig |
| `translation` | `translationKey` + `lang`, unique | Übersetzung je Sprache eindeutig |
`navigation.yml` und `medialib.yml` nutzen nur einen `default`-Index im Modus `regex` (kein Textindex nötig).
## search-Blöcke
```yaml title="content.yml — search-Blöcke (gekürzt)"
# content.yml — search (gekürzt)
search:
- name: combined # Hybrid aus semantic + fuzzy
mode: combined
rrf: { k: 60, topK: 100, weights: { semantic: 1.5, fuzzy: 1.0 } }
- name: fuzzy
mode: ngram
autoRegenerate: true
fields: [name, path, meta.title, meta.description, blocks.headline, blocks.subline,
blocks.text, blocks.items.title, blocks.items.text, blocks.items.question, blocks.items.answer]
- name: semantic
mode: vector
autoRegenerate: true
fields: [name, meta.title, meta.description, blocks.headline, blocks.subline,
blocks.text, blocks.items.title, blocks.items.text, blocks.items.question, blocks.items.answer]
vector: { provider: bge_m3 }
```
| `qName` | Sammlung | Modus |
|---|---|---|
| `combined` | `content` | Hybrid (RRF aus `semantic` + `fuzzy`) |
| `fuzzy` | `content` | n-gram, fehlertolerant |
| `semantic` | `content` | Vektor (`bge_m3`) |
| `default` | `tag`, `navigation`, `comment`, `medialib` | regex |
## Lesen mit q
Grundlagen (Filter, Lookup, Facetten) stehen unter [Lesen und suchen](/projekt/abfragen-lesen/). Hier nur die Suche:
```text title="Suche per q und qName"
GET …/content?q=kontakt&qName=combined
GET …/tag?q=hero
GET …/content?q=kontakt&qName=combined&qExplain=1
```
- `q` ist der Suchtext, `qName` wählt den Block (ohne `qName` greift der Standard).
- `qExplain=1` zeigt die Gewichtung — nützlich beim Debuggen.
- Suche mit und ohne `lookup` testen: `?q=…&qName=combined&lookup=blocks.heroImage.image:medialib,blocks.image:medialib` löst Treffer-Bilder gleich auf.
## Hybrid-Kurzüberblick
`combined` mischt Vektor- und n-gram-Treffer per Reciprocal Rank Fusion (`internal/rankfusion`, tibi-server):
```text title="RRF-Formel (combined)"
score(doc) += weight / (k + rank) # k = 60, weights: semantic 1.5, fuzzy 1.0
```
- `topK: 100` begrenzt die Kandidaten je Quelle **und** kappt das Fusionsergebnis (`MergeRRF`).
- Weitere Strategien (`score_rrf`, `score_sum`, `rank_score`, `hybrid`), Normierung (`minmax`, `zscore`), Gewichte (`scoreWeight`) und Quellen-Config (`sourceConfig`) existieren im Code **und** sind als `rrf`-Block in der Projekt-YAML einstellbar — aus Projektsicht reicht `combined` mit den Starter-Gewichten.
- Alte Score-Beispiele aus Erzähl-Docs nicht übernehmen — nur diese Formel gilt.
## Suchmodi im Überblick
Alle Modi aus `collection.go:314` — `fields` gilt für `regex` und `ngram`, `vector.provider` muss auf einen Server-Provider zeigen (`collection.go:377`):
| Modus | Einsatzfall | Braucht Index/Aufbau |
|---|---|---|
| `text` | MongoDB-Volltext, eine Textstelle pro Sammlung | Ja — Mongo-Textindex (`hasTextIndex`; nur **ein** Textindex je Sammlung) |
| `regex` | Einfache Teilübereinstimmung (`default`-Block in `tag`, `navigation`, `comment`, `medialib`) | Nein — läuft direkt auf den Feldern, kein Aufbau, keine Queue |
| `eval` | Eigene JS-Bewertung pro Treffer (`eval`-Ausdruck) | Nein — Ausdruck läuft zur Abfragezeit, optional `score.expr` |
| `filter` | Feste MongoDB-Filtervorlage mit `$subst` | Nein — Filter zur Abfragezeit, optional `score.expr` |
| `ngram` | Fehlertolerante Teilwortsuche (Starter-`fuzzy`) | Ja — `_search`-Anreicherung mit `autoRegenerate`, Queue bis zum Worker-Lauf |
| `vector` | Semantische Suche (Starter-`semantic`, Provider `bge_m3`) | Ja — Embeddings über Embedding-Provider, Queue bis zum Worker-Lauf |
| `combined` | Hybrid aus mehreren Blöcken (Starter-`combined` = RRF aus `semantic` + `fuzzy`) | Je beteiligtem Block (siehe oben); Fusion selbst braucht keinen eigenen Index |
<Aside type="note">
`qName` wählt den Block, `q` liefert den Suchtext. Ohne `qName` greift der erste Block der Sammlung. `text`, `eval` und `filter` kommen im Starter nicht vor — dort zählen `regex` (kein Aufbau), `ngram`/`vector` (Aufbau nötig) und `combined` (Fusion).
</Aside>
## Index aufbauen und prüfen
<Steps>
1. Bearbeite Einträge normal — `fuzzy` und `semantic` haben `autoRegenerate: true` und bauen sich selbst nach.
2. Beobachte im tibi-admin den Hinweis `SearchIndexBuildNotice`: Er erscheint im Projekt-Header (Summe offener `_queue`-Einträge aller `ngram`-/`vector`-Sammlungen) und im Sammlungs-Header der betroffenen Sammlung.
3. Warte, bis der Hinweis nach dem nächsten Polling von selbst verschwindet (`_queue` = 0). Der `regex`-Modus (`default`) hat keine Queue und keinen Hinweis.
4. Bleibt die Queue stehen oder hast du die Config geändert: `POST /_/:project/_/admin/regenerate-search` aufrufen — optional mit Body `{ collection, modes, names, ids, filter, force }` (`admin_regenerate_search.go:17-25`). Mit `?async=1` nur einreihen und sofort antworten (`{"message": "queued", …}`), ohne `filter` auch synchron möglich. Ungültige IDs ohne einzige gültige Hex-ID brechen mit `400 no valid document IDs in request` ab.
5. Status prüfen: `GET /_/:project/_/admin/search-worker/status` zeigt pro Provider Aktiv-Flag, Queue-Tiefe, laufenden Job und Fortschrittszähler (`admin_search_status.go:10-15`). Auth jeweils: Scope `project.regenerate-search` plus Token-Flag `AllowRegenerateSearch`.
</Steps>
<Aside type="note" title="Neuaufbau aus Sicht des Projekts">
Aus Projektsicht musst du nichts einrichten — lass `autoRegenerate: true` in der YAML stehen, dann pflegen sich `fuzzy`- und `semantic`-Indizes selbst. Der Regenerate-Flow im tibi-admin (Test-Plan `41-search-index-status`) nutzt genau den Endpunkt aus Schritt 4.
</Aside>
## Was der Vektor-Pfad zusätzlich braucht
- **Provider-Name:** `vector.provider` (z. B. `bge_m3`) muss mit einem Servernamen aus der Embedding-Registry übereinstimmen (`collection.go:377`) — sonst löst die Config nicht auf. Unbekannte Provider-Typen brechen ab, defekte einzelne Provider werden übersprungen (`registry.go:58-70`).
- **Gesundheit prüfen:** `GET /_/:project/_/admin/embedding/status` zeigt Gruppen-/Mitglieder-Gesundheit (an diesem Scope sind Fehlertexte redigiert, nur Zustände und Zähler; Volldetail nur am Server-Endpunkt `GET /api/v1/admin/embedding/status`, `admin_embedding_status.go:49-72`).
- **Feinheiten (optional):** `documentPrefix`/`queryPrefix`, `overflow: truncate` (Standard) oder `chunk` mit `chunkSize` (Standard 512) und `chunkOverlap` (Standard 50) — `collection.go:376-383`.
- **Debug:** Mit Header `X-Search-Debug: true` antwortet die Suche mit `X-Search-Config`, `X-Search-Timing` und `X-Search-Sources` (Treffer-/Score-Spannen je Quelle, `search_debug.go:145-159`).
## Fehlerbilder
| Symptom | Ursache | Lösung |
|---|---|---|
| Leere Scores / keine Treffer trotz Daten | Index fehlt oder `_queue` noch > 0 | Hinweis im Admin abwarten, dann erneut suchen |
| `combined` findet nichts, `fuzzy` schon | Vektor-Anteil ohne Embeddings | `semantic`-Felder und Provider (`bge_m3`) prüfen |
| `400 no valid document IDs in request` | Regenerate mit nur ungültigen `ids` | Hex-IDs prüfen oder `ids` weglassen (Vollaufbau) |
| `404 collection "…" not found` | Regenerate mit falschem `collection`-Namen | Namen gegen `api/collections/*.yml` prüfen |
| Worker-Queue steht | Provider defekt oder überlastet | `search-worker/status` und `embedding/status` lesen, dann Regenerate mit `force: true` |
| Suche über `tag`/`navigation` unscharf | `default` ist `regex`, kein Fuzzy | Erwartung anpassen oder `content`-Indexe nutzen |
| `?facet=` an der Suche wirkungslos | Facetten sind eigene Route (`…/_facet`) | [Lesen und suchen](/projekt/abfragen-lesen/) beachten |
Weiter: [Lesen und suchen](/projekt/abfragen-lesen/), [Sammlungen anlegen](/projekt/sammlungen-anlegen/), [Felder und Widgets](/projekt/felder-widgets/).
Verifiziert am e3f92eb 2026-10-14 (tibi-svelte-starter), am 68b4278 2026-10-15 (tibi-server, rankfusion), am 2b091d8 2026-10-13 (tibi-admin-nova, Test-Plan 41-search-index-status)