Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
5.9 KiB
5.9 KiB
Nuxt shadcn Demo
Workflow
- No headless-browser verification after UI changes - the user tests manually. Only ask/debug after repeated failures. Syntax checks are fine; run
npm run buildonly 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(containernuxt-shadcn-demo-db), URL inDATABASE_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 components underapp/components/ui/. - Theme colors via semantic tokens (
bg-primary,border-primary, …), never hardcode them. Themes are maintained inapp/assets/css/tailwind.css,app/composables/useTheme.tsandapp/components/ThemeSelector.vue---primaryis the signature color,--accentthe contrast;neohas--radius: 0(square), so userounded-md/rounded-sminstead ofrounded-2xl/rounded-xlthere. - Tailwind:
size-*instead ofw-* h-*,gap-*instead ofspace-x/y-*,cn()for conditional classes. - Live updates run through
server/routes/_ws.ts+broadcastToAll; client-side handling inapp/plugins/notifications.client.ts. - Comment reactions:
useCommentReactions(state map per comment ID). Emoji selection viaUnicodeEmojiPicker.vue(emoji-picker-element web component, all Unicode emojis with search; local data fromemoji-picker-element-data, UI texts fromemoji-picker-element/i18n). - Internal terminology: Emoji = Unicode reactions (
UnicodeEmojiPicker.vue,CommentReactionPicker.vue,emojicolumns in the reaction tables); Sticker = uploaded media (StickerPicker.vue,StickerMedia.vue,useStickers,shared/utils/stickers.ts, tablestickers,/api/stickers,/uploads/stickers, permissionsticker.standard). Shortcode rendering:name:viastickerParts(). - Thread notifications:
threadSubscriptionsstores subscriptions and explicit opt-outs. Creating, commenting, favoriting and post reactions callensureThreadSubscription(); 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 viapushToUserwithdmevent (message + sender/recipient user info) anddm-readread receipt. Client state inuseDirectMessages-openPartnerIdmarks the open conversation;liveMessageHandler/liveReadHandler/liveReactionHandlerare module singletons registered by the messages page. DM reactions: tabledirect_message_reactions(message/user/emoji,seen_at), toggle under/api/messages/reactions/:messageId, WS eventdm-reactionto both participants. Unseen foreign reactions count like unread messages for the unread badge;readsetsseen_atas well. DM attachments:attachmentsJSON column inCommentAttachmentshape (meta.voice= voice message);POST /api/messages/:userIdaccepts multipart withfiles/voicefields like the comment form, rendered viaCommentAttachments. - Permissions: catalog in
shared/utils/permissions.ts(PERMISSIONS), server check viarequirePermission(event, key)inserver/utils/auth.ts- theadminrole is an implicit wildcard. Client-sidecan(key)fromuseAuth. Seeding viaseedPermissions()in the migration plugin/script. - i18n:
@nuxtjs/i18n, localesde(default) +enini18n/locales/*.json- always keep both files with identical key structure. All UI strings via$t()/t(), never hardcoded.strategy: 'no_prefix', locale cookiei18n_locale. Permission labels in admin viaadmin.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.5togap-2 - Grouped elements (form fields, lists):
space-y-3/gap-3 - Section spacing:
space-y-6/gap-6 - Layout/Page level:
py-8topy-12, max containermax-w-7xl mx-auto px-4 sm:px-6 lg:px-8
- Tight (inline/tags):
- Do not mix random paddings (e.g.
p-5,p-7). Stick top-4(compact) orp-6(standard).
3. Typography & Contrast
- Contrast comes from weight and color, not just font size.
- Secondary text must consistently use
text-muted-foreground text-xsortext-sm. - Main headers: Use
tracking-tight font-semiboldinstead of genericfont-bold. - Avoid 5 different font sizes on a single screen. Max 3 sizes per view (e.g. Title
text-xl, Bodytext-sm, Metadatatext-xs).
4. Nuxt 4 & Shadcn Vue Specifics
- Always leverage semantic Shadcn primitives (
CardHeader,CardTitle,CardDescription,CardContent,CardFooter) properly—never inline custom headers insideCardContent. - Keep templates clean: Move complex inline Tailwind classes into reusable Nuxt components or tailwind-variants (
cva) if reused. - Ensure proper empty states (
EmptyStatepattern) and loading skeletons instead of blank spaces.