fix: surface errors and polish UX across Step Library and Batch pages (#92)

- StepLibraryPage: replace silent console.error with toast.error on edit/save
  failures; replace manual setTimeout toast with toast.success helper
- BatchStatusPage: add error state with retry button for failed session loads
  instead of showing ambiguous "No sessions found"
- StepDetailModal: guard clipboard copy against browser denial (no false
  "Copied!" state on permission error); fix dead "See all reviews" button
  with inline expand/collapse toggle
- StepLibraryBrowser: add "Try again" retry button to error state; retry
  increments a counter that re-triggers both load effects

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #92.
This commit is contained in:
chihlasm
2026-02-26 03:00:14 -05:00
committed by GitHub
parent 8fa6ee1801
commit 1f77b7fc32
4 changed files with 53 additions and 29 deletions

View File

@@ -29,6 +29,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [copiedCommandIndex, setCopiedCommandIndex] = useState<number | null>(null)
const [showAllReviews, setShowAllReviews] = useState(false)
useEffect(() => {
const loadStepDetails = async () => {
@@ -53,9 +54,13 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
}, [stepId])
const handleCopyCommand = async (command: string, index: number) => {
await navigator.clipboard.writeText(command)
setCopiedCommandIndex(index)
setTimeout(() => setCopiedCommandIndex(null), 2000)
try {
await navigator.clipboard.writeText(command)
setCopiedCommandIndex(index)
setTimeout(() => setCopiedCommandIndex(null), 2000)
} catch {
// Clipboard access denied — don't show false "Copied!" state
}
}
const handleInsert = () => {
@@ -66,7 +71,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
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)
const allTextReviews = reviews.filter(r => r.review_text)
const visibleReviews = showAllReviews ? allTextReviews : allTextReviews.slice(0, 3)
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
@@ -228,18 +234,21 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
)}
{/* Reviews */}
{topReviews.length > 0 && (
{visibleReviews.length > 0 && (
<div>
<div className="mb-2 flex items-center justify-between">
<h3 className="text-sm font-semibold text-foreground">Reviews</h3>
{reviews.length > 3 && (
<button className="text-xs text-muted-foreground hover:text-foreground hover:underline">
See all {reviews.length} reviews
{allTextReviews.length > 3 && (
<button
onClick={() => setShowAllReviews(v => !v)}
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
>
{showAllReviews ? 'Show less' : `See all ${allTextReviews.length} reviews`}
</button>
)}
</div>
<div className="space-y-3">
{topReviews.map(review => (
{visibleReviews.map(review => (
<div key={review.id} className="rounded-lg border border-border bg-accent/50 p-3">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2 text-sm">

View File

@@ -37,6 +37,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
// UI state
const [previewStepId, setPreviewStepId] = useState<string | null>(null)
const [collapsedSections, setCollapsedSections] = useState<Record<string, boolean>>({})
const [retryCount, setRetryCount] = useState(0)
// Load initial data
useEffect(() => {
@@ -59,7 +60,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
}
loadInitialData()
}, [])
}, [retryCount])
// Load steps when filters change
useEffect(() => {
@@ -92,7 +93,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
}
loadSteps()
}, [searchQuery, selectedCategoryId, selectedStepType, minRating, sortBy, selectedTag, refreshKey])
}, [searchQuery, selectedCategoryId, selectedStepType, minRating, sortBy, selectedTag, refreshKey, retryCount])
// Group steps by visibility
const groupedSteps = useMemo(() => {
@@ -250,8 +251,14 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : error ? (
<div className="rounded-lg border border-red-400/20 bg-red-400/10 p-4 text-center text-sm text-red-400">
{error}
<div className="rounded-lg border border-red-400/20 bg-red-400/10 p-4 text-center">
<p className="text-sm text-red-400 mb-3">{error}</p>
<button
onClick={() => setRetryCount(c => c + 1)}
className="rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
>
Try again
</button>
</div>
) : steps.length === 0 ? (
<div className="rounded-lg border border-border bg-accent/50 p-12 text-center">