Translate AGENTS.md to English and add UI design rules

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-23 19:50:45 +02:00
co-authored by Devin
parent 1940ec29be
commit e9561ddb2a
+46 -14
View File
@@ -2,20 +2,52 @@
## Workflow
- Keine Headless-Browser-Verifizierung nach UI-Änderungen - der User testet selbst. Nur bei wiederholten Fehlern nachfragen/debuggen. Syntax-Checks sind okay; `npm run build` nur ausführen, wenn der User es ausdrücklich will.
- Dev-Server: `npm run dev` (Port 3000). Der User lässt ihn meist selbst laufen - nicht blind starten, erst prüfen ob bereits aktiv.
- DB: MariaDB via `docker-compose.yml` (Container `nuxt-shadcn-demo-db`), URL in `DATABASE_URL`. Migrationen: `npm run db:generate` (Schema → SQL), `npm run db:migrate` (anwenden; im Dev-Modus auch automatisch per Nitro-Plugin).
- Keine Git-Commits von sich aus - nur committen, wenn der User es explizit anweist.
- No headless-browser verification after UI changes - the user tests manually. Only ask/debug after repeated failures. Syntax checks are fine; run `npm run build` only if the user explicitly asks for it.
- Dev server: `npm run dev` (port 3000). The user usually runs it themselves - do not start it blindly, check whether it is already running first.
- DB: MariaDB via `docker-compose.yml` (container `nuxt-shadcn-demo-db`), URL in `DATABASE_URL`. Migrations: `npm run db:generate` (schema → SQL), `npm run db:migrate` (apply; also runs automatically via Nitro plugin in dev mode).
- No Git commits on your own - only commit when the user explicitly instructs it.
## Conventions
- Vue 3 `<script setup lang="ts">`, Composition API, shadcn-vue-Komponenten unter `app/components/ui/`.
- Theme-Farben über semantische Tokens (`bg-primary`, `border-primary`, …), nie hardcoden. Themes werden in `app/assets/css/tailwind.css`, `app/composables/useTheme.ts` und `app/components/ThemeSelector.vue` gepflegt - `--primary` ist die Signaturfarbe, `--accent` der Kontrast; `neo` hat `--radius: 0` (eckig), daher dort `rounded-md`/`rounded-sm` statt `rounded-2xl`/`rounded-xl` verwenden.
- Tailwind: `size-*` statt `w-* h-*`, `gap-*` statt `space-x/y-*`, `cn()` für Konditionales.
- Live-Updates laufen über `server/routes/_ws.ts` + `broadcastToAll`; Client-Verarbeitung in `app/plugins/notifications.client.ts`.
- Kommentar-Reactions: `useCommentReactions` (State-Map pro Comment-Id). Emoji-Auswahl über `UnicodeEmojiPicker.vue` (emoji-picker-element Web-Component, alle Unicode-Emojis mit Suche; Daten lokal aus `emoji-picker-element-data`, UI-Texte aus `emoji-picker-element/i18n`).
- Terminologie intern: **Emoji** = Unicode-Reactions (`UnicodeEmojiPicker.vue`, `CommentReactionPicker.vue`, `emoji`-Spalten in den Reaction-Tabellen); **Sticker** = hochgeladene Medien (`StickerPicker.vue`, `StickerMedia.vue`, `useStickers`, `shared/utils/stickers.ts`, Tabelle `stickers`, `/api/stickers`, `/uploads/stickers`, Permission `sticker.standard`). Shortcode-Rendering `:name:` über `stickerParts()`.
- Thread-Benachrichtigungen: `threadSubscriptions` speichert Abos und explizite Deaktivierungen. Erstellen, Kommentieren, Favorisieren und Post-Reactions rufen `ensureThreadSubscription()` auf; ein deaktiviertes Abo darf dadurch nicht reaktiviert werden. `notifyComment()` benachrichtigt aktive Abonnenten und erwähnte Nutzer, respektiert Deaktivierungen auch bei Erwähnungen und fasst Meldungen pro Nutzer/Thread zusammen.
- Direktnachrichten: Tabelle `direct_messages` (sender/recipient/content/read_at). API unter `/api/messages` (Liste, Verlauf mit `?before=`, Senden, `read`). Live per `pushToUser` mit `dm`-Event (Message + sender/recipient-Userinfos) und `dm-read`-Lesebestätigung. Client-State in `useDirectMessages` - `openPartnerId` markiert die offene Konversation; `liveMessageHandler`/`liveReadHandler`/`liveReactionHandler` sind Modul-Singletons, die die Messages-Seite registriert. DM-Reactions: Tabelle `direct_message_reactions` (message/user/emoji, `seen_at`), Toggle unter `/api/messages/reactions/:messageId`, WS-Event `dm-reaction` an beide Teilnehmer. Ungesehene fremde Reactions zählen wie ungelesene Nachrichten für den Unread-Badge; `read` setzt `seen_at` mit. DM-Anhänge: `attachments`-JSON-Spalte in `CommentAttachment`-Form (`meta.voice` = Sprachnachricht); `POST /api/messages/:userId` akzeptiert multipart mit `files`-/`voice`-Feldern wie das Kommentar-Formular, Rendering über `CommentAttachments`.
- Berechtigungen: Katalog in `shared/utils/permissions.ts` (`PERMISSIONS`), Server-Check per `requirePermission(event, key)` in `server/utils/auth.ts` - `admin`-Rolle ist implizit wildcard. Client-seitig `can(key)` aus `useAuth`. Seeding via `seedPermissions()` im Migrations-Plugin/Script.
- i18n: `@nuxtjs/i18n`, Locales `de` (Default) + `en` in `i18n/locales/*.json` - beide Dateien immer gleiche Key-Struktur halten. Alle UI-Strings über `$t()`/`t()`, nie hardcoden. `strategy: 'no_prefix'`, Locale-Cookie `i18n_locale`. Permission-Labels im Admin über `admin.perms.<key>`; Effekt-/Theme-Namen bleiben Eigennamen.
- Vue 3 `<script setup lang="ts">`, Composition API, shadcn-vue components under `app/components/ui/`.
- Theme colors via semantic tokens (`bg-primary`, `border-primary`, …), never hardcode them. Themes are maintained in `app/assets/css/tailwind.css`, `app/composables/useTheme.ts` and `app/components/ThemeSelector.vue` - `--primary` is the signature color, `--accent` the contrast; `neo` has `--radius: 0` (square), so use `rounded-md`/`rounded-sm` instead of `rounded-2xl`/`rounded-xl` there.
- Tailwind: `size-*` instead of `w-* h-*`, `gap-*` instead of `space-x/y-*`, `cn()` for conditional classes.
- Live updates run through `server/routes/_ws.ts` + `broadcastToAll`; client-side handling in `app/plugins/notifications.client.ts`.
- Comment reactions: `useCommentReactions` (state map per comment ID). Emoji selection via `UnicodeEmojiPicker.vue` (emoji-picker-element web component, all Unicode emojis with search; local data from `emoji-picker-element-data`, UI texts from `emoji-picker-element/i18n`).
- Internal terminology: **Emoji** = Unicode reactions (`UnicodeEmojiPicker.vue`, `CommentReactionPicker.vue`, `emoji` columns in the reaction tables); **Sticker** = uploaded media (`StickerPicker.vue`, `StickerMedia.vue`, `useStickers`, `shared/utils/stickers.ts`, table `stickers`, `/api/stickers`, `/uploads/stickers`, permission `sticker.standard`). Shortcode rendering `:name:` via `stickerParts()`.
- Thread notifications: `threadSubscriptions` stores subscriptions and explicit opt-outs. Creating, commenting, favoriting and post reactions call `ensureThreadSubscription()`; a disabled subscription must not be reactivated by this. `notifyComment()` notifies active subscribers and mentioned users, respects opt-outs even for mentions and groups notifications per user/thread.
- Direct messages: table `direct_messages` (sender/recipient/content/read_at). API under `/api/messages` (list, history with `?before=`, send, `read`). Live via `pushToUser` with `dm` event (message + sender/recipient user info) and `dm-read` read receipt. Client state in `useDirectMessages` - `openPartnerId` marks the open conversation; `liveMessageHandler`/`liveReadHandler`/`liveReactionHandler` are module singletons registered by the messages page. DM reactions: table `direct_message_reactions` (message/user/emoji, `seen_at`), toggle under `/api/messages/reactions/:messageId`, WS event `dm-reaction` to both participants. Unseen foreign reactions count like unread messages for the unread badge; `read` sets `seen_at` as well. DM attachments: `attachments` JSON column in `CommentAttachment` shape (`meta.voice` = voice message); `POST /api/messages/:userId` accepts multipart with `files`/`voice` fields like the comment form, rendered via `CommentAttachments`.
- Permissions: catalog in `shared/utils/permissions.ts` (`PERMISSIONS`), server check via `requirePermission(event, key)` in `server/utils/auth.ts` - the `admin` role is an implicit wildcard. Client-side `can(key)` from `useAuth`. Seeding via `seedPermissions()` in the migration plugin/script.
- i18n: `@nuxtjs/i18n`, locales `de` (default) + `en` in `i18n/locales/*.json` - always keep both files with identical key structure. All UI strings via `$t()`/`t()`, never hardcoded. `strategy: 'no_prefix'`, locale cookie `i18n_locale`. Permission labels in admin via `admin.perms.<key>`; effect/theme names remain proper nouns.
## UI & Design System Rules (Anti-Vibe-Coding)
When auditing, refactoring, or building UI, strictly apply these design rules:
### 1. Visual Hierarchy & Card Fatigue
- **Eliminate "Card-in-Card" nesting**: Never place a Card inside another Card. Use whitespace, divider lines (`<Separator />`), or subtle background shifts instead of borders.
- **Icon Restraint**: Do NOT add icons to every button, table header, or card title. Icons should only exist when they materially aid scanning (max 1 icon per actionable group).
- **Border Reduction**: If a border doesn't separate distinct functional contexts, remove it. Favor spacing (`gap`, `space-y`) over border boxes.
### 2. Spacing & Rhythm (8pt Grid)
- Stick strictly to a 4-step spacing hierarchy:
- Tight (inline/tags): `gap-1.5` to `gap-2`
- Grouped elements (form fields, lists): `space-y-3` / `gap-3`
- Section spacing: `space-y-6` / `gap-6`
- Layout/Page level: `py-8` to `py-12`, max container `max-w-7xl mx-auto px-4 sm:px-6 lg:px-8`
- Do not mix random paddings (e.g. `p-5`, `p-7`). Stick to `p-4` (compact) or `p-6` (standard).
### 3. Typography & Contrast
- Contrast comes from weight and color, not just font size.
- Secondary text must consistently use `text-muted-foreground text-xs` or `text-sm`.
- Main headers: Use `tracking-tight font-semibold` instead of generic `font-bold`.
- Avoid 5 different font sizes on a single screen. Max 3 sizes per view (e.g. Title `text-xl`, Body `text-sm`, Metadata `text-xs`).
### 4. Nuxt 4 & Shadcn Vue Specifics
- Always leverage semantic Shadcn primitives (`CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`) properly—never inline custom headers inside `CardContent`.
- Keep templates clean: Move complex inline Tailwind classes into reusable Nuxt components or tailwind-variants (`cva`) if reused.
- Ensure proper empty states (`EmptyState` pattern) and loading skeletons instead of blank spaces.