jedmund-svelte/prd/PRD-enhanced-tag-system.md
2025-06-11 00:54:05 -07:00

16 KiB
Raw Blame History

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

-- 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

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

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

<script lang="ts">
	let {
		tags = $bindable([]),
		suggestions = [],
		placeholder = 'Add tags...',
		maxTags = 10,
		allowNew = true,
		size = 'medium',
		disabled = false,
		onTagAdd,
		onTagRemove,
		onTagCreate
	}: TagInputProps = $props()

	let inputValue = $state('')
	let showSuggestions = $state(false)
	let selectedIndex = $state(-1)
	let inputElement: HTMLInputElement

	// Filtered suggestions based on input
	let filteredSuggestions = $derived(
		suggestions.filter(
			(tag) =>
				tag.name.toLowerCase().includes(inputValue.toLowerCase()) &&
				!tags.some((t) => t.id === tag.id)
		)
	)

	// Handle keyboard navigation
	function handleKeydown(e: KeyboardEvent) {
		switch (e.key) {
			case 'Enter':
				e.preventDefault()
				if (selectedIndex >= 0) {
					addExistingTag(filteredSuggestions[selectedIndex])
				} else if (inputValue.trim() && allowNew) {
					createNewTag(inputValue.trim())
				}
				break
			case 'ArrowDown':
				e.preventDefault()
				selectedIndex = Math.min(selectedIndex + 1, filteredSuggestions.length - 1)
				break
			case 'ArrowUp':
				e.preventDefault()
				selectedIndex = Math.max(selectedIndex - 1, -1)
				break
			case 'Backspace':
				if (!inputValue && tags.length > 0) {
					removeTag(tags[tags.length - 1])
				}
				break
			case 'Escape':
				showSuggestions = false
				selectedIndex = -1
				break
		}
	}
</script>

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

// 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
	}>
}

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
<script lang="ts">
	let {
		postId,
		tags,
		limit = 4
	}: {
		postId: number
		tags: Tag[]
		limit?: number
	} = $props()

	let relatedPosts = $state<Post[]>([])

	$effect(async () => {
		const tagIds = tags.map((t) => t.id)
		const response = await fetch(
			`/api/posts/related?postId=${postId}&tagIds=${tagIds.join(',')}&limit=${limit}`
		)
		relatedPosts = await response.json()
	})
</script>

API Specification

Tag Management APIs

// 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

// 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

<script lang="ts">
	let {
		tag,
		size = 'medium',
		removable = false,
		clickable = false,
		showCount = false,
		onRemove,
		onClick
	}: TagPillProps = $props()
</script>

<span
	class="tag-pill tag-pill-{size}"
	style="--tag-color: {tag.color}"
	class:clickable
	class:removable
	onclick={onClick}
>
	{tag.name}
	{#if showCount}
		<span class="tag-count">({tag.usageCount})</span>
	{/if}
	{#if removable}
		<button onclick={onRemove} class="tag-remove">×</button>
	{/if}
</span>

TagCloud Component

<script lang="ts">
	let { tags, maxTags = 50, minFontSize = 12, maxFontSize = 24, onClick }: TagCloudProps = $props()

	// Calculate font sizes based on usage
	let tagSizes = $derived(calculateTagSizes(tags, minFontSize, maxFontSize))
</script>

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.