Click to upload photo
-Click to upload photos
- Select multiple photos -diff --git a/PRD-enhanced-tag-system.md b/PRD-enhanced-tag-system.md new file mode 100644 index 0000000..fe7cb95 --- /dev/null +++ b/PRD-enhanced-tag-system.md @@ -0,0 +1,610 @@ +# Product Requirements Document: Enhanced Tag System + +## Overview + +Upgrade the current JSON-based tag system to a relational database model with advanced tagging features including tag filtering, related posts, tag management, and an improved tag input UI with typeahead functionality. + +## Goals + +- Enable efficient querying and filtering of posts by tags +- Provide tag management capabilities for content curation +- Show related posts based on shared tags +- Implement intuitive tag input with typeahead and keyboard shortcuts +- Build analytics and insights around tag usage +- Maintain backward compatibility during migration + +## Technical Constraints + +- **Framework**: SvelteKit with Svelte 5 runes mode +- **Database**: PostgreSQL with Prisma ORM +- **Hosting**: Railway (existing infrastructure) +- **Design System**: Use existing admin component library +- **Performance**: Tag operations should be sub-100ms + +## Current State vs Target State + +### Current Implementation +- Tags stored as JSON arrays: `tags: ['announcement', 'meta', 'cms']` +- Simple display-only functionality +- No querying capabilities +- Manual tag input with Add button + +### Target Implementation +- Relational many-to-many tag system +- Full CRUD operations for tags +- Advanced filtering and search +- Typeahead tag input with keyboard navigation +- Tag analytics and management interface + +## Database Schema Changes + +### New Tables + +```sql +-- Tags table +CREATE TABLE tags ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) UNIQUE NOT NULL, + slug VARCHAR(100) UNIQUE NOT NULL, + description TEXT, + color VARCHAR(7), -- Hex color for tag styling + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Post-Tag junction table +CREATE TABLE post_tags ( + id SERIAL PRIMARY KEY, + post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE, + tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(post_id, tag_id) +); + +-- Tag usage analytics (optional) +CREATE TABLE tag_analytics ( + id SERIAL PRIMARY KEY, + tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE, + usage_count INTEGER DEFAULT 1, + last_used TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +### Prisma Schema Updates + +```prisma +model Tag { + id Int @id @default(autoincrement()) + name String @unique @db.VarChar(100) + slug String @unique @db.VarChar(100) + description String? @db.Text + color String? @db.VarChar(7) // Hex color + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + posts PostTag[] + + @@index([name]) + @@index([slug]) +} + +model PostTag { + id Int @id @default(autoincrement()) + postId Int + tagId Int + createdAt DateTime @default(now()) + + // Relations + post Post @relation(fields: [postId], references: [id], onDelete: Cascade) + tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) + + @@unique([postId, tagId]) + @@index([postId]) + @@index([tagId]) +} + +// Update existing Post model +model Post { + // ... existing fields + tags PostTag[] // Replace: tags Json? +} +``` + +## Core Features + +### 1. Tag Management Interface + +#### Admin Tag Manager (`/admin/tags`) +- **Tag List View** + - DataTable with tag name, usage count, created date + - Search and filter capabilities + - Bulk operations (delete, merge, rename) + - Color coding and visual indicators + +- **Tag Detail/Edit View** + - Edit tag name, description, color + - View all posts using this tag + - Usage analytics and trends + - Merge with other tags functionality + +#### Tag Analytics Dashboard +- **Usage Statistics** + - Most/least used tags + - Tag usage trends over time + - Orphaned tags (no posts) + - Tag co-occurrence patterns + +- **Tag Insights** + - Suggested tag consolidations + - Similar tags detection + - Tag performance metrics + +### 2. Enhanced Tag Input Component (`TagInput.svelte`) + +#### Features +- **Typeahead Search**: Real-time search of existing tags +- **Keyboard Navigation**: Arrow keys to navigate suggestions +- **Instant Add**: Press Enter to add tag without button click +- **Visual Feedback**: Highlight matching text in suggestions +- **Tag Validation**: Prevent duplicates and invalid characters +- **Quick Actions**: Backspace to remove last tag + +#### Component API +```typescript +interface TagInputProps { + tags: string[] | Tag[] // Current tags + suggestions?: Tag[] // Available tags for typeahead + placeholder?: string // Input placeholder text + maxTags?: number // Maximum number of tags + allowNew?: boolean // Allow creating new tags + size?: 'small' | 'medium' | 'large' + disabled?: boolean + onTagAdd?: (tag: Tag) => void + onTagRemove?: (tag: Tag) => void + onTagCreate?: (name: string) => void +} +``` + +#### Svelte 5 Implementation +```svelte + +``` + +### 3. Post Filtering by Tags + +#### Frontend Components +- **Tag Filter Bar**: Multi-select tag filtering +- **Tag Cloud**: Visual tag representation with usage counts +- **Search Integration**: Combine text search with tag filters + +#### API Endpoints +```typescript +// GET /api/posts?tags=javascript,react&operation=AND +// GET /api/posts?tags=design,ux&operation=OR +interface PostsQueryParams { + tags?: string[] // Tag names or IDs + operation?: 'AND' | 'OR' // How to combine multiple tags + page?: number + limit?: number + status?: 'published' | 'draft' +} + +// GET /api/tags/suggest?q=java +interface TagSuggestResponse { + tags: Array<{ + id: number + name: string + slug: string + usageCount: number + }> +} +``` + +### 4. Related Posts Feature + +#### Implementation +- **Algorithm**: Find posts sharing the most tags +- **Weighting**: Consider tag importance and recency +- **Exclusions**: Don't show current post in related list +- **Limit**: Show 3-6 related posts maximum + +#### Component (`RelatedPosts.svelte`) +```svelte + +``` + +## API Specification + +### Tag Management APIs + +```typescript +// GET /api/tags - List all tags +interface TagsResponse { + tags: Tag[] + total: number + page: number + limit: number +} + +// POST /api/tags - Create new tag +interface CreateTagRequest { + name: string + description?: string + color?: string +} + +// PUT /api/tags/[id] - Update tag +interface UpdateTagRequest { + name?: string + description?: string + color?: string +} + +// DELETE /api/tags/[id] - Delete tag +// Returns: 204 No Content + +// POST /api/tags/merge - Merge tags +interface MergeTagsRequest { + sourceTagIds: number[] + targetTagId: number +} + +// GET /api/tags/[id]/posts - Get posts for tag +interface TagPostsResponse { + posts: Post[] + tag: Tag + total: number +} + +// GET /api/tags/analytics - Tag usage analytics +interface TagAnalyticsResponse { + mostUsed: Array<{ tag: Tag; count: number }> + leastUsed: Array<{ tag: Tag; count: number }> + trending: Array<{ tag: Tag; growth: number }> + orphaned: Tag[] +} +``` + +### Enhanced Post APIs + +```typescript +// GET /api/posts/related?postId=123&tagIds=1,2,3&limit=4 +interface RelatedPostsResponse { + posts: Array<{ + id: number + title: string + slug: string + excerpt?: string + publishedAt: string + tags: Tag[] + sharedTagsCount: number // Number of tags in common + }> +} + +// PUT /api/posts/[id]/tags - Update post tags +interface UpdatePostTagsRequest { + tagIds: number[] +} +``` + +## User Interface Components + +### 1. TagInput Component Features + +#### Visual States +- **Default**: Clean input with placeholder +- **Focused**: Show suggestions dropdown +- **Typing**: Filter and highlight matches +- **Selected**: Navigate with keyboard +- **Adding**: Smooth animation for new tags +- **Full**: Disable input when max tags reached + +#### Accessibility +- **ARIA Labels**: Proper labeling for screen readers +- **Keyboard Navigation**: Full keyboard accessibility +- **Focus Management**: Logical tab order +- **Announcements**: Screen reader feedback for actions + +### 2. Tag Display Components + +#### TagPill Component +```svelte + + + + {tag.name} + {#if showCount} + ({tag.usageCount}) + {/if} + {#if removable} + + {/if} + +``` + +#### TagCloud Component +```svelte + +``` + +### 3. Admin Interface Updates + +#### Posts List with Tag Filtering +- **Filter Bar**: Multi-select tag filter above posts list +- **Tag Pills**: Show tags on each post item +- **Quick Filter**: Click tag to filter by that tag +- **Clear Filters**: Easy way to reset all filters + +#### Posts Edit Form Integration +- **Replace Current**: Swap existing tag input with new TagInput +- **Preserve UX**: Maintain current metadata popover +- **Tag Management**: Quick access to create/edit tags + +## Migration Strategy + +### Phase 1: Database Migration (Week 1) +1. **Create Migration Script** + - Create new tables (tags, post_tags) + - Migrate existing JSON tags to relational format + - Create indexes for performance + +2. **Data Migration** + - Extract unique tags from existing posts + - Create tag records with auto-generated slugs + - Create post_tag relationships + - Validate data integrity + +3. **Backward Compatibility** + - Keep original tags JSON field temporarily + - Dual-write to both systems during transition + +### Phase 2: API Development (Week 1-2) +1. **Tag Management APIs** + - CRUD operations for tags + - Tag suggestions and search + - Analytics endpoints + +2. **Enhanced Post APIs** + - Update post endpoints for relational tags + - Related posts algorithm + - Tag filtering capabilities + +3. **Testing & Validation** + - Unit tests for all endpoints + - Performance testing for queries + - Data consistency checks + +### Phase 3: Frontend Components (Week 2-3) +1. **Core Components** + - TagInput with typeahead + - TagPill and TagCloud + - Tag management interface + +2. **Integration** + - Update MetadataPopover + - Add tag filtering to posts list + - Implement related posts component + +3. **Admin Interface** + - Tag management dashboard + - Analytics views + - Bulk operations interface + +### Phase 4: Features & Polish (Week 3-4) +1. **Advanced Features** + - Tag merging functionality + - Usage analytics + - Tag suggestions based on content + +2. **Performance Optimization** + - Query optimization + - Caching strategies + - Load testing + +3. **Cleanup** + - Remove JSON tags field + - Documentation updates + - Final testing + +## Success Metrics + +### Performance +- Tag search responses under 50ms +- Post filtering responses under 100ms +- Page load times maintained or improved + +### Usability +- Reduced clicks to add tags (eliminate Add button) +- Faster tag input with typeahead +- Improved content discovery through related posts + +### Content Management +- Ability to merge duplicate tags +- Insights into tag usage patterns +- Better content organization capabilities + +### Analytics +- Track tag usage growth over time +- Identify content gaps through tag analysis +- Measure impact on content engagement + +## Technical Considerations + +### Performance +- **Database Indexes**: Proper indexing on tag names and relationships +- **Query Optimization**: Efficient joins for tag filtering +- **Caching**: Cache popular tag lists and related posts +- **Pagination**: Handle large tag lists efficiently + +### Data Integrity +- **Constraints**: Prevent duplicate tag names +- **Cascading Deletes**: Properly handle tag/post deletions +- **Validation**: Ensure tag names follow naming conventions +- **Backup Strategy**: Safe migration with rollback capability + +### User Experience +- **Progressive Enhancement**: Graceful degradation if JS fails +- **Loading States**: Smooth loading indicators +- **Error Handling**: Clear error messages for users +- **Responsive Design**: Works well on all device sizes + +## Future Enhancements + +### Advanced Features (Post-MVP) +- **Hierarchical Tags**: Parent/child tag relationships +- **Tag Synonyms**: Alternative names for the same concept +- **Auto-tagging**: ML-based tag suggestions from content +- **Tag Templates**: Predefined tag sets for different content types + +### Integrations +- **External APIs**: Import tags from external sources +- **Search Integration**: Enhanced search with tag faceting +- **Analytics**: Deep tag performance analytics +- **Content Recommendations**: AI-powered related content + +## Risk Assessment + +### High Risk +- **Data Migration**: Complex migration of existing tag data +- **Performance Impact**: New queries might affect page load times +- **User Adoption**: Users need to learn new tag input interface + +### Mitigation Strategies +- **Staged Rollout**: Deploy to staging first, then gradual production rollout +- **Performance Monitoring**: Continuous monitoring during migration +- **User Training**: Clear documentation and smooth UX transitions +- **Rollback Plan**: Ability to revert to JSON tags if needed + +## Success Criteria + +### Must Have +- ✅ All existing tags migrated successfully +- ✅ Tag input works with keyboard-only navigation +- ✅ Posts can be filtered by single or multiple tags +- ✅ Related posts show based on shared tags +- ✅ Performance remains acceptable (< 100ms for most operations) + +### Should Have +- ✅ Tag management interface for admins +- ✅ Tag usage analytics and insights +- ✅ Ability to merge duplicate tags +- ✅ Tag color coding and visual improvements + +### Could Have +- Tag auto-suggestions based on post content +- Tag trending and popularity metrics +- Advanced tag analytics and reporting +- Integration with external tag sources + +## Timeline + +**Total Duration**: 4 weeks + +- **Week 1**: Database migration and API development +- **Week 2**: Core frontend components and basic integration +- **Week 3**: Advanced features and admin interface +- **Week 4**: Polish, testing, and production deployment + +## Conclusion + +This enhanced tag system will significantly improve content organization, discoverability, and management capabilities while providing a modern, intuitive user interface built with Svelte 5 runes. The migration strategy ensures minimal disruption while delivering substantial improvements in functionality and user experience. \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts index 791f5e5..58cba1c3 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -169,24 +169,28 @@ async function main() { console.log(`✅ Created ${labsProjects.length} labs projects`) - // Create test posts + // Create test posts using new simplified types const posts = await Promise.all([ prisma.post.create({ data: { slug: 'hello-world', - postType: 'blog', + postType: 'essay', title: 'Hello World', content: { blocks: [ - { type: 'paragraph', content: 'This is my first blog post on the new CMS!' }, + { type: 'paragraph', content: 'This is my first essay on the new CMS!' }, { type: 'paragraph', - content: 'The system supports multiple post types and rich content editing.' + content: 'The system now uses a simplified post type system with just essays and posts.' + }, + { + type: 'paragraph', + content: 'Essays are perfect for longer-form content with titles and excerpts, while posts are great for quick thoughts and updates.' } ] }, - excerpt: 'Welcome to my new blog powered by a custom CMS.', - tags: ['announcement', 'meta'], + excerpt: 'Welcome to my new blog powered by a custom CMS with simplified post types.', + tags: ['announcement', 'meta', 'cms'], status: 'published', publishedAt: new Date() } @@ -194,30 +198,79 @@ async function main() { prisma.post.create({ data: { slug: 'quick-thought', - postType: 'microblog', + postType: 'post', content: { blocks: [ { type: 'paragraph', - content: 'Just pushed a major update to the site. Feeling good about the progress!' + content: 'Just pushed a major update to the site. The new simplified post types are working great! 🎉' } ] }, + tags: ['update', 'development'], status: 'published', publishedAt: new Date(Date.now() - 86400000) // Yesterday } }), prisma.post.create({ data: { - slug: 'great-article', - postType: 'link', - title: 'Great Article on Web Performance', - linkUrl: 'https://web.dev/performance', - linkDescription: - 'This article perfectly explains the core web vitals and how to optimize for them.', + slug: 'design-systems-thoughts', + postType: 'essay', + title: 'Thoughts on Design Systems', + content: { + blocks: [ + { + type: 'paragraph', + content: 'Design systems have become essential for maintaining consistency across large products.' + }, + { + type: 'paragraph', + content: 'The key is finding the right balance between flexibility and constraints.' + }, + { + type: 'paragraph', + content: 'Too rigid, and designers feel boxed in. Too flexible, and you lose consistency.' + } + ] + }, + excerpt: 'Exploring the balance between flexibility and constraints in design systems.', + tags: ['design', 'systems', 'ux'], status: 'published', publishedAt: new Date(Date.now() - 172800000) // 2 days ago } + }), + prisma.post.create({ + data: { + slug: 'morning-coffee', + postType: 'post', + content: { + blocks: [ + { + type: 'paragraph', + content: 'Perfect morning for coding with a fresh cup of coffee ☕' + } + ] + }, + tags: ['life', 'coffee'], + status: 'published', + publishedAt: new Date(Date.now() - 259200000) // 3 days ago + } + }), + prisma.post.create({ + data: { + slug: 'weekend-project', + postType: 'post', + content: { + blocks: [ + { + type: 'paragraph', + content: 'Built a small CLI tool over the weekend. Sometimes the best projects come from scratching your own itch.' + } + ] + }, + tags: ['projects', 'cli', 'weekend'], + status: 'draft' + } }) ]) diff --git a/src/assets/icons/metadata.svg b/src/assets/icons/metadata.svg new file mode 100644 index 0000000..371b5da --- /dev/null +++ b/src/assets/icons/metadata.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/assets/styles/globals.scss b/src/assets/styles/globals.scss index 10ee1ea..3090706 100644 --- a/src/assets/styles/globals.scss +++ b/src/assets/styles/globals.scss @@ -12,11 +12,24 @@ body { } // Heading font weights -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { font-weight: 500; } // Button and input font inheritance -button, input, textarea, select { +button, +input, +textarea, +select { font-family: inherit; -} \ No newline at end of file +} + +input, +textarea { + box-sizing: border-box; +} diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss index ae28c14..74108c6 100644 --- a/src/assets/styles/variables.scss +++ b/src/assets/styles/variables.scss @@ -101,7 +101,7 @@ $yellow-30: #cc9900; $yellow-20: #996600; $yellow-10: #664400; -$salmon-pink: #ffbdb3; // Desaturated salmon pink for hover states +$salmon-pink: #ffd5cf; // Desaturated salmon pink for hover states $bg-color: #e8e8e8; $page-color: #ffffff; diff --git a/src/lib/components/admin/AdminNavBar.svelte b/src/lib/components/admin/AdminNavBar.svelte index 86c4a73..d0c0d76 100644 --- a/src/lib/components/admin/AdminNavBar.svelte +++ b/src/lib/components/admin/AdminNavBar.svelte @@ -191,13 +191,12 @@ } &:hover { - color: $red-60; - background-color: $salmon-pink; + background-color: $grey-70; } &.active { - color: white; - background-color: $red-60; + color: $red-60; + background-color: $salmon-pink; } .nav-icon { diff --git a/src/lib/components/admin/AlbumListItem.svelte b/src/lib/components/admin/AlbumListItem.svelte new file mode 100644 index 0000000..312f06f --- /dev/null +++ b/src/lib/components/admin/AlbumListItem.svelte @@ -0,0 +1,303 @@ + + +
Loading albums...
++ {#if photographyFilter === 'all'} + No albums found. Create your first album! + {:else} + No albums found matching the current filters. Try adjusting your filters or create a new album. + {/if} +
{loadError}
+ +Click to upload photo
-Click to upload photos
- Select multiple photos -