diff --git a/frontend/src/components/step-library/StepCard.tsx b/frontend/src/components/step-library/StepCard.tsx
new file mode 100644
index 00000000..5345ac24
--- /dev/null
+++ b/frontend/src/components/step-library/StepCard.tsx
@@ -0,0 +1,144 @@
+import { Star, User, Calendar, TrendingUp, Eye, Plus, HelpCircle, Zap, CheckCircle } from 'lucide-react'
+import { cn } from '@/lib/utils'
+import type { StepListItem } from '@/types/step'
+
+interface StepCardProps {
+ step: StepListItem
+ onPreview: (step: StepListItem) => void
+ onInsert: (step: StepListItem) => void
+}
+
+const stepTypeIcons = {
+ decision: HelpCircle,
+ action: Zap,
+ solution: CheckCircle
+}
+
+const stepTypeColors = {
+ decision: 'bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30',
+ action: 'bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/30',
+ solution: 'bg-green-500/20 text-green-600 dark:text-green-400 border-green-500/30'
+}
+
+export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
+ const Icon = stepTypeIcons[step.step_type as keyof typeof stepTypeIcons] || HelpCircle
+ const hasRating = step.rating_count > 0
+ const visibleTags = step.tags.slice(0, 3)
+ const remainingTags = step.tags.length - 3
+
+ return (
+
+ {/* Header */}
+
+
+
+ {/* Step Type Badge */}
+
+
+ {step.step_type}
+
+
+ {/* Featured Badge */}
+ {step.is_featured && (
+
+ Featured
+
+ )}
+
+
+ {/* Title */}
+
{step.title}
+
+
+
+ {/* Metadata */}
+
+ {/* Category */}
+ {step.category_name && (
+
+ 📁
+ {step.category_name}
+
+ )}
+
+ {/* Rating */}
+
+
+ {hasRating ? (
+
+ {step.rating_average.toFixed(1)} ({step.rating_count} {step.rating_count === 1 ? 'rating' : 'ratings'})
+
+ ) : (
+ Not rated
+ )}
+
+
+ {/* Usage Count */}
+
+
+ Used {step.usage_count} {step.usage_count === 1 ? 'time' : 'times'}
+
+
+ {/* Author & Date */}
+
+
+
+ {step.author_name || 'Unknown'}
+
+
+
+ {new Date(step.created_at).toLocaleDateString()}
+
+
+
+
+ {/* Tags */}
+ {step.tags.length > 0 && (
+
+ {visibleTags.map(tag => (
+
+ {tag}
+
+ ))}
+ {remainingTags > 0 && (
+
+ +{remainingTags} more
+
+ )}
+
+ )}
+
+ {/* Actions */}
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/components/step-library/StepDetailModal.tsx b/frontend/src/components/step-library/StepDetailModal.tsx
new file mode 100644
index 00000000..39934ae7
--- /dev/null
+++ b/frontend/src/components/step-library/StepDetailModal.tsx
@@ -0,0 +1,326 @@
+import { useState, useEffect } from 'react'
+import { X, Star, Copy, Check, HelpCircle, Zap, CheckCircle, User, Calendar } from 'lucide-react'
+import { cn } from '@/lib/utils'
+import { MarkdownContent } from '@/components/ui/MarkdownContent'
+import { stepsApi } from '@/api'
+import type { Step, Review } from '@/types/step'
+
+interface StepDetailModalProps {
+ stepId: string
+ onClose: () => void
+ onInsert: (step: Step) => void
+}
+
+const stepTypeIcons = {
+ decision: HelpCircle,
+ action: Zap,
+ solution: CheckCircle
+}
+
+const stepTypeColors = {
+ decision: 'bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30',
+ action: 'bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/30',
+ solution: 'bg-green-500/20 text-green-600 dark:text-green-400 border-green-500/30'
+}
+
+export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalProps) {
+ const [step, setStep] = useState(null)
+ const [reviews, setReviews] = useState([])
+ const [isLoading, setIsLoading] = useState(true)
+ const [error, setError] = useState(null)
+ const [copiedCommandIndex, setCopiedCommandIndex] = useState(null)
+
+ useEffect(() => {
+ const loadStepDetails = async () => {
+ setIsLoading(true)
+ setError(null)
+ try {
+ const [stepData, reviewsData] = await Promise.all([
+ stepsApi.get(stepId),
+ stepsApi.getReviews(stepId)
+ ])
+ setStep(stepData)
+ setReviews(reviewsData)
+ } catch (err) {
+ console.error('Failed to load step details:', err)
+ setError('Failed to load step details')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ loadStepDetails()
+ }, [stepId])
+
+ const handleCopyCommand = async (command: string, index: number) => {
+ await navigator.clipboard.writeText(command)
+ setCopiedCommandIndex(index)
+ setTimeout(() => setCopiedCommandIndex(null), 2000)
+ }
+
+ const handleInsert = () => {
+ if (step) {
+ onInsert(step)
+ }
+ }
+
+ const Icon = step ? stepTypeIcons[step.step_type as keyof typeof stepTypeIcons] : HelpCircle
+ const hasRating = step && step.rating_count > 0
+ const topReviews = reviews.filter(r => r.review_text).slice(0, 3)
+
+ return (
+
+
+ {/* Header */}
+
+ {isLoading ? (
+
+ ) : error ? (
+
{error}
+ ) : step ? (
+
+
+
+
+ {step.step_type}
+
+ {step.category_name && (
+ 📁 {step.category_name}
+ )}
+ {step.is_featured && (
+
+ Featured
+
+ )}
+ {step.is_verified && (
+
+ ✓ Verified
+
+ )}
+
+
{step.title}
+
+ ) : null}
+
+
+
+ {/* Body - Scrollable */}
+
+ {isLoading ? (
+
+ ) : error ? (
+
{error}
+ ) : step ? (
+
+ {/* Rating */}
+ {hasRating && (
+
+
Rating
+
+
+ {[1, 2, 3, 4, 5].map(i => (
+
+ ))}
+
+
+ {step.rating_average.toFixed(1)} ({step.rating_count} {step.rating_count === 1 ? 'rating' : 'ratings'})
+
+
+
+ )}
+
+ {/* Tags */}
+ {step.tags.length > 0 && (
+
+
Tags
+
+ {step.tags.map(tag => (
+
+ {tag}
+
+ ))}
+
+
+ )}
+
+ {/* Instructions */}
+
+
+ {/* Help Text */}
+ {step.content.help_text && (
+
+ )}
+
+ {/* Commands */}
+ {step.content.commands && step.content.commands.length > 0 && (
+
+
Commands
+
+ {step.content.commands.map((cmd, index) => (
+
+
+ {cmd.label}
+
+
+
+ {cmd.command}
+
+
+ ))}
+
+
+ )}
+
+ {/* Reviews */}
+ {topReviews.length > 0 && (
+
+
+
Reviews
+ {reviews.length > 3 && (
+
+ )}
+
+
+ {topReviews.map(review => (
+
+
+
+
+ {review.user_name || 'Anonymous'}
+ {review.verified_use && (
+
+ ✓ Verified Use
+
+ )}
+
+
+ {[1, 2, 3, 4, 5].map(i => (
+
+ ))}
+
+
+
{review.review_text}
+
+
+ {new Date(review.created_at).toLocaleDateString()}
+
+
+ ))}
+
+
+ )}
+
+ {/* Metadata */}
+
+
+
+ Author:
+ {step.author_name || 'Unknown'}
+
+
+ Usage Count:
+ {step.usage_count}
+
+
+ Created:
+ {new Date(step.created_at).toLocaleDateString()}
+
+
+ Visibility:
+ {step.visibility}
+
+
+
+
+ ) : null}
+
+
+ {/* Footer - Actions */}
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/components/step-library/StepLibraryBrowser.tsx b/frontend/src/components/step-library/StepLibraryBrowser.tsx
new file mode 100644
index 00000000..f45ec12c
--- /dev/null
+++ b/frontend/src/components/step-library/StepLibraryBrowser.tsx
@@ -0,0 +1,364 @@
+import { useState, useEffect, useMemo } from 'react'
+import { Search, ChevronDown, ChevronUp, Loader2 } from 'lucide-react'
+import { cn } from '@/lib/utils'
+import { stepsApi, stepCategoriesApi } from '@/api'
+import { StepCard } from './StepCard'
+import { StepDetailModal } from './StepDetailModal'
+import type { Step, StepListItem, StepCategory, PopularTag, StepListParams } from '@/types/step'
+
+interface StepLibraryBrowserProps {
+ onInsert: (step: Step) => void
+ onCreateNew?: () => void
+ showCreateButton?: boolean
+}
+
+export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = false }: StepLibraryBrowserProps) {
+ // State
+ const [steps, setSteps] = useState([])
+ const [categories, setCategories] = useState([])
+ const [popularTags, setPopularTags] = useState([])
+ const [isLoading, setIsLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ // Filters
+ const [searchQuery, setSearchQuery] = useState('')
+ const [selectedCategoryId, setSelectedCategoryId] = useState()
+ const [selectedStepType, setSelectedStepType] = useState<'decision' | 'action' | 'solution' | undefined>()
+ const [minRating, setMinRating] = useState()
+ const [sortBy, setSortBy] = useState<'recent' | 'popular' | 'highest_rated' | 'most_used'>('recent')
+ const [selectedTag, setSelectedTag] = useState()
+
+ // UI state
+ const [previewStepId, setPreviewStepId] = useState(null)
+ const [collapsedSections, setCollapsedSections] = useState>({})
+
+ // Load initial data
+ useEffect(() => {
+ const loadInitialData = async () => {
+ setIsLoading(true)
+ setError(null)
+ try {
+ const [categoriesData, tagsData] = await Promise.all([
+ stepCategoriesApi.list(),
+ stepsApi.getPopularTags()
+ ])
+ setCategories(categoriesData.filter(c => c.is_active))
+ setPopularTags(tagsData.slice(0, 10)) // Show top 10 tags
+ } catch (err) {
+ console.error('Failed to load initial data:', err)
+ setError('Failed to load categories and tags')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ loadInitialData()
+ }, [])
+
+ // Load steps when filters change
+ useEffect(() => {
+ const loadSteps = async () => {
+ setIsLoading(true)
+ setError(null)
+ try {
+ const params: StepListParams = {
+ category_id: selectedCategoryId,
+ step_type: selectedStepType,
+ min_rating: minRating,
+ sort_by: sortBy,
+ tags: selectedTag ? [selectedTag] : undefined
+ }
+
+ let stepsData: StepListItem[]
+ if (searchQuery.trim()) {
+ stepsData = await stepsApi.search(searchQuery)
+ } else {
+ stepsData = await stepsApi.list(params)
+ }
+
+ setSteps(stepsData)
+ } catch (err) {
+ console.error('Failed to load steps:', err)
+ setError('Failed to load steps')
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ loadSteps()
+ }, [searchQuery, selectedCategoryId, selectedStepType, minRating, sortBy, selectedTag])
+
+ // Group steps by visibility
+ const groupedSteps = useMemo(() => {
+ return {
+ private: steps.filter(s => s.visibility === 'private'),
+ team: steps.filter(s => s.visibility === 'team'),
+ public: steps.filter(s => s.visibility === 'public')
+ }
+ }, [steps])
+
+ const toggleSection = (section: string) => {
+ setCollapsedSections(prev => ({ ...prev, [section]: !prev[section] }))
+ }
+
+ const handlePreview = (step: StepListItem) => {
+ setPreviewStepId(step.id)
+ }
+
+ const handleInsertFromPreview = (step: Step) => {
+ setPreviewStepId(null)
+ onInsert(step)
+ }
+
+ const handleInsertFromCard = (stepItem: StepListItem) => {
+ // Need to fetch full step details for insert
+ stepsApi.get(stepItem.id).then(onInsert)
+ }
+
+ const handleTagClick = (tag: string) => {
+ setSelectedTag(selectedTag === tag ? undefined : tag)
+ }
+
+ const clearFilters = () => {
+ setSearchQuery('')
+ setSelectedCategoryId(undefined)
+ setSelectedStepType(undefined)
+ setMinRating(undefined)
+ setSelectedTag(undefined)
+ }
+
+ const hasActiveFilters = searchQuery || selectedCategoryId || selectedStepType || minRating || selectedTag
+
+ return (
+
+ {/* Header - Filters */}
+
+ {/* Search */}
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full rounded-md border border-input bg-background py-2 pl-10 pr-4 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
+ />
+
+
+ {/* Filter Row */}
+
+ {/* Category Filter */}
+
+
+ {/* Type Filter */}
+
+
+ {/* Min Rating Filter */}
+
+
+ {/* Sort By */}
+
+
+
+ {/* Popular Tags */}
+ {popularTags.length > 0 && (
+
+
Popular Tags:
+
+ {popularTags.map(tag => (
+
+ ))}
+
+
+ )}
+
+ {/* Clear Filters */}
+ {hasActiveFilters && (
+
+ )}
+
+
+ {/* Body - Step List */}
+
+ {isLoading ? (
+
+
+
+ ) : error ? (
+
+ {error}
+
+ ) : steps.length === 0 ? (
+
+
No steps found
+
+ {hasActiveFilters ? 'Try adjusting your filters' : 'Create your first step to get started!'}
+
+
+ ) : (
+
+ {/* My Steps */}
+ {groupedSteps.private.length > 0 && (
+
+
+ {!collapsedSections.private && (
+
+ {groupedSteps.private.map(step => (
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* Team Steps */}
+ {groupedSteps.team.length > 0 && (
+
+
+ {!collapsedSections.team && (
+
+ {groupedSteps.team.map(step => (
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* Community Steps */}
+ {groupedSteps.public.length > 0 && (
+
+
+ {!collapsedSections.public && (
+
+ {groupedSteps.public.map(step => (
+
+ ))}
+
+ )}
+
+ )}
+
+ )}
+
+
+ {/* Footer - Optional Create Button */}
+ {showCreateButton && onCreateNew && (
+
+
+
+ )}
+
+ {/* Preview Modal */}
+ {previewStepId && (
+
setPreviewStepId(null)}
+ onInsert={handleInsertFromPreview}
+ />
+ )}
+
+ )
+}