Built a complete Netflix-style streaming interface for IndeeHub's decentralized media platform with real film content. Features: - Vue 3 + TypeScript + Vite setup with hot module reloading - Netflix-inspired UI with hero section and horizontal scrolling content rows - Glass morphism design system with custom Tailwind configuration - 20+ real IndeeHub films organized into 6 categories (Bitcoin, Documentaries, Drama, etc.) - Full-featured video player component with custom controls - Mobile-responsive design with bottom navigation - Nostr integration ready (nostr-tools, relay pool, NIP-71 support) - Pinia state management for content - MCP tools configured (Filesystem, Memory, Nostr, Puppeteer) Components: - Browse.vue: Main streaming interface with hero and content rows - ContentRow.vue: Horizontal scrolling film cards with navigation arrows - VideoPlayer.vue: Custom video player with play/pause, seek, volume, fullscreen - MobileNav.vue: Bottom tab navigation for mobile devices Tech Stack: - Frontend: Vue 3 (Composition API), TypeScript - Build: Vite 7 - Styling: Tailwind CSS with custom theme - State: Pinia 3 - Router: Vue Router 4.6 - Protocol: Nostr (nostr-tools 2.22) Design: - 4px grid spacing system - Glass morphism UI components - Netflix-style hero section with featured content - Smooth animations and hover effects - Mobile-first responsive breakpoints - Dark theme with custom color palette Content: - 20+ IndeeHub films with titles, descriptions, categories - Bitcoin documentaries: God Bless Bitcoin, Dirty Coin, Searching for Satoshi - Independent films and documentaries - Working Unsplash CDN images for thumbnails and backdrops Ready for deployment to Umbrel, Start9, and Archy nodes. Co-authored-by: Cursor <cursoragent@cursor.com>
201 lines
3.9 KiB
Plaintext
201 lines
3.9 KiB
Plaintext
---
|
|
description: Vue 3 Composition API conventions and best practices
|
|
alwaysApply: false
|
|
globs: **/*.vue
|
|
---
|
|
|
|
# Vue.js Conventions & Best Practices
|
|
|
|
## Component Architecture
|
|
|
|
### Use Composition API with `<script setup>`
|
|
|
|
```vue
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
|
|
// Props
|
|
const props = defineProps({
|
|
userId: {
|
|
type: String,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
// State
|
|
const user = ref(null)
|
|
const isLoading = ref(false)
|
|
|
|
// Computed
|
|
const displayName = computed(() => {
|
|
return user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
|
|
})
|
|
|
|
// Methods
|
|
const fetchUser = async () => {
|
|
isLoading.value = true
|
|
try {
|
|
// Fetch logic
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Lifecycle
|
|
onMounted(() => {
|
|
fetchUser()
|
|
})
|
|
</script>
|
|
```
|
|
|
|
### Component Organization Order
|
|
|
|
1. **Imports** - External, then internal
|
|
2. **Props** - TypeScript-style validation
|
|
3. **Emits** - Explicitly defined
|
|
4. **State** (refs and reactive)
|
|
5. **Computed** - Derived values
|
|
6. **Watchers** - Side effects
|
|
7. **Methods** - Business logic
|
|
8. **Lifecycle hooks** - Ordered by execution
|
|
9. **Expose** - Public API (if needed)
|
|
|
|
## Props Best Practices
|
|
|
|
### Always Validate Props
|
|
|
|
```javascript
|
|
defineProps({
|
|
title: {
|
|
type: String,
|
|
required: true,
|
|
validator: (value) => value.length > 0
|
|
},
|
|
status: {
|
|
type: String,
|
|
default: 'pending',
|
|
validator: (value) => ['pending', 'active', 'complete'].includes(value)
|
|
}
|
|
})
|
|
```
|
|
|
|
### Prop Naming
|
|
- Use descriptive names: `isLoading` not `loading`
|
|
- Boolean props: `is`, `has`, `can`, `should`
|
|
- Avoid abbreviations: `projectData` not `projData`
|
|
|
|
## Reactive State
|
|
|
|
### When to Use ref vs reactive
|
|
|
|
**Use `ref` for:**
|
|
- Primitives (string, number, boolean)
|
|
- Single values
|
|
- When you need `.value` explicit access
|
|
|
|
**Use `reactive` for:**
|
|
- Objects with multiple properties
|
|
- Complex nested data structures
|
|
|
|
## Event Handling
|
|
|
|
### Define Emits Explicitly
|
|
|
|
```javascript
|
|
const emit = defineEmits(['update', 'delete', 'close'])
|
|
|
|
const handleUpdate = () => {
|
|
emit('update', { id: 1, name: 'Updated' })
|
|
}
|
|
```
|
|
|
|
## Template Best Practices
|
|
|
|
### Keep Templates Clean
|
|
|
|
```vue
|
|
<!-- Good - Logic in script -->
|
|
<template>
|
|
<div class="project-card" :class="cardClasses">
|
|
<h3>{{ project.title }}</h3>
|
|
<p v-if="hasDescription">{{ project.description }}</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
const cardClasses = computed(() => ({
|
|
'is-featured': project.featured,
|
|
'is-complete': project.status === 'complete'
|
|
}))
|
|
|
|
const hasDescription = computed(() => {
|
|
return project.description && project.description.length > 0
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## Performance Optimization
|
|
|
|
### Lazy Loading Components
|
|
|
|
```javascript
|
|
// Router lazy loading
|
|
const ProjectDetail = () => import('@/pages/ProjectDetail.vue')
|
|
|
|
// Component lazy loading
|
|
const HeavyChart = defineAsyncComponent(() =>
|
|
import('@/components/HeavyChart.vue')
|
|
)
|
|
```
|
|
|
|
### Use `v-once` for Static Content
|
|
|
|
```vue
|
|
<div v-once>
|
|
<h1>{{ staticTitle }}</h1>
|
|
</div>
|
|
```
|
|
|
|
### Shallow Reactive for Large Objects
|
|
|
|
```javascript
|
|
import { shallowRef } from 'vue'
|
|
const projects = shallowRef([...largeArray])
|
|
```
|
|
|
|
## Common Pitfalls to Avoid
|
|
|
|
### ❌ Mutating Props
|
|
Never mutate props directly - emit events instead
|
|
|
|
### ❌ Forgetting .value in script
|
|
```javascript
|
|
const count = ref(0)
|
|
if (count.value === 0) { } // ✅ Correct
|
|
```
|
|
|
|
### ❌ Creating Objects in Template
|
|
```vue
|
|
<!-- Bad - Creates new object every render -->
|
|
<Child :config="{ theme: 'dark' }" />
|
|
|
|
<!-- Good - Stable reference -->
|
|
<script setup>
|
|
const config = { theme: 'dark' }
|
|
</script>
|
|
<Child :config="config" />
|
|
```
|
|
|
|
## Summary Checklist
|
|
|
|
- ✅ Use Composition API with `<script setup>`
|
|
- ✅ Validate all props with types and defaults
|
|
- ✅ Define emits explicitly
|
|
- ✅ Keep templates clean - move logic to script
|
|
- ✅ Use computed for derived state
|
|
- ✅ Always provide `:key` for lists
|
|
- ✅ Handle errors gracefully
|
|
- ✅ Lazy load heavy components
|
|
- ✅ Never mutate props
|
|
- ✅ Remember `.value` for refs in script
|