Files
rustycms/CLAUDE.md

137 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RustyCMS AI Context
> **Für AI-Tools**: Diese Datei (CLAUDE.md) wird von Claude Code gelesen.
> Cursor liest `.cursor/rules/project.mdc`.
> Beide Dateien haben identischen Inhalt — beim Ändern bitte **beide** aktualisieren.
---
## Arbeitsweise: Erklärungen beim Implementieren
Beim Umsetzen von Aufgaben soll die AI **kurz und nachvollziehbar erklären, was sie tut**:
1. **Ziel und Vorgehen**: Zu Beginn (oder im ersten Schritt) in 12 Sätzen sagen, **was** umgesetzt wird und **wie** (welche Dateien, welches Muster).
2. **Schritte sichtbar machen**: Bei mehreren Änderungen z. B. kurz benennen: „Als Nächstes: …“, „Dann: …“, damit der Ablauf klar ist.
3. **Ungewöhnliche Entscheidungen begründen**: Wenn etwas von Konventionen abweicht (andere Lib, anderer Ordner, Workaround), **warum** (z. B. „Damit Tailwind-Klassen zuverlässig greifen“).
4. **Am Ende zusammenfassen**: In 24 Sätzen: Was wurde geändert, wo es liegt, was der Nutzer prüfen kann.
Die Erklärungen sollen **knapp** bleiben, aber so formuliert sein, dass jemand den Gedankengang und die Änderungen nachvollziehen kann, ohne alles selbst zu lesen.
---
## Projektstruktur
```
rustycms/
├── src/ # Rust API
│ ├── api/
│ │ ├── handlers.rs # AppState + alle Content-Handler
│ │ ├── response.rs # format_references, expand/collapse_asset_urls
│ │ ├── assets.rs # Asset-Endpoints (Upload, Serve, Delete, Folders)
│ │ └── routes.rs # Axum-Router
│ ├── schema/
│ │ └── validator.rs # normalize_reference_arrays (single + array refs)
│ └── store/ # FileStore / SqliteStore
├── types/ # Schema-Definitionen (*.json5)
├── content/ # Inhalte als JSON5-Dateien
│ └── assets/ # Bild-Assets (mit Unterordnern)
└── admin-ui/ # Next.js Admin UI (Port 3001)
├── src/components/ # React-Komponenten
├── src/lib/api.ts # API-Client
└── messages/ # i18n (en.json, de.json)
```
## Dev starten
```bash
./dev.sh # API (Port 3000) + Admin UI (Port 3001)
cd admin-ui && npm run dev # nur Admin UI
cargo run # nur API
```
## Konfiguration
Alle Optionen per Env-Variable (`.env.example` als Vorlage) oder CLI-Flag:
| Env-Variable | CLI-Flag | Default | Zweck |
|---|---|---|---|
| `RUSTYCMS_TYPES_DIR` | `--types-dir` | `./types` | Schema-Definitionen |
| `RUSTYCMS_CONTENT_DIR` | `--content-dir` | `./content` | Content-Dateien |
| `RUSTYCMS_BASE_URL` | — | `http://host:port` | Öffentliche API-URL (für Asset-URLs) |
| `RUSTYCMS_API_KEY` | — | unset | Auth für POST/PUT/DELETE |
| `RUSTYCMS_LOCALES` | — | unset | z.B. `de,en` (erstes = Default) |
| `RUSTYCMS_STORE` | — | `file` | `file` oder `sqlite` |
| `RUSTYCMS_CORS_ORIGIN` | — | `*` | Erlaubte CORS-Origin |
| `RUSTYCMS_CACHE_TTL_SECS` | — | `60` | Response-Cache TTL (0 = aus) |
## Asset-URL-Strategie
Assets werden immer mit **relativem Pfad** auf Disk gespeichert:
```
src: "/api/assets/ordner/datei.jpg"
```
Die API expandiert beim Ausliefern automatisch abhängig vom Kontext:
- **GET mit `_resolve`** (Frontend-Consumer): relativ → `https://api.example.com/api/assets/...`
- **GET ohne `_resolve`** (Admin UI bearbeitet): Pfad bleibt relativ
- **POST/PUT** (Speichern): absolute URLs werden vor dem Schreiben kollabiert
Implementierung: `src/api/response.rs``expand_asset_urls()` / `collapse_asset_urls()`
## Collection-Hierarchie (wichtig!)
| Collection | Zweck | Pflichtfelder |
|---|---|---|
| `img` | Rohes Bild-Asset | `src` (relativer Pfad), `description` |
| `image` | Layout-Komponente | `name`, `img` (Referenz auf `img`-Collection) |
**Regel:** `postImage` → referenziert `img` direkt. `row1Content` / `row2Content` etc. → erwarten `image`-Einträge, **nicht** `img`.
Workflow beim Hinzufügen eines Bildes zu einer Content-Row:
1. `img`-Eintrag erstellen (`src: "/api/assets/..."`)
2. `image`-Eintrag erstellen (referenziert den `img`-Eintrag)
3. `image`-Slug in `rowXContent` des Posts eintragen
## Referenz-Handling
### ReferenceOrInlineField (Admin UI)
- `value` ist String → Reference-Modus (Slug-Picker)
- `value` ist Objekt ohne `_slug` → Inline-Modus (Felder direkt eingeben)
- `value` ist Objekt mit `_slug` → aufgelöste Referenz vom API → wird automatisch zu Reference-Modus normalisiert
Normalisierung: `admin-ui/src/components/ReferenceOrInlineField.tsx` via `normalizedValue` memo.
### Normalisierung beim Schreiben (Rust)
`validator::normalize_reference_arrays()` konvertiert vor dem Speichern:
- Einzelne `reference`/`referenceOrInline`-Felder: `{_slug: "foo", ...}``"foo"`
- Array-Items: gleiche Normalisierung per Element
- Aufgerufen in `create_entry` und `update_entry`
## Admin UI Konventionen
- **i18n**: next-intl, Cookie-basiert. Neue Keys immer in **beiden** Dateien eintragen: `admin-ui/messages/en.json` + `messages/de.json`
- **Tailwind v4**: `@plugin "tailwindcss-animate"` (nicht `@import`)
- **Kein Dark Mode**: `@media (prefers-color-scheme: dark)` wurde entfernt, keine `dark:`-Klassen
- **Scroll**: `html, body { height: 100%; overflow: hidden }` — Sidebar scrollt unabhängig von der Seite
- **Neue Komponente**: immer prüfen ob Übersetzungs-Namespace in beiden Message-Dateien vorhanden
### Asset-/Bild-URL-Felder (generisch halten)
- **Keine Heuristik**: Die Admin-UI soll String-Felder nicht anhand von Feldname oder Beschreibung (z.B. „Image URL“) als Bild-URL interpretieren. Das wäre nicht generisch.
- **Explizites Widget**: Bildvorschau + Asset-Picker nur anzeigen, wenn im Schema für das Feld **explizit** `widget: "imageUrl"` (oder `"assetUrl"`) gesetzt ist. Implementierung: `ContentForm.tsx` Entscheidung nur über `def.widget === "imageUrl"`, nicht über `isImageUrlField()` mit Name/Description.
- **Typ `img`**: Bleibt generisch (kein Widget auf `src`) → normales Textfeld. Wo gewollt, kann ein eigener Typ oder ein Feld mit `widget: "imageUrl"` die Bild-UI bekommen.
## Axum Routing
Literale Routen haben Vorrang vor Wildcard-Routen — Reihenfolge egal, Axum löst korrekt auf:
```rust
.route("/api/assets/folders", get(...).post(...)) // matcht vor:
.route("/api/assets/*path", get(...).delete(...))
```
## Rust-spezifisch
- `clap` braucht Feature `"env"` für Env-Var-Support bei CLI-Args
- `AppState` in `src/api/handlers.rs` — alle neuen geteilten Ressourcen hier hinzufügen
- Hot-Reload: Änderungen in `types/` werden automatisch geladen (kein Neustart nötig)
- Cache wird bei Schreib-Operationen invalidiert (`cache.invalidate_collection()`)