Initial commit: IndeeHub decentralized streaming platform
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>
This commit is contained in:
200
.cursor/rules/vue-conventions.mdc
Normal file
200
.cursor/rules/vue-conventions.mdc
Normal file
@@ -0,0 +1,200 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user